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 }