Package com.shop.cache.imp.storage.ccdb2

Source Code of com.shop.cache.imp.storage.ccdb2.CCDB2Storage

/*
* Copyright 2008-2009 SHOP.COM
*
* 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 com.shop.cache.imp.storage.ccdb2;

import com.shop.cache.api.storage.SCStorage;
import com.shop.cache.api.storage.SCStorageServerDriver;
import com.shop.cache.api.common.SCDataSpec;
import com.shop.cache.api.common.SCGroup;
import com.shop.cache.api.common.SCGroupSpec;
import com.shop.util.ccdb2.CCDB2DataSpec;
import com.shop.util.ccdb2.CCDB2Driver;
import com.shop.util.ccdb2.CCDB2Instance;
import com.shop.util.ccdb2.CCDB2SetFileLengthException;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
* SHOP.COM's Storage implementation
*
* @author Jordan Zimmerman
*/
class CCDB2Storage implements SCStorage, CCDB2Driver
{
  CCDB2Storage(CCDB2Parameters parameters)
  {
    fParameters = parameters.clone();
    fGetQty = new AtomicLong(0);
    fSuccessfulGetQty = new AtomicLong(0);
    fPutQty = new AtomicLong(0);
    fLock = new ReentrantReadWriteLock();
    fInstances = new ArrayList<CCDB2Instance>();

    fDriver = new SCStorageServerDriver()
    {
      @Override
      public void log(String s, Throwable e, boolean addNewline)
      {
        // do nothing
      }

      @Override
      public void notifyException(Exception e)
      {
        // do nothing
      }

      @Override
      public void setErrorState(String errorState)
      {
        // do nothing
      }

      @Override
      public void remove(String key)
      {
        // do nothing
      }
    };

    fCleanupThread = new Thread
    (
      new Runnable()
      {
        @Override
        public void run()
        {
          boolean     firstTime = true;
          while ( !Thread.currentThread().isInterrupted() )
          {
            try
            {
              Thread.sleep(firstTime ? FIRST_TIME_SLEEP_TICKS : SLEEP_TICKS);
              firstTime = false;
            }
            catch ( InterruptedException dummy )
            {
              break;
            }

            doCleanupTasks();
          }
        }
      }
    );
    fCleanupThread.setDaemon(true);
  }

  @Override
  public void setStorageServerDriver(SCStorageServerDriver driver)
  {
    fDriver = driver;
  }

  @Override
  public void handleException(Exception e)
  {
    fDriver.notifyException(e);
  }

  @Override
  public void log(String s, Throwable e, boolean newline)
  {
    fDriver.log(s, e, newline);
  }

  @Override
  public String getDBExtension()
  {
    return fParameters.getDBFileExtension();
  }

  @Override
  public String getIndexExtension()
  {
    return fParameters.getIndexFileExtension();
  }

  @Override
  public boolean doChunking()
  {
    return true;
  }

  @Override
  public void callRemoveObject(String s)
  {
    fDriver.remove(s);
  }

  @Override
  public long getAllocationChunkSize()
  {
    return 0;
  }

  @Override
  public void open(File path) throws IOException
  {
    fDirectoryPath = path;
    fDirectoryPath.mkdirs();

    List<CCDB2Instance>   localList = new ArrayList<CCDB2Instance>();
    File[]          files = fDirectoryPath.listFiles
    (
      new FilenameFilter()
      {
        @Override
        public boolean accept(File dir, String name)
        {
          return name.toLowerCase().endsWith(fParameters.getDBFileExtension());
        }
      }
    );

    for ( File f : files )
    {
      try
      {
        int        dotIndex = f.getName().indexOf('.');
        String       unextendedName = (dotIndex > 0) ? f.getName().substring(0, dotIndex) : f.getName();
        CCDB2Instance   instance = new CCDB2Instance(this, f.getParentFile(), unextendedName, fParameters.getBackgroundPutLength());
        if ( (System.currentTimeMillis() - instance.getCreationTime()) < (fParameters.getMaxAgeMilliseconds() * 2) )
        {
          localList.add(instance);
        }
        else
        {
          instance.delete();
        }
      }
      catch ( CCDB2Instance.OldFileException e )
      {
        System.out.println(e.getMessage());
      }
      catch ( IOException e )
      {
        log("", e, true);
      }
    }

    Collections.sort
    (
      localList,
      new Comparator<CCDB2Instance>()
      {
        @Override
        public int compare(CCDB2Instance o1, CCDB2Instance o2)
        {
          long diff = o1.getCreationTime() - o2.getCreationTime();
          return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
        }
      }
    );

    if ( localList.size() == 0 )
    {
      CCDB2Instance     newInstance = makeNewInstance();
      localList.add(newInstance);
    }

    while ( localList.size() > fParameters.getMaxInstances() )
    {
      CCDB2Instance    instance = localList.remove(0);
      instance.delete();
    }

    CCDB2InstanceLoader loader = new CCDB2InstanceLoader
    (
      this,
      localList,
      new CCDB2InstanceLoader.ProcessDriver()
      {
        @Override
        public void process(CCDB2Instance instance, AtomicInteger percentDone) throws IOException
        {
          instance.loadFile(percentDone);
        }
      }
    );
    log("Loading...", null, true);
    loader.load();

    fInstances.clear();
    fInstances.addAll(localList);

    fCleanupThread.start();
  }

