/*
  Copyright (C) 2006-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.appserver.publisher;

/**
 * JoSecuredObject
 * <p>
 * This interface should be implemented by JoObject's which implement their
 * own security strategy, which is often not necessary.
 * In all other cases this will pass over control to the JoSecurityManager.
 */
public interface IJoSecuredObject {

  public Exception validateName(String _name, IJoContext _ctx);
  
  /* utility class (use those methods in code to work on arbitrary objects) */
  public static class Utility {

    /**
     * This method wraps IJoObject.lookupName() and ensures that the object and
     * key are properly secured. Hence it should be the <em>primary method to
     * perform a lookup</em>!
     * The method also does the aquisition of the name when requested by the
     * path object.
     * <ol>
     *   <li>it first invokes IJoSecuredObject.Utility.validateNameOfObject()
     *   <li>then, lookupName is invoked to find the object
     *   <li>IJoSecuredObject.Utility.validateValueForNameOfObject() is called
     * </ol>
     * <p>
     * If the lookup fails, the method sets the lastException of the path.
     * 
     * @param _object - base object to lookup name in
     * @param _name   - the name to lookup in the object
     * @return the object or null if it was not found
     */
    public static Object lookupName
      (Object _object, String _name, IJoContext _ctx, boolean _acquire)
    {
      if (_object == null)
        return null;

      /* ensure that the user is allowed to access the key */
      
      Exception error = IJoSecuredObject.Utility
        .validateNameOfObject(_object, _name, _ctx);
      if (error != null) return error;
      
      /* lookup object for name */
      
      // TBD: Don't we need to perform the acquisition in here? So that internal
      //      steps are properly protected.
      Object result = IJoObject.Utility.lookupName
        (_object, _name, _ctx, _acquire);
      
      /* process result object */
      
      if (result == null)
        return null;
      
      /* Catch exceptions. They cannot be returned as results and are not valid
       * inside lookup hierarchies.
       */
      if (result instanceof Exception) {
        // TODO: do we need special handling for 404 exceptions?
        return result;
      }

      /* validate access to object if one was found */

      error = IJoSecuredObject.Utility
        .validateValueForNameOfObject(_object, _name, result, _ctx);

      if (error != null) return error;
      
      /* lookup was successful, return value */
      return result;
    }

    public static Object traversePath
      (Object _object, String[] _path, IJoContext _ctx, boolean _acquire)
    {
      if (_object == null)
        return null;
      if (_path == null || _path.length == 0)
        return _object;
      
      Object o = _object;
      for (int i = 0; i < _path.length && o != null; i++) {
        o = lookupName(o, _path[i], _ctx, _acquire);
        if (o instanceof Exception)
          return o;
      }
      return o;
    }

    
    /**
     * Validates whether the user associated with the context is allowed to
     * access the given name in the given object.
     * <p>
     * It first checks whether the object is an IJoSecuredObject, hence whether
     * it manages the security on its own. If not, it will ask the
     * joSecurityManager of the context.
     * 
     * @param _self - the base object
     * @param _name - the name of the base object which is requested
     * @param _ctx  - the context containing the authentication information
     * @return null if access is granted, an Exception if not
     */
    public static Exception validateNameOfObject
      (Object _self, String _name, IJoContext _ctx)
    {
      if (_self == null)
        return null;
      
      if (_self instanceof IJoSecuredObject)
        return ((IJoSecuredObject)_self).validateName(_name, _ctx);
      
      if (_ctx == null)
        return new JoAccessDeniedException("missing JoContext");
      
      JoSecurityManager jm = _ctx.joSecurityManager();
      if (jm == null)
        return new JoAccessDeniedException("no security manager is configured");
      
      return jm.validateNameOfObject(_self, _name, _ctx);
    }
    
    /**
     * Validates whether the user associated with the context is allowed to
     * access the values looked up for the given name in the given object.
     * That is, the lookup was performed and returned a value. Now we check
     * whether the value is an object the user is allowed to access.
     * 
     * @param _self  - the base object
     * @param _name  - the name of the base object which was requested
     * @param _value - the value the base object returned for the name
     * @param _ctx   - the context containing the authentication information
     * @return null if access is granted, an Exception if not
     */
    public static Exception validateValueForNameOfObject
      (Object _self, String _name, Object _value, IJoContext _ctx)
    {
      if (_self == null || _value == null)
        return null;
      
      if (_ctx == null)
        return new JoAccessDeniedException("missing JoContext");
      
      JoSecurityManager jm = _ctx.joSecurityManager();
      if (jm == null)
        return new JoAccessDeniedException("no security manager is configured");
      
      return jm.validateValueForNameOfObject(_self, _name, _value, _ctx);
    }
  }
}
