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