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    
015    package org.apache.hivemind.lib.strategy;
016    
017    import java.lang.reflect.Constructor;
018    import java.lang.reflect.Modifier;
019    import java.util.Iterator;
020    import java.util.List;
021    
022    import org.apache.hivemind.ApplicationRuntimeException;
023    import org.apache.hivemind.HiveMind;
024    import org.apache.hivemind.ServiceImplementationFactory;
025    import org.apache.hivemind.ServiceImplementationFactoryParameters;
026    import org.apache.hivemind.lib.util.StrategyRegistry;
027    import org.apache.hivemind.lib.util.StrategyRegistryImpl;
028    import org.apache.hivemind.service.ClassFab;
029    import org.apache.hivemind.service.ClassFabUtils;
030    import org.apache.hivemind.service.ClassFactory;
031    import org.apache.hivemind.service.MethodIterator;
032    import 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     */
047    public 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    }