001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. 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: 2007-07-13 09:39:25 -0400 (Fri, 13 Jul 2007) $
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(), e);
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 //Add the reference to all applicable reference collections before possibly starting the gbean having an
379 //explicit reference to the reference.
380 for (int i = 0; i < references.length; i++) {
381 references[i].online();
382 }
383 for (int i = 0; i < dependencies.length; i++) {
384 dependencies[i].online();
385 }
386 }
387
388 public void die() throws GBeanNotFoundException {
389 synchronized (this) {
390 if (dead) {
391 // someone beat us to the punch... this instance should have never been found in the first place
392 throw new GBeanNotFoundException(abstractName);
393 }
394 dead = true;
395 }
396
397 // if the bean is already stopped or failed, this will do nothing; otherwise it will shutdown the bean
398 gbeanInstanceState.fail();
399
400 for (int i = 0; i < references.length; i++) {
401 references[i].offline();
402 }
403 for (int i = 0; i < dependencies.length; i++) {
404 dependencies[i].offline();
405 }
406
407 // tell everyone we are done
408 lifecycleBroadcaster.fireUnloadedEvent();
409
410 manageableStore = null;
411 }
412
413 public synchronized void setInstanceRegistry(InstanceRegistry instanceRegistry) {
414 this.instanceRegistry = instanceRegistry;
415 }
416
417 /**
418 * Gets the name of the GBean as defined in the gbean info.
419 *
420 * @return the gbean name
421 */
422 public String getName() {
423 return name;
424 }
425
426 /**
427 * The class loader used to build this gbean. This class loader is set into the thread context
428 * class loader before callint the target instace.
429 *
430 * @return the class loader used to build this gbean
431 */
432 public ClassLoader getClassLoader() {
433 return classLoader;
434 }
435
436 /**
437 * Has this gbean instance been destroyed. An destroyed gbean can no longer be used.
438 *
439 * @return true if the gbean has been destroyed
440 */
441 public synchronized boolean isDead() {
442 return dead;
443 }
444
445 /**
446 * Gets the reason we are in the current state.
447 * @return the reason we are in the current state
448 */
449 public String getStateReason() {
450 return stateReason;
451 }
452
453 /**
454 * Sets the reason we are in the current state.
455 * @param reason The reason we are in the current state
456 */
457 public void setStateReason(String reason) {
458 stateReason = reason;
459 }
460
461 /**
462 * The java type of the wrapped gbean instance
463 *
464 * @return the java type of the gbean
465 */
466 public Class getType() {
467 return type;
468 }
469
470 public synchronized Object getTarget() {
471 return target;
472 }
473
474 public final String getObjectName() {
475 return abstractName.getObjectName().getCanonicalName();
476 }
477
478 public final ObjectName getObjectNameObject() {
479 return abstractName.getObjectName();
480 }
481
482 public final AbstractName getAbstractName() {
483 return abstractName;
484 }
485
486 public synchronized final long getStartTime() {
487 return startTime;
488 }
489
490 public int getState() {
491 return gbeanInstanceState.getState();
492 }
493
494 public final State getStateInstance() {
495 return gbeanInstanceState.getStateInstance();
496 }
497
498 /**
499 * Gets an unmodifiable map from attribute names to index number (Integer). This index number
500 * can be used to efficiently set or retrieve an attribute value.
501 *
502 * @return an unmodifiable map of attribute indexes by name
503 */
504 public Map getAttributeIndex() {
505 return Collections.unmodifiableMap(new HashMap(attributeIndex));
506 }
507
508 /**
509 * Gets an unmodifiable map from operation signature (GOperationSignature) to index number (Integer).
510 * This index number can be used to efficciently invoke the operation.
511 *
512 * @return an unmodifiable map of operation indexec by signature
513 */
514 public Map getOperationIndex() {
515 return Collections.unmodifiableMap(new HashMap(operationIndex));
516 }
517
518 /**
519 * Gets the GBeanInfo used to build this gbean.
520 *
521 * @return the GBeanInfo used to build this gbean
522 */
523 public GBeanInfo getGBeanInfo() {
524 return gbeanInfo;
525 }
526
527 /**
528 * Moves this GBeanInstance to the starting state and then attempts to move this MBean immediately
529 * to the running state.
530 *
531 * @throws IllegalStateException If the gbean is disabled
532 */
533 public final void start() {
534 synchronized (this) {
535 if (dead) {
536 throw new IllegalStateException("A dead GBean can not be started: abstractName=" + abstractName);
537 }
538 }
539 gbeanInstanceState.start();
540 }
541
542 /**
543 * Starts this GBeanInstance and then attempts to start all of its start dependent children.
544 *
545 * @throws IllegalStateException If the gbean is disabled
546 */
547 public final void startRecursive() {
548 synchronized (this) {
549 if (dead) {
550 throw new IllegalStateException("A dead GBean can not be started: abstractName=" + abstractName);
551 }
552 }
553 gbeanInstanceState.startRecursive();
554 }
555
556 /**
557 * Moves this GBeanInstance to the STOPPING state, calls stop on all start dependent children, and then attempt
558 * to move this MBean to the STOPPED state.
559 */
560 public final void stop() {
561 gbeanInstanceState.stop();
562 }
563
564 /**
565 * Moves this GBeanInstance to the FAILED state. There are no calls to dependent children, but they will be
566 * notified using standard J2EE management notification.
567 */
568 final void referenceFailed() {
569 gbeanInstanceState.fail();
570 }
571
572 /**
573 * Gets the gbean data for the gbean held by this gbean mbean.
574 *
575 * @return the gbean data
576 */
577 public GBeanData getGBeanData() {
578 GBeanData gbeanData = new GBeanData(abstractName, gbeanInfo);
579
580 // copy target into local variables from within a synchronized block to gaurentee a consistent read
581 int state;
582 Object instance;
583 synchronized (this) {
584 state = instanceState;
585 instance = target;
586 }
587
588 // add the attributes
589 for (int i = 0; i < attributes.length; i++) {
590 GBeanAttribute attribute = attributes[i];
591 if (attribute.isPersistent()) {
592 String name = attribute.getName();
593 Object value;
594 if ((state != DESTROYED || attribute.isFramework()) && attribute.isReadable()) {
595 try {
596 value = attribute.getValue(instance);
597 } catch (Throwable throwable) {
598 value = attribute.getPersistentValue();
599 log.debug("Could not get the current value of persistent attribute. The persistent " +
600 "attribute will not reflect the current state attribute. " + attribute.getDescription(), throwable);
601 }
602 } else {
603 value = attribute.getPersistentValue();
604 }
605 gbeanData.setAttribute(name, value);
606 }
607 }
608
609 // add the references
610 for (int i = 0; i < references.length; i++) {
611 GBeanReference reference = references[i];
612 String name = reference.getName();
613 if(reference instanceof GBeanSingleReference) {
614 AbstractName abstractName = ((GBeanSingleReference) reference).getTargetName();
615 if(abstractName != null) {
616 gbeanData.setReferencePattern(name, abstractName);
617 }
618 } else if(reference instanceof GBeanCollectionReference) {
619 Set patterns = ((GBeanCollectionReference) reference).getPatterns();
620 if(patterns != null) {
621 gbeanData.setReferencePatterns(name, patterns);
622 }
623 } else {
624 throw new IllegalStateException("Unrecognized GBeanReference '"+reference.getClass().getName()+"'");
625 }
626 }
627 //TODO copy the dependencies??
628 return gbeanData;
629 }
630
631 /**
632 * Gets the attribute value using the attribute index. This is the most efficient way to get
633 * an attribute as it avoids a HashMap lookup.
634 *
635 * @param index the index of the attribute
636 * @return the attribute value
637 * @throws Exception if a target instance throws and exception
638 * @throws IndexOutOfBoundsException if the index is invalid
639 */
640 public Object getAttribute(int index) throws Exception {
641 GBeanAttribute attribute = attributes[index];
642
643 // copy target into local variables from within a synchronized block to gaurentee a consistent read
644 int state;
645 Object instance;
646 synchronized (this) {
647 state = instanceState;
648 instance = target;
649 }
650
651 if (state != DESTROYED || attribute.isFramework()) {
652 return attribute.getValue(instance);
653 } else {
654 if (attribute.isPersistent()) {
655 return attribute.getPersistentValue();
656 } else {
657 throw new IllegalStateException("Cannot retrieve the value for non-persistent attribute \"" + attribute.getName() + "\" when GBeanInstance is DESTROYED");
658 }
659 }
660 }
661
662 /**
663 * Gets an attribute's value by name. This get style is less efficient becuse the attribute must
664 * first be looked up in a HashMap.
665 *
666 * @param attributeName the name of the attribute to retrieve
667 * @return the attribute value
668 * @throws Exception if a problem occurs while getting the value
669 * @throws NoSuchAttributeException if the attribute name is not found in the map
670 */
671 public Object getAttribute(String attributeName) throws NoSuchAttributeException, Exception {
672 GBeanAttribute attribute;
673 try {
674 attribute = getAttributeByName(attributeName);
675 } catch (NoSuchAttributeException e) {
676 if (attributeName.equals(RAW_INVOKER)) {
677 return rawInvoker;
678 }
679 throw e;
680 }
681
682 // copy target into local variables from within a synchronized block to gaurentee a consistent read
683 int state;
684 Object instance;
685 synchronized (this) {
686 state = instanceState;
687 instance = target;
688 }
689
690 if (state != DESTROYED || attribute.isFramework()) {
691 return attribute.getValue(instance);
692 } else {
693 if (attribute.isPersistent()) {
694 return attribute.getPersistentValue();
695 } else {
696 throw new IllegalStateException("Cannot retrieve the value for non-persistent attribute " + attributeName + " when gbean has been destroyed: " + abstractName);
697 }
698 }
699 }
700
701 /**
702 * Sets the attribute value using the attribute index. This is the most efficient way to set
703 * an attribute as it avoids a HashMap lookup.
704 *
705 * @param index the index of the attribute
706 * @param value the new value of attribute value
707 * @throws Exception if a target instance throws and exception
708 * @throws IndexOutOfBoundsException if the index is invalid
709 */
710 public void setAttribute(int index, Object value) throws Exception, IndexOutOfBoundsException {
711 setAttribute(index, value, true);
712 }
713
714 private void setAttribute(int index, Object value, boolean manage) throws Exception, IndexOutOfBoundsException {
715 GBeanAttribute attribute = attributes[index];
716
717 // copy target into local variables from within a synchronized block to gaurentee a consistent read
718 int state;
719 Object instance;
720 synchronized (this) {
721 state = instanceState;
722 instance = target;
723 }
724
725 if (state != DESTROYED || attribute.isFramework()) {
726 attribute.setValue(instance, value);
727 } else {
728 attribute.setPersistentValue(value);
729 }
730 if (manage && attribute.isManageable()) {
731 updateManageableAttribute(attribute, value);
732 }
733 }
734
735 /**
736 * Sets an attribute's value by name. This set style is less efficient becuse the attribute must
737 * first be looked up in a HashMap.
738 *
739 * @param attributeName the name of the attribute to retrieve
740 * @param value the new attribute value
741 * @throws Exception if a target instance throws and exception
742 * @throws NoSuchAttributeException if the attribute name is not found in the map
743 */
744 public void setAttribute(String attributeName, Object value) throws Exception, NoSuchAttributeException {
745 setAttribute(attributeName, value, true);
746 }
747
748 public void setAttribute(String attributeName, Object value, boolean manage) throws Exception, NoSuchAttributeException {
749 GBeanAttribute attribute = getAttributeByName(attributeName);
750
751 // copy target into local variables from within a synchronized block to gaurentee a consistent read
752 int state;
753 Object instance;
754 synchronized (this) {
755 state = instanceState;
756 instance = target;
757 }
758
759 if (state != DESTROYED || attribute.isFramework()) {
760 attribute.setValue(instance, value);
761 } else {
762 attribute.setPersistentValue(value);
763 }
764 if (manage && attribute.isManageable()) {
765 updateManageableAttribute(attribute, value);
766 }
767 }
768
769 private void updateManageableAttribute(GBeanAttribute attribute, Object value) {
770 if (manageableStore == null) {
771 manageableStore = getManageableAttributeStore();
772 if (manageableStore == null) {
773 return;
774 }
775 }
776 Artifact configName = abstractName.getArtifact();
777 if (configName != null) {
778 manageableStore.setValue(configName, abstractName, attribute.getAttributeInfo(), value, classLoader);
779 } else {
780 log.error("Unable to identify Configuration for GBean " + abstractName + ". Manageable attribute " + attribute.getName() + " was not updated in persistent store.");
781 }
782 }
783
784 private ManageableAttributeStore getManageableAttributeStore() {
785 Set set = kernel.listGBeans(new AbstractNameQuery(ManageableAttributeStore.class.getName()));
786 for (Iterator iterator = set.iterator(); iterator.hasNext();) {
787 AbstractName abstractName1 = (AbstractName) iterator.next();
788 try {
789 return (ManageableAttributeStore) kernel.getGBean(abstractName1);
790 } catch (GBeanNotFoundException e) {
791 // ignored... gbean was unregistered
792 }
793 }
794 return null;
795 }
796
797 private GBeanAttribute getAttributeByName(String name) throws NoSuchAttributeException {
798 Integer index = (Integer) attributeIndex.get(name);
799 if (index == null) {
800 throw new NoSuchAttributeException("Unknown attribute \"" + name + "\" in gbean " + abstractName);
801 }
802 return attributes[index.intValue()];
803 }
804
805 /**
806 * Invokes an opreation using the operation index. This is the most efficient way to invoke
807 * an operation as it avoids a HashMap lookup.
808 *
809 * @param index the index of the attribute
810 * @param arguments the arguments to the operation
811 * @return the result of the operation
812 * @throws Exception if a target instance throws and exception
813 * @throws IndexOutOfBoundsException if the index is invalid
814 * @throws IllegalStateException if the gbean instance has been destroyed
815 */
816 public Object invoke(int index, Object[] arguments) throws Exception {
817 GBeanOperation operation = operations[index];
818
819 // copy target into local variables from within a synchronized block to gaurentee a consistent read
820 int state;
821 Object instance;
822 synchronized (this) {
823 state = instanceState;
824 instance = target;
825 }
826
827 if (state == DESTROYED && !operation.isFramework()) {
828 throw new IllegalStateException("Operations can only be invoke while the GBean instance is running: " + abstractName);
829 }
830 return operation.invoke(instance, arguments);
831 }
832
833 /**
834 * Invokes an operation on the target gbean by method signature. This style if invocation is
835 * inefficient, because the target method must be looked up in a hashmap using a freshly constructed
836 * GOperationSignature object.
837 *
838 * @param operationName the name of the operation to invoke
839 * @param arguments arguments to the operation
840 * @param types types of the operation arguemtns
841 * @return the result of the operation
842 * @throws Exception if a target instance throws and exception
843 * @throws NoSuchOperationException if the operation signature is not found in the map
844 * @throws IllegalStateException if the gbean instance has been destroyed
845 */
846 public Object invoke(String operationName, Object[] arguments, String[] types) throws Exception, NoSuchOperationException {
847 GOperationSignature signature = new GOperationSignature(operationName, types);
848 Integer index = (Integer) operationIndex.get(signature);
849 if (index == null) {
850 throw new NoSuchOperationException("Unknown operation " + signature);
851 }
852 GBeanOperation operation = operations[index.intValue()];
853
854 // copy target into local variables from within a synchronized block to gaurentee a consistent read
855 int state;
856 Object instance;
857 synchronized (this) {
858 state = instanceState;
859 instance = target;
860 }
861
862 if (state == DESTROYED && !operation.isFramework()) {
863 throw new IllegalStateException("Operations can only be invoke while the GBean is running: " + abstractName);
864 }
865 return operation.invoke(instance, arguments);
866 }
867
868 private GBeanReference getReferenceByName(String name) {
869 Integer index = (Integer) referenceIndex.get(name);
870 if (index == null) {
871 throw new IllegalArgumentException("Unknown reference " + name);
872 }
873 return references[index.intValue()];
874 }
875
876 boolean createInstance() throws Exception {
877 synchronized (this) {
878 // first check we are still in the correct state to start
879 if (instanceState == CREATING || instanceState == RUNNING) {
880 // another thread already completed starting
881 return false;
882 } else if (instanceState == DESTROYING) {
883 // this should never ever happen... this method is protected by the GBeanState class which should
884 // prevent stuff like this happening, but check anyway
885 stateReason = "an internal error has occurred. An attempt was made to start an instance that was still stopping which is an illegal state transition.";
886 throw new IllegalStateException("A stopping instance can not be started until fully stopped");
887 }
888 assert instanceState == DESTROYED;
889
890 stateReason = null;
891
892 // Call all start on every reference. This way the dependecies are held until we can start
893 LinkedHashSet unstarted = new LinkedHashSet();
894 for (int i = 0; i < dependencies.length; i++) {
895 if (!dependencies[i].start()) {
896 unstarted.add(dependencies[i].getTargetName());
897 }
898 }
899 for (int i = 0; i < references.length; i++) {
900 if (!references[i].start()) {
901 if (references[i] instanceof GBeanSingleReference) {
902 GBeanSingleReference reference = (GBeanSingleReference) references[i];
903 unstarted.add(reference.getTargetName());
904 }
905 }
906 }
907 if (!unstarted.isEmpty()) {
908 if (unstarted.size() == 1) {
909 stateReason = unstarted.iterator().next() + " did not start.";
910 } else {
911 stateReason = "the following dependent services did not start: " + unstarted;
912 }
913 return false;
914 }
915
916 // we are definately going to (try to) start... if this fails the must clean up these variables
917 instanceState = CREATING;
918 startTime = System.currentTimeMillis();
919 }
920
921 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
922 Thread.currentThread().setContextClassLoader(classLoader);
923 Object instance = null;
924 try {
925 GConstructorInfo constructorInfo = gbeanInfo.getConstructor();
926 Class[] parameterTypes = constructor.getParameterTypes();
927
928 // create constructor parameter array
929 Object[] parameters = new Object[parameterTypes.length];
930 Iterator names = constructorInfo.getAttributeNames().iterator();
931 for (int i = 0; i < parameters.length; i++) {
932 String name = (String) names.next();
933 if (referenceIndex.containsKey(name)) {
934 parameters[i] = getReferenceByName(name).getProxy();
935 } else if (attributeIndex.containsKey(name)) {
936 GBeanAttribute attribute = getAttributeByName(name);
937 parameters[i] = attribute.getPersistentValue();
938 } else {
939 stateReason = "the service constructor definition contained the name '" + name + "' which is not a known attribute or reference of the service.";
940 throw new InvalidConfigurationException("Unknown attribute or reference name in constructor: referenceName=" + name + ", gbean=" + abstractName);
941 }
942 }
943
944 // create instance
945 try {
946 instance = constructor.newInstance(parameters);
947 } catch (InvocationTargetException e) {
948 Throwable targetException = e.getTargetException();
949 if (targetException instanceof Exception) {
950 stateReason = "the service constructor threw an exception. \n" + printException(targetException);
951 throw (Exception) targetException;
952 } else if (targetException instanceof Error) {
953 stateReason = "the service constructor threw an exception. \n" + printException(targetException);
954 throw (Error) targetException;
955 }
956 stateReason = "the service constructor threw an exception. \n" + printException(e);
957 throw e;
958 } catch (IllegalArgumentException e) {
959 stateReason = "the service constructor threw an exception due to a parameter type mismatch. \n" + printException(e);
960 log.warn("Constructor mismatch for " + abstractName, e);
961 throw e;
962 }
963
964 // write the target variable in a synchronized block so it is available to all threads
965 // we do this before calling the setters or start method so the bean can be called back
966 // from a setter start method
967 synchronized (this) {
968 target = instance;
969 }
970
971 // inject the persistent attribute value into the new instance
972 for (int i = 0; i < attributes.length; i++) {
973 checkIfShouldFail();
974 try {
975 attributes[i].inject(instance);
976 } catch (Exception e) {
977 stateReason = "the setter for attribute '" + attributes[i].getName() + "' threw an exception. \n" + printException(e);
978 throw e;
979 }
980 }
981
982 // inject the proxies into the new instance
983 for (int i = 0; i < references.length; i++) {
984 checkIfShouldFail();
985 try {
986 references[i].inject(instance);
987 } catch (Exception e) {
988 stateReason = "the setter for reference '" + references[i].getName() + "' threw an exception. \n" + printException(e);
989 throw e;
990 }
991 }
992
993 if (instance instanceof GBeanLifecycle) {
994 checkIfShouldFail();
995 try {
996 ((GBeanLifecycle) instance).doStart();
997 } catch (Exception e) {
998 stateReason = "the doStart method threw an exception. \n" + printException(e);
999 throw e;
1000 }
1001 }
1002
1003
1004 // all done... we are now fully running
1005 synchronized (this) {
1006 checkIfShouldFail();
1007 if (instanceRegistry != null) {
1008 instanceRegistry.instanceCreated(instance, this);
1009 }
1010 instanceState = RUNNING;
1011 this.notifyAll();
1012 }
1013
1014
1015 stateReason = null;
1016 return true;
1017 } catch (Throwable t) {
1018 stateReason = "Throwable during start of gbean: \n" + printException(t);
1019 // something went wrong... we need to destroy this instance
1020 synchronized (this) {
1021 instanceState = DESTROYING;
1022 }
1023
1024 if (instance instanceof GBeanLifecycle) {
1025 try {
1026 ((GBeanLifecycle) instance).doFail();
1027 } catch (Throwable ignored) {
1028 log.error("Problem in doFail of " + abstractName, ignored);
1029 }
1030 }
1031
1032 // bean has been notified... drop our reference
1033 synchronized (this) {
1034 // stop all of the references
1035 for (int i = 0; i < references.length; i++) {
1036 references[i].stop();
1037 }
1038 for (int i = 0; i < dependencies.length; i++) {
1039 dependencies[i].stop();
1040 }
1041
1042 target = null;
1043 instanceState = DESTROYED;
1044 startTime = 0;
1045 this.notifyAll();
1046 }
1047
1048 if (t instanceof Exception) {
1049 throw (Exception) t;
1050 } else if (t instanceof Error) {
1051 throw (Error) t;
1052 } else {
1053 throw new Error(t);
1054 }
1055 } finally {
1056 Thread.currentThread().setContextClassLoader(oldCL);
1057 }
1058 }
1059
1060 private String printException(Throwable t) {
1061 StringWriter stringWriter = new StringWriter();
1062 PrintWriter printWriter = new PrintWriter(stringWriter);
1063 t.printStackTrace(printWriter);
1064 printWriter.flush();
1065 return stringWriter.toString();
1066 }
1067
1068 private synchronized void checkIfShouldFail() throws Exception {
1069 if (shouldFail) {
1070 shouldFail = false;
1071 throw new Exception("A reference has failed so construction can not complete");
1072 }
1073 }
1074
1075 boolean destroyInstance(boolean stop) throws Exception {
1076 Object instance;
1077 synchronized (this) {
1078 if (!stop && instanceState == CREATING) {
1079 // signal to the creating thead that it should fail
1080 shouldFail = true;
1081 return false;
1082 }
1083
1084 // if the instance is being created we need to wait
1085 // for it to finish before we can try to stop it
1086 while (instanceState == CREATING) {
1087 // todo should we limit this wait? If so, how do we configure the wait time?
1088 try {
1089 this.wait();
1090 } catch (InterruptedException e) {
1091 // clear the interrupted flag
1092 Thread.interrupted();
1093 // rethrow the interrupted exception.... someone was sick of us waiting
1094 throw e;
1095 }
1096 }
1097
1098 if (instanceState == DESTROYING || instanceState == DESTROYED) {
1099 // another thread is already stopping or has already stopped
1100 return false;
1101 }
1102 assert instanceState == RUNNING;
1103 stateReason = null;
1104
1105 // we are definately going to stop... if this fails the must clean up these variables
1106 instanceState = DESTROYING;
1107 instance = target;
1108 }
1109
1110 // update the persistent attributes
1111 // do not update the persistent attibute values in the case of a failure
1112 // failed gbeans may have corrupted attributes that would be persisted
1113 Exception problem = null;
1114 if (stop && instance != null) {
1115 try {
1116 // get all the data but don't update in case there is an exception
1117 Map data = new HashMap();
1118 for (int i = 0; i < attributes.length; i++) {
1119 GBeanAttribute attribute = attributes[i];
1120 if (attribute.isPersistent() && attribute.isReadable()) {
1121 // copy the current attribute value to the persistent value
1122 Object value;
1123 try {
1124 value = attribute.getValue(instance);
1125 } catch (Throwable e) {
1126 // There is no reason to create a new Exception sub class as this exception will
1127 // simply be caught and logged on GBeanInstanceState
1128 throw new Exception("Problem while updaing the persistent value of attibute: " +
1129 "Attribute Name: " + attribute.getName() + ", " +
1130 "Type: " + attribute.getType() + ", " +
1131 "GBeanInstance: " + getName(), e);
1132 }
1133 data.put(attribute, value);
1134 }
1135 }
1136 // now we have all the data we can update the persistent values
1137 for (int i = 0; i < attributes.length; i++) {
1138 GBeanAttribute attribute = attributes[i];
1139 if (attribute.isPersistent() && attribute.isReadable()) {
1140 // copy the current attribute value to the persistent value
1141 Object value = data.get(attribute);
1142 attribute.setPersistentValue(value);
1143 }
1144 }
1145 } catch (Exception e) {
1146 // the getter threw an exception; now we must to fail
1147 stop = false;
1148 problem = e;
1149 }
1150 }
1151
1152 // we notify the bean before removing our reference so the bean can be called back while stopping
1153 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
1154 Thread.currentThread().setContextClassLoader(classLoader);
1155 try {
1156 if (instance instanceof GBeanLifecycle) {
1157 if (stop) {
1158 try {
1159 ((GBeanLifecycle) instance).doStop();
1160 } catch (Throwable ignored) {
1161 log.error("Problem in doStop of " + abstractName, ignored);
1162 }
1163 } else {
1164 try {
1165 ((GBeanLifecycle) instance).doFail();
1166 } catch (Throwable ignored) {
1167 log.error("Problem in doFail of " + abstractName, ignored);
1168 }
1169 }
1170 }
1171 } finally {
1172 Thread.currentThread().setContextClassLoader(oldCL);
1173 }
1174
1175 // bean has been notified... drop our reference
1176 synchronized (this) {
1177 // stop all of the references
1178 for (int i = 0; i < references.length; i++) {
1179 references[i].stop();
1180 }
1181 for (int i = 0; i < dependencies.length; i++) {
1182 dependencies[i].stop();
1183 }
1184
1185 target = null;
1186 instanceState = DESTROYED;
1187 if (instanceRegistry != null) {
1188 instanceRegistry.instanceDestroyed(instance);
1189 }
1190 startTime = 0;
1191 }
1192
1193 if (problem != null) {
1194 throw problem;
1195 }
1196 return true;
1197 }
1198
1199 private void addManagedObjectAttributes(Map attributesMap) {
1200 //
1201 // Special attributes
1202 //
1203 attributesMap.put("abstractName",
1204 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("abstractName"),
1205 this,
1206 "abstractName",
1207 AbstractName.class,
1208 getAbstractName()));
1209
1210 attributesMap.put("objectName",
1211 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("objectName"),
1212 this,
1213 "objectName",
1214 String.class,
1215 getObjectName()));
1216
1217 attributesMap.put("classLoader",
1218 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("classLoader"),
1219 this,
1220 "classLoader",
1221 ClassLoader.class,
1222 classLoader));
1223
1224 attributesMap.put("kernel",
1225 GBeanAttribute.createSpecialAttribute((GBeanAttribute) attributesMap.get("kernel"),
1226 this,
1227 "kernel",
1228 Kernel.class,
1229 kernel));
1230
1231 }
1232
1233 private GBeanInfo rebuildGBeanInfo(GConstructorInfo constructor, String j2eeType) {
1234 Set attributeInfos = new HashSet();
1235 for (int i = 0; i < attributes.length; i++) {
1236 GBeanAttribute attribute = attributes[i];
1237 attributeInfos.add(attribute.getAttributeInfo());
1238 }
1239 Set operationInfos = new HashSet();
1240 for (int i = 0; i < operations.length; i++) {
1241 operationInfos.add(operations[i].getOperationInfo());
1242 }
1243
1244 Set referenceInfos = new HashSet();
1245 for (int i = 0; i < references.length; i++) {
1246 referenceInfos.add(references[i].getReferenceInfo());
1247 }
1248
1249 Set interfaceInfos = new HashSet();
1250 for (int i = 0; i < interfaces.length; i++) {
1251 interfaceInfos.add(interfaces[i]);
1252 }
1253
1254 return new GBeanInfo(name,
1255 type.getName(),
1256 j2eeType,
1257 attributeInfos,
1258 constructor,
1259 operationInfos,
1260 referenceInfos,
1261 interfaceInfos);
1262 }
1263
1264 public boolean equals(Object obj) {
1265 if (obj == this) return true;
1266 if (!(obj instanceof GBeanInstance)) return false;
1267 return abstractName.equals(((GBeanInstance) obj).abstractName);
1268 }
1269
1270 public int hashCode() {
1271 return abstractName.hashCode();
1272 }
1273
1274 public String toString() {
1275 return abstractName.toString();
1276 }
1277 }