head	1.8;
access;
symbols;
locks; strict;
comment	@# @;


1.8
date	2001.08.30.16.57.42;	author dlr;	state Exp;
branches;
next	1.7;

1.7
date	2001.08.27.08.33.21;	author dlr;	state Exp;
branches;
next	1.6;

1.6
date	2001.08.27.03.25.07;	author dlr;	state Exp;
branches;
next	1.5;

1.5
date	2001.08.24.23.40.52;	author dlr;	state Exp;
branches;
next	1.4;

1.4
date	2001.08.24.23.36.26;	author dlr;	state Exp;
branches;
next	1.3;

1.3
date	2001.08.24.23.30.05;	author leonardr;	state Exp;
branches;
next	1.2;

1.2
date	2001.08.24.23.12.32;	author leonardr;	state Exp;
branches;
next	1.1;

1.1
date	2001.08.24.22.39.03;	author leonardr;	state Exp;
branches;
next	;


desc
@@


1.8
log
@Updated for latest Turbine API changes.
@
text
@/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool;

import java.util.*;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.util.exception.NestableException;
import org.apache.commons.collections.ExtendedProperties;

import org.apache.fulcrum.BaseService;
import org.apache.fulcrum.TurbineServices;
import org.apache.turbine.ParameterParser;
import org.apache.turbine.ParameterParser;

import org.apache.turbine.util.Log;
import org.apache.velocity.context.Context;

/**
 * Used by HelmServlet to instantiate business objects according to
 * IDs specified in request data. Components can add hooks into this
 * code by defining BOFactoryService keys in their
 * TurbineResources.properties fragments.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@version $Id: BOFactoryService.java,v 1.7 2001/08/27 08:33:21 dlr Exp $ 
 */
public class BOFactoryService extends BaseService implements HelmService
{
    /**
     * The name of this service.  Used to retrive it from the broker.
     */
    public static final String SERVICE_NAME = "BOFactoryService";
   
    public static final String CONTEXT_KEY_VAR = "contextKey";
    public static final String PEER_CLASS_VAR = "peer";
    public static final String PARAMETER_KEY_VAR = "parameterKey";
    public static final String INSTANTIATION_METHOD_VAR = 
        "instantiationMethod";
    public static final String DEFAULT_CONTEXT_KEY_VAR = "default";
    public static final String SUBCLASS_VAR = "subclass";

    /**
     * The peer method to use to instantiate objects when no method is
     * specified.
     */
    public static final String DEFAULT_INSTANTIATION_METHOD_NAME = 
        "getInstance";

    /**
     * The configuration for this service.
     */
    private Map cachedConfig = new Hashtable();

    /**
     * Required signature for any method used to instantiate objects.
     */
    private static final Class[] ONE_STRING = { String.class };

    /**
     * Performs intialization of this service.  Called during Fulcrum
     * initialization.  Should be configured to be initialized early.
     */
    public void init()
    {
        if (!isInitialized())
        {
            try
            {
                cachedConfig.clear();
                parseConfig(getConfiguration());
                setInit(true);
            }
            catch (Exception e)
            {
                Log.error("Initialization of " + SERVICE_NAME + "failed", e);
            }
        }
    }

    /**
     * Returns the status of this service. 
     */
    public final String getStatus()
    {
        return describeConfig();
    }

    /**
     * Returns the relative path to the template which contains controls
     * for controlling this service.
     */
    public String getAdminTemplateName()
    {
        return null;
    }

    /**
     * A wrapper method for getting the single instance of this
     * service from the Turbine services list.
     *
     * @@return Turbine's single instance of this service.
     */
    public static final BOFactoryService getService()
    {
        return (BOFactoryService)TurbineServices.getInstance()
            .getService(SERVICE_NAME);
    }

    public final String describeConfig()
    {
        StringBuffer s = new StringBuffer();
        for (Iterator i = cachedConfig.keySet().iterator(); i.hasNext();)
        {
            String k = (String)i.next();
            ConfigurationMapping m = (ConfigurationMapping)cachedConfig.get(k);
            s.append(m.describe());
            s.append('\n');
        }
        return s.toString();
    }

    /**
     * Parses this service's configuration files through a series of partial
     * parses, one for each configuration entry.
     *
     * @@param config Business object configuration.
     */
    protected void parseConfig(ExtendedProperties config)
        throws Exception
    {
        Set contextKeys = new HashSet();
        for (Iterator i = config.getKeys(); i.hasNext();)
        {
            String key = (String)i.next();
            if (!key.equals("classname") && !key.equals("earlyInit"))
            {
                String value = (String)config.get(key);
                
                String contextKey = key.substring(0, key.indexOf('.'));
                
                if (!contextKeys.contains(contextKey))
                {
                    contextKeys.add(contextKey);
                    parsePartialConfig(contextKey, config.subset(contextKey));
                }
            }
        }
    }

