001 /**
002 *
003 * Copyright 2003-2004 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.geronimo.gbean.runtime;
019
020 import java.io.PrintWriter;
021 import java.io.StringWriter;
022 import java.lang.reflect.Constructor;
023 import java.lang.reflect.InvocationTargetException;
024 import java.util.Collection;
025 import java.util.Collections;
026 import java.util.HashMap;
027 import java.util.HashSet;
028 import java.util.Iterator;
029 import java.util.LinkedHashSet;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Set;
033 import java.util.Arrays;
034
035 import javax.management.ObjectName;
036
037 import org.apache.commons.logging.Log;
038 import org.apache.commons.logging.LogFactory;
039 import org.apache.geronimo.gbean.AbstractName;
040 import org.apache.geronimo.gbean.AbstractNameQuery;
041 import org.apache.geronimo.gbean.GAttributeInfo;
042 import org.apache.geronimo.gbean.GBeanData;
043 import org.apache.geronimo.gbean.GBeanInfo;
044 import org.apache.geronimo.gbean.GBeanLifecycle;
045 import org.apache.geronimo.gbean.GConstructorInfo;
046 import org.apache.geronimo.gbean.GOperationInfo;
047 import org.apache.geronimo.gbean.GOperationSignature;
048 import org.apache.geronimo.gbean.GReferenceInfo;
049 import org.apache.geronimo.gbean.InvalidConfigurationException;
050 import org.apache.geronimo.gbean.ReferencePatterns;
051 import org.apache.geronimo.kernel.DependencyManager;
052 import org.apache.geronimo.kernel.GBeanNotFoundException;
053 import org.apache.geronimo.kernel.Kernel;
054 import org.apache.geronimo.kernel.NoSuchAttributeException;
055 import org.apache.geronimo.kernel.NoSuchOperationException;
056 import org.apache.geronimo.kernel.repository.Artifact;
057 import org.apache.geronimo.kernel.config.ManageableAttributeStore;
058 import org.apache.geronimo.kernel.management.State;
059 import org.apache.geronimo.kernel.management.StateManageable;
060
061 /**
062 * A GBeanInstance is a J2EE Management Managed Object, and is standard base for Geronimo services.
063 *
064 * @version $Rev:385718 $ $Date: 2006-08-17 15:28:09 -0700 (Thu, 17 Aug 2006) $
065 */
066 public final class GBeanInstance implements StateManageable {
067 private static final Log log = LogFactory.getLog(GBeanInstance.class);
068
069 private static final int DESTROYED = 0;
070 private static final int CREATING = 1;
071 private static final int RUNNING = 2;
072 private static final int DESTROYING = 3;
073
074 /**
075 * Attribute name used to retrieve the RawInvoker for the GBean
076 */
077 public static final String RAW_INVOKER = "$$RAW_INVOKER$$";
078
079 /**
080 * The kernel in which this server is registered.
081 */
082 private final Kernel kernel;
083
084 /**
085 * The ManageableAttributeStore notified of any changes to manageable
086 * attributes. This is lazy-loaded as manageable attributes are set.
087 */
088 private ManageableAttributeStore manageableStore;
089
090 /**
091 * the abstract name of this service
092 */
093 private final AbstractName abstractName;
094
095 /**
096 * This handles all state transiitions for this instance.
097 */
098 private final GBeanInstanceState gbeanInstanceState;
099
100 /**
101 * The constructor used to create the instance
102 */
103 private final Constructor constructor;
104
105 /**
106 * A fast index based raw invoker for this GBean.
107 */
108 private final RawInvoker rawInvoker;
109
110 /**
111 * The single listener to which we broadcast lifecycle change events.
112 */
113 private final LifecycleBroadcaster lifecycleBroadcaster;
114
115 /**
116 * Interfaces for this GBean
117 */
118 private final String[] interfaces;
119
120 /**
121 * Attributes lookup table
122 */
123 private final GBeanAttribute[] attributes;
124
125 /**
126 * Attributes supported by this GBeanMBean by (String) name.
127 */
128 private final Map attributeIndex = new HashMap();
129
130 /**
131 * References lookup table
132 */
133 private final GBeanReference[] references;
134
135 /**
136 * References supported by this GBeanMBean by (String) name.
137 */
138 private final Map referenceIndex = new HashMap();
139
140 /**
141 * Dependencies supported by this GBean.
142 */
143 private final GBeanDependency[] dependencies;
144
145 /**
146 * Operations lookup table
147 */
148 private final GBeanOperation[] operations;
149
150 /**
151 * Operations supported by this GBeanMBean by (GOperationSignature) name.
152 */
153 private final Map operationIndex = new HashMap();
154
155 /**
156 * The classloader used for all invocations and creating targets.
157 */
158 private final ClassLoader classLoader;
159
160 /**
161 * Metadata describing the attributes, operations and references of this GBean
162 */
163 private final GBeanInfo gbeanInfo;
164
165 /**
166 * Our name
167 */
168 private final String name;
169
170 /**
171 * Java type of the wrapped GBean class
172 */
173 private final Class type;
174
175 /**
176 * Has this instance been destroyed?
177 */
178 private boolean dead = false;
179
180 /**
181 * The state of the internal gbean instance that we are wrapping.
182 */
183 private int instanceState = DESTROYED;
184
185 /**
186 * Target instance of this GBean wrapper
187 */
188 private Object target;
189
190 /**
191 * The time this application started.
192 */
193 private long startTime;
194
195 /**
196 * This is used to signal the creating thread that it should
197 * fail when it returns from usercode. This is set when a
198 * reference has gone offline during construction.
199 */
200 private boolean shouldFail = false;
201
202 /**
203 * Used to track instance
204 */
205 private InstanceRegistry instanceRegistry;
206
207 private String stateReason;
208
209 /**
210 * Construct a GBeanMBean using the supplied GBeanData and class loader
211 *
212 * @param gbeanData the data for the new GBean including GBeanInfo, intial attribute values, and reference patterns
213 * @param classLoader the class loader used to load the gbean instance and attribute/reference types
214 * @throws org.apache.geronimo.gbean.InvalidConfigurationException
215 * if the gbeanInfo is inconsistent with the actual java classes, such as
216 * mismatched attribute types or the intial data cannot be set
217 */
218 public GBeanInstance(GBeanData gbeanData, Kernel kernel, DependencyManager dependencyManager, LifecycleBroadcaster lifecycleBroadcaster, ClassLoader classLoader) throws InvalidConfigurationException {
219 this.abstractName = gbeanData.getAbstractName();
220 this.kernel = kernel;
221 this.lifecycleBroadcaster = lifecycleBroadcaster;
222 this.gbeanInstanceState = new GBeanInstanceState(abstractName, kernel, dependencyManager, this, lifecycleBroadcaster);
223 this.classLoader = classLoader;
224
225 GBeanInfo gbeanInfo = gbeanData.getGBeanInfo();
226 try {
227 type = classLoader.loadClass(gbeanInfo.getClassName());
228 } catch (ClassNotFoundException e) {
229 throw new InvalidConfigurationException("Could not load GBeanInfo class from classloader: " + classLoader +
230 " className=" + gbeanInfo.getClassName());
231 }
232
233 name = gbeanInfo.getName();
234
235 //
236 Set constructorArgs = new HashSet(gbeanInfo.getConstructor().getAttributeNames());
237
238 // interfaces
239 interfaces = (String[]) gbeanInfo.getInterfaces().toArray(new String[0]);
240
241 // attributes
242 Map attributesMap = new HashMap();
243 for (Iterator iterator = gbeanInfo.getAttributes().iterator(); iterator.hasNext();) {
244 GAttributeInfo attributeInfo = (GAttributeInfo) iterator.next();
245 attributesMap.put(attributeInfo.getName(), new GBeanAttribute(this, attributeInfo, constructorArgs.contains(attributeInfo.getName())));
246 }
247 addManagedObjectAttributes(attributesMap);
248 attributes = (GBeanAttribute[]) attributesMap.values().toArray(new GBeanAttribute[attributesMap.size()]);
249 for (int i = 0; i < attributes.length; i++) {
250 attributeIndex.put(attributes[i].getName(), new Integer(i));
251 }
252
253 // references
254 Set referencesSet = new HashSet();
255 Set dependencySet = new HashSet();
256 // add the references
257 Map dataReferences = gbeanData.getReferences();
258 for (Iterator iterator = gbeanInfo.getReferences().iterator(); iterator.hasNext();) {
259 GReferenceInfo referenceInfo = (GReferenceInfo) iterator.next();
260 String referenceName = referenceInfo.getName();
261 ReferencePatterns referencePatterns = (ReferencePatterns) dataReferences.remove(referenceName);
262 if (referenceInfo.getProxyType().equals(Collection.class.getName())) {
263 referencesSet.add(new GBeanCollectionReference(this, referenceInfo, kernel, referencePatterns));
264
265 } else {
266 referencesSet.add(new GBeanSingleReference(this, referenceInfo, kernel, referencePatterns));
267 if (referencePatterns != null) {
268 dependencySet.add(new GBeanDependency(this, referencePatterns.getAbstractName(), kernel));
269 }
270 }
271 }
272 if (!dataReferences.isEmpty()) {
273 throw new IllegalStateException("Attempting to set unknown references: " + dataReferences.keySet());
274 }
275
276 references = (GBeanReference[]) referencesSet.toArray(new GBeanReference[referencesSet.size()]);
277 for (int i = 0; i < references.length; i++) {
278 referenceIndex.put(references[i].getName(), new Integer(i));
279 }
280
281 //dependencies
282 for (Iterator iterator = gbeanData.getDependencies().iterator(); iterator.hasNext();) {
283 AbstractName dependencyName = ((ReferencePatterns) iterator.next()).getAbstractName();
284 dependencySet.add(new GBeanDependency(this, dependencyName, kernel));
285 }
286
287 dependencies = (GBeanDependency[]) dependencySet.toArray(new GBeanDependency[dependencySet.size()]);
288
289 // framework operations -- all framework operations have currently been removed
290
291 // operations
292 Map operationsMap = new HashMap();
293 for (Iterator iterator = gbeanInfo.getOperations().iterator(); iterator.hasNext();) {
294 GOperationInfo operationInfo = (GOperationInfo) iterator.next();
295 GOperationSignature signature = new GOperationSignature(operationInfo.getName(), operationInfo.getParameterList());
296 // do not allow overriding of framework operations
297 if (!operationsMap.containsKey(signature)) {
298 GBeanOperation operation = new GBeanOperation(this, operationInfo);
299 operationsMap.put(signature, operation);
300 }
301 }
302 operations = new GBeanOperation[operationsMap.size()];
303 int opCounter = 0;
304 for (Iterator iterator = operationsMap.entrySet().iterator(); iterator.hasNext();) {
305 Map.Entry entry = (Map.Entry) iterator.next();
306 operations[opCounter] = (GBeanOperation) entry.getValue();
307 operationIndex.put(entry.getKey(), new Integer(opCounter));
308 opCounter++;
309 }
310
311 // get the constructor
312 List arguments = gbeanInfo.getConstructor().getAttributeNames();
313 Class[] parameterTypes = new Class[arguments.size()];
314 for (int i = 0; i < parameterTypes.length; i++) {
315 String argumentName = (String) arguments.get(i);
316 if (referenceIndex.containsKey(argumentName)) {
317 Integer index = (Integer) referenceIndex.get(argumentName);
318 GBeanReference reference = references[index.intValue()];
319 parameterTypes[i] = reference.getProxyType();
320 } else if (attributeIndex.containsKey(argumentName)) {
321 Integer index = (Integer) attributeIndex.get(argumentName);
322 GBeanAttribute attribute = attributes[index.intValue()];
323 parameterTypes[i] = attribute.getType();
324 }
325 }
326 try {
327 constructor = type.getConstructor(parameterTypes);
328 } catch (NoSuchMethodException e) {
329 StringBuffer buf = new StringBuffer("Could not find a valid constructor for GBean: ").append(gbeanInfo.getName()).append("\n");
330 buf.append("ParameterTypes: ").append(Arrays.asList(parameterTypes)).append("\n");
331 Constructor[] constructors = type.getConstructors();
332 for (int i = 0; i < constructors.length; i++) {
333 Constructor testConstructor = constructors[i];
334 buf.append("constructor types: ").append(Arrays.asList(testConstructor.getParameterTypes())).append("\n");
335 if (testConstructor.getParameterTypes().length == parameterTypes.length) {
336 Class[] testParameterTypes = testConstructor.getParameterTypes();
337 for (int k = 0; k < testParameterTypes.length; k++) {
338 Class testParameterType = testParameterTypes[k];
339 if (parameterTypes[k].getName().equals(testParameterType.getName())) {
340 if (parameterTypes[k].getClassLoader() != testParameterType.getClassLoader()) {
341 buf.append("different classloaders in position: ").append(k).append(" class name: ").append(testParameterType.getName()).append("\n");
342 buf.append("parameter type classloader: ").append(parameterTypes[k].getClassLoader()).append("\n");
343 buf.append("constructor type classloader: ").append(testParameterType.getClassLoader()).append("\n");
344 }
345 } else {
346 buf.append("different type in position: ").append(k).append("\n");
347 }
348 }
349 }
350 }
351 throw new InvalidConfigurationException(buf.toString());
352 }
353
354 // rebuild the gbean info based on the current attributes, operations, and references because
355 // the above code add new attributes and operations
356 this.gbeanInfo = rebuildGBeanInfo(gbeanInfo.getConstructor(), gbeanInfo.getJ2eeType());
357
358 // create the raw invokers
359 rawInvoker = new RawInvoker(this);
360
361 // set the initial attribute values
362 try {
363 // set the attributes
364 Map dataAttributes = gbeanData.getAttributes();
365 for (Iterator iterator = dataAttributes.entrySet().iterator(); iterator.hasNext();) {
366 Map.Entry entry = (Map.Entry) iterator.next();
367 String attributeName = (String) entry.getKey();
368 Object attributeValue = entry.getValue();
369 if (entry.getValue() != null) {
370 setAttribute(attributeName, attributeValue, false);
371 }
372 }
373
374 } catch (Exception e) {
375 throw new InvalidConfigurationException("Could not inject configuration data into the GBean " + abstractName, e);
376 }
377
378 for (int i = 0; i < dependencies.length; i++) {
379 dependencies[i].online();
380 }
381 for (int i = 0; i < references.length; i++) {
382 references[i].online();
383 }
384 }
385
386 public void die() throws GBeanNotFoundException {
387 synchronized (this) {
388 if (dead) {
389 // someone beat us to the punch... this instance should have never been found in the first place
390 throw new GBeanNotFoundException(abstractName);
391 }
392 dead = true;
393 }
394
395 // if the bean is already stopped or failed, this will do nothing; otherwise it will shutdown the bean
396 gbeanInstanceState.fail();
397
398 for (int i = 0; i < references.length; i++) {
399 references[i].offline();
400 }
401 for (int i = 0; i < dependencies.length; i++) {
402 dependencies[i].offline();
403 }
404
405 // tell everyone we are done
406 lifecycleBroadcaster.fireUnloadedEvent();
407
408 manageableStore = null;
409 }
410
411 public synchronized void setInstanceRegistry(InstanceRegistry instanceRegistry) {
412 this.instanceRegistry = instanceRegistry;
413 }
414
415 /**
416 * Gets the name of the GBean as defined in the gbean info.
417 *
418 * @return the gbean name
419 */
420 public String getName() {
421 return name;
422 }
423
424 /**
425 * The class loader used to build this gbean. This class loader is set into the thread context
426 * class loader before callint the target instace.
427 *
428 * @return the class loader used to build this gbean
429 */
430 public ClassLoader getClassLoader() {
431 return classLoader;
432 }
433
434 /**
435 * Has this gbean instance been destroyed. An destroyed gbean can no longer be used.
436 *
437 * @return true if the gbean has been destroyed
438 */
439 public synchronized boolean isDead() {
440 return dead;
441 }
442
443 /**
444 * Gets the reason we are in the current state.
445 * @return the reason we are in the current state
446 */
447 public String getStateReason() {
448 return stateReason;
449 }
450
451 /**
452 * The java type of the wrapped gbean instance
453 *
454 * @return the java type of the gbean
455 */
456 public Class getType() {
457 return type;
458 }
459
460 public synchronized Object getTarget() {
461 return target;
462 }
463
464 public final String getObjectName() {
465 return abstractName.getObjectName().getCanonicalName();
466 }
467
468 public final ObjectName getObjectNameObject() {
469 return abstractName.getObjectName();
470 }
471
472 public final AbstractName getAbstractName() {
473 return abstractName;
474 }
475
476 public synchronized final long getStartTime() {
477 return startTime;
478 }
479
480 public int getState() {
481 return gbeanInstanceState.getState();
482 }
483
484 public final State getStateInstance() {
485 return gbeanInstanceState.getStateInstance();
486 }
487
488 /**
489 * Gets an unmodifiable map from attribute names to index number (Integer). This index number
490 * can be used to efficiently set or retrieve an attribute value.
491 *
492 * @return an unmodifiable map of attribute indexes by name
493 */
494 public Map getAttributeIndex() {
495 return Collections.unmodifiableMap(new HashMap(attributeIndex));
496 }
497
498 /**
499 * Gets an unmodifiable map from operation signature (GOperationSignature) to index number (Integer).
500 * This index number can be used to efficciently invoke the operation.
501 *
502 * @return an unmodifiable map of operation indexec by signature
503 */
504 public Map getOperationIndex() {
505 return Collections.unmodifiableMap(new HashMap(operationIndex));
506 }
507
508 /**
509 * Gets the GBeanInfo used to build this gbean.
510 *
511 * @return the GBeanInfo used to build this gbean
512 */
513 public GBeanInfo getGBeanInfo() {
514 return gbeanInfo;
515 }
516
517 /**
518 * Moves this GBeanInstance to the starting state and then attempts to move this MBean immediately
519 * to the running state.
520 *
521 * @throws IllegalStateException If the gbean is disabled
522 */
523 public final void start() {
524 synchronized (this) {
525 if (dead) {
526 throw new IllegalStateException("A dead GBean can not be started: abstractName=" + abstractName);
527 }
528 }
529 gbeanInstanceState.start();
530 }
531
532 /**
533 * Starts this GBeanInstance and then attempts to start all of its start dependent children.
534 *
535 * @throws IllegalStateException If the gbean is disabled
536 */
537 public final void startRecursive() {
538 synchronized (this) {
539 if (dead) {
540 throw new IllegalStateException("A dead GBean can not be started: abstractName=" + abstractName);
541 }
542 }
543 gbeanInstanceState.startRecursive();
544 }
545
546 /**
547 * Moves this GBeanInstance to the STOPPING state, calls stop on all start dependent children, and then attempt
548 * to move this MBean to the STOPPED state.
549 */
550 public final void stop() {
551 gbeanInstanceState.stop();
552 }
553
554 /**
555 * Moves this GBeanInstance to the FAILED state. There are no calls to dependent children, but they will be
556 * notified using standard J2EE management notification.
557 */
558 final void referenceFailed() {
559 gbeanInstanceState.fail();
560 }
561
562 /**
563 * Gets the gbean data for the gbean held by this gbean mbean.
564 *
565 * @return the gbean data
566 */
567 public GBeanData getGBeanData() {
568 GBeanData gbeanData = new GBeanData(abstractName, gbeanInfo);
569
570 // copy target into local variables from within a synchronized block to gaurentee a consistent read
571 int state;
572 Object instance;
573 synchronized (this) {
574 state = instanceState;
575 instance = target;
576 }
577
578 // add the attributes
579 for (int i = 0; i < attributes.length; i++) {
580 GBeanAttribute attribute = attributes[i];
581 if (attribute.isPersistent()) {
582 String name = attribute.getName();
583 Object value;
584 if ((state != DESTROYED || attribute.isFramework()) && attribute.isReadable()) {
585 try {
586 value = attribute.getValue(instance);
587 } catch (Throwable throwable) {
588 value = attribute.getPersistentValue();
589 log.debug("Could not get the current value of persistent attribute. The persistent " +
590 "attribute will not reflect the current state attribute. " + attribute.getDescription(), throwable);
591 }
592 } else {
593 value = attribute.getPersistentValue();
594 }
595 gbeanData.setAttribute(name, value);
596 }
597 }
598
599 // add the references
600 for (int i = 0; i < references.length; i++) {
601 GBeanReference reference = references[i];
602 String name = reference.getName();
603 if(reference instanceof GBeanSingleReference) {
604 AbstractName abstractName = ((GBeanSingleReference) reference).getTargetName();
605 if(abstractName != null) {
606 gbeanData.setReferencePattern(name, abstractName);
607 }
608 } else if(reference instanceof GBeanCollectionReference) {
609 Set patterns = ((GBeanCollectionReference) reference).getPatterns();
610 if(patterns != null) {
611 gbeanData.setReferencePatterns(name, patterns);
612 }
613 } else {
614 throw new IllegalStateException("Unrecognized GBeanReference '"+reference.getClass().getName()+"'");
615 }
616 }
617 //TODO copy the dependencies??
618 return gbeanData;
619 }
620
621 /**
622 * Gets the attribute value using the attribute index. This is the most efficient way to get
623 * an attribute as it avoids a HashMap lookup.
624 *
625 * @param index the index of the attribute
626 * @return the attribute value
627 * @throws Exception if a target instance throws and exception
628 * @throws IndexOutOfBoundsException if the index is invalid
629 */
630 public Object getAttribute(int index) throws Exception {
631 GBeanAttribute attribute = attributes[index];
632
633 // copy target into local variables from within a synchronized block to gaurentee a consistent read
634 int state;
635 Object instance;
636 synchronized (this) {
637 state = instanceState;
638 instance = target;
639 }
640
641 if (state != DESTROYED || attribute.isFramework()) {
642 return attribute.getValue(instance);
643 } else {
644 if (attribute.isPersistent()) {
645 return attribute.getPersistentValue();
646 } else {
647 throw new IllegalStateException("Cannot retrieve the value for non-persistent attribute \"" + attribute.getName() + "\" when GBeanInstance is DESTROYED");
648 }
649 }
650 }
651
652 /**
653 * Gets an attribute's value by name. This get style is less efficient becuse the attribute must
654 * first be looked up in a HashMap.
655 *
656 * @param attributeName the name of the attribute to retrieve
657 * @return the attribute value
658 * @throws Exception if a problem occurs while getting the value
659 * @throws NoSuchAttributeException if the attribute name is not found in the map
660 */
661 public Object getAttribute(String attributeName) throws NoSuchAttributeException, Exception {
662 GBeanAttribute attribute;
663 try {
664 attribute = getAttributeByName(attributeName);
665 } catch (NoSuchAttributeException e) {
666 if (attributeName.equals(RAW_INVOKER)) {
667 return rawInvoker;
668 }
669 throw e;
670 }
671
672 // copy target into local variables from within a synchronized block to gaurentee a consistent read
673 int state;
674 Object instance;
675 synchronized (this) {
676 state = instanceState;
677 instance = target;
678 }
679
680 if (state != DESTROYED || attribute.isFramework()) {
681 return attribute.getValue(instance);
682 } else {
683 if (attribute.isPersistent()) {
684 return attribute.getPersistentValue();
685 } else {
686 throw new IllegalStateException("Cannot retrieve the value for non-persistent attribute " + attributeName + " when gbean has been destroyed: " + abstractName);
687 }
688 }
689 }
690
691 /**
692 * Sets the attribute value using the attribute index. This is the most efficient way to set
693 * an attribute as it avoids a HashMap lookup.
694 *
695 * @param index the index of the attribute
696 * @param value the new value of attribute value
697 * @throws Exception if a target instance throws and exception
698 * @throws IndexOutOfBoundsException if the index is invalid
699 */
700 public void setAttribute(int index, Object value) throws Exception, IndexOutOfBoundsException {
701 setAttribute(index, value, true);
702 }
703
704 private void setAttribute(int index, Object value, boolean manage) throws Exception, IndexOutOfBoundsException {
705 GBeanAttribute attribute = attributes[index];
706
707 // copy target into local variables from within a synchronized block to gaurentee a consistent read
708 int state;
709 Object instance;
710 synchronized (this) {
711 state = instanceState;
712 instance = target;
713 }
714
715 if (state != DESTROYED || attribute.isFramework()) {
716 attribute.setValue(instance, value);
717 } else {
718 attribute.setPersistentValue(value);
719 }
720 if (manage && attribute.isManageable()) {
721 updateManageableAttribute(attribute, value);
722 }
723 }
724
725 /**
726 * Sets an attribute's value by name. This set style is less efficient becuse the attribute must
727 * first be looked up in a HashMap.
728 *
729 * @param attributeName the name of the attribute to retrieve
730 * @param value the new attribute value
731 * @throws Exception if a target instance throws and exception
732 * @throws NoSuchAttributeException if the attribute name is not found in the map
733 */
734 public void setAttribute(String attributeName, Object value) throws Exception, NoSuchAttributeException {
735 setAttribute(attributeName, value, true);
736 }
737
738 public void setAttribute(String attributeName, Object value, boolean manage) throws Exception, NoSuchAttributeException {
739 GBeanAttribute attribute = getAttributeByName(attributeName);
740
741 // copy target into local variables from within a synchronized block to gaurentee a consistent read
742 int state;
743 Object instance;
744 synchronized (this) {
745 state = instanceState;
746 instance = target;
747 }
748
749 if (state != DESTROYED || attribute.isFramework()) {
750 attribute.setValue(instance, value);
751 } else {
752 attribute.setPersistentValue(value);
753 }
754 if (manage && attribute.isManageable()) {
755 updateManageableAttribute(attribute, value);
756 }
757 }
758
759 private void updateManageableAttribute(GBeanAttribute attribute, Object value) {
760 if (manageableStore == null) {
761 manageableStore = getManageableAttributeStore();
762 if (manageableStore == null) {
763 return;
764 }
765 }
766 Artifact configName = abstractName.getArtifact();
767 if (configName != null) {
768 manageableStore.setValue(configName, abstractName, attribute.getAttributeInfo(), value);
769 } else {
770 log.error("Unable to identify Configuration for GBean " + abstractName + ". Manageable attribute " + attribute.getName() + " was not updated in persistent store.");
771 }
772 }
773
774 private ManageableAttributeStore getManageableAttributeStore() {
775 Set set = kernel.listGBeans(new AbstractNameQuery(ManageableAttributeStore.class.getName()));
776 for (Iterator iterator = set.iterator(); iterator.hasNext();) {
777 AbstractName abstractName1 = (AbstractName) iterator.next();
778 try {
779 return (ManageableAttributeStore) kernel.getGBean(abstractName1);
780 } catch (GBeanNotFoundException e) {
781 // ignored... gbean was unregistered
782 }
783 }
784 return null;
785 }
786
787 private GBeanAttribute getAttributeByName(String name) throws NoSuchAttributeException {
788 Integer index = (Integer) attributeIndex.get(name);
789 if (index == null) {
790 throw new NoSuchAttributeException("Unknown attribute \"" + name + "\" in gbean " + abstractName);
791 }
792 return attributes[index.intValue()];
793 }
794
795 /**
796 * Invokes an opreation using the operation index. This is the most efficient way to invoke
797 * an operation as it avoids a HashMap lookup.
798 *
799 * @param index the index of the attribute
800 * @param arguments the arguments to the operation
801 * @return the result of the operation
802 * @throws Exception if a target instance throws and exception
803 * @throws IndexOutOfBoundsException if the index is invalid
804 * @throws IllegalStateException if the gbean instance has been destroyed
805 */
806 public Object invoke(int index, Object[] arguments) throws Exception {
807 GBeanOperation operation = operations[index];
808
809 // copy target into local variables from within a synchronized block to gaurentee a consistent read
810 int state;
811 Object instance;
812 synchronized (this) {
813 state = instanceState;
814 instance = target;
815 }
816
817 if (state == DESTROYED && !operation.isFramework()) {
818 throw new IllegalStateException("Operations can only be invoke while the GBean instance is running: " + abstractName);
819 }
820 return operation.invoke(instance, arguments);
821 }
822
823 /**
824 * Invokes an operation on the target gbean by method signature. This style if invocation is
825 * inefficient, because the target method must be looked up in a hashmap using a freshly constructed
826 * GOperationSignature object.
827 *
828 * @param operationName the name of the operation to invoke
829 * @param arguments arguments to the operation
830 * @param types types of the operation arguemtns
831 * @return the result of the operation
832 * @throws Exception if a target instance throws and exception
833 * @throws NoSuchOperationException if the operation signature is not found in the map
834 * @throws IllegalStateException if the gbean instance has been destroyed
835 */
836 public Object invoke(String operationName, Object[] arguments, String[] types) throws Exception, NoSuchOperationException {
837 GOperationSignature signature = new GOperationSignature(operationName, types);
838 Integer index = (Integer) operationIndex.get(signature);
839 if (index == null) {
840 throw new NoSuchOperationException("Unknown operation " + signature);
841 }
842 GBeanOperation operation = operations[index.intValue()];
843
844 // copy target into local variables from within a synchronized block to gaurentee a consistent read
845 int state;
846 Object instance;
847 synchronized (this) {
848 state = instanceState;
849 instance = target;
850 }
851
852 if (state == DESTROYED && !operation.isFramework()) {
853 throw new IllegalStateException("Operations can only be invoke while the GBean is running: " + abstractName);
854 }
855 return operation.invoke(instance, arguments);
856 }
857
858 private GBeanReference getReferenceByName(String name) {
859 Integer index = (Integer) referenceIndex.get(name);
860 if (index == null) {
861 throw new IllegalArgumentException("Unknown reference " + name);
862 }
863 return references[index.intValue()];
864 }
865
866 boolean createInstance() throws Exception {
867 synchronized (this) {
868 // first check we are still in the correct state to start
869 if (instanceState == CREATING || instanceState == RUNNING) {
870 // another thread already completed starting
871 return false;
872 } else if (instanceState == DESTROYING) {
873 // this should never ever happen... this method is protected by the GBeanState class which should
874 // prevent stuff like this happening, but check anyway
875 stateReason = "an internal error has occurred. An was made to start an instance that was still stopping which is an illegal state transition.";
876 throw new IllegalStateException("A stopping instance can not be started until fully stopped");
877 }
878 assert instanceState == DESTROYED;
879
880 stateReason = null;
881
882 // Call all start on every reference. This way the dependecies are held until we can start
883 LinkedHashSet unstarted = new LinkedHashSet();
884 for (int i = 0; i < dependencies.length; i++) {
885 if (!dependencies[i].start()) {
886 unstarted.add(dependencies[i].getTargetName());
887 }
888 }
889 for (int i = 0; i < references.length; i++) {
890 if (!references[i].start()) {
891 if (references[i] instanceof GBeanSingleReference) {
892 GBeanSingleReference reference = (GBeanSingleReference) references[i];
893 unstarted.add(reference.getTargetName());
894 }
895 }
896 }
897 if (!unstarted.isEmpty()) {
898 if (unstarted.size() == 1) {
899 stateReason = unstarted.iterator().next() + " did not start.";
900 } else {
901 stateReason = "the following dependent services did not start: " + unstarted;
902 }
903 return false;
904 }
905
906 // we are definately going to (try to) start... if this fails the must clean up these variables
907 instanceState = CREATING;
908 startTime = System.currentTimeMillis();
909 }
910
911 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
912 Thread.currentThread().setContextClassLoader(classLoader);
913 Object instance = null;
914 try {
915 GConstructorInfo constructorInfo = gbeanInfo.getConstructor();
916 Class[] parameterTypes = constructor.getParameterTypes();
917
918 // create constructor parameter array
919 Object[] parameters = new Object[parameterTypes.length];
920 Iterator names = constructorInfo.getAttributeNames().iterator();
921 for (int i = 0; i < parameters.length; i++) {
922 String name = (String) names.next();
923 if (referenceIndex.containsKey(name)) {
924 parameters[i] = getReferenceByName(name).getProxy();
925 } else if (attributeIndex.containsKey(name)) {
926 GBeanAttribute attribute = getAttributeByName(name);
927 parameters[i] = attribute.getPersistentValue();
928 } else {
929 stateReason = "the service constructor definition contained the name '" + name + "' which is not a known attribute or reference of the service.";
930 throw new InvalidConfigurationException("Unknown attribute or reference name in constructor: referenceName=" + name + ", gbean=" + abstractName);
931 }
932 }
933
934 // create instance
935 try {
936 instance = constructor.newInstance(parameters);
937 } catch (InvocationTargetException e) {
938 Throwable targetException = e.getTargetException();
939 if (targetException instanceof Exception) {
940 throw (Exception) targetException;
941 } else if (targetException instanceof Error) {
942 throw (Error) targetException;
943 }
944 stateReason = "the service constructor threw an exception. \n" + printException(e);
945 throw e;
946 } catch (IllegalArgumentException e) {
947 stateReason = "the service constructor threw an exception due to a parameter type mismatch. \n" + printException(e);
948 log.warn("Constructor mismatch for " + abstractName, e);
949 throw e;
950 }
951
952 // write the target variable in a synchronized block so it is available to all threads
953 // we do this before calling the setters or start method so the bean can be called back
954 // from a setter start method
955 synchronized (this) {
956 target = instance;
957 }
958
959 // inject the persistent attribute value into the new instance
960 for (int i = 0; i < attributes.length; i++) {
961 checkIfShouldFail();
962 try {
963 attributes[i].inject(instance);
964 } catch (Exception e) {
965 stateReason = "the setter for attribute '" + attributes[i].getName() + "' threw an exception. \n" + printException(e);
966 throw e;
967 }
968 }
969
970 // inject the proxies into the new instance
971 for (int i = 0; i < references.length; i++) {
972 checkIfShouldFail();
973 try {
974 references[i].inject(instance);
975 } catch (Exception e) {
976 stateReason = "the setter for reference '" + references[i].getName() + "' threw an exception. \n" + printException(e);
977 throw e;
978 }
979 }
980
981 if (instance instanceof GBeanLifecycle) {
982 checkIfShouldFail();
983 try {
984 ((GBeanLifecycle) instance).doStart();
985 } catch (Exception e) {
986 stateReason = "the doStart method threw an exception. \n" + printException(e);
987 throw e;
988 }
989 }
990
991
992 // all done... we are now fully running
993 synchronized (this) {
994 checkIfShouldFail();
995 if (instanceRegistry != null) {
996 instanceRegistry.instanceCreated(instance, this);
997 }
998 instanceState = RUNNING;
999 this.notifyAll();
1000 }
1001
1002
1003 stateReason = null;
1004 return true;
1005 } catch (Throwable t) {
1006 // something went wrong... we need to destroy this instance
1007 synchronized (this) {
1008 instanceState = DESTROYING;
1009 }
1010
1011 if (instance instanceof GBeanLifecycle) {
1012 try {
1013 ((GBeanLifecycle) instance).doFail();
1014 } catch (Throwable ignored) {
1015 log.error("Problem in doFail of " + abstractName, ignored);
1016 }
1017 }
1018
1019 // bean has been notified... drop our reference
1020 synchronized (this) {
1021 // stop all of the references
1022 for (int i = 0; i < references.length; i++) {
1023 references[i].stop();
1024 }
1025 for (int i = 0; i < dependencies.length; i++) {
1026 dependencies[i].stop();
1027 }
1028
1029 target = null;
1030 instanceState = DESTROYED;
1031 startTime = 0;
1032 this.notifyAll();
1033 }
1034
1035 if (t instanceof Exception) {
1036 throw (Exception) t;
1037 } else if (t instanceof Error) {
1038 throw (Error) t;
1039 } else {
1040 throw new Error(t);
1041 }
1042 } finally {
1043 Thread.currentThread().setContextClassLoader(oldCL);
1044 }
1045 }
1046
1047 private String printException(Throwable t) {
1048 StringWriter stringWriter = new StringWriter();
1049 PrintWriter printWriter = new PrintWriter(stringWriter);
1050 t.printStackTrace(printWriter);
1051 printWriter.flush();
1052 return stringWriter.toString();
1053 }
1054
1055 private synchronized void checkIfShouldFail() throws Exception {
1056 if (shouldFail) {
1057 shouldFail = false;
1058 throw new Exception("A reference has failed so construction can not complete");
1059 }
1060 }
1061
1062 boolean destroyInstance(boolean stop) throws Exception {
1063 Object instance;
1064 synchronized (this) {
1065 if (!stop && instanceState == CREATING) {
1066 // signal to the creating thead that it should fail
1067 shouldFail = true;
1068 return false;
1069 }
1070
1071 // if the instance is being created we need to wait
1072 // for it to finish before we can try to stop it
1073 while (instanceState == CREATING) {
1074 // todo should we limit this wait? If so, how do we configure the wait time?
1075 try {
1076 this.wait();
1077 } catch (InterruptedException e) {
1078 // clear the interrupted flag
1079 Thread.interrupted();
1080 // rethrow the interrupted exception.... someone was sick of us waiting
1081 throw e;
1082 }
1083 }
1084
1085 if (instanceState == DESTROYING || instanceState == DESTROYED) {
1086 // another thread is already stopping or has already stopped
1087 return false;
1088 }
1089 assert instanceState == RUNNING;
1090 stateReason = null;
1091
1092 // we are definately going to stop... if this fails the must clean up these variables
1093 instanceState = DESTROYING;
1094 instance = target;
1095 }
1096
1097 // update the persistent attributes
1098 // do not update the persistent attibute values in the case of a failure
1099 // failed gbeans may have corrupted attributes that would be persisted
1100 Exception problem = null;
1101 if (stop && instance != null) {
1102 try {
1103 // get all the data but don't update in case there is an exception
1104 Map data = new HashMap();
1105 for (int i = 0; i < attributes.length; i++) {
1106 GBeanAttribute attribute = attributes[i];
1107 if (attribute.isPersistent() && attribute.isReadable()) {
1108 // copy the current attribute value to the persistent value
1109 Object value;
1110 try {
1111 value = attribute.getValue(instance);
1112 } catch (Throwable e) {
1113 // There is no reason to create a new Exception sub class as this exception will
1114 // simply be caught and logged on GBeanInstanceState
1115 throw new Exception("Problem while updaing the persistent value of attibute: " +
1116 "Attribute Name: " + attribute.getName() + ", " +
1117 "Type: " + attribute.getType() + ", " +
1118 "GBeanInstance: " + getName(), e);
1119 }
1120 data.put(attribute, value);
1121 }
1122 }
1123 // now we have all the data we can update the persistent values
1124 for (int i = 0; i < attributes.length; i++) {
1125 GBeanAttribute attribute = attributes[i];
1126 if (attribute.isPersistent() && attribute.isReadable()) {
1127 // copy the current attribute value to the persistent value
1128 Object value = data.get(attribute);
1129 attribute.setPersistentValue(value);
1130 }
1131 }
1132 } catch (Exception e) {
1133 // the getter threw an exception; now we must to fail
1134 stop = false;
1135 problem = e;
1136 }
1137 }
1138
1139 // we notify the bean before removing our reference so the bean can be called back while stopping
1140 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
1141 Thread.currentThread().setContextClassLoader(classLoader);
1142 try {
1143 if (instance instanceof GBeanLifecycle) {
1144 if (stop) {
1145 try {
1146 ((GBeanLifecycle) instance).doStop();
1147 } catch (Throwable ignored) {
1148 log.error("Problem in doStop of " + abstractName, ignored);
1149 }
1150 } else {
1151 try {
1152 ((GBeanLifecycle) instance).doFail();
1153 } catch (Throwable ignored) {
1154 log.error("Problem in doFail of " + abstractName, ignored);
1155 }
1156 }
1157 }
1158 } finally {
1159 Thread.currentThread().setContextClassLoader(oldCL);
1160 }
1161
1162 // bean has been notified... drop our reference
1163 synchronized (this) {
1164 // stop all of the references
1165 for (int i = 0; i < references.length; i++) {
1166 references[i].stop();
1167 }
1168 for (int i = 0; i < dependencies.length; i++) {
1169 dependencies[i].stop();
1170 }
1171
1172 target = null;
1173 instanceState = DESTROYED;
1174 if (instanceRegistry != null) {
1175 instanceRegistry.instanceDestroyed(instance);
1176 }
1177 startTime = 0;
1178 }
1179
1180 if (problem != null) {
1181 throw problem;
1182 }
1183 return true;
1184 }
1185
1186 private void addManagedObjectAttributes(Map attributesMap) {
1187 //
1188 // Special attributes
1189 //
1190 attributesMap.put("abstractName",
1191 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("abstractName"),
1192 this,
1193 "abstractName",
1194 AbstractName.class,
1195 getAbstractName()));
1196
1197 attributesMap.put("objectName",
1198 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("objectName"),
1199 this,
1200 "objectName",
1201 String.class,
1202 getObjectName()));
1203
1204 attributesMap.put("classLoader",
1205 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("classLoader"),
1206 this,
1207 "classLoader",
1208 ClassLoader.class,
1209 classLoader));
1210
1211 attributesMap.put("kernel",
1212 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("kernel"),
1213 this,
1214 "kernel",
1215 Kernel.class,
1216 kernel));
1217
1218 }
1219
1220 private GBeanInfo rebuildGBeanInfo(GConstructorInfo constructor, String j2eeType) {
1221 Set attributeInfos = new HashSet();
1222 for (int i = 0; i < attributes.length; i++) {
1223 GBeanAttribute attribute = attributes[i];
1224 attributeInfos.add(attribute.getAttributeInfo());
1225 }
1226 Set operationInfos = new HashSet();
1227 for (int i = 0; i < operations.length; i++) {
1228 operationInfos.add(operations[i].getOperationInfo());
1229 }
1230
1231 Set referenceInfos = new HashSet();
1232 for (int i = 0; i < references.length; i++) {
1233 referenceInfos.add(references[i].getReferenceInfo());
1234 }
1235
1236 Set interfaceInfos = new HashSet();
1237 for (int i = 0; i < interfaces.length; i++) {
1238 interfaceInfos.add(interfaces[i]);
1239 }
1240
1241 return new GBeanInfo(name,
1242 type.getName(),
1243 j2eeType,
1244 attributeInfos,
1245 constructor,
1246 operationInfos,
1247 referenceInfos,
1248 interfaceInfos);
1249 }
1250
1251 public boolean equals(Object obj) {
1252 if (obj == this) return true;
1253 if (!(obj instanceof GBeanInstance)) return false;
1254 return abstractName.equals(((GBeanInstance) obj).abstractName);
1255 }
1256
1257 public int hashCode() {
1258 return abstractName.hashCode();
1259 }
1260
1261 public String toString() {
1262 return abstractName.toString();
1263 }
1264 }