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