    /**
     * Parses that portion of the configuration dealing with a
     * particular context key.  
     */
    private void parsePartialConfig(String contextKey, 
                                    ExtendedProperties thisConfig)
        throws Exception
    {
        String parameterKey = thisConfig.getString(PARAMETER_KEY_VAR);
        String defaultContextKey =
            thisConfig.getString(DEFAULT_CONTEXT_KEY_VAR);
        
        String peerClassName = thisConfig.getString(PEER_CLASS_VAR);
        if (peerClassName == null)
        {
            throw new Exception(SERVICE_NAME + ": Invalid configuration: " +
                                "Unable to retrieve property named " +
                                PEER_CLASS_VAR);
        }
        Class peerClass = Class.forName(peerClassName);

        String instantiationMethodName = thisConfig.getString
            (INSTANTIATION_METHOD_VAR);
        if (instantiationMethodName == null)
        {
            instantiationMethodName = DEFAULT_INSTANTIATION_METHOD_NAME;
        }
        Method instantiationMethod = peerClass.getMethod
            (instantiationMethodName, ONE_STRING);
        
        Class subclass = null;
        String subclassName = thisConfig.getString(SUBCLASS_VAR);
        if (subclassName != null)
        {
            subclass = Class.forName(subclassName);
        }
        ConfigurationMapping m = new ConfigurationMapping
            (contextKey, parameterKey, peerClass, instantiationMethod, 
             defaultContextKey, subclass);

        cachedConfig.put(parameterKey, m);
    }

    /**
     * Derives <code>ParameterParser</code> data from the supplied
     * <code>HttpServletRequest</code>.
     *
     * @@see #processParameters(ParameterParser p, Context context)
     */
    public final void processParameters(HttpServletRequest r, Context context)
        throws Exception
    {
        ParameterParser p = new ParameterParser();
        p.setRequest(r);
        processParameters(p, context);
    }

    /**
     * Instantiate objects according to input in the request, using
     * defaults if available, and put them into the context under
     * the appropriate names.
     */
    protected void processParameters(ParameterParser p, Context context)
        throws Exception
    {
        //List of keys to configuration mappings which provide
        //defaults.
        List checkDefaults = new ArrayList(1);

        //Try to instantiate business objects from the request.
        for (Iterator i = cachedConfig.keySet().iterator(); i.hasNext();)
        {
            String key = (String)i.next();
            ConfigurationMapping m = (ConfigurationMapping)cachedConfig
                .get(key);
            String parameterKey = m.parameterKey;
            String parameterValue = p.getString(parameterKey);
            boolean found = false;
            if (parameterValue != null)
            {
                found = m.processParameterValue(parameterValue, context);
            }
            if (!found && m.defaultContextKey != null)
            {
                //Nothing found; check default after processing
                //all other entries.
                checkDefaults.add(key);
            }
        }

        //For objects that were instantiated, process defaults from
        //values already in the context.
        for (Iterator i = checkDefaults.iterator(); i.hasNext();)
        {
            String key = (String)i.next();
            ConfigurationMapping m = (ConfigurationMapping)cachedConfig
                .get(key);
            m.processDefault(context);
        }
    }

    /**
     * Inner class containing information about a business object to
     * be instantiated from request data.
     */
    private class ConfigurationMapping
    {
        public String contextKey;
        public String parameterKey;
        public Class peerClass;
        public Method instantiationMethod;
        public String defaultContextKey;
        public Class subclass;

        public ConfigurationMapping(String contextKey, String parameterKey,
                                    Class peerClass, 
                                    Method instantiationMethod, 
                                    String defaultContextKey, Class subclass)
        {
            this.contextKey = contextKey;
            this.parameterKey = parameterKey;
            this.peerClass = peerClass;
            this.instantiationMethod = instantiationMethod;
            this.defaultContextKey = defaultContextKey;
            this.subclass = subclass;
        }

        /**
         * Tries to instantiate the specified business object and put
         * it into the context under the correct name. Returns true iff
         * a value was found.
         */
        public boolean processParameterValue(String value, Context context)
            throws Exception
        {
            boolean found = false;

            String[] args = {value};
            Object o = instantiationMethod.invoke(null, args);

            if (o != null)
            {                
                if (subclass == null 
                    || subclass.isAssignableFrom(o.getClass()))
                {
                    found = true;
                    context.put(contextKey, o);
                }
                else if (subclass != null)
                {
                    throw new NestableException
                        ("You specified an object of the wrong type for "
                         + parameterKey);
                }
            }               
            return found;
        }

