/*
  Copyright (C) 2007 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
package org.opengroupware.jope.ofs;

import java.io.File;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.core.WOApplication;
import org.opengroupware.jope.appserver.core.WOComponent;
import org.opengroupware.jope.appserver.core.WOContext;
import org.opengroupware.jope.appserver.core.WORequestHandler;
import org.opengroupware.jope.appserver.core.WOResourceManager;
import org.opengroupware.jope.appserver.publisher.IJoAuthenticator;
import org.opengroupware.jope.appserver.publisher.IJoAuthenticatorContainer;
import org.opengroupware.jope.appserver.publisher.IJoCallable;
import org.opengroupware.jope.appserver.publisher.IJoContext;
import org.opengroupware.jope.appserver.publisher.IJoSecuredObject;
import org.opengroupware.jope.appserver.publisher.JoCallDirectActionRequestHandler;
import org.opengroupware.jope.appserver.publisher.JoContainerResourceManager;
import org.opengroupware.jope.appserver.publisher.JoHTTPAuthenticator;
import org.opengroupware.jope.foundation.UString;
import org.opengroupware.jope.ofs.fs.IOFSFileInfo;
import org.opengroupware.jope.ofs.fs.IOFSFileManager;
import org.opengroupware.jope.ofs.fs.OFSHostFileManager;

/**
 * OFSApplication
 * <p>
 * An OFS application is a JOPE application which runs on top of a directory.
 * This adds an OFSRestorationFactory to the application object and overrides
 * the rootObjectInContext() method to return the root of the OFS hierarchy.
 */
