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.service.impl;
016
017import java.lang.reflect.Constructor;
018import java.lang.reflect.InvocationTargetException;
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Set;
029
030import org.apache.commons.logging.Log;
031import org.apache.hivemind.ApplicationRuntimeException;
032import org.apache.hivemind.ErrorHandler;
033import org.apache.hivemind.HiveMind;
034import org.apache.hivemind.Location;
035import org.apache.hivemind.ServiceImplementationFactoryParameters;
036import org.apache.hivemind.internal.Module;
037import org.apache.hivemind.service.EventLinker;
038import org.apache.hivemind.util.ConstructorUtils;
039import org.apache.hivemind.util.PropertyUtils;
040
041/**
042 * Created by {@link org.apache.hivemind.service.impl.BuilderFactory} for each service to be
043 * created; encapsulates all the direct and indirect parameters used to construct a service.
044 * 
045 * @author Howard Lewis Ship
046 */
047public class BuilderFactoryLogic
048{
049    /** @since 1.1 */
050    private ServiceImplementationFactoryParameters _factoryParameters;
051
052    private String _serviceId;
053
054    private BuilderParameter _parameter;
055
056    private Log _log;
057
058    private Module _contributingModule;
059
060    public BuilderFactoryLogic(ServiceImplementationFactoryParameters factoryParameters,
061            BuilderParameter parameter)
062    {
063        _factoryParameters = factoryParameters;
064        _parameter = parameter;
065
066        _log = _factoryParameters.getLog();
067        _serviceId = factoryParameters.getServiceId();
068        _contributingModule = factoryParameters.getInvokingModule();
069    }
070
071    public Object createService()
072    {
073        try
074        {
075            Object result = instantiateCoreServiceInstance();
076
077            setProperties(result);
078
079            registerForEvents(result);
080
081            invokeInitializer(result);
082
083            return result;
084        }
085        catch (Exception ex)
086        {
087            throw new ApplicationRuntimeException(ServiceMessages.failureBuildingService(
088                    _serviceId,
089                    ex), _parameter.getLocation(), ex);
090        }
091    }
092
093    private void error(String message, Location location, Throwable cause)
094    {
095        _factoryParameters.getErrorLog().error(message, location, cause);
096    }
097
098    private Object instantiateCoreServiceInstance()
099    {
100        Class serviceClass = _contributingModule.resolveType(_parameter.getClassName());
101
102        List parameters = _parameter.getParameters();
103
104        if (_parameter.getAutowireServices() && parameters.isEmpty())
105        {
106            return instantiateConstructorAutowiredInstance(serviceClass);
107        }
108
109        return instantiateExplicitConstructorInstance(serviceClass, parameters);
110    }
111
112    private Object instantiateExplicitConstructorInstance(Class serviceClass, List builderParameters)
113    {
114        int numberOfParams = builderParameters.size();
115        List constructorCandidates = getServiceConstructorsOfLength(serviceClass, numberOfParams);
116
117        outer: for (Iterator candidates = constructorCandidates.iterator(); candidates.hasNext();)
118        {
119            Constructor candidate = (Constructor) candidates.next();
120
121            Class[] parameterTypes = candidate.getParameterTypes();
122
123            Object[] parameters = new Object[parameterTypes.length];
124
125            for (int i = 0; i < numberOfParams; i++)
126            {
127                BuilderFacet facet = (BuilderFacet) builderParameters.get(i);
128
129                if (!facet.isAssignableToType(_factoryParameters, parameterTypes[i]))
130                    continue outer;
131
132                parameters[i] = facet.getFacetValue(_factoryParameters, parameterTypes[i]);
133            }
134
135            return ConstructorUtils.invoke(candidate, parameters);
136        }
137
138        throw new ApplicationRuntimeException(ServiceMessages.unableToFindAutowireConstructor(),
139                _parameter.getLocation(), null);
140    }
141
142    private List getServiceConstructorsOfLength(Class serviceClass, int length)
143    {
144        List fixedLengthConstructors = new ArrayList();
145
146        Constructor[] constructors = serviceClass.getDeclaredConstructors();
147
148        outer: for (int i = 0; i < constructors.length; i++)
149        {
150            if (!Modifier.isPublic(constructors[i].getModifiers()))
151                continue;
152
153            Class[] parameterTypes = constructors[i].getParameterTypes();
154
155            if (parameterTypes.length != length)
156                continue;
157
158            fixedLengthConstructors.add(constructors[i]);
159        }
160
161        return fixedLengthConstructors;
162    }
163
164    private Object instantiateConstructorAutowiredInstance(Class serviceClass)
165    {
166        List serviceConstructorCandidates = getOrderedServiceConstructors(serviceClass);
167
168        outer: for (Iterator candidates = serviceConstructorCandidates.iterator(); candidates
169                .hasNext();)
170        {
171            Constructor candidate = (Constructor) candidates.next();
172
173            Class[] parameterTypes = candidate.getParameterTypes();
174
175            Object[] parameters = new Object[parameterTypes.length];
176
177            for (int i = 0; i < parameters.length; i++)
178            {
179                BuilderFacet facet = _parameter.getFacetForType(
180                        _factoryParameters,
181                        parameterTypes[i]);
182
183                if (facet != null && facet.canAutowireConstructorParameter())
184                    parameters[i] = facet.getFacetValue(_factoryParameters, parameterTypes[i]);
185                else if (_contributingModule.containsService(parameterTypes[i]))
186                    parameters[i] = _contributingModule.getService(parameterTypes[i]);
187                else
188                    continue outer;
189            }
190
191            return ConstructorUtils.invoke(candidate, parameters);
192        }
193
194        throw new ApplicationRuntimeException(ServiceMessages.unableToFindAutowireConstructor(),
195                _parameter.getLocation(), null);
196    }
197
198    private List getOrderedServiceConstructors(Class serviceClass)
199    {
200        List orderedInterfaceConstructors = new ArrayList();
201
202        Constructor[] constructors = serviceClass.getDeclaredConstructors();
203
204        outer: for (int i = 0; i < constructors.length; i++)
205        {
206            if (!Modifier.isPublic(constructors[i].getModifiers()))
207                continue;
208
209            Class[] parameterTypes = constructors[i].getParameterTypes();
210
211            if (parameterTypes.length > 0)
212            {
213                Set seenTypes = new HashSet();
214
215                for (int j = 0; j < parameterTypes.length; j++)
216                {
217                    if (!parameterTypes[j].isInterface() || seenTypes.contains(parameterTypes[j]))
218                        continue outer;
219
220                    seenTypes.add(parameterTypes[j]);
221                }
222            }
223
224            orderedInterfaceConstructors.add(constructors[i]);
225        }
226
227        Collections.sort(orderedInterfaceConstructors, new Comparator()
228        {
229            public int compare(Object o1, Object o2)
230            {
231                return ((Constructor) o2).getParameterTypes().length
232                        - ((Constructor) o1).getParameterTypes().length;
233            }
234        });
235
236        return orderedInterfaceConstructors;
237    }
238
239    private void invokeInitializer(Object service)
240    {
241        String methodName = _parameter.getInitializeMethod();
242
243        boolean allowMissing = HiveMind.isBlank(methodName);
244
245        String searchMethodName = allowMissing ? "initializeService" : methodName;
246
247        try
248        {
249            findAndInvokeInitializerMethod(service, searchMethodName, allowMissing);
250        }
251        catch (InvocationTargetException ex)
252        {
253            Throwable cause = ex.getTargetException();
254
255            error(ServiceMessages.unableToInitializeService(_serviceId, searchMethodName, service
256                    .getClass(), cause), _parameter.getLocation(), cause);
257        }
258        catch (Exception ex)
259        {
260            error(ServiceMessages.unableToInitializeService(_serviceId, searchMethodName, service
261                    .getClass(), ex), _parameter.getLocation(), ex);
262        }
263    }
264
265    private void findAndInvokeInitializerMethod(Object service, String methodName,
266            boolean allowMissing) throws IllegalAccessException, InvocationTargetException,
267            NoSuchMethodException
268    {
269        Class serviceClass = service.getClass();
270
271        try
272        {
273            Method m = serviceClass.getMethod(methodName, null);
274
275            m.invoke(service, null);
276        }
277        catch (NoSuchMethodException ex)
278        {
279            if (allowMissing)
280                return;
281
282            throw ex;
283        }
284    }
285
286    private void registerForEvents(Object result)
287    {
288        List eventRegistrations = _parameter.getEventRegistrations();
289
290        if (eventRegistrations.isEmpty())
291            return;
292
293        EventLinker linker = new EventLinkerImpl(_factoryParameters.getErrorLog());
294
295        Iterator i = eventRegistrations.iterator();
296        while (i.hasNext())
297        {
298            EventRegistration er = (EventRegistration) i.next();
299
300            // Will log any errors to the errorHandler
301
302            linker.addEventListener(er.getProducer(), er.getEventSetName(), result, er
303                    .getLocation());
304        }
305    }
306
307    private void setProperties(Object service)
308    {
309        List properties = _parameter.getProperties();
310        int count = properties.size();
311
312        // Track the writeable properties, removing names as they are wired or autowired.
313
314        Set writeableProperties = new HashSet(PropertyUtils.getWriteableProperties(service));
315
316        for (int i = 0; i < count; i++)
317        {
318            BuilderFacet facet = (BuilderFacet) properties.get(i);
319
320            String propertyName = wireProperty(service, facet);
321
322            if (propertyName != null)
323                writeableProperties.remove(propertyName);
324        }
325
326        if (_parameter.getAutowireServices())
327            autowireServices(service, writeableProperties);
328
329    }
330
331    /**
332     * Wire (or auto-wire) the property; return the name of the property actually set (if a property
333     * is set, which is not always the case).
334     */
335    private String wireProperty(Object service, BuilderFacet facet)
336    {
337        String propertyName = facet.getPropertyName();
338
339        try
340        {
341            // Autowire the property (if possible).
342
343            String autowirePropertyName = facet.autowire(service, _factoryParameters);
344
345            if (autowirePropertyName != null)
346                return autowirePropertyName;
347
348            // There will be a facet for log, messages, service-id, etc. even if no
349            // property name is specified, so we skip it here. In many cases, those
350            // facets will have just done an autowire.
351
352            if (propertyName == null)
353                return null;
354
355            Class targetType = PropertyUtils.getPropertyType(service, propertyName);
356
357            Object value = facet.getFacetValue(_factoryParameters, targetType);
358
359            PropertyUtils.write(service, propertyName, value);
360
361            if (_log.isDebugEnabled())
362                _log.debug("Set property " + propertyName + " to " + value);
363
364            return propertyName;
365        }
366        catch (Exception ex)
367        {
368            error(ex.getMessage(), facet.getLocation(), ex);
369
370            return null;
371        }
372    }
373
374    private void autowireServices(Object service, Collection propertyNames)
375    {
376        Iterator i = propertyNames.iterator();
377        while (i.hasNext())
378        {
379            String propertyName = (String) i.next();
380
381            autowireProperty(service, propertyName);
382        }
383    }
384
385    private void autowireProperty(Object service, String propertyName)
386    {
387        Class propertyType = PropertyUtils.getPropertyType(service, propertyName);
388
389        try
390        {
391            // Ignore properties for which there are no corresponding
392            // service points...
393            if( _contributingModule.containsService( propertyType ) )
394            {
395                Object collaboratingService = _contributingModule.getService(propertyType);
396                PropertyUtils.write(service, propertyName, collaboratingService);
397            
398                if (_log.isDebugEnabled())
399                {
400                    _log.debug("Autowired service property " + propertyName + " to "
401                        + collaboratingService);
402                }
403            }
404        }
405        catch (Exception ex)
406        {
407            getErrorHandler().error(
408                    _log,
409                    ServiceMessages.autowirePropertyFailure(propertyName, _serviceId, ex),
410                    _parameter.getLocation(),
411                    ex);
412        }
413    }
414
415    private ErrorHandler getErrorHandler()
416    {
417        return _contributingModule.getErrorHandler();
418    }
419
420}