  @Override
  public SCDataSpec get(String key) throws IOException
  {
    fGetQty.incrementAndGet();

    CCDB2DataSpec   ccdb2Spec;

    fLock.readLock().lock();
    try
    {
      AtomicReference<Boolean>     wasDeleted = new AtomicReference<Boolean>(false);
      ListIterator<CCDB2Instance> iterator = reverseIterator();
      ccdb2Spec = null;
      while ( (ccdb2Spec == null) && iterator.hasPrevious() )
      {
        CCDB2Instance instance = iterator.previous();
        if ( wasDeleted.get() )
        {
          instance.removeFromIndex(key);
        }
        else
        {
          ccdb2Spec = instance.get(key, wasDeleted);
        }
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }

    if ( ccdb2Spec != null )
    {
      fSuccessfulGetQty.incrementAndGet();
      return new SCDataSpec(ccdb2Spec.data, ccdb2Spec.ttl);
    }

    return null;
  }

  @Override
  public void put(String key, SCDataSpec spec, SCGroupSpec groups)
  {
    if ( (key.length() == 0) || (spec.data.size() == 0) )
    {
      return;
    }

    CCDB2DataSpec     ccdb2Spec = new CCDB2DataSpec(spec.data, spec.ttl);
    long[]        ccdb2Groups = (groups != null) ? new long[groups.size()] : null;
    if ( groups != null )
    {
      for ( int i = 0; i < groups.size(); ++i )
      {
        ccdb2Groups[i] = groups.get(i).value;
      }
    }

    fPutQty.incrementAndGet();

    fLock.readLock().lock();
    try
    {
      boolean             isFirst = true;
      ListIterator<CCDB2Instance>   iterator = reverseIterator();
      while ( iterator.hasPrevious() )
      {
        CCDB2Instance    instance = iterator.previous();
        try
        {
          if ( isFirst )
          {
            isFirst = false;
            instance.put(key, ccdb2Spec, ccdb2Groups);
          }
          else
          {
            instance.removeFromIndex(key);
          }
        }
        catch ( CCDB2SetFileLengthException e )
        {
          fDriver.setErrorState("Out of Disk Space");
        }
        catch ( IOException e )
        {
          handleException(e);
        }
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }
  }

  @Override
  public void close() throws IOException
  {
    log("DM closing", null, true);

    fCleanupThread.interrupt();
    try
    {
      fCleanupThread.join();
    }
    catch ( InterruptedException ignore )
    {
    }

    fLock.writeLock().lock();
    try
    {
      CCDB2InstanceLoader    loader = new CCDB2InstanceLoader
      (
        this,
        fInstances,
        new CCDB2InstanceLoader.ProcessDriver()
        {
          @Override
          public void process(CCDB2Instance instance, AtomicInteger percentDone) throws IOException
          {
            instance.close(percentDone);
          }
        }
      );
      loader.load();

      fInstances.clear();
    }
    finally
    {
      fLock.writeLock().unlock();
    }
  }

  @Override
  public Set<String> regexFindKeys(String regex)
  {
    Set<String>            keys = new HashSet<String>();
    try
    {
      Pattern p = Pattern.compile(regex);

      fLock.readLock().lock();
      try
      {
        for ( CCDB2Instance instance : fInstances )
        {
          instance.regexFindKeys(p, keys);
        }
      }
      finally
      {
        fLock.readLock().unlock();
      }
    }
    catch ( PatternSyntaxException e )
    {
      log("Bad Regular Expression", e, true)// safe to ignore. Treat as a zero result match
    }
    return keys;
  }

  @Override
  public void remove(String key) throws IOException
  {
    fLock.readLock().lock();
    try
    {
      boolean             isFirst = true;
      ListIterator<CCDB2Instance>   iterator = reverseIterator();
      while ( iterator.hasPrevious() )
      {
        CCDB2Instance   instance = iterator.previous();
        if ( isFirst )
        {
          isFirst = false;
          instance.remove(key);
        }
        else
        {
          instance.removeFromIndex(key);
        }
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }
  }

  @Override
  public List<String> listGroup(SCGroup group) throws IOException
  {
    List<String>    keys = new ArrayList<String>();
    fLock.readLock().lock();
    try
    {
      for ( CCDB2Instance instance : fInstances )
      {
        List<String>     instanceKeys =  instance.listGroup(group.value);
        keys.addAll(instanceKeys);
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }

    return keys;
  }

  @Override
  public List<String> removeGroup(SCGroup group) throws IOException
  {
    List<String>    keys = new ArrayList<String>();
    fLock.readLock().lock();
    try
    {
      for ( CCDB2Instance instance : fInstances )
      {
        List<String>     instanceKeys =  instance.removeGroup(group.value);
        keys.addAll(instanceKeys);
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }

    return keys;
  }

  @Override
  public List<String> dumpStats(boolean verbose) throws IOException
  {
    List<String>  tab = new ArrayList<String>();
    long      successful_get_qty = fSuccessfulGetQty.get();
    long      put_qty = fPutQty.get();
    long      total_qty = successful_get_qty + put_qty;

    ByteArrayOutputStream  work = new ByteArrayOutputStream();
    PrintStream        out = new PrintStream(work);

    fLock.readLock().lock();
    try
    {
      for ( CCDB2Instance instance : fInstances )
      {
        instance.dumpStats(out);
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }

    out.close();
    BufferedReader    in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(work.toByteArray())));
    for(;;)
    {
      String    line = in.readLine();
      if ( line == null )
      {
        break;
      }
      tab.add(line);
    }

    tab.add("");

    tab.add("Total Gets:  " + fGetQty.get());
    tab.add("Gets:        " + successful_get_qty);
    tab.add("Puts:        " + put_qty);
    if ( total_qty > 0 )
    {
      tab.add("Gets v Puts: " + ((successful_get_qty * 100) / total_qty) + "%");
    }

    tab.add("");

    return tab;
  }

  @Override
  public void writeKeyData(File f) throws IOException
  {
    PrintStream      out = new PrintStream(new BufferedOutputStream(new FileOutputStream(f), 0x10000));
    out.println("File\tCacheKey\tAddress\tTTL");

    try
    {
      fLock.readLock().lock();
      try
      {
        for ( CCDB2Instance instance : fInstances )
        {
          instance.writeKeyData(out);
        }
      }
      finally
      {
        fLock.readLock().unlock();
      }
    }
    finally
    {
      out.close();
    }
  }
 
  private void doCleanupTasks()
  {
    cleanupInstances();
    cleanupOldObjects();
  }

  private void cleanupOldObjects()
  {
    fLock.readLock().lock();
    try
    {
      // important - do this from oldest to newest so that old objects don't suddenly become active
      for ( CCDB2Instance instance : fInstances )
      {
        instance.removeOldKeysFromIndex();
      }
    }
    finally
    {
      fLock.readLock().unlock();
    }
  }

  private void cleanupInstances()
  {
    /**
     * allocate a new instance just in case it's needed - that way it
     * won't be done while synchronized
     */
    CCDB2Instance   newInstance;
    try
    {
      newInstance = makeNewInstance();
    }
    catch ( IOException e )
    {
      handleException(e);
      return;
    }

    CCDB2Instance     deleteInstance = null;

    fLock.writeLock().lock();
    try
    {
      if ( fInstances.size() > 0 )
      {
        CCDB2Instance     mainInstance = fInstances.get(fInstances.size() - 1);
        if ( (System.currentTimeMillis() - mainInstance.getCreationTime()) >= fParameters.getShuffleMilliseconds() )
        {
          if ( fInstances.size() >= fParameters.getMaxInstances() )
          {
            deleteInstance = fInstances.remove(0);
          }

          fInstances.add(newInstance);
          newInstance = null// so it won't be deleted
        }
      }
    }
    finally
    {
      fLock.writeLock().unlock();
    }

    if ( newInstance != null )
    {
      try
      {
        newInstance.delete();
      }
      catch ( IOException e )
      {
        handleException(e);
      }
    }

    if ( deleteInstance != null )
    {
      try
      {
        deleteInstance.delete();
      }
      catch ( IOException e )
      {
        handleException(e);
      }
    }
  }

  private ListIterator<CCDB2Instance> reverseIterator()
  {
    return fInstances.listIterator(fInstances.size());
  }

  private CCDB2Instance makeNewInstance() throws IOException
  {
    String      filename = fParameters.getFilePrefix() + System.nanoTime();
    return new CCDB2Instance(this, fDirectoryPath, filename, fParameters.getBackgroundPutLength());
  }

  private static final int      SLEEP_TICKS = 15 * 60 * 1000// 15 minutes
  private static final int      FIRST_TIME_SLEEP_TICKS = 2 * 60 * 1000// 2 minutes

  private  File                 fDirectoryPath;
  private final AtomicLong           fGetQty;
  private final AtomicLong           fSuccessfulGetQty;
  private final AtomicLong           fPutQty;
  private final List<CCDB2Instance>       fInstances;
  private final ReentrantReadWriteLock     fLock;
  private final Thread             fCleanupThread;
  private final CCDB2Parameters         fParameters;
  private SCStorageServerDriver fDriver;
}
TOP

Related Classes of com.shop.cache.imp.storage.ccdb2.CCDB2Storage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.