001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.hivemind.lib.strategy;
016
017import java.lang.reflect.Constructor;
018import java.lang.reflect.Modifier;
019import java.util.Iterator;
020import java.util.List;
021
022import org.apache.hivemind.ApplicationRuntimeException;
023import org.apache.hivemind.HiveMind;
024import org.apache.hivemind.ServiceImplementationFactory;
025import org.apache.hivemind.ServiceImplementationFactoryParameters;
026import org.apache.hivemind.lib.util.StrategyRegistry;
027import org.apache.hivemind.lib.util.StrategyRegistryImpl;
028import org.apache.hivemind.service.ClassFab;
029import org.apache.hivemind.service.ClassFabUtils;
030import org.apache.hivemind.service.ClassFactory;
031import org.apache.hivemind.service.MethodIterator;
032import org.apache.hivemind.service.MethodSignature;
033
034/**
035 * Implementation of the <code>hivemind.lib.StrategyFactory</code> service that constructs a
036 * service where the first parameter of each method is used to selecte a strategy from an
037 * {@link org.apache.hivemind.lib.util.StrategyRegistry}. The method invocation is then delegated
038 * to the strategy instance.
039 * <p>
040 * The service factory parameter defines a configuration (of
041 * {@link org.apache.hivemind.lib.strategy.StrategyContribution}s) that provide the mapping from
042 * Java classes (or interfaces) to adapter instances.
043 * 
044 * @author Howard M. Lewis Ship
045 * @since 1.1
046 */
047public class StrategyFactory implements ServiceImplementationFactory
048{
049    private ClassFactory _classFactory;
050
051    public Object createCoreServiceImplementation(
052            ServiceImplementationFactoryParameters factoryParameters)
053    {
054        StrategyRegistry ar = new StrategyRegistryImpl();
055
056        buildRegistry(factoryParameters, ar);
057
058        Class implClass = buildImplementationClass(factoryParameters);
059
060        try
061        {
062            Constructor c = implClass.getConstructors()[0];
063
064            return c.newInstance(new Object[]
065            { ar });
066        }
067        catch (Exception ex)
068        {
069            throw new ApplicationRuntimeException(ex.getMessage(), HiveMind
070                    .getLocation(factoryParameters.getFirstParameter()), ex);
071        }
072
073    }
074
075    // package private for testing purposes
076
077    void buildRegistry(ServiceImplementationFactoryParameters factoryParameters, StrategyRegistry ar)
078    {
079        Class serviceInterface = factoryParameters.getServiceInterface();
080
081        StrategyParameter p = (StrategyParameter) factoryParameters.getFirstParameter();
082
083        List contributions = p.getContributions();
084
085        Iterator i = contributions.iterator();
086
087        while (i.hasNext())
088        {
089            StrategyContribution c = (StrategyContribution) i.next();
090
091            try
092            {
093                Object adapter = c.getStrategy();
094
095                if (!serviceInterface.isAssignableFrom(adapter.getClass()))
096                    throw new ClassCastException(StrategyMessages.strategyWrongInterface(adapter, c
097                            .getRegisterClass(), serviceInterface));
098
099                ar.register(c.getRegisterClass(), adapter);
100            }
101            catch (Exception ex)
102            {
103                factoryParameters.getErrorLog().error(ex.getMessage(), c.getLocation(), ex);
104            }
105
106        }
107
108    }
109
110    // package private for testing purposes
111
112    private Class buildImplementationClass(ServiceImplementationFactoryParameters factoryParameters)
113    {
114        String name = ClassFabUtils.generateClassName(factoryParameters.getServiceInterface());
115
116        return buildImplementationClass(factoryParameters, name);
117    }
118
119    // package private for testing purposes
120
121    Class buildImplementationClass(ServiceImplementationFactoryParameters factoryParameters,
122            String name)
123    {
124        Class serviceInterface = factoryParameters.getServiceInterface();
125
126        ClassFab cf = _classFactory.newClass(name, Object.class);
127
128        cf.addInterface(serviceInterface);
129
130        cf.addField("_registry", StrategyRegistry.class);
131
132        cf.addConstructor(new Class[]
133        { StrategyRegistry.class }, null, "_registry = $1;");
134
135        // TODO: Should we add a check for $1 == null?
136
137        cf.addMethod(Modifier.PRIVATE, new MethodSignature(serviceInterface, "_getStrategy",
138                new Class[]
139                { Object.class }, null), "return (" + serviceInterface.getName()
140                + ") _registry.getStrategy($1.getClass());");
141
142        MethodIterator i = new MethodIterator(serviceInterface);
143
144        while (i.hasNext())
145        {
146            MethodSignature sig = i.next();
147
148            if (proper(sig))
149            {
150                addAdaptedMethod(cf, sig);
151            }
152            else
153            {
154                ClassFabUtils.addNoOpMethod(cf, sig);
155
156                factoryParameters.getErrorLog().error(
157                        StrategyMessages.improperServiceMethod(sig),
158                        HiveMind.getLocation(factoryParameters.getFirstParameter()),
159                        null);
160            }
161
162        }
163
164        if (!i.getToString())
165            ClassFabUtils.addToStringMethod(cf, StrategyMessages.toString(factoryParameters
166                    .getServiceId(), serviceInterface));
167
168        return cf.createClass();
169    }
170
171    private void addAdaptedMethod(ClassFab cf, MethodSignature sig)
172    {
173        String body = "return ($r) _getStrategy($1)." + sig.getName() + "($$);";
174
175        cf.addMethod(Modifier.PUBLIC, sig, body);
176    }
177
178    /**
179     * A "proper" method is one with at least one parameter and whose first parameter is an object
180     * (not primitive) type.
181     */
182
183    private boolean proper(MethodSignature sig)
184    {
185        Class[] parameterTypes = sig.getParameterTypes();
186
187        return parameterTypes != null && parameterTypes.length > 0
188                && !parameterTypes[0].isPrimitive();
189    }
190
191    public void setClassFactory(ClassFactory classFactory)
192    {
193        _classFactory = classFactory;
194    }
195}