public class OFSApplication extends WOApplication
  implements IJoAuthenticatorContainer
{
  protected static final Log ofslog = LogFactory.getLog("JoOFS");
  
  protected OFSRestorationFactory defaultRestorationFactory;
  protected ConcurrentHashMap<String, IOFSFileManager> pathToFileManager;
  
  /* setup */

  @Override
  public void init() {
    super.init();
    
    this.pathToFileManager= new ConcurrentHashMap<String, IOFSFileManager>(4);
    
    this.defaultRestorationFactory = new OFSRestorationFactory();

    /* load OFS product */
    this.joProductManager.loadProduct
      (null, OFSApplication.class.getPackage().getName());
  }
  
  
  /* request handlers */
  
  @Override
  protected void registerInitialRequestHandlers() {
    super.registerInitialRequestHandlers();
    
    /* redefine direct action handler */
    WORequestHandler rh = new JoCallDirectActionRequestHandler(this);
    this.registerRequestHandler(rh, this.directActionRequestHandlerKey());
    this.registerRequestHandler(rh, "x");
    this.setDefaultRequestHandler(rh);
  }
  
  
  /* resource manager */
  
  @Override
  public WOComponent pageWithName(String _pageName, WOContext _ctx) {
    if (pageLog.isDebugEnabled()) pageLog.debug("pageWithName:" + _pageName);

    WOResourceManager rm = null;
    WOComponent cursor = _ctx != null ? _ctx.component() : null;
    
    if (cursor != null) /* first check whether the component has a manager */
      rm = cursor.resourceManager();
    
    if (rm == null && _ctx != null) {
      /* check whether the traversal path contains a manager */
      // TBD: should we cache that?
      rm = JoContainerResourceManager.lookupResourceManager
        (_ctx.joTraversalPath().resultObject(), _ctx);
    }
    
    if (rm == null) /* fallback to app resource manager */
      rm = this.resourceManager();

    if (rm == null) {
      pageLog.error("did not find a resource manager to instantiate: " +
          _pageName);
      return null;
    }

    WOComponent page = rm.pageWithName(_pageName, _ctx);
    if (page == null) {
      pageLog.error("could not instantiate page " + _pageName + " using: " +rm);
      return null;
    }

    page.ensureAwakeInContext(_ctx);
    return page;
  }
  
  
  /* factories */
  
  public OFSRestorationFactory defaultRestorationFactory() {
    return this.defaultRestorationFactory;
  }
  
  /* root object */

  /**
   * This splits the namespace into two pathes, the "regular" root
   * object is mapped to the OFS root folder.
   * And all pathes starting with '-ControlPanel' are mapped to the
   * application.
   * 
   * @param _ctx  - the WOContext the lookup will happen in
   * @param _path - the path to be looked up
   * @return the Object where the JoLookup process will start
   */
  @Override
  public Object rootObjectInContext(WOContext _ctx, String[] _path) {
    if (_path != null && _path.length > 0) {
      if (_path[0].startsWith("-")) {
        /* application code namespace */
        if (_path[0].startsWith("-ControlPanel"))
          return this;
      }
      
      /* eg: wa,Main,default */
      
      if (this.directActionRequestHandlerKey().equals(_path[0]))
        return this; /* application object */
      if (this.componentRequestHandlerKey().equals(_path[0]))
        return this; /* application object */
    }
    
    /* directly pass on to document hierarchy */
    
    Object root = this.ofsRootObjectInContext(_ctx, _path);
    if (root != null)
      return root;
    
    log.error("got no OFS root object, using application as root" +
        "\n  path: " + UString.componentsJoinedByString(_path, " / ") +
        "\n  ctx:  " + _ctx);
    return this;
  }
  
  /**
   * Returns the Filesystem path which should be used as the root of the
   * OFS file hierarchy.
   * <p>
   * Per default this returns the current directory.
   * 
   * @param _ctx  - the context representing the current transaction
   * @param _path - the URL path which got requested
   * @return a filesystem path
   */
  public String ofsDatabasePathInContext(WOContext _ctx, String[] _path) {
    return System.getProperty("user.dir");
  }
  
  /**
   * Returns the root of the OFS hierarchy.
   * 
   * @param _ctx  - the context of the next lookup
   * @param _path - the path which is going to be looked up in the root object
   * @return the root object, or null if none could be created
   */
  public Object ofsRootObjectInContext(WOContext _ctx, String[] _path) {
    final String rootPath = this.ofsDatabasePathInContext(_ctx, _path);

    /* lookup filemanager */
    
    IOFSFileManager fm = this.pathToFileManager.get(rootPath);
    if (fm == null) {
      if ((fm = new OFSHostFileManager(new File(rootPath))) == null) {
        log().error("could not create filemanager for file: " + rootPath);
        return null;
      }
      
      /* cache filemanager */
      this.pathToFileManager.put(rootPath, fm);
    }
    
    /* lookup fileinfo in filemanager */
    
    IOFSFileInfo fileInfo = fm.fileInfoForPath(null /* root */);
    if (fileInfo == null) {
      log().error("got no info for root directory: " + rootPath);
      return null;
    }
    
    /* restore root object using restoration factory */
    
    return this.defaultRestorationFactory()
      .restoreObjectFromFileInContext(null, fm, fileInfo, _ctx);
  }
  
  
  /* authentication */
  
  public IJoAuthenticator authenticatorInContext(IJoContext _ctx) {
    return new JoHTTPAuthenticator();
  }
  
  
  /* default methods */
  
  @Override
  public IJoCallable lookupDefaultMethod(Object _object, WOContext _ctx) {
    if (_object instanceof OFSFolder) {
      Object folderIndex = IJoSecuredObject.Utility.lookupName
        (_object, "index", _ctx, false /* do not acquire */);
      
      if (folderIndex instanceof IJoCallable) {
        IJoCallable indexMethod = (IJoCallable)folderIndex;
        if (indexMethod.isCallableInContext(_ctx)) {
          // TBD: probably better to return a redirect
          return indexMethod;
        }
      }
    }
      
    return super.lookupDefaultMethod(_object, _ctx);
  }
  
  /* logging */
  
  public Log log() {
    return ofslog;
  }
}
