Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jackrabbit.core.data;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.util.TransientFileFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class implements a LRU cache used by {@link CachingDataStore}. If cache
* size exceeds limit, this cache goes in purge mode. In purge mode any
* operation to cache is no-op. After purge cache size would be less than
* cachePurgeResizeFactor * maximum size.
*/
public class LocalCache {
/**
* Logger instance.
*/
static final Logger LOG = LoggerFactory.getLogger(LocalCache.class);
/**
* The file names of the files that need to be deleted.
*/
final Set toBeDeleted = new HashSet();
/**
* The filename Vs file size LRU cache.
*/
LRUCache cache;
/**
* The directory where the files are created.
*/
private final File directory;
/**
* The directory where tmp files are created.
*/
private final File tmp;
/**
* If true cache is in purgeMode and not available. All operation would be
* no-op.
*/
private volatile boolean purgeMode;
private AsyncUploadCache asyncUploadCache;
private AtomicLong cacheMissCounter = new AtomicLong();
private AtomicLong cacheMissDuration = new AtomicLong();
/**
* Build LRU cache of files located at 'path'. It uses lastModified property
* of file to build LRU cache. If cache size exceeds limit size, this cache
* goes in purge mode. In purge mode any operation to cache is no-op.
*
* @param path file system path
* @param tmpPath temporary directory used by cache.
* @param maxSizeInBytes maximum size of cache.
* @param cachePurgeTrigFactor factor which triggers cache to purge mode.
* That is if current size exceed (cachePurgeTrigFactor * maxSizeInBytes), the
* cache will go in auto-purge mode.
* @param cachePurgeResizeFactor after cache purge size of cache will be
* just less (cachePurgeResizeFactor * maxSizeInBytes).
* @param asyncUploadCache {@link AsyncUploadCache}
*/
public LocalCache(String path, String tmpPath, long maxSizeInBytes, double cachePurgeTrigFactor,
double cachePurgeResizeFactor, AsyncUploadCache asyncUploadCache) {
directory = new File(path);
tmp = new File(tmpPath);
LOG.info(
"cachePurgeTrigFactor =[{}], cachePurgeResizeFactor =[{}], " +
"cachePurgeTrigFactorSize =[{}], cachePurgeResizeFactorSize =[{}]",
new Object[] { cachePurgeTrigFactor, cachePurgeResizeFactor,
(cachePurgeTrigFactor * maxSizeInBytes),
(cachePurgeResizeFactor * maxSizeInBytes) });
cache = new LRUCache(maxSizeInBytes, cachePurgeTrigFactor, cachePurgeResizeFactor);
this.asyncUploadCache = asyncUploadCache;
new Thread(new CacheBuildJob()).start();
}
/**
* Store an item in the cache and return the input stream. If cache is in
* purgeMode or file doesn't exists, inputstream from a
* {@link TransientFileFactory#createTransientFile(String, String, File)} is
* returned. Otherwise inputStream from cached file is returned. This method
* doesn't close the incoming inputstream.
*
* @param fileName the key of cache.
* @param in {@link InputStream}
* @return the (new) input stream.
*/
public InputStream store(String fileName, final InputStream in)
throws IOException {
fileName = fileName.replace("\\", "/");
File f = getFile(fileName);
long length = 0;
if (!f.exists() || isInPurgeMode()) {
OutputStream out = null;
File transFile = null;
try {
TransientFileFactory tff = TransientFileFactory.getInstance();
transFile = tff.createTransientFile("s3-", "tmp", tmp);
out = new BufferedOutputStream(new FileOutputStream(transFile));
length = IOUtils.copyLarge(in, out);
} finally {
IOUtils.closeQuietly(out);
}
// rename the file to local fs cache
if (canAdmitFile(length)
&& (f.getParentFile().exists() || f.getParentFile().mkdirs())
&& transFile.renameTo(f) && f.exists()) {
if (transFile.exists() && transFile.delete()) {
LOG.info("tmp file [{}] not deleted successfully",
transFile.getAbsolutePath());
}
transFile = null;
LOG.debug(
"file [{}] doesn't exists. adding to local cache using inputstream.",
fileName);
cache.put(fileName, f.length());
} else {
LOG.debug(
"file [{}] doesn't exists. returning transient file [{}].",
fileName, transFile.getAbsolutePath());
f = transFile;
}
} else {
f.setLastModified(System.currentTimeMillis());
LOG.debug(
"file [{}] exists. adding to local cache using inputstream.",
fileName);
cache.put(fileName, f.length());
}
tryPurge();
return new LazyFileInputStream(f);
}
/**
* Store an item along with file in cache. Cache size is increased by
* {@link File#length()} If file already exists in cache,
* {@link File#setLastModified(long)} is updated with current time.
*
* @param fileName the key of cache.
* @param src file to be added to cache.
*/
public File store(String fileName, final File src) {
try {
return store(fileName, src, false).getFile();
} catch (IOException ioe) {
LOG.warn("Exception in addding file [" + fileName + "] to local cache.", ioe);
}
return null;
}
/**
* This method add file to {@link LocalCache} and tries that file can be
* added to {@link AsyncUploadCache}. If file is added to
* {@link AsyncUploadCache} successfully, it sets
* {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to true.
*
* @param fileName name of the file.
* @param src source file.
* @param tryForAsyncUpload If true it tries to add fileName to
* {@link AsyncUploadCache}
* @return {@link AsyncUploadCacheResult}. This method sets
* {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to true, if
* fileName is added to {@link AsyncUploadCache} successfully else
* it sets {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to
* false. {@link AsyncUploadCacheResult#getFile()} contains cached
* file, if it is added to {@link LocalCache} or original file.
* @throws IOException
*/
public AsyncUploadCacheResult store(String fileName, File src,
boolean tryForAsyncUpload) throws IOException {
fileName = fileName.replace("\\", "/");
File dest = getFile(fileName);
File parent = dest.getParentFile();
AsyncUploadCacheResult result = new AsyncUploadCacheResult();
result.setFile(src);
result.setAsyncUpload(false);
boolean destExists = false;
if ((destExists = dest.exists())
|| (src.exists() && !dest.exists() && !src.equals(dest)
&& canAdmitFile(src.length())
&& (parent.exists() || parent.mkdirs()) && (src.renameTo(dest)))) {
if (destExists) {
dest.setLastModified(System.currentTimeMillis());
}
LOG.debug("file [{}] moved to [{}] ", src.getAbsolutePath(), dest.getAbsolutePath());
LOG.debug(
"file [{}] exists= [{}] added to local cache, isLastModified [{}]",
new Object[] { dest.getAbsolutePath(), dest.exists(),
destExists });
cache.put(fileName, dest.length());
result.setFile(dest);
if (tryForAsyncUpload) {
result.setAsyncUpload(asyncUploadCache.add(fileName).canAsyncUpload());
}
} else {
LOG.info("file [{}] exists= [{}] not added to local cache.",
fileName, destExists);
}
tryPurge();
return result;
}
/**
* Return the inputstream from from cache, or null if not in the cache.
*
* @param fileName name of file.
* @return stream or null.
*/
public InputStream getIfStored(String fileName) throws IOException {
File file = getFileIfStored(fileName);
return file == null ? null : new LazyFileInputStream(file);
}
public File getFileIfStored(String fileName) throws IOException {
fileName = fileName.replace("\\", "/");
File f = getFile(fileName);
long diff = (System.currentTimeMillis() - cacheMissDuration.get()) / 1000;
// logged at 5 minute interval minimum
if (diff > 5 * 60) {
LOG.info("local cache misses [{}] in [{}] sec", new Object[] {
cacheMissCounter.getAndSet(0), diff });
cacheMissDuration.set(System.currentTimeMillis());
}
// return file in purge mode = true and file present in asyncUploadCache
// as asyncUploadCache's files will be not be deleted in cache purge.
if (!f.exists() || (isInPurgeMode() && !asyncUploadCache.hasEntry(fileName, false))) {
LOG.debug(
"getFileIfStored returned: purgeMode=[{}], file=[{}] exists=[{}]",
new Object[] { isInPurgeMode(), f.getAbsolutePath(), f.exists() });
cacheMissCounter.incrementAndGet();
return null;
} else {
// touch entry in LRU caches
f.setLastModified(System.currentTimeMillis());
cache.get(fileName);
return f;
}
}
/**
* Delete file from cache. Size of cache is reduced by file length. The
* method is no-op if file doesn't exist in cache.
*
* @param fileName file name that need to be removed from cache.
*/
public void delete(String fileName) {
if (isInPurgeMode()) {
LOG.debug("purgeMode true :delete returned");
return;
}
fileName = fileName.replace("\\", "/");
cache.remove(fileName);
}
/**
* Returns length of file if exists in cache else returns null.
* @param fileName name of the file.
*/
public Long getFileLength(String fileName) {
Long length = null;
try {
length = cache.get(fileName);
if( length == null ) {
File f = getFileIfStored(fileName);
if (f != null) {
length = f.length();
}
}
} catch (IOException ignore) {
}
return length;
}
/**
* Close the cache. Cache maintain set of files which it was not able to
* delete successfully. This method will an attempt to delete all
* unsuccessful delete files.
*/
public void close() {
LOG.debug("close");
deleteOldFiles();
}
/**
* Check if cache can admit file of given length.
* @param length of the file.
* @return true if yes else return false.
*/
private boolean canAdmitFile(final long length) {
//order is important here
boolean value = !isInPurgeMode() && (cache.canAdmitFile(length));
if (!value) {
LOG.debug("cannot admit file of length=[{}] and currentSizeInBytes=[{}] ",
length, cache.currentSizeInBytes);
}
return value;
}
/**
* Return true if cache is in purge mode else return false.
*/
synchronized boolean isInPurgeMode() {
return purgeMode;
}
/**
* Set purge mode. If set to true all cache operation will be no-op. If set
* to false, all operations to cache are available.
*
* @param purgeMode purge mode
*/
synchronized void setPurgeMode(final boolean purgeMode) {
this.purgeMode = purgeMode;
}
File getFile(final String fileName) {
return new File(directory, fileName);
}
private void deleteOldFiles() {
int initialSize = toBeDeleted.size();
int count = 0;
for (String fileName : new ArrayList(toBeDeleted)) {
fileName = fileName.replace("\\", "/");
if( cache.remove(fileName) != null) {
count++;
}
}
LOG.info("deleted [{}]/[{}] files.", count, initialSize);
}
/**
* This method tries to delete a file. If it is not able to delete file due
* to any reason, it add it toBeDeleted list.
*
* @param fileName name of the file which will be deleted.
* @return true if this method deletes file successfuly else return false.
*/
boolean tryDelete(final String fileName) {
LOG.debug("try deleting file [{}]", fileName);
File f = getFile(fileName);
if (f.exists() && f.delete()) {
LOG.info("File [{}] deleted successfully", f.getAbsolutePath());
toBeDeleted.remove(fileName);
while (true) {
f = f.getParentFile();
if (f.equals(directory) || f.list().length > 0) {
break;
}
// delete empty parent folders (except the main directory)
f.delete();
}
return true;
} else if (f.exists()) {
LOG.info("not able to delete file [{}]", f.getAbsolutePath());
toBeDeleted.add(fileName);
return false;
}
return true;
}
static int maxSizeElements(final long bytes) {
// after a CQ installation, the average item in
// the data store is about 52 KB
int count = (int) (bytes / 65535);
count = Math.max(1024, count);
count = Math.min(64 * 1024, count);
return count;
}
/**
* This method tries purging of local cache. It checks if local cache
* has exceeded the defined limit then it triggers purge cache job in a
* seperate thread.
*/
synchronized void tryPurge() {
if (!isInPurgeMode()
&& cache.currentSizeInBytes > cache.cachePurgeTrigSize) {
setPurgeMode(true);
LOG.info(
"cache.entries = [{}], currentSizeInBytes=[{}] exceeds cachePurgeTrigSize=[{}]",
new Object[] { cache.size(), cache.currentSizeInBytes,
cache.cachePurgeTrigSize });
new Thread(new PurgeJob()).start();
} else {
LOG.debug(
"currentSizeInBytes=[{}],cachePurgeTrigSize=[{}], isInPurgeMode =[{}]",
new Object[] { cache.currentSizeInBytes,
cache.cachePurgeTrigSize, isInPurgeMode() });
}
}
/**
* A LRU based extension {@link LinkedHashMap}. The key is file name and
* value is length of file.
*/
private class LRUCache extends LinkedHashMap {
private static final long serialVersionUID = 1L;
volatile long currentSizeInBytes;
final long maxSizeInBytes;
final long cachePurgeResize;
final long cachePurgeTrigSize;
LRUCache(final long maxSizeInBytes,
final double cachePurgeTrigFactor,
final double cachePurgeResizeFactor) {
super(maxSizeElements(maxSizeInBytes), (float) 0.75, true);
this.maxSizeInBytes = maxSizeInBytes;
this.cachePurgeTrigSize = new Double(cachePurgeTrigFactor
* maxSizeInBytes).longValue();
this.cachePurgeResize = new Double(cachePurgeResizeFactor
* maxSizeInBytes).longValue();
}
/**
* Overridden {@link Map#remove(Object)} to delete corresponding file
* from file system.
*/
@Override
public synchronized Long remove(final Object key) {
String fileName = (String) key;
fileName = fileName.replace("\\", "/");
try {
// not removing file from local cache, if there is in progress
// async upload on it.
if (asyncUploadCache.hasEntry(fileName, false)) {
LOG.info(
"AsyncUploadCache upload contains file [{}]. Not removing it from LocalCache.",
fileName);
return null;
}
} catch (IOException e) {
LOG.debug("error: ", e);
return null;
}
Long flength = null;
if (tryDelete(fileName)) {
flength = super.remove(key);
if (flength != null) {
LOG.debug("cache entry [{}], with size [{}] removed.",
fileName, flength);
currentSizeInBytes -= flength.longValue();
}
} else if (!getFile(fileName).exists()) {
// second attempt. remove from cache if file doesn't exists
flength = super.remove(key);
if (flength != null) {
LOG.debug(
"file not exists. cache entry [{}], with size [{}] removed.",
fileName, flength);
currentSizeInBytes -= flength.longValue();
}
} else {
LOG.info("not able to remove cache entry [{}], size [{}]", key,
super.get(key));
}
return flength;
}
@Override
public Long put(final String fileName, final Long value) {
if( isInPurgeMode()) {
LOG.debug("cache is purge mode: put is no-op");
return null;
}
synchronized (this) {
Long oldValue = cache.get(fileName);
if (oldValue == null) {
long flength = value.longValue();
currentSizeInBytes += flength;
return super.put(fileName.replace("\\", "/"), value);
}
toBeDeleted.remove(fileName);
return oldValue;
}
}
@Override
public Long get(Object key) {
if( isInPurgeMode()) {
LOG.debug("cache is purge mode: get is no-op");
return null;
}
synchronized (this) {
return super.get(key);
}
}
/**
* This method check if cache can admit file of given length.
* @param length length of file.
* @return true if cache size + length is less than maxSize.
*/
synchronized boolean canAdmitFile(final long length) {
return cache.currentSizeInBytes + length < cache.maxSizeInBytes;
}
}
/**
* This class performs purging of local cache. It implements
* {@link Runnable} and should be invoked in a separate thread.
*/
private class PurgeJob implements Runnable {
public PurgeJob() {
// TODO Auto-generated constructor stub
}
/**
* This method purges local cache till its size is less than
* cacheResizefactor * maxSize
*/
@Override
public void run() {
try {
synchronized (cache) {
// first try to delete toBeDeleted files
int initialSize = cache.size();
LOG.info(" cache purge job started. initial cache entries = [{}]", initialSize);
for (String fileName : new ArrayList(toBeDeleted)) {
cache.remove(fileName);
}
int skipCount = 0;
Iterator> itr = cache.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry entry = itr.next();
if (entry.getKey() != null) {
if (cache.currentSizeInBytes > cache.cachePurgeResize) {
if (cache.remove(entry.getKey()) != null) {
itr = cache.entrySet().iterator();
for (int i = 0; i < skipCount && itr.hasNext(); i++) {
itr.next();
}
} else {
skipCount++;
}
} else {
break;
}
}
}
LOG.info(
" cache purge job completed: cleaned [{}] files and currentSizeInBytes = [{}]",
(initialSize - cache.size()), cache.currentSizeInBytes);
}
} catch (Exception e) {
LOG.error("error in purge jobs:", e);
} finally {
setPurgeMode(false);
}
}
}
/**
* This class implements {@link Runnable} interface to build LRU cache
* asynchronously.
*/
private class CacheBuildJob implements Runnable {
public void run() {
long startTime = System.currentTimeMillis();
ArrayList allFiles = new ArrayList();
Iterator it = FileUtils.iterateFiles(directory, null, true);
while (it.hasNext()) {
File f = it.next();
allFiles.add(f);
}
long t1 = System.currentTimeMillis();
LOG.debug("Time taken to recursive [{}] took [{}] sec",
allFiles.size(), ((t1 - startTime) / 1000));
String dataStorePath = directory.getAbsolutePath();
// convert to java path format
dataStorePath = dataStorePath.replace("\\", "/");
LOG.info("directoryPath = " + dataStorePath);
String tmpPath = tmp.getAbsolutePath();
tmpPath = tmpPath.replace("\\", "/");
LOG.debug("tmp path [{}]", tmpPath);
long time = System.currentTimeMillis();
int count = 0;
for (File f : allFiles) {
if (f.exists()) {
count++;
String name = f.getPath();
String filePath = f.getAbsolutePath();
// convert to java path format
name = name.replace("\\", "/");
filePath = filePath.replace("\\", "/");
// skipped any temp file
if(filePath.startsWith(tmpPath) ) {
LOG.info ("tmp file [{}] skipped ", filePath);
continue;
}
if (filePath.startsWith(dataStorePath)) {
name = filePath.substring(dataStorePath.length());
}
if (name.startsWith("/") || name.startsWith("\\")) {
name = name.substring(1);
}
store(name, f);
long now = System.currentTimeMillis();
if (now > time + 10000) {
LOG.info("Processed {" + (count) + "}/{" + allFiles.size() + "}");
time = now;
}
}
}
LOG.debug(
"Processed [{}]/[{}], currentSizeInBytes = [{}], maxSizeInBytes = [{}], cache.filecount = [{}]",
new Object[] { count, allFiles.size(),
cache.currentSizeInBytes, cache.maxSizeInBytes,
cache.size() });
long t3 = System.currentTimeMillis();
LOG.info("Time to build cache of [{}] files took [{}] sec",
allFiles.size(), ((t3 - startTime) / 1000));
}
}
}