/* $Id$ */
/**
* 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.manifoldcf.core.lockmanager;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.core.system.Logging;
import org.apache.manifoldcf.core.system.ManifoldCF;
import java.util.*;
import java.io.*;
/** This is the file-based lock manager.
*/
public class FileLockManager extends BaseLockManager
{
public static final String _rcsid = "@(#)$Id$";
/** Synchronization directory property - local to this implementation of ILockManager */
public static final String synchDirectoryProperty = "org.apache.manifoldcf.synchdirectory";
// These are for file-based locks (which cross JVM boundaries)
protected final static Integer lockPoolInitialization = new Integer(0);
protected static LockPool myFileLocks = null;
// This is the directory used for cross-JVM synchronization, or null if off
protected File synchDirectory = null;
public FileLockManager(File synchDirectory)
throws ManifoldCFException
{
this.synchDirectory = synchDirectory;
if (synchDirectory == null)
throw new ManifoldCFException("Synch directory cannot be null");
if (!synchDirectory.isDirectory())
throw new ManifoldCFException("Synch directory must point to an existing, writeable directory!",ManifoldCFException.SETUP_ERROR);
synchronized(lockPoolInitialization)
{
if (myFileLocks == null)
{
myFileLocks = new LockPool(new FileLockObjectFactory(synchDirectory));
}
}
}
public FileLockManager()
throws ManifoldCFException
{
this(getSynchDirectoryProperty());
}
/** Get the synch directory property. */
public static File getSynchDirectoryProperty()
throws ManifoldCFException
{
return ManifoldCF.getFileProperty(synchDirectoryProperty);
}
/** Calculate the name of a flag resource.
*@param flagName is the name of the flag.
*@return the name for the flag resource.
*/
protected static String getFlagResourceName(String flagName)
{
return "flag-"+flagName;
}
/** Raise a flag. Use this method to assert a condition, or send a global signal. The flag will be reset when the
* entire system is restarted.
*@param flagName is the name of the flag to set.
*/
@Override
public void setGlobalFlag(String flagName)
throws ManifoldCFException
{
String resourceName = getFlagResourceName(flagName);
String path = makeFilePath(resourceName);
(new File(path)).mkdirs();
File f = new File(path,ManifoldCF.safeFileName(resourceName));
try
{
f.createNewFile();
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException(e.getMessage(),e);
}
}
/** Clear a flag. Use this method to clear a condition, or retract a global signal.
*@param flagName is the name of the flag to clear.
*/
@Override
public void clearGlobalFlag(String flagName)
throws ManifoldCFException
{
String resourceName = getFlagResourceName(flagName);
File f = new File(makeFilePath(resourceName),ManifoldCF.safeFileName(resourceName));
f.delete();
}
/** Check the condition of a specified flag.
*@param flagName is the name of the flag to check.
*@return true if the flag is set, false otherwise.
*/
@Override
public boolean checkGlobalFlag(String flagName)
throws ManifoldCFException
{
String resourceName = getFlagResourceName(flagName);
File f = new File(makeFilePath(resourceName),ManifoldCF.safeFileName(resourceName));
return f.exists();
}
/** Read data from a shared data resource. Use this method to read any existing data, or get a null back if there is no such resource.
* Note well that this is not necessarily an atomic operation, and it must thus be protected by a lock.
*@param resourceName is the global name of the resource.
*@return a byte array containing the data, or null.
*/
@Override
public byte[] readData(String resourceName)
throws ManifoldCFException
{
File f = new File(makeFilePath(resourceName),ManifoldCF.safeFileName(resourceName));
try
{
InputStream is = new FileInputStream(f);
try
{
ByteArrayBuffer bab = new ByteArrayBuffer();
while (true)
{
int x = is.read();
if (x == -1)
break;
bab.add((byte)x);
}
return bab.toArray();
}
finally
{
is.close();
}
}
catch (FileNotFoundException e)
{
return null;
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO exception: "+e.getMessage(),e);
}
}
/** Write data to a shared data resource. Use this method to write a body of data into a shared resource.
* Note well that this is not necessarily an atomic operation, and it must thus be protected by a lock.
*@param resourceName is the global name of the resource.
*@param data is the byte array containing the data. Pass null if you want to delete the resource completely.
*/
@Override
public void writeData(String resourceName, byte[] data)
throws ManifoldCFException
{
try
{
String path = makeFilePath(resourceName);
// Make sure the directory exists
(new File(path)).mkdirs();
File f = new File(path,ManifoldCF.safeFileName(resourceName));
if (data == null)
{
f.delete();
return;
}
FileOutputStream os = new FileOutputStream(f);
try
{
os.write(data,0,data.length);
}
finally
{
os.close();
}
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO exception: "+e.getMessage(),e);
}
}
/** Override this method to change the nature of global locks.
*/
@Override
protected LockPool getGlobalLockPool()
{
return myFileLocks;
}
/** Create a file path given a key name.
*@param key is the key name.
*@return the file path.
*/
protected String makeFilePath(String key)
{
int hashcode = key.hashCode();
int outerDirNumber = (hashcode & (1023));
int innerDirNumber = ((hashcode >> 10) & (1023));
String fullDir = synchDirectory.toString();
if (fullDir.length() == 0 || !fullDir.endsWith("/"))
fullDir = fullDir + "/";
fullDir = fullDir + Integer.toString(outerDirNumber)+"/"+Integer.toString(innerDirNumber);
return fullDir;
}
protected static final int BASE_SIZE = 128;
protected static class ByteArrayBuffer
{
protected byte[] buffer;
protected int length;
public ByteArrayBuffer()
{
buffer = new byte[BASE_SIZE];
length = 0;
}
public void add(byte b)
{
if (length == buffer.length)
{
byte[] oldbuffer = buffer;
buffer = new byte[length * 2];
System.arraycopy(oldbuffer,0,buffer,0,length);
}
buffer[length++] = b;
}
public byte[] toArray()
{
byte[] rval = new byte[length];
System.arraycopy(buffer,0,rval,0,length);
return rval;
}
}
}