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.impl;
016    
017    import java.lang.reflect.Constructor;
018    import java.lang.reflect.Modifier;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.hivemind.ApplicationRuntimeException;
029    import org.apache.hivemind.ShutdownCoordinator;
030    import org.apache.hivemind.definition.ConfigurationPointDefinition;
031    import org.apache.hivemind.definition.Contribution;
032    import org.apache.hivemind.definition.ContributionContext;
033    import org.apache.hivemind.definition.ContributionDefinition;
034    import org.apache.hivemind.definition.Occurances;
035    import org.apache.hivemind.impl.servicemodel.SingletonInnerProxy;
036    import org.apache.hivemind.internal.AbstractConstructionContext;
037    import org.apache.hivemind.internal.ConfigurationPoint;
038    import org.apache.hivemind.internal.Module;
039    import org.apache.hivemind.service.BodyBuilder;
040    import org.apache.hivemind.service.ClassFab;
041    import org.apache.hivemind.service.MethodSignature;
042    import org.apache.hivemind.util.ToStringBuilder;
043    
044    /**
045     * Implementation of the {@link org.apache.hivemind.internal.ConfigurationPoint} interface; a
046     * container for {@link org.apache.hivemind.definition.Contribution}s.
047     * 
048     * @author Howard Lewis Ship
049     */
050    public final class ConfigurationPointImpl extends AbstractExtensionPoint implements
051            ConfigurationPoint
052    {
053        private static final Log LOG = LogFactory.getLog(ConfigurationPointImpl.class);
054    
055        /**
056         * The cached elements for the extension point (if caching is enabled).
057         */
058        private Object _configuration;
059        
060        private Class _configurationInterface;
061    
062        private Object _configurationProxy;
063    
064        private Occurances _expectedCount;
065    
066        private boolean _building;
067    
068        // TODO: use ShutdownCoordinator 
069        private ShutdownCoordinator _shutdownCoordinator;
070    
071        public ConfigurationPointImpl(Module module, ConfigurationPointDefinition definition)
072        {
073            super(module, definition);
074        }
075    
076        public ConfigurationPointDefinition getConfigurationPointDefinition()
077        {
078            return (ConfigurationPointDefinition) super.getDefinition();
079        }
080        
081        protected void extendDescription(ToStringBuilder builder)
082        {
083            builder.append("type", getConfigurationTypeName());
084            builder.append("expectedCount", _expectedCount);
085        }
086        
087        public Collection getContributions()
088        {
089            return getConfigurationPointDefinition().getContributions();
090        }
091    
092        /**
093         * Returns the number of contributions; it is expected that each top-level
094         * {@link org.apache.hivemind.Element} in each {@link Contribution} will convert to one element
095         * instance; the value returned is the total number of top-level elements in all contributed
096         * Extensions.
097         */
098        public int getContributionCount()
099        {
100            if (getConfigurationPointDefinition() == null)
101                return 0;
102    
103            return getContributions().size();
104        }
105    
106        public Occurances getExpectedCount()
107        {
108            return getConfigurationPointDefinition().getExpectedContributions();
109        }
110    
111        /**
112         * @return true if configuration should be created lazy, that means a proxy must be created.
113         */
114        public boolean isLazy()
115        {
116            // TODO annotations: make configurable
117            // exclude ServiceModels, otherwise a cycle occurs because the proxy generation
118            // requires the {@link ClassFactory service}
119            return !getExtensionPointId().equals("hivemind.ServiceModels") && 
120              getConfigurationType().isInterface() &&
121              !Modifier.isFinal(getConfigurationType().getModifiers());
122        }
123    
124        /**
125         * @see org.apache.hivemind.internal.ConfigurationPoint#getConfiguration()
126         */
127        public synchronized Object getConfiguration()
128        {
129            if (_configuration != null)
130                return _configuration;
131    
132            if (isLazy()) {
133                // Configuration is lazy, so return a proxy that generates the configuration
134                // the first time a member method is called
135                if (_configurationProxy == null)
136                {
137                    _configurationProxy = createSingletonProxy();
138                }
139                return _configurationProxy;
140                
141            } else {
142                // construct the container immediately
143                _configuration = constructConfiguration();
144                return _configuration;
145            }
146        }
147    
148        /**
149         * Called by the proxy responsible for lazy construction of the configuration when 
150         * the first time a method of the container proxy is called. 
151         * Generates the real configuration object and stores it in a field.
152         * Must be public so the proxy can access it.
153         */
154        public synchronized Object constructConfiguration()
155        {
156            // It's nice to have this protection, but (unlike services), you
157            // would really have to go out of your way to provoke
158            // a recursive configuration.
159    
160            if (_building)
161                throw new ApplicationRuntimeException(ImplMessages
162                        .recursiveConfiguration(getExtensionPointId()));
163    
164            try
165            {
166                if (_configuration == null)
167                {
168                    _building = true;
169                    
170                    processContributions();
171                }
172    
173                // Now that we have the real list, we don't need the proxy anymore, either.
174    
175                _configurationProxy = null;
176    
177                return _configuration;
178            }
179            finally
180            {
181                _building = false;
182            }
183        }
184    
185        /**
186         * Adds all contributions to the configuration container.
187         */
188        private void processContributions()
189        {
190            if (LOG.isDebugEnabled())
191                LOG.debug("Constructing extension point " + getExtensionPointId());
192    
193            Collection contributions = getContributions();
194            
195            if (contributions == null)
196                return;
197    
198            try
199            {
200                for (Iterator iterContrib = contributions.iterator(); iterContrib.hasNext();)
201                {
202                    ContributionDefinition cd = (ContributionDefinition) iterContrib.next();
203                    Module definingModule = getModule().getRegistry().getModule(cd.getModuleId());
204                    ContributionContext context = new ContributionContextImpl(definingModule, this);
205                    cd.getContribution().contribute(context);
206                }
207                // For backward compatibility create empty collections if nothing was contributed
208                if (_configuration == null) {
209                    initEmptyCollection();
210                }
211            }
212            catch (Exception ex)
213            {
214                throw new ApplicationRuntimeException(ImplMessages.unableToConstructConfiguration(
215                        getExtensionPointId(),
216                        ex), ex);
217            }
218    
219        }
220        
221        /**
222         * Implementation of {@link ContributionContext}.
223         * Currently defined inline since it needs access to private methods of the outer configuration point. 
224         */
225        class ContributionContextImpl extends AbstractConstructionContext implements ContributionContext 
226        {
227            private ConfigurationPoint _configurationPoint;
228    
229            public ContributionContextImpl(Module definingModule, ConfigurationPoint configurationPoint)
230            {
231                super(definingModule);
232                _configurationPoint = configurationPoint;
233            }
234    
235            public Object getConfigurationData()
236            {
237                return _configuration;
238            }
239    
240            public void mergeContribution(Object contributionData)
241            {
242                ConfigurationPointImpl.this.mergeContribution(contributionData);
243            }
244    
245            public void setConfigurationData(Object data)
246            {
247                _configuration = data;
248            }
249    
250            public ConfigurationPoint getConfigurationPoint()
251            {
252                return _configurationPoint;
253            }
254        }
255        
256        private void initEmptyCollection()
257        {
258            // TODO: this should be xml specific and maybe realized as initial empty contribution?
259            // But what happens with the contribution count?
260            // Move contribution count to xml?
261            if (List.class.equals(getConfigurationType())) {
262                _configuration = new ArrayList();
263            }
264            else if (Map.class.equals(getConfigurationType())) {
265                _configuration = new HashMap(); 
266            }
267        }
268    
269        /**
270         * Merges a contribution with the configuration data already present in the field _configuration.
271         * TODO: Refactor as configurable service
272         * @param contribution
273         */
274        private void mergeContribution(Object contribution)
275        {
276            if (!getConfigurationType().isAssignableFrom(contribution.getClass())) {
277                throw new ApplicationRuntimeException("contribution of of type " +
278                        contribution.getClass().getName() + " is not compatible to configuration type " + 
279                        getConfigurationType().getName());
280            }
281            if (_configuration == null) {
282                _configuration = contribution;
283            } else {
284                if (_configuration instanceof Collection) {
285                    ((Collection) _configuration).addAll((Collection) contribution); 
286                }
287                else if (_configuration instanceof Map) {
288                    ((Map) _configuration).putAll((Map) contribution); 
289                }
290            }
291            
292            
293        }
294    
295        public void setShutdownCoordinator(ShutdownCoordinator coordinator)
296        {
297            _shutdownCoordinator = coordinator;
298        }
299    
300        public String getConfigurationTypeName()
301        {
302            return getConfigurationPointDefinition().getConfigurationTypeName();
303        }
304    
305        /**
306         * @see org.apache.hivemind.internal.ConfigurationPoint#getConfigurationType()
307         */
308        public Class getConfigurationType()
309        {
310            if (_configurationInterface == null)
311                _configurationInterface = getModule().resolveType(getConfigurationTypeName());
312    
313            return _configurationInterface;
314        }
315    
316        /**
317         * Creates a proxy class for the service and then construct the class itself.
318         */
319        private Object createSingletonProxy()
320        {
321            if (LOG.isDebugEnabled())
322                LOG.debug("Creating LazyConstructionProxy for configuration "
323                        + getExtensionPointId());
324    
325            try
326            {
327    
328                // Create the outer proxy, the one visible to client code (including
329                // other services). It is dependent on an inner proxy.
330    
331                Class proxyClass = getSingletonProxyClass();
332    
333                // Create the inner proxy, whose job is to replace itself
334                // when the first service method is invoked.
335    
336                Class innerProxyClass = getInnerProxyClass(proxyClass);
337    
338                // Create the outer proxy.
339    
340                Constructor co = proxyClass.getConstructor(new Class[]
341                { String.class });
342    
343                Object result = co.newInstance(new Object[] { getExtensionPointId() });
344    
345                // The inner proxy's construct invokes a method on the
346                // outer proxy to connect the two.
347    
348                Constructor ci = innerProxyClass.getConstructor(new Class[]
349                { String.class, proxyClass, getClass() });
350    
351                ci.newInstance(new Object[] { getExtensionPointId(), result, this });
352    
353                return result;
354            }
355            catch (Exception ex)
356            {
357                throw new ApplicationRuntimeException(ex);
358            }
359    
360        }
361        
362        private final static Map SINGLETON_PROXY_CACHE = new HashMap();
363        private final static Map INNER_PROXY_CACHE = new HashMap();
364        
365        private Class getSingletonProxyClass()
366        {
367            Class configurationInterface = getConfigurationType();
368            Class result = (Class) SINGLETON_PROXY_CACHE.get(configurationInterface);
369            if (result == null) {
370              result = createSingletonProxyClass();
371              SINGLETON_PROXY_CACHE.put(configurationInterface, result);
372            }
373            return result;
374        }
375        
376        private Class getInnerProxyClass(Class deferredProxyClass)
377        {
378            Class result = (Class) INNER_PROXY_CACHE.get(deferredProxyClass);
379            if (result == null) {
380              result = createInnerProxyClass(deferredProxyClass);
381              INNER_PROXY_CACHE.put(deferredProxyClass, result);
382            }
383            return result;
384        }
385    
386        /**
387         * Creates a class that implements the service interface. Implements a private synchronized
388         * method, _configuration(), that constructs the service as needed, and has each service interface
389         * method re-invoke on _configuration(). Adds a toString() method if the service interface does not
390         * define toString().
391         */
392        private Class createSingletonProxyClass()
393        {
394            ProxyBuilder proxyBuilder = new ProxyBuilder("LazyConstructionProxy", getModule(), getConfigurationType(), 
395                    getConfigurationType(), true);
396    
397            ClassFab classFab = proxyBuilder.getClassFab();
398    
399    
400            // This will initally be the inner proxy, then switch over to the
401            // service implementation.
402    
403            classFab.addField("_inner", getConfigurationType());
404            classFab.addMethod(
405                    Modifier.PUBLIC | Modifier.SYNCHRONIZED | Modifier.FINAL,
406                    new MethodSignature(void.class, "_setInner", new Class[]
407                    { getConfigurationType() }, null),
408                    "{ _inner = $1; }");
409    
410            BodyBuilder builder = new BodyBuilder();
411            builder.begin();
412    
413            builder.addln("return _inner;");
414            builder.end();
415    
416            classFab.addMethod(Modifier.PRIVATE, new MethodSignature(getConfigurationType(), "_getInner",
417                    null, null), builder.toString());
418    
419            proxyBuilder.addServiceMethods("_getInner()", false);
420    
421            // The toString calls the toString method of the configuration if it is
422            // created already
423            // TODO: Implement like described
424    //        String proxyToStringMessage = "<LazyConstructionProxy for "
425    //            + getExtensionPointId() + "(" + configurationInterface.getName() + ")>";
426            builder.clear();
427            builder.begin();
428            builder.addln(" return _inner.toString();");
429            builder.end();
430    
431            MethodSignature toStringSignature = new MethodSignature(String.class, "toString", null,
432                    null);
433            if (!classFab.containsMethod(toStringSignature)) {
434                classFab.addMethod(Modifier.PUBLIC, toStringSignature, builder.toString());
435            }
436    
437            return classFab.createClass();
438        }
439    
440        private Class createInnerProxyClass(Class deferredProxyClass)
441        {
442            ProxyBuilder builder = new ProxyBuilder("InnerProxy", getModule(), getConfigurationType(), 
443                    getConfigurationType(), false);
444    
445            ClassFab classFab = builder.getClassFab();
446    
447            classFab.addField("_deferredProxy", deferredProxyClass);
448            classFab.addField("_configuration", getConfigurationType());
449            classFab.addField("_configurationPoint", ConfigurationPointImpl.class);
450    
451            BodyBuilder body = new BodyBuilder();
452    
453            // The constructor remembers the outer proxy and registers itself
454            // with the outer proxy.
455    
456            body.begin();
457    
458            body.addln("this($1);");
459            body.addln("_deferredProxy = $2;");
460            body.addln("_configurationPoint = $3;");
461            body.addln("_deferredProxy._setInner(this);");
462    
463            body.end();
464    
465            classFab.addConstructor(new Class[]
466            { String.class, deferredProxyClass, ConfigurationPointImpl.class }, null, body.toString());
467    
468            // Method _configuration() will look up the configuration,
469            // then update the deferred proxy to go directly to the
470            // configuration, bypassing itself!
471    
472            body.clear();
473            body.begin();
474    
475            body.add("if (_configuration == null)");
476            body.begin();
477    
478            body.add("_configuration = (");
479            body.add(getConfigurationType().getName());
480            body.addln(") _configurationPoint.constructConfiguration();");
481    
482            body.add("_deferredProxy._setInner(_configuration);");
483    
484            body.end();
485    
486            body.add("return _configuration;");
487    
488            body.end();
489    
490            classFab.addMethod(
491                    Modifier.PRIVATE | Modifier.FINAL | Modifier.SYNCHRONIZED,
492                    new MethodSignature(getConfigurationType(), "_configuration", null, null),
493                    body.toString());
494    
495            builder.addServiceMethods("_configuration()");
496    
497            // Build the implementation of interface SingletonInnerProxy
498    
499            body.clear();
500            body.begin();
501    
502            body.add("_configuration();");
503    
504            body.end();
505    
506            classFab.addMethod(Modifier.PUBLIC | Modifier.FINAL, new MethodSignature(void.class,
507                    "_instantiateServiceImplementation", null, null), body.toString());
508    
509            classFab.addInterface(SingletonInnerProxy.class);
510    
511            return classFab.createClass();
512        }
513    
514    }