// ResourceSpaceImpl.java
// $Id: ResourceSpaceImpl.java,v 1.6 1997/07/30 14:08:02 ylafon Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.tools.resources.impl;

import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;

import w3c.util.*;
import w3c.tools.resources.*;
import w3c.tools.resources.event.*;
import w3c.tools.resources.http.*;
import w3c.tools.resources.impl.*;

/**
 * A property resource space 
 */

public class ResourceSpaceImpl implements ResourceSpace, CacheRevalidator {

  /**
   * the ResourceSaver
   */
  protected ResourceSaver saver = null;

  /**
   * the space directory.
   */
  protected File rootdir = null;

  /**
   * The cache.
   */
  protected ResourceCache cache = null;

  /**
   * The root container.
   */
  protected ResourceContainerImpl root = null;

  protected ResourceReference rootReference = null;

  protected ResourceStore rootStore = null;

  public ResourceReference getRootReference() {
    return rootReference;
  }
  
  public ResourceStore getRootStore() {
    return rootStore;
  }

  /**
   * The list of structure changed event listener.
   */
  protected transient StructureChangedListener structListener = null;
  
  /**
   * Properties - Name of the space directory
   */
  public static final 
  String ROOTDIR_P = "w3c.tools.resources.impl.rootDir";

  /**
   * Properties - Name of the resource saver class.
   */
  public static final
  String SAVERCLASS_P = "w3c.tools.resources.impl.Saver";

  /**
   * Properties - Name of the resource store class.
   */
  public static final
  String STORECLASS_P = "w3c.tools.resources.impl.Store";


  public ResourceCache getCache() {
    return cache;
  }

  /**
   * revalidate the Resource relative to the given PersistentReference.
   * Reload resource from disk.
   * @param pr The PersistentReference of the resource to revalidate
   * @return the resource revalidated.
   */

  public Resource revalidate(PersistentReference pr) {
    Resource result = null;
    PersistentReferenceImpl prImpl = (PersistentReferenceImpl) pr;
    String contPath = prImpl.containerPath;
    contPath = contPath.substring((rootdir.getAbsolutePath()).length());
    LookupState lookup = new LookupState( this, contPath);
    ResourceReference rr = resolve(lookup);

    try {
      ResourceContainerImpl r = (ResourceContainerImpl)rr.lock();
      result = r.revalidate(prImpl.resourceName);
    } finally {
      rr.unlock();
    }
    return result;
  }


  /**
   * Resolve the given <em>state</em> into a resource.
   * This metho is allowed (and should) perform any security check before 
   * granting access to the resource. Resources themselves may be used
   * to ensure finer grain access control.
   * @param state The lookup state to resolve into a resource.
   * @return A resource <em>reference</em> which guarantees correct accesses
   * to resources even if these are cached versions of what really 
   * is in some store.
   */
  public ResourceReference resolve(LookupState state) {
    return root.resolve(state);
  }

  /**
   * Check point that resource space.
   * The caller wants to be sure that any changes to that space has been
   * persistently applied, whatever this means.
   */
  public void checkpoint(){
    root.unload();
  }

  /**
   * Save the given resource in a properties file located in the
   * container directory.
   * @param r The PropertyResource to save
   */
  public synchronized void save (Resource r) 
    throws ChildNotSupportedException
  {
    saver.save(r);
  }

  public synchronized ResourceReference load(PersistentReference pr,
					     ResourceReference container) 
    throws ClassNotFoundException
  {
    return saver.load(pr,container);
  }

  public synchronized void delete (Resource r) {
    saver.delete(r);
  }

  public synchronized void delete (String name, ResourceReference container) {
    saver.delete(name,container);
  }

  /**
   * Get access (if granted) to that space configuration.
   * As for anything else, the configuration is made accessible through
   * a resource which describes (and export) the possible configuration 
   * options to the outside world.
   * @return A Resource instance, describing and providing access to the
   * various configuration options.
   * @exception SecurityException If access to the configuration of that
   * space is denied to the caller.
   */
  public Resource getConfigResource() {
    return null;
  }

  public PropertyHolder getPropertyHolder(Runner run, Resource r) {
    return  saver.getPropertyHolder(run,r);
  }

  /**
   * Trigger a resource unloaded event.
   * This method is called by resources themselves to notify the resource
   * space manager of the event.
   * @param resource The resource being unloaded.
   */
  public void resourceUnloaded(Resource resource) {
      fireStructureEvent(StructureChangedEvent.RESOURCE_UNLOADED, resource);
  }

