You are viewing a plain text version of this content. The canonical link for it is here.
Posted to slide-dev@jakarta.apache.org by oz...@apache.org on 2004/05/05 14:20:44 UTC

cvs commit: jakarta-slide/src/stores/org/apache/slide/store/txfile/rm/impl FileSequence.java

ozeigermann    2004/05/05 05:20:44

  Added:       src/stores/org/apache/slide/store/txfile
                        FileSequenceStore.java
               src/stores/org/apache/slide/store/txfile/rm/impl
                        FileSequence.java
  Log:
  Added file system sequence implementation
  
  Revision  Changes    Path
  1.1                  jakarta-slide/src/stores/org/apache/slide/store/txfile/FileSequenceStore.java
  
  Index: FileSequenceStore.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-slide/src/stores/org/apache/slide/store/txfile/FileSequenceStore.java,v 1.1 2004/05/05 12:20:44 ozeigermann Exp $
   * $Revision: 1.1 $
   * $Date: 2004/05/05 12:20:44 $
   *
   * ====================================================================
   *
   * Copyright 1999-2002 The Apache Software Foundation 
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  package org.apache.slide.store.txfile;
  
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.Map;
  
  import javax.transaction.xa.XAException;
  import javax.transaction.xa.XAResource;
  import javax.transaction.xa.Xid;
  
  import org.apache.slide.common.ServiceAccessException;
  import org.apache.slide.common.ServiceConnectionFailedException;
  import org.apache.slide.common.ServiceDisconnectionFailedException;
  import org.apache.slide.common.ServiceParameterErrorException;
  import org.apache.slide.common.ServiceParameterMissingException;
  import org.apache.slide.common.ServiceResetFailedException;
  import org.apache.slide.common.AbstractServiceBase;
  import org.apache.slide.store.SequenceStore;
  
  import org.apache.slide.store.txfile.rm.ResourceManagerException;
  import org.apache.slide.store.txfile.rm.impl.FileSequence;
  import org.apache.slide.util.logger.Logger;
  import org.apache.slide.util.logger.StoreLogger;
  
  /**
   * Sequence store using the file system. 
   * 
   * @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
   */
  public class FileSequenceStore extends AbstractServiceBase implements SequenceStore {
  
      protected static final String LOG_CHANNEL = FileSequenceStore.class.getName();
  
      protected static final String STORE_DIR_PARAMETER = "rootpath";
  
      protected FileSequence fileSequence;
      protected String storeDir = null;
      protected Map sequenceHash = new HashMap();
      protected int sequenceIncrement = 100;
      protected long sequenceStartValue = 1;
  
      public void setParameters(Hashtable parameters)
          throws ServiceParameterErrorException, ServiceParameterMissingException {
  
          storeDir = (String) parameters.get(STORE_DIR_PARAMETER);
          if (storeDir == null) {
              throw new ServiceParameterMissingException(this, STORE_DIR_PARAMETER);
          }
  
          try {
              fileSequence = new FileSequence(storeDir, new StoreLogger(getLogger(), LOG_CHANNEL));
              getLogger().log("File Sequence Store configured to " + storeDir, LOG_CHANNEL, Logger.INFO);
          } catch (ResourceManagerException e) {
              getLogger().log("Can not initialize File Sequence Store", e, LOG_CHANNEL, Logger.CRITICAL);
              throw new ServiceParameterErrorException(this, STORE_DIR_PARAMETER);
          }
      }
  
      public String toString() {
          return "FileSequenceStore at " + storeDir;
      }
  
      /**
       * @see org.apache.slide.common.AbstractServiceBase#connect()
       */
      public void connect() throws ServiceConnectionFailedException {
      }
  
      /**
       * @see org.apache.slide.common.AbstractServiceBase#disconnect()
       */
      public void disconnect() throws ServiceDisconnectionFailedException {
      }
  
      /**
       * @see org.apache.slide.common.AbstractServiceBase#reset()
       */
      public void reset() throws ServiceResetFailedException {
      }
  
      /**
       * @see org.apache.slide.common.AbstractServiceBase#isConnected()
       */
      public boolean isConnected() throws ServiceAccessException {
          return true;
      }
  
      /**
       * @see org.apache.slide.store.SequenceStore#isSequenceSupported()
       */
      public boolean isSequenceSupported() {
          return true;
      }
  
      /**
       * @see org.apache.slide.store.SequenceStore#sequenceExists(java.lang.String)
       */
      public boolean sequenceExists(String sequenceName) throws ServiceAccessException {
          SeqContext seq = (SeqContext) sequenceHash.get(sequenceName);
          if (seq != null) {
              return true;
          } else {
              return fileSequence.exists(sequenceName);
          }
      }
  
      /**
       * @see org.apache.slide.store.SequenceStore#createSequence(java.lang.String)
       */
      public synchronized boolean createSequence(String sequenceName) throws ServiceAccessException {
          try {
              if (fileSequence.create(sequenceName, sequenceStartValue)) {
                  sequenceHash.put(sequenceName, new SeqContext(sequenceName, sequenceStartValue, sequenceStartValue));
                  return true;
              } else {
                  return false;
              }
          } catch (ResourceManagerException e) {
              throw new ServiceAccessException(this, e);
          }
      }
  
      /**
       * @see org.apache.slide.store.SequenceStore#nextSequenceValue(java.lang.String)
       */
      public synchronized long nextSequenceValue(String sequenceName) throws ServiceAccessException {
          // get chunks of values to save disk access
          SeqContext seq = (SeqContext) sequenceHash.get(sequenceName);
          if (seq != null && seq.pos < seq.end - 1) {
              seq.pos++;
              return seq.pos;
          } else {
              try {
                  if (seq == null) {
                      seq = new SeqContext(sequenceName);
                  }
                  long pos = fileSequence.nextSequenceValueBottom(sequenceName, sequenceIncrement);
                  seq.pos = pos;
                  seq.end = seq.pos + sequenceIncrement;
                  return seq.pos;
              } catch (ResourceManagerException e) {
                  throw new ServiceAccessException(this, e);
              }
          }
      }
  
      /**
       * @see javax.transaction.xa.XAResource#commit(javax.transaction.xa.Xid, boolean)
       */
      public void commit(Xid xid, boolean onePhase) throws XAException {
          // XXX we are not transactional, this is our trick
      }
  
      /**
       * @see javax.transaction.xa.XAResource#end(javax.transaction.xa.Xid, int)
       */
      public void end(Xid xid, int flags) throws XAException {
          // XXX we are not transactional, this is our trick
      }
  
      /**
       * @see javax.transaction.xa.XAResource#forget(javax.transaction.xa.Xid)
       */
      public void forget(Xid xid) throws XAException {
          // XXX we are not transactional, this is our trick
      }
  
      /**
       * @see javax.transaction.xa.XAResource#getTransactionTimeout()
       */
      public int getTransactionTimeout() throws XAException {
          // XXX we are not transactional, this is our trick
          return 0;
      }
  
      /**
       * @see javax.transaction.xa.XAResource#isSameRM(javax.transaction.xa.XAResource)
       */
      public boolean isSameRM(XAResource xaResource) throws XAException {
          return (xaResource == this);
      }
  
      /**
       * @see javax.transaction.xa.XAResource#prepare(javax.transaction.xa.Xid)
       */
      public int prepare(Xid xid) throws XAException {
          // tell TM we do not need to commit
          return XA_RDONLY;
      }
  
      /**
       * @see javax.transaction.xa.XAResource#recover(int)
       */
      public Xid[] recover(int flag) throws XAException {
          // XXX we are not transactional, this is our trick
          return null;
      }
  
      /**
       * @see javax.transaction.xa.XAResource#rollback(javax.transaction.xa.Xid)
       */
      public void rollback(Xid xid) throws XAException {
          // XXX we are not transactional, this is our trick
      }
  
      /**
       * @see javax.transaction.xa.XAResource#setTransactionTimeout(int)
       */
      public boolean setTransactionTimeout(int seconds) throws XAException {
          // XXX we are not transactional, this is our trick
          return true;
      }
  
      /**
       * @see javax.transaction.xa.XAResource#start(javax.transaction.xa.Xid, int)
       */
      public void start(Xid xid, int flags) throws XAException {
          // XXX we are not transactional, this is our trick
  
      }
  
      protected static class SeqContext {
          public String name;
          public long pos;
          public long end;
  
          public SeqContext(String name) {
              this(name, -1, -1);
          }
          public SeqContext(String name, long pos, long end) {
              this.name = name;
              this.pos = pos;
              this.end = end;
          }
      }
  }
  
  
  
  1.1                  jakarta-slide/src/stores/org/apache/slide/store/txfile/rm/impl/FileSequence.java
  
  Index: FileSequence.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-slide/src/stores/org/apache/slide/store/txfile/rm/impl/FileSequence.java,v 1.1 2004/05/05 12:20:44 ozeigermann Exp $
   * $Revision: 1.1 $
   * $Date: 2004/05/05 12:20:44 $
   *
   * ====================================================================
   *
   * Copyright 1999-2002 The Apache Software Foundation 
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  package org.apache.slide.store.txfile.rm.impl;
  
  import java.io.BufferedReader;
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.UnsupportedEncodingException;
  
  import org.apache.slide.store.txfile.rm.*;
  
  import org.apache.slide.store.util.FileHelper;
  import org.apache.slide.util.logger.StoreLogger;
  
  /**
   * Fail-Safe sequence store implementation using the file system.
   * 
   * 
   * @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
   */
  public class FileSequence {
  
      protected final String storeDir;
      protected final StoreLogger logger;
  
      /**
       * Creates a new resouce manager operation on the specified directories.
       * 
       * @param storeDir directory where main data should go after commit
       * @param workDir directory where transactions store temporary data 
       */
      public FileSequence(String storeDir, StoreLogger logger) throws ResourceManagerException {
          this.storeDir = storeDir;
          this.logger = logger;
          File file = new File(storeDir);
          file.mkdirs();
          if (!file.exists()) {
              throw new ResourceManagerException("Can not create working directory " + storeDir);
          }
      }
  
      public synchronized boolean exists(String sequenceName) {
          String pathI = getPathI(sequenceName);
          String pathII = getPathII(sequenceName);
  
          return (FileHelper.fileExists(pathI) || FileHelper.fileExists(pathII));
      }
  
      public synchronized boolean create(String sequenceName, long initialValue) throws ResourceManagerException {
          if (exists(sequenceName))
              return false;
          write(sequenceName, initialValue);
          return true;
      }
  
      public synchronized boolean delete(String sequenceName) {
          if (!exists(sequenceName))
              return false;
          String pathI = getPathI(sequenceName);
          String pathII = getPathII(sequenceName);
  
          // XXX be careful no to use shortcut eval with || might not delete second file        
          boolean res1 = FileHelper.deleteFile(pathI);
          boolean res2 = FileHelper.deleteFile(pathII);
  
          return (res1 || res2);
      }
  
      public synchronized long nextSequenceValueBottom(String sequenceName, long increment)
          throws ResourceManagerException {
          if (!exists(sequenceName)) {
              throw new ResourceManagerException("Sequence " + sequenceName + " does not exist");
          }
          if (increment <= 0) {
              throw new IllegalArgumentException("Increment must be greater than 0, was " + increment);
          }
          long value = read(sequenceName);
          long newValue = value + increment;
          write(sequenceName, newValue);
          return value;
      }
  
      protected long read(String sequenceName) throws ResourceManagerException {
          String pathI = getPathI(sequenceName);
          String pathII = getPathII(sequenceName);
  
          long returnValue = -1;
  
          long valueI = -1;
          if (FileHelper.fileExists(pathI)) {
              try {
                  valueI = readFromPath(pathI);
              } catch (NumberFormatException e) {
                  throw new ResourceManagerException("Fatal internal error: Backup sequence value corrupted");
              } catch (FileNotFoundException e) {
                  throw new ResourceManagerException("Fatal internal error: Backup sequence vanished");
              } catch (IOException e) {
                  throw new ResourceManagerException("Fatal internal error: Backup sequence value corrupted");
              }
          }
  
          long valueII = -1;
          if (FileHelper.fileExists(pathII)) {
              try {
                  valueII = readFromPath(pathII);
                  if (valueII > valueI) {
                      returnValue = valueII;
                  } else {
                      // if it is smaller than previous this *must* be an error as we constantly increment
                      logger.logWarning("Latest sequence value smaller than previous, reverting to previous");
                      FileHelper.deleteFile(pathII);
                      returnValue = valueI;
                  }
              } catch (NumberFormatException e) {
                  logger.logWarning("Latest sequence value corrupted, reverting to previous");
                  FileHelper.deleteFile(pathII);
                  returnValue = valueI;
              } catch (FileNotFoundException e) {
                  logger.logWarning("Can not find latest sequence value, reverting to previous");
                  FileHelper.deleteFile(pathII);
                  returnValue = valueI;
              } catch (IOException e) {
                  logger.logWarning("Can not read latest sequence value, reverting to previous");
                  FileHelper.deleteFile(pathII);
                  returnValue = valueI;
              }
          } else {
              logger.logWarning("Can not read latest sequence value, reverting to previous");
              returnValue = valueI;
          }
  
          if (returnValue != -1) {
              return returnValue;
          } else {
              throw new ResourceManagerException("Fatal internal error: Could not compute valid sequence value");
          }
      }
  
      protected void write(String sequenceName, long value) throws ResourceManagerException {
          String pathII = getPathII(sequenceName);
  
          File f2 = new File(pathII);
          // by contract when this method is called an f2 exists it must be valid
          if (f2.exists()) {
              // move previous value to backup position
              String pathI = getPathI(sequenceName);
              File f1 = new File(pathI);
              f1.delete();
              if (!f2.renameTo(f1)) {
                  throw new ResourceManagerException("Fatal internal error: Can not create backup value at" + pathI);
              }
          }
          try {
              if (!f2.createNewFile()) {
                  throw new ResourceManagerException("Fatal internal error: Can not create new value at" + pathII);
              }
          } catch (IOException e) {
              throw new ResourceManagerException("Fatal internal error: Can not create new value at" + pathII, e);
          }
          writeToPath(pathII, value);
      }
  
      protected String getPathI(String sequenceName) {
          return storeDir + "/" + sequenceName + "_1.seq";
      }
  
      protected String getPathII(String sequenceName) {
          return storeDir + "/" + sequenceName + "_2.seq";
      }
  
      protected long readFromPath(String path)
          throws ResourceManagerException, NumberFormatException, FileNotFoundException, IOException {
          File file = new File(path);
          BufferedReader reader = null;
          try {
              InputStream is = new FileInputStream(file);
  
              // we do not care for encoding as we only have numbers
              reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
              String valueString = reader.readLine();
              long value = Long.parseLong(valueString);
              return value;
          } catch (UnsupportedEncodingException e) {
              throw new ResourceManagerException("Fatal internal error, encoding UTF-8 unknown");
          } finally {
              if (reader != null) {
                  try {
                      reader.close();
                  } catch (IOException e) {
                  }
  
              }
          }
      }
  
      protected void writeToPath(String path, long value) throws ResourceManagerException {
          File file = new File(path);
          BufferedWriter writer = null;
          try {
              OutputStream os = new FileOutputStream(file);
              writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
              String valueString = Long.toString(value);
              writer.write(valueString);
              writer.write('\n');
          } catch (FileNotFoundException e) {
              throw new ResourceManagerException("Fatal internal error: Can not find sequence at " + path);
          } catch (IOException e) {
              throw new ResourceManagerException("Fatal internal error: Can not write to sequence at " + path);
          } finally {
              if (writer != null) {
                  try {
                      writer.close();
                  } catch (IOException e) {
                  }
  
              }
          }
      }
  
      // just for testing
      public static void main(String[] args) {
          try {
              FileSequence seq = new FileSequence("E:/tmp/seq", new StoreLogger() {
                  public void logWarning(String message) {
                      System.err.println(message);
                  }
              });
              seq.create("olli", 10L);
              long value = seq.nextSequenceValueBottom("olli", 100);
              value = seq.nextSequenceValueBottom("olli", 1000);
              seq.delete("olli");
          } catch (ResourceManagerException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
      }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: slide-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: slide-dev-help@jakarta.apache.org