DirectoryResource.java
Author: Benoit Mahe

    // DirectoryResource.java
    // $Id: DirectoryResource.html,v 1.2 1999/10/27 22:10:34 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.*;
    import java.io.*;

    import org.w3c.tools.resources.indexer.*;
    import org.w3c.tools.resources.event.*;

    /**
     * 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 ;

        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) ;
        }

        /**
         * 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 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) ;
        }

        /**
         * 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);
    	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("identifier", 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 usefull 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;
        }

        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("."))
    	    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("identifier");
    	if (( name != null ) && (getDirectory() !=  null)) {
    	    attrs.put("directory", new File(getDirectory(), name));
    	    return context;
    	} else {
    	    return null;
    	}
        }

        /**
         * Reindex recursivly all the resources from this DirectoryResource.
         */
        public synchronized void reindex() {
    	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(10);
    			    defs.put("context", dir.getContext());
    			    //indexing ...
    			    Resource newdir = index(name, defs);
    			    // do we want it to keep its indexer?
    			    if (newdir == null) {
    				dir.reindex();
    			    } 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("")) {
    				    dir.reindex();
    				    indexer =
    					dir.getString(ATTR_INDEXER, null);
    				    reindexed.setValue(ATTR_INDEXER, indexer);
    				} else {
    				    dir.setValue(ATTR_INDEXER, indexer);
    				    dir.reindex();
    				}
    				//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 (lookup(lst[i]) == 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 (lookup(lst[i]) == 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.
         */

        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("directory")) {
    			File pdir = (File) parent.getValue("directory", 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();
        }

    }