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    }