// DirectoryResource.java
// $Id: DirectoryResource.java,v 1.24 2003/06/06 14:23:14 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.tools.resources ;
import java.util.Enumeration;
import java.util.Hashtable;
import java.io.File;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import org.w3c.tools.resources.indexer.IndexerModule;
import org.w3c.tools.resources.indexer.ResourceIndexer;
import org.w3c.tools.resources.event.StructureChangedEvent;
/**
* A simple, and reasonably efficient directory resource.
*/
public class DirectoryResource extends ContainerResource {
/**
* Attribute index - The index for our directory attribute.
*/
protected static int ATTR_DIRECTORY = -1 ;
/**
* Attribute index - The last time we physically visited the directory.
*/
protected static int ATTR_DIRSTAMP = -1 ;
/**
* Attribute index - The indexer to use for that directory, if any.
*/
protected static int ATTR_INDEXER = -1;
/**
* Attribute index - The index of wether we are extensible.
*/
protected static int ATTR_EXTENSIBLE = -1 ;
/**
* Attribute index - The index of wether we can be shrinked.
*/
protected static int ATTR_SHRINKABLE = -1 ;
static String di = "directory".intern();
static {
Attribute a = null ;
Class cls = null ;
// Get a pointer to our class.
try {
cls = Class.forName("org.w3c.tools.resources.DirectoryResource") ;
} catch (Exception ex) {
ex.printStackTrace() ;
System.exit(1) ;
}
// The directory attribute.
a = new FileAttribute("directory"
, null
, Attribute.COMPUTED|Attribute.DONTSAVE);
ATTR_DIRECTORY = AttributeRegistry.registerAttribute(cls, a) ;
// The last time we visited the directory
a = new DateAttribute("dirstamp"
, null
, Attribute.COMPUTED) ;
ATTR_DIRSTAMP = AttributeRegistry.registerAttribute(cls, a) ;
// Our indexer name (optional).
a = new StringAttribute("indexer"
, null
, Attribute.EDITABLE) ;
ATTR_INDEXER = AttributeRegistry.registerAttribute(cls, a) ;
// Are we extensible (can we create resources on the fly):
a = new BooleanAttribute("extensible"
, Boolean.TRUE
, Attribute.EDITABLE) ;
ATTR_EXTENSIBLE = AttributeRegistry.registerAttribute(cls, a) ;
// Are we shrinkable (can we delete resources on the fly):
a = new BooleanAttribute("shrinkable"
, Boolean.TRUE
, Attribute.EDITABLE) ;
ATTR_SHRINKABLE = AttributeRegistry.registerAttribute(cls, a) ;
}
/**
* Get the indexer out of the given context.
* @return A ResourceIndexer instance, guaranteeed not to be <strong>
* null</strong>.
*/
protected ResourceReference getIndexer(ResourceContext c) {
IndexerModule m = (IndexerModule) c.getModule(IndexerModule.NAME);
ResourceReference rr = m.getIndexer(c);
return rr;
}
public void setValue(int idx, Object value) {
super.setValue(idx, value);
if ( idx == ATTR_INDEXER ) {
String indexer = getString(ATTR_INDEXER, null);
if ( indexer != null ) {
ResourceContext c = null;
IndexerModule m = null;
c = getContext();
m = (IndexerModule) c.getModule(IndexerModule.NAME);
m.registerIndexer(c, indexer);
}
}
}
/**
* Get the physical directory exported by this resource.
* @return A non-null File object giving the directory of this resource.
*/
public File getDirectory() {
return (File) getValue(ATTR_DIRECTORY, null) ;
}
/**
* Get the physical directory exported by this resource.
* @return A non-null File object giving the directory of this resource.
*/
public File unsafeGetDirectory() {
return (File) unsafeGetValue(ATTR_DIRECTORY, null) ;
}
/**
* Get the absolute time at which we examined the physicall directory.
* @return The date (as a long number of ms since Java epoch), or
* <strong>-1</strong> if we never examined it before.
*/
public long getDirStamp() {
return getLong(ATTR_DIRSTAMP, -1) ;
}
/**
* Get the extensible flag value.
* A DirectoryResource is extensible, if it is allowed to create new
* resources out of the file system knowledge on the fly.
* <p>Setting this flag might slow down the server. It unfortunatelly
* defaults to <strong>true</strong> until I have a decent admin
* program.
* @return A boolean <strong>true</strong> if the directory is
* extensible.
*/
public boolean getExtensibleFlag() {
return getBoolean(ATTR_EXTENSIBLE, true) ;
}
/**
* Get the extensible flag value.
* A DirectoryResource is extensible, if it is allowed to create new
* resources out of the file system knowledge on the fly.
* <p>Setting this flag might slow down the server. It unfortunatelly
* defaults to <strong>true</strong> until I have a decent admin
* program.
* @return A boolean <strong>true</strong> if the directory is
* extensible.
*/
public boolean getShrinkableFlag() {
return getBoolean(ATTR_SHRINKABLE, true) ;
}
/**
* Get the extensible flag value.
* A DirectoryResource is extensible, if it is allowed to create new
* resources out of the file system knowledge on the fly.
* <p>Setting this flag might slow down the server. It unfortunatelly
* defaults to <strong>true</strong> until I have a decent admin
* program.
* @return A boolean <strong>true</strong> if the directory is
* extensible.
*/
public boolean unsafeGetShrinkableFlag() {
Object value = unsafeGetValue(ATTR_SHRINKABLE, null) ;
if (value == null) {
return true;
} else if ( value instanceof Boolean ) {
return ((Boolean) value).booleanValue() ;
} else {
throw new IllegalAttributeAccess(this
, attributes[ATTR_SHRINKABLE]
, "getBoolean") ;
}
}
/**
* A resource is about to be removed
* This handles the <code>RESOURCE_REMOVED</code> kind of events.
* @param evt The event describing the change.
*/
public void resourceRemoved(StructureChangedEvent evt) {
super.resourceRemoved(evt);
if (! isUnloaded())
markModified();
}
/**
* Create a DirectoryResource and the physical directory too.
* @param name the name of the resource.
* @return A ResourceReference instance.
*/
public ResourceReference createDirectoryResource(String name) {
// Create an empty file:
File file = new File(getDirectory(), name) ;
boolean created = false ;
boolean exists_before = false ;
try {
if (file.exists()) {
if (! file.isDirectory())
created = false;
else
exists_before = true;
} else {
file.mkdir();
created = true;
}
} catch (Exception ex) {
created = false;
}
if (! created)
return null;
ResourceReference rr = createDefaultResource(name);
if (rr == null) {
if (!exists_before)
file.delete();
return null;
}
try {
Resource r = rr.lock();
if (! (r instanceof DirectoryResource)) {
try {
r.delete();
} catch (MultipleLockException ex) {
//OUCH!
//manual delete
}
if (!exists_before)
file.delete();
return null;
}
} catch (InvalidResourceException ex) {
if (!exists_before)
file.delete();
return null;
} finally {
rr.unlock();
}
return rr;
}
/**
* Create a Resource and the physical file too.
* @param name the name of the resource.
* @return A ResourceReference instance.
*/
public ResourceReference createResource(String name) {
return createResource(name, null);
}
/**
* Create a Resource and the physical file too.
* @param name the name of the resource.
* @param req the protocol request.
* @return A ResourceReference instance.
*/
public ResourceReference createResource(String name,
RequestInterface req)
{
// Create an empty file:
File file = new File(getDirectory(), name) ;
boolean created = false ;
if ( ! file.exists() ) {
try {
(new RandomAccessFile(file, "rw")).close() ;
created = true ;
} catch (Exception ex) {
created = false ;
}
}
if (! created)
return null;
ResourceReference rr = createDefaultResource(name, req);
//if (rr == null)
file.delete();
return rr;
}
/**
* Index a Resource. Call the indexer.
* @param name The name of the resource to index.
* @param defs The defaults attributes.
* @return A resource instance.
* @see org.w3c.tools.resources.indexer.SampleResourceIndexer
*/
private Resource index(String name, Hashtable defs) {
return index(name, defs, null);
}
/**
* Index a Resource. Call the indexer.
* @param name The name of the resource to index.
* @param defs The defaults attributes.
* @param req The protocol request.
* @return A resource instance.
* @see org.w3c.tools.resources.indexer.SampleResourceIndexer
*/
protected Resource index(String name,
Hashtable defs,
RequestInterface req)
{
// Prepare a set of default parameters for the resource:
defs.put(id, name);
updateDefaultChildAttributes(defs);
ResourceContext context = getContext();
// Try to get the indexer to create the resource:
Resource resource = null;
ResourceReference rr_indexer = null;
ResourceReference rr_lastidx = null;
while ( context != null ) {
// Lookup for next indexer in hierarchy:
do {
rr_indexer = getIndexer(context);
context = context.getParent();
} while ((rr_indexer == rr_lastidx) && (context != null));
// Is this a useful indexer ?
if ((rr_lastidx = rr_indexer) != null ) {
try {
ResourceIndexer indexer =
(ResourceIndexer)rr_indexer.lock();
resource = indexer.createResource(this,
req,
getDirectory(),
name,
defs) ;
if ( resource != null )
break;
} catch (InvalidResourceException ex) {
resource = null;
} finally {
rr_indexer.unlock();
}
}
}
return resource;
}
/**
* Get the name of the resource relative to the given filename.
* @param name The name of the file.
* @return a String, the resource name.
* @see org.w3c.tools.resources.indexer.SampleResourceIndexer
*/
protected String getIndexedName(String name) {
ResourceContext context = getContext();
String indexed = null;
ResourceReference rr_indexer = null;
ResourceReference rr_lastidx = null;
while ( context != null ) {
// Lookup for next indexer in hierarchy:
do {
rr_indexer = getIndexer(context);
context = context.getParent();
} while ((rr_indexer == rr_lastidx) && (context != null));
if ((rr_lastidx = rr_indexer) != null ) {
try {
ResourceIndexer indexer =
(ResourceIndexer)rr_indexer.lock();
indexed = indexer.getIndexedName(getDirectory(), name);
if ( indexed != null )
break;
} catch (InvalidResourceException ex) {
indexed = null;
} finally {
rr_indexer.unlock();
}
}
}
return ((indexed == null) ? name : indexed);
}
public synchronized ResourceReference createDefaultResource(String name) {
return createDefaultResource(name, null);
}
/**
* Try creating a default resource having the given name.
* This method will make its best effort to create a default resource
* having this name in the directory. If a file with this name exists,
* it will check the pre-defined admin extensions and look for a match.
* If a directory with this name exists, and admin allows to do so, it
* will create a sub-directory resource.
* @param name The name of the resource to try to create.
* @param req The incomming request
* @return A Resource instance, if possible, <strong>null</strong>
* otherwise.
*/
protected synchronized
ResourceReference createDefaultResource(String name,
RequestInterface req)
{
// Don't automagically create resources of name '..' or '.'
if (name.equals("..") || name.equals(".")
|| (name.indexOf('\\') >= 0 )) {
return null ;
}
Hashtable defs = new Hashtable(10) ;
Resource resource = index(name, defs, req);
// Did we finally create a resource ?
ResourceReference rr = null;
if ( resource != null ) {
// Register this child in our store:
rr = addResource(resource, defs) ;
markModified() ;
}
return rr ;
}
/**
* Initialize and register a new resource into this directory.
* @param resource The uninitialized resource to be added.
*/
protected ResourceContext updateDefaultChildAttributes(Hashtable attrs) {
ResourceContext context = null;
context = super.updateDefaultChildAttributes(attrs);
String name = (String) attrs.get(id);
if (( name != null ) && (getDirectory() != null)) {
attrs.put(di, new File(getDirectory(), name));
}
return context;
}
/**
* Reindex recursivly all the resources from this DirectoryResource.
* @param rec recursivly?
*/
public synchronized void reindex(boolean rec) {
if (getExtensibleFlag()) {
Enumeration e = enumerateAllResourceIdentifiers();
String name = null;
ResourceReference rr = null;
Resource r = null;
while (e.hasMoreElements()) {
name = (String) e.nextElement();
rr = lookup(name);
if (rr != null) {
try {
r = rr.lock();
// forbid cycles
if (r == this)
continue;
if (r instanceof DirectoryResource) {
//launch reindex
DirectoryResource dir = (DirectoryResource) r;
//reindex directory itself
//the new diretory must have the same context
Hashtable defs = new Hashtable(5);
defs.put(co, dir.getContext());
//indexing ...
Resource newdir = index(name, defs);
// do we want it to keep its indexer?
if ((newdir == null) && rec) {
dir.reindex(true);
} else {
if (! (newdir instanceof DirectoryResource)) {
throw new RuntimeException(
"Reindex Error : "+
name+" can't be reindexed. "+
"The reindexed resource is "+
"no more a DirectoryResource.");
}
DirectoryResource reindexed =
(DirectoryResource) newdir;
String indexer =
reindexed.getString(ATTR_INDEXER, "");
if (indexer.equals("")) {
if (rec) {
dir.reindex(true);
}
indexer =
dir.getString(ATTR_INDEXER, null);
reindexed.setValue(ATTR_INDEXER, indexer);
} else {
dir.setValue(ATTR_INDEXER, indexer);
if (rec) {
dir.reindex(true);
}
}
//move children to the reindexed directory
reindexed.setValue(ATTR_KEY, dir.getKey());
dir.setValue(ATTR_IDENTIFIER,
name+"-bakindex");
addResource(reindexed, defs);
// Now replace the old DirectoryResource
// by the new one
try {
dir.replace(reindexed);
} catch (MultipleLockException ex) {
throw new RuntimeException(
"Reindex Error : "+
ex.getMessage());
}
}
} else if (! (r instanceof AbstractContainer)) {
//leaf
Hashtable resdefs = new Hashtable(10);
Resource resource = index(name, resdefs);
if (resource != null) {
try {
r.delete();
} catch (MultipleLockException ex) {
throw new RuntimeException(
"Reindex Error : "+
ex.getMessage());
}
addResource(resource, resdefs);
}
}
} catch (InvalidResourceException ex) {
System.out.println(ex.getMessage());
} finally {
rr.unlock();
}
}
}
markModified();
}
}
/**
* Enumerate all available children resource identifiers.
* This method <em>requires</em> that we create all our pending resources.
* @return An enumeration of all our resources.
*/
protected synchronized Enumeration enumerateAllResourceIdentifiers() {
File directory = getDirectory() ;
if ( directory != null ) {
synchronized(this) {
String lst[] = directory.list() ;
if ( lst != null ) {
for (int i = 0 ; i < lst.length ; i++) {
if (lst[i].equals(".") || lst[i].equals(".."))
continue ;
if (super.lookup(lst[i]) == null) {
String indexed = getIndexedName(lst[i]);
if (indexed.equals(lst[i]))
createDefaultResource(lst[i]) ;
else if (super.lookup(indexed) == null)
createDefaultResource(lst[i]) ;
}
}
}
}
}
return super.enumerateResourceIdentifiers(true);
}
/**
* Enumerate all available children resource identifiers.
* This method <em>requires</em> that we create all our pending resources
* if we are in the extensible mode...too bad !
* @return An enumeration of all our resources.
*/
public synchronized Enumeration enumerateResourceIdentifiers(boolean all) {
// If extensible, update if needed:
if (all && getExtensibleFlag() ) {
File directory = getDirectory() ;
if ( directory != null ) {
synchronized(this) {
long dirstamp = directory.lastModified() ;
if ( dirstamp > getDirStamp() ) {
String lst[] = directory.list() ;
if ( lst != null ) {
for (int i = 0 ; i < lst.length ; i++) {
if (lst[i].equals(".") || lst[i].equals(".."))
continue ;
if (super.lookup(lst[i]) == null) {
String indexed = getIndexedName(lst[i]);
if (indexed.equals(lst[i]))
createDefaultResource(lst[i]) ;
else if (super.lookup(indexed) == null)
createDefaultResource(lst[i]) ;
}
}
}
setLong(ATTR_DIRSTAMP, dirstamp) ;
}
}
}
}
return super.enumerateResourceIdentifiers(all);
}
/**
* Lookup the resource having the given name in this directory.
* @param name The name of the resource.
* @return A resource instance, or <strong>null</strong>.
*/
public ResourceReference lookup(String name)
{
ResourceReference rr = null;
// Try our store:
rr = super.lookup(name);
if (rr != null)
return rr;
// If allowed, than try a default fallback:
return getExtensibleFlag() ? createDefaultResource(name) : null ;
}
/**
* Delete this directory resource, for ever.
* This method will delete the directory resource, and its associated
* store, <strong>along</strong> with any of the sub-resources it contains.
* Deleting the root directory of your server might take sometime...
* <p>Once the resource is deleted, it isx1 removed from its inital store
* and will not be unpickleable any more.
* @exception MultipleLockException if someone has locked this resource.
*/
public synchronized void delete()
throws MultipleLockException
{
disableEvent();
// Remove all the defined resources in this directory
// Set the extensible flag to false, otherwise, the directory grows
// as we shrink it :-)
setBoolean(ATTR_EXTENSIBLE, false);
super.delete();
}
/**
* Was return false (don't khow why)
*/
public synchronized boolean verify() {
return getDirectory().exists();
}
/**
* Initialize this directory resource with the given set of attributes.
* @param values The attribute values.
*/
public void initialize(Object values[]) {
super.initialize(values) ;
disableEvent();
// Get our parent resource and compute our directory:
File dir = null ;
if ( ! definesAttribute(ATTR_DIRECTORY) ) {
// Get our parent:
ResourceReference rr = getParent();
if (rr != null) {
try {
Resource parent = rr.lock();
if (parent.definesAttribute(di)) {
File pdir = (File) parent.getValue(di, null);
if ( pdir != null ) {
// Compute and set our directory attribute:
dir = new File(pdir, getIdentifier()) ;
setValue(ATTR_DIRECTORY, dir) ;
}
}
} catch (InvalidResourceException ex) {
} finally {
rr.unlock();
}
}
} else {
dir = getDirectory();
}
// Register our specific indexer, if any:
ResourceContext c = getContext();
String indexer = getString(ATTR_INDEXER, null);
if (( indexer != null ) && (!indexer.equals(""))) {
IndexerModule m = (IndexerModule)c.getModule(IndexerModule.NAME);
m.registerIndexer(c, indexer);
}
enableEvent();
}
}