Java tutorial
/* * (C) Copyright 2012, IBM Corporation * * 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.ibm.jaggr.service.impl.cache; 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.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Serializable; import java.io.Writer; import java.text.MessageFormat; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.lang.StringUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import com.ibm.jaggr.service.IAggregator; import com.ibm.jaggr.service.IShutdownListener; import com.ibm.jaggr.service.cache.ICache; import com.ibm.jaggr.service.cache.ICacheManager; import com.ibm.jaggr.service.config.IConfig; import com.ibm.jaggr.service.config.IConfigListener; import com.ibm.jaggr.service.deps.IDependencies; import com.ibm.jaggr.service.deps.IDependenciesListener; import com.ibm.jaggr.service.options.IOptions; import com.ibm.jaggr.service.options.IOptionsListener; import com.ibm.jaggr.service.util.ConsoleService; import com.ibm.jaggr.service.util.CopyUtil; public class CacheManagerImpl implements ICacheManager, IShutdownListener, IConfigListener, IDependenciesListener, IOptionsListener { private static final Logger log = Logger.getLogger(ICacheManager.class.getName()); private static final String CACHEDIR_NAME = "cache"; //$NON-NLS-1$ /** * Reference the cache with an atomic reference so that we don't need to synchronize * access to it. The atomic reference is needed for when we swap the cache out with * a new empty one when processing the clearcache command. */ private final AtomicReference<CacheImpl> _cache = new AtomicReference<CacheImpl>(); /** The filename for the serialized cache object */ private static final String CACHE_META_FILENAME = "metadata.cache"; //$NON-NLS-1$ /** The cache directory */ private final File _directory; private CacheControl _control; private IAggregator _aggregator; private ServiceRegistration _shutdownListener = null; private ServiceRegistration _configUpdateListener = null; private ServiceRegistration _depsUpdateListener = null; private ServiceRegistration _optionsUpdateListener = null; private long updateSequenceNumber = 0; private Object cacheSerializerSyncObj = new Object(); private static class CacheControl implements Serializable { private static final long serialVersionUID = 1276701428723406198L; volatile String rawConfig = null; volatile Map<String, String> optionsMap = null; volatile long depsLastMod = -1; long initStamp = -1; } /** * Starts up the cache. Attempts to de-serialize a previously serialized * cache from disk and starts the periodic serializer task. * * @param aggregator * the aggregator instance this cache manager belongs to * @param stamp * a time stamp used to determine if the cache should be cleared. * The cache should be cleared if the time stamp is later than * the one associated with the cached resources. * @throws IOException */ public CacheManagerImpl(IAggregator aggregator, long stamp) throws IOException { _directory = new File(aggregator.getWorkingDirectory(), CACHEDIR_NAME); _aggregator = aggregator; // Make sure the cache directory exists if (!_directory.exists()) { if (!_directory.mkdirs()) { throw new IOException(MessageFormat.format(Messages.CacheManagerImpl_0, new Object[] { _directory.getAbsoluteFile() })); } } // Attempt to de-serialize the cache from disk CacheImpl cache = null; try { File file = new File(_directory, CACHE_META_FILENAME); ObjectInputStream is = new ObjectInputStream(new FileInputStream(file)); try { cache = (CacheImpl) is.readObject(); } finally { try { is.close(); } catch (Exception ignore) { } } } catch (FileNotFoundException e) { if (log.isLoggable(Level.INFO)) log.log(Level.INFO, Messages.CacheManagerImpl_1); } catch (InvalidClassException e) { if (log.isLoggable(Level.INFO)) log.log(Level.INFO, Messages.CacheManagerImpl_2); // one or more of the serializable classes has changed. Delete the stale // cache files } catch (Exception e) { if (log.isLoggable(Level.SEVERE)) log.log(Level.SEVERE, e.getMessage(), e); } if (cache != null) { _control = (CacheControl) cache.getControlObj(); } if (_control != null) { // stamp == 0 means no overrides. Need to check for this explicitly // in case the overrides directory has been removed. if (stamp == 0 && _control.initStamp == 0 || stamp != 0 && stamp <= _control.initStamp) { // Use AggregatorProxy so that getCacheManager will return non-null // if called from within setAggregator. Need to do this because // IAggregator.getCacheManager() is unable to return this object // since it is still being constructed. cache.setAggregator(AggregatorProxy.newInstance(_aggregator, this)); _cache.set(cache); } } else { _control = new CacheControl(); _control.initStamp = stamp; } // Start up the periodic serializer task. Serializes the cache every 10 minutes. // This is done so that we can recover from an unexpected shutdown aggregator.getExecutors().getScheduledExecutor().scheduleAtFixedRate(new Runnable() { public void run() { try { File file = new File(_directory, CACHE_META_FILENAME); // Synchronize on the cache object to keep the scheduled cache sync thread and // the thread processing servlet destroy from colliding. synchronized (cacheSerializerSyncObj) { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file)); try { os.writeObject(_cache.get()); } finally { try { os.close(); } catch (Exception ignore) { } } } } catch (Exception e) { if (log.isLoggable(Level.SEVERE)) log.log(Level.SEVERE, e.getMessage(), e); } } }, 10, 10, TimeUnit.MINUTES); Properties dict; BundleContext bundleContext = aggregator.getBundleContext(); if (bundleContext != null) { // Register listeners dict = new Properties(); dict.put("name", aggregator.getName()); //$NON-NLS-1$ _shutdownListener = bundleContext.registerService(IShutdownListener.class.getName(), this, dict); dict = new Properties(); dict.put("name", aggregator.getName()); //$NON-NLS-1$ _configUpdateListener = bundleContext.registerService(IConfigListener.class.getName(), this, dict); dict = new Properties(); dict.put("name", aggregator.getName()); //$NON-NLS-1$ _depsUpdateListener = bundleContext.registerService(IDependenciesListener.class.getName(), this, dict); dict = new Properties(); dict.put("name", aggregator.getName()); //$NON-NLS-1$ _optionsUpdateListener = bundleContext.registerService(IOptionsListener.class.getName(), this, dict); optionsUpdated(aggregator.getOptions(), 1); configLoaded(aggregator.getConfig(), 1); dependenciesLoaded(aggregator.getDependencies(), 1); } // Now invoke the listeners for objects that have already been initialized IOptions options = _aggregator.getOptions(); if (options != null) { optionsUpdated(options, 1); } IConfig config = _aggregator.getConfig(); if (config != null) { configLoaded(config, 1); } IDependencies deps = _aggregator.getDependencies(); if (deps != null) { dependenciesLoaded(deps, 1); } } public synchronized void clearCache() { CacheImpl newCache = new CacheImpl(_aggregator.newLayerCache(), _aggregator.newModuleCache(), _control); newCache.setAggregator(_aggregator); clean(_directory); CacheImpl oldCache = _cache.getAndSet(newCache); if (oldCache != null) { oldCache.clear(); } } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#dumpCache(java.io.Writer, java.util.regex.Pattern) */ @Override public void dumpCache(Writer writer, Pattern filter) throws IOException { _cache.get().dump(writer, filter); } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#shutdown() */ @Override public void shutdown(IAggregator aggregator) { if (_shutdownListener != null) { _shutdownListener.unregister(); } if (_configUpdateListener != null) { _configUpdateListener.unregister(); } if (_depsUpdateListener != null) { _depsUpdateListener.unregister(); } if (_optionsUpdateListener != null) { _optionsUpdateListener.unregister(); } // Serialize the cache metadata one last time serializeCache(); // avoid memory leaks caused by circular references _aggregator = null; _cache.set(null); } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#getCacheDir() */ @Override public File getCacheDir() { return _directory; } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#getCache() */ @Override public ICache getCache() { return _cache.get(); } /** * Serializes the specified cache object to the sepecified directory. Note that we * actually serialize a clone of the specified cache because some of the objects * that are serialized require synchronization and we don't want to cause service * threads to block while we are doing file I/O. * * @param cache The object to serialize * @param directory The target directory */ protected void serializeCache() { try { File file = new File(_directory, CACHE_META_FILENAME); // Synchronize on the cache object to keep the scheduled cache sync thread and // the thread processing servlet destroy from colliding. synchronized (cacheSerializerSyncObj) { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file)); os.writeObject(_cache.get()); os.close(); } } catch (Exception e) { if (log.isLoggable(Level.SEVERE)) log.log(Level.SEVERE, e.getMessage(), e); } } private void clean(File directory) { final File[] oldCacheFiles = directory.listFiles(); if (oldCacheFiles != null) { _aggregator.getExecutors().getScheduledExecutor().submit(new Runnable() { public void run() { for (File file : oldCacheFiles) { try { if (!file.delete()) { if (log.isLoggable(Level.WARNING)) { log.warning(MessageFormat.format(Messages.CacheManagerImpl_8, new Object[] { file.getAbsolutePath() })); } } } catch (Exception e) { if (log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, e.getMessage(), e); } } } } }); } } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#createCacheFileAsync(java.lang.String, java.io.Reader, com.ibm.jaggr.service.cache.ICacheManager.CreateCompletionCallback) */ @Override public void createCacheFileAsync(final String fileNamePrefix, final Reader reader, final CreateCompletionCallback callback) { createCacheFileAsync(fileNamePrefix, new ReaderInputStream(reader, "UTF-8"), callback); //$NON-NLS-1$ } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#createCacheFileAsync(java.lang.String, java.io.InputStream, com.ibm.jaggr.service.cache.ICacheManager.CreateCompletionCallback) */ @Override public void createCacheFileAsync(final String fileNamePrefix, final InputStream is, final CreateCompletionCallback callback) { _aggregator.getExecutors().getFileCreateExecutor().submit(new Runnable() { public void run() { File file = null; try { file = File.createTempFile(fileNamePrefix, ".cache", _directory); //$NON-NLS-1$ OutputStream os = new FileOutputStream(file); CopyUtil.copy(is, os); if (callback != null) { callback.completed(file.getName(), null); } } catch (IOException e) { if (log.isLoggable(Level.WARNING)) log.log(Level.WARNING, MessageFormat.format(Messages.CacheManagerImpl_4, new Object[] { file.getPath() }), e); if (callback != null) { callback.completed(file.getName(), e); } } } }); } @Override public void createNamedCacheFileAsync(final String filename, final InputStream is, final CreateCompletionCallback callback) { _aggregator.getExecutors().getFileCreateExecutor().submit(new Runnable() { File file = null; public void run() { try { file = new File(_directory, filename); OutputStream os = new FileOutputStream( file.isAbsolute() ? file : new File(_directory, file.getPath())); CopyUtil.copy(is, os); if (callback != null) { callback.completed(file.getName(), null); } } catch (IOException e) { if (log.isLoggable(Level.WARNING)) log.log(Level.WARNING, MessageFormat.format(Messages.CacheManagerImpl_4, new Object[] { file.getPath() }), e); if (callback != null) { callback.completed(file != null ? file.getName() : null, e); } } } }); } @Override public void createNamedCacheFileAsync(String fileNamePrefix, Reader reader, CreateCompletionCallback callback) { createNamedCacheFileAsync(fileNamePrefix, new ReaderInputStream(reader, "UTF-8"), callback); //$NON-NLS-1$ } /* (non-Javadoc) * @see com.ibm.jaggr.service.cache.ICacheManager#deleteFileDelayed(java.lang.String) */ public void deleteFileDelayed(final String fname) { _aggregator.getExecutors().getFileDeleteExecutor().schedule(new Runnable() { public void run() { File file = new File(_directory, fname); try { if (!file.delete()) { if (log.isLoggable(Level.WARNING)) { log.warning(MessageFormat.format(Messages.CacheManagerImpl_8, new Object[] { file.getAbsolutePath() })); } } } catch (Exception e) { if (log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, e.getMessage(), e); } } } }, _aggregator.getOptions().getDeleteDelay(), TimeUnit.SECONDS); } @Override public synchronized void optionsUpdated(IOptions options, long sequence) { if (options == null) { return; } if (_cache.get() == null || !options.getOptionsMap().equals(_control.optionsMap)) { Map<String, String> previousOptions = _control.optionsMap; _control.optionsMap = options.getOptionsMap(); if (_cache.get() == null || previousOptions != null) { if (sequence > updateSequenceNumber) { if (_cache.get() != null) { updateSequenceNumber = sequence; String msg = MessageFormat.format(Messages.CacheManagerImpl_5, new Object[] { _aggregator.getName() }); new ConsoleService().println(msg); if (log.isLoggable(Level.INFO)) { log.info(msg); } } clearCache(); } } } } @Override public synchronized void dependenciesLoaded(IDependencies deps, long sequence) { if (deps == null) { return; } long lastMod = deps.getLastModified(); if (_cache.get() == null || lastMod > _control.depsLastMod) { long previousLastMod = _control.depsLastMod; _control.depsLastMod = lastMod; if (previousLastMod != -1 || _cache.get() == null) { if (sequence > updateSequenceNumber) { if (_cache.get() != null) { updateSequenceNumber = sequence; String msg = MessageFormat.format(Messages.CacheManagerImpl_6, new Object[] { _aggregator.getName() }); new ConsoleService().println(msg); if (log.isLoggable(Level.INFO)) { log.info(msg); } } clearCache(); } } } } @Override public synchronized void configLoaded(IConfig config, long sequence) { if (config == null) { return; } String rawConfig = config.toString(); if (_cache.get() == null || !StringUtils.equals(rawConfig, _control.rawConfig)) { Object previousConfig = _control.rawConfig; _control.rawConfig = rawConfig; if (_cache.get() == null || previousConfig != null) { if (sequence > updateSequenceNumber) { if (_cache.get() != null) { updateSequenceNumber = sequence; String msg = MessageFormat.format(Messages.CacheManagerImpl_7, new Object[] { _aggregator.getName() }); new ConsoleService().println(msg); if (log.isLoggable(Level.INFO)) { log.info(msg); } } clearCache(); } } } } }