        /**
         * If there's no value for this business object, but there is a 
         * default we can use, use the default.
         */        
        public void processDefault(Context context)
        {            
            if (context.get(contextKey) == null)
            {
                Object defaultObject = context.get(defaultContextKey);
                if (defaultObject != null && 
                    (subclass == null
                     || subclass.isAssignableFrom(defaultObject.getClass())))
                {
                    context.put(contextKey, defaultObject);
                }
            }
        }

        /**
         * Describes the setup of this portion of service
         * configuration.  
         */
        public String describe()
        {
            StringBuffer s = new StringBuffer(contextKey)
                .append(" configuration:\n");
            s.append(" Parameter key: ").append(parameterKey).append('\n');
            s.append(" Peer class: ").append(peerClass).append('\n');
            s.append(" Instantiation method: ").append(instantiationMethod)
                .append('\n');
            if (defaultContextKey != null)
            {
                s.append(" Context key for default object: ")
                    .append(defaultContextKey).append('\n');
                if (subclass != null)
                {
                    s.append(" Subclass: ")
                        .append(subclass).append('\n');
                }
            }
            return s.toString();
        }
    }
}
@


1.7
log
@Turbine API change and refactoring to allow more easy testing.
@
text
@d58 2
a59 2
import org.apache.fulcrum.util.parser.DefaultParameterParser;
import org.apache.fulcrum.util.parser.ParameterParser;
d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.6 2001/08/27 03:25:07 dlr Exp $ 
d248 1
a248 1
        ParameterParser p = new DefaultParameterParser();
@


1.6
log
@* Cleaned up imports.

* Loosened access on parseConfig() from private to protected.

* Better error reporting in case of invalid properties file
configuration.
@
text
@d58 2
a61 2
import org.apache.turbine.util.parser.DefaultParameterParser;

d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.5 2001/08/24 23:40:52 dlr Exp $ 
d240 14
d258 1
a258 1
    public void processParameters(HttpServletRequest r, Context context)
a260 3
        DefaultParameterParser p = new DefaultParameterParser();
        p.setRequest(r);

@


1.5
log
@Removed static initializer block and inlined initialization of now
final (and renamed) ONE_STRING parameter.
@
text
@d53 3
d59 1
a62 4
import org.apache.commons.util.exception.NestableException;
import org.apache.commons.collections.ExtendedProperties;

import org.apache.turbine.util.Log;
d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.4 2001/08/24 23:36:26 dlr Exp $ 
d171 2
d174 1
a174 1
    private void parseConfig(ExtendedProperties config)
d205 2
a206 2
        String defaultContextKey = thisConfig.getString
            (DEFAULT_CONTEXT_KEY_VAR);
d209 6
@


1.4
log
@More obvious method of getting String's class object.
@
text
@d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.3 2001/08/24 23:30:05 leonardr Exp $ 
d103 1
a103 9
    private static Class[] oneString = new Class[1];

    /**
     * Static initializer.
     */
    static
    {
        oneString[0] = String.class;
    }
d216 1
a216 1
            (instantiationMethodName, oneString);
@


1.3
log
@Always put the found object into the context, unless it's not of the right class.
@
text
@d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.2 2001/08/24 23:12:32 leonardr Exp $ 
d110 1
a110 1
        oneString[0] = "".getClass();
@


1.2
log
@Optimization to not check all the business objects for defaults, only the ones which weren't instantiated and might have defaults.
@
text
@d71 1
a71 1
 * @@version $Id: BOFactoryService.java,v 1.1 2001/08/24 22:39:03 leonardr Exp $ 
d327 2
a328 1
                if (subclass != null)
d330 10
a339 13
                    if (subclass.isAssignableFrom(o.getClass()))
                    {
                        found = true;
                        context.put(contextKey, o);
                    }
                    else
                    {
                        throw new NestableException
                            ("You specified an object of the wrong type for "
                             + parameterKey);
                    }
                }               
            }    
@


1.1
log
@Factored code out of HelmServlet.populateBOs into BOFactoryService. Components can now specify the business objects they want populated in a TurbineResources.properties fragment.
@
text
@d71 1
a71 1
 * @@version $Id: CryptoProviderService.java,v 1.17 2001/08/11 09:08:08 dlr Exp $ 
d250 4
d262 1
d265 7
a271 1
                m.processParameterValue(parameterValue, context);
d277 1
a277 1
        for (Iterator i = cachedConfig.keySet().iterator(); i.hasNext();)
a283 1

d314 2
a315 1
         * it into the context under the correct name.
d317 1
a317 1
        public void processParameterValue(String value, Context context)
d320 2
d331 1
d340 3
a342 2
                }
            }            
@

