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 }