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