  /**
   * Trigger a resource deleted event.
   * This method is called by resources themselves to notify the resource
   * space manager of the event.
   * @param resource The resource being deleted.
   */
  public void resourceDeleted(Resource resource) {
    fireStructureEvent(StructureChangedEvent.RESOURCE_DELETED, resource);
  }
  
  /**
   * Trigger a resource inited event.
   * This method is called by resources themselves to notify the space
   * manager of the event.
   * @param resource The resource that has been initialized.
   */
  public void resourceInited(Resource resource) {
    fireStructureEvent(StructureChangedEvent.RESOURCE_INITED, resource);
  }

  /**
   * Trigger a resource created event.
   * This method is called by resources themselves to notify the space
   * manager of the event.
   * @param resource The resource that has been created.
   */
  public void resourceCreated(Resource resource) {
    fireStructureEvent(StructureChangedEvent.RESOURCE_CREATED, resource);
  }

  /**
   * Fire a resource deleted,created, unloaded event.
   * @param resource The resource that has been deleted.
   */
  protected void fireStructureEvent(int type, Resource resource) {
    if ( structListener == null )
      return;
    StructureChangedEvent e = null;
    e = new StructureChangedEvent(this, resource, type);
    switch(type) {
    case e.RESOURCE_INITED:
      structListener.resourceInited(e);
      break;
    case e.RESOURCE_UNLOADED:
      structListener.resourceUnloaded(e);
      break;
    case e.RESOURCE_CREATED:
      structListener.resourceCreated(e);
      break;
    case e.RESOURCE_DELETED:
      structListener.resourceDeleted(e);
      break;
    }
  }

  /**
   * Add a structure listener on that space.
   * Structure listeners are meant to track structural changes to a resource
   * space. This includes creation and deletion of resources, as well
   * as loading and unloading.
   * @param l The StructureChangeListener to plug in.
   */
  public void addStructureChangedListener(StructureChangedListener l) {
    structListener = ResourceEventMulticaster.add(structListener, l);
  }
   
  /**
   * Remove a structure listener on that space.
   * @param l The listener to remove.
   */
  public void removeStructureChangedListener(StructureChangedListener l) {
    structListener = ResourceEventMulticaster.remove(structListener, l);
  }

  /**
   * Initialize that resource space with given properties.
   * This method gets called by the resource space manager, right after the 
   * instance of that space is created.
   * <p>ResourceSpace implementors are expected to understand at least
   * the following set of properties:
   * <dl>
   * <dt>w3c.tools.resources.space.class<dd>The class that the space manager
   * will instantiate to get an instance.
   * <dt>w3c.tools.resources.space.loader<dd>The class of the class loader
   * to be used when loading classes for that space.
   * <dt>w3c.tools.resources.space.owner<dd>The owner of the space
   * </dl>
   * <p>And any additional properties required to initialize the space, as
   * defined by its class.
   */
  public void init(Properties properties, ResourceContext context)
    throws ResourceSpaceInitException, ResourceSaverInitException
  {
    this.rootdir = new File((String)properties.get(ROOTDIR_P));
    if (rootdir == null)
      throw new ResourceSpaceInitException("no "+ROOTDIR_P+" prop.");
    if (!rootdir.exists()) rootdir.mkdirs();

    PersistentReference pr  = null;
    //    ResourceContext context = new ResourceContextImpl(this,null);
    //    String name = rootdir.getName();
    String name = "";

    pr = new PersistentReferenceImpl(rootdir.getAbsolutePath(),name);
    context.addProperty("name",name);
    try {
      String storeClass = (String)properties.get(STORECLASS_P);
      if (storeClass == null)
	throw new ResourceSpaceInitException("no "+STORECLASS_P+" prop.");
      this.rootStore = (ResourceStore)
	(Class.forName(storeClass)).newInstance();
      
      rootStore.init(rootdir.getAbsolutePath());

      String saverClass = (String)properties.get(SAVERCLASS_P);
      if (saverClass == null)
	throw new ResourceSpaceInitException("no "+SAVERCLASS_P+" prop.");
      ResourceSaver S = (ResourceSaver)
	(Class.forName(saverClass)).newInstance();
      // must stay in this order !!!
      this.saver = S; 
      S.init(this,properties);

      root = 
	new DirectoryResource (this, rootStore);
      rootReference = cache.getReference(pr,root);
      context.setResourceReference(rootReference);
      root.create(context);
      root.init(context);
    } catch (ClassNotFoundException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
    } catch (InstantiationException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
    } catch (ResourceInitException ex) {
      throw new ResourceSpaceInitException("Unable to create root container");
    }
  }

  public ResourceSpaceImpl () {
    this.cache = new ResourceCache(this);
  }

}
