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.lang.reflect.Method;
021    
022    import org.apache.geronimo.crypto.EncryptionManager;
023    import org.apache.geronimo.gbean.DynamicGAttributeInfo;
024    import org.apache.geronimo.gbean.DynamicGBean;
025    import org.apache.geronimo.gbean.GAttributeInfo;
026    import org.apache.geronimo.gbean.InvalidConfigurationException;
027    import org.apache.geronimo.kernel.ClassLoading;
028    
029    /**
030     * @version $Rev: 889880 $ $Date: 2009-12-12 10:45:24 +0800 (Sat, 12 Dec 2009) $
031     */
032    public class GBeanAttribute {
033        private final GBeanInstance gbeanInstance;
034    
035        private final String name;
036    
037        private final Class type;
038    
039        private final boolean readable;
040    
041        private final MethodInvoker getInvoker;
042    
043        private final boolean writable;
044    
045        private final MethodInvoker setInvoker;
046    
047        private final boolean isConstructorArg;
048    
049        private final boolean persistent;
050    
051        private final boolean manageable;
052    
053        private final boolean encrypted;
054    
055        private Object persistentValue;
056    
057        /**
058         * Is this a special attribute like objectName, classLoader or gbeanContext?
059         * Special attributes are injected at startup just like persistent attrubutes, but are
060         * otherwise unmodifiable.
061         */
062        private final boolean special;
063    
064        private final boolean framework;
065    
066        private final boolean dynamic;
067    
068        private final GAttributeInfo attributeInfo;
069    
070        static GBeanAttribute createSpecialAttribute(GBeanAttribute attribute, GBeanInstance gbeanInstance, String name, Class type, Object value) {
071            return new GBeanAttribute(attribute, gbeanInstance, name, type, value);
072        }
073    
074        private GBeanAttribute(GBeanAttribute attribute, GBeanInstance gbeanInstance, String name, Class type, Object value) {
075            this.special = true;
076            this.framework = false;
077            this.dynamic = false;
078    
079            if (gbeanInstance == null || name == null || type == null) {
080                throw new IllegalArgumentException("null param(s) supplied");
081            }
082    
083            // if we have an attribute verify the gbean instance, name and types match
084            if (attribute != null) {
085                assert (gbeanInstance == attribute.gbeanInstance);
086                assert (name.equals(attribute.name));
087                if (type != attribute.type) {
088                    throw new InvalidConfigurationException("Special attribute " + name +
089                            " must have the type " + type.getName() + ", but is " +
090                            attribute.type.getName() + ": targetClass=" + gbeanInstance.getType().getName());
091                }
092                if (attribute.isPersistent()) {
093                    throw new InvalidConfigurationException("Special attributes must not be persistent:" +
094                            " name=" + name + ", targetClass=" + gbeanInstance.getType().getName());
095                }
096            }
097    
098            this.gbeanInstance = gbeanInstance;
099            this.name = name;
100            this.type = type;
101    
102            // getter
103            this.getInvoker = null;
104            this.readable = true;
105    
106            // setter
107            if (attribute != null) {
108                this.setInvoker = attribute.setInvoker;
109                this.isConstructorArg = attribute.isConstructorArg;
110            } else {
111                this.setInvoker = null;
112                this.isConstructorArg = false;
113            }
114            this.writable = false;
115    
116            // persistence
117            this.persistent = false;
118            initializePersistentValue(value);
119    
120            // not manageable
121            this.manageable = false;
122    
123            // special attributes are not encrypted
124            this.encrypted = false;
125            
126            // create an attribute info for this gbean
127            if (attribute != null) {
128                GAttributeInfo attributeInfo = attribute.getAttributeInfo();
129                this.attributeInfo = new GAttributeInfo(this.name,
130                        this.type.getName(),
131                        this.persistent,
132                        this.manageable,
133                        this.encrypted,
134                        this.readable,
135                        this.writable,
136                        attributeInfo.getGetterName(),
137                        attributeInfo.getSetterName());
138            } else {
139                this.attributeInfo = new GAttributeInfo(this.name,
140                        this.type.getName(),
141                        this.persistent,
142                        this.manageable,
143                        this.encrypted,
144                        this.readable,
145                        this.writable,
146                        null,
147                        null);
148            }
149        }
150    
151        static GBeanAttribute createFrameworkAttribute(GBeanInstance gbeanInstance, String name, Class type, MethodInvoker getInvoker) {
152            return new GBeanAttribute(gbeanInstance, name, type, getInvoker, null, false, null, true);
153        }
154    
155        static GBeanAttribute createFrameworkAttribute(GBeanInstance gbeanInstance, String name, Class type, MethodInvoker getInvoker, MethodInvoker setInvoker, boolean persistent, Object persistentValue, boolean manageable) {
156            return new GBeanAttribute(gbeanInstance, name, type, getInvoker, setInvoker, persistent, persistentValue, manageable);
157        }
158    
159        private GBeanAttribute(GBeanInstance gbeanInstance, String name, Class type, MethodInvoker getInvoker, MethodInvoker setInvoker, boolean persistent, Object persistentValue, boolean manageable) {
160            this.special = false;
161            this.framework = true;
162            this.dynamic = false;
163    
164            if (gbeanInstance == null || name == null || type == null) {
165                throw new IllegalArgumentException("null param(s) supplied");
166            }
167    
168            this.gbeanInstance = gbeanInstance;
169            this.name = name;
170            this.type = type;
171    
172            // getter
173            this.getInvoker = getInvoker;
174            this.readable = (this.getInvoker != null);
175    
176            // setter
177            this.setInvoker = setInvoker;
178            this.isConstructorArg = false;
179            this.writable = (this.setInvoker != null);
180    
181            // persistence
182            this.persistent = persistent;
183    
184            // manageable
185            this.manageable = manageable;
186            
187             // create an attribute info for this gbean
188            attributeInfo = new GAttributeInfo(this.name,
189                    this.type.getName(),
190                    this.persistent,
191                    this.manageable,
192                    this.readable,
193                    this.writable,
194                    null,
195                    null);
196    
197            this.encrypted = attributeInfo.isEncrypted();
198    
199            if (this.encrypted && persistentValue != null) {
200                this.persistentValue = EncryptionManager.decrypt((String) persistentValue);
201            } else {
202                initializePersistentValue(persistentValue);
203            }
204        }
205    
206        public GBeanAttribute(GBeanInstance gbeanInstance, GAttributeInfo attributeInfo, boolean isConstructorArg) throws InvalidConfigurationException {
207            this.special = false;
208            this.framework = false;
209    
210            if (gbeanInstance == null || attributeInfo == null) {
211                throw new IllegalArgumentException("null param(s) supplied");
212            }
213            if (!attributeInfo.isReadable() && !attributeInfo.isWritable() && !attributeInfo.isPersistent() && !isConstructorArg)
214            {
215                throw new InvalidConfigurationException("An attribute must be readable, writable, persistent or a constructor arg: " +
216                        " name=" + attributeInfo.getName() + " targetClass=" + gbeanInstance.getType().getName());
217            }
218            this.gbeanInstance = gbeanInstance;
219            this.attributeInfo = attributeInfo;
220            this.name = attributeInfo.getName();
221            this.isConstructorArg = isConstructorArg;
222            try {
223                this.type = ClassLoading.loadClass(attributeInfo.getType(), gbeanInstance.getClassLoader());
224            } catch (ClassNotFoundException e) {
225                throw new InvalidConfigurationException("Could not load attribute class: " + attributeInfo.getType(), e);
226            }
227            this.persistent = attributeInfo.isPersistent();
228            this.manageable = attributeInfo.isManageable();
229            this.encrypted = attributeInfo.isEncrypted();
230    
231            readable = attributeInfo.isReadable();
232            writable = attributeInfo.isWritable();
233    
234            // If attribute is persistent or not tagged as unreadable, search for a
235            // getter method
236            if (attributeInfo instanceof DynamicGAttributeInfo) {
237                this.dynamic = true;
238                if (readable) {
239                    getInvoker = new DynamicGetterMethodInvoker(name);
240                } else {
241                    getInvoker = null;
242                }
243                if (writable) {
244                    setInvoker = new DynamicSetterMethodInvoker(name);
245                } else {
246                    setInvoker = null;
247                }
248            } else {
249                this.dynamic = false;
250                if (attributeInfo.getGetterName() != null) {
251                    try {
252                        String getterName = attributeInfo.getGetterName();
253                        Method getterMethod = gbeanInstance.getType().getMethod(getterName, null);
254    
255                        if (!getterMethod.getReturnType().equals(type)) {
256                            if (getterMethod.getReturnType().getName().equals(type.getName())) {
257                                throw new InvalidConfigurationException("Getter return type in wrong classloader: type: " + type + " wanted in classloader: " + type.getClassLoader() + " actual: " + getterMethod.getReturnType().getClassLoader());
258                            } else {
259                                throw new InvalidConfigurationException("Getter method of wrong type: " + getterMethod.getReturnType() + " expected " + getDescription());
260                            }
261                        }
262                        if (AbstractGBeanReference.NO_PROXY) {
263                            getInvoker = new ReflectionMethodInvoker(getterMethod);
264                        } else {
265                            getInvoker = new FastMethodInvoker(getterMethod);
266                        }
267                    } catch (NoSuchMethodException e) {
268                        throw new InvalidConfigurationException("Getter method not found " + getDescription(), e);
269                    }
270                } else {
271                    getInvoker = null;
272                }
273    
274                // If attribute is persistent or not tagged as unwritable, search
275                // for a setter method
276                if (attributeInfo.getSetterName() != null) {
277                    try {
278                        String setterName = attributeInfo.getSetterName();
279                        Method setterMethod = gbeanInstance.getType().getMethod(setterName, new Class[]{type});
280                        if (AbstractGBeanReference.NO_PROXY) {
281                            setInvoker = new ReflectionMethodInvoker(setterMethod);
282                        } else {
283                            setInvoker = new FastMethodInvoker(setterMethod);
284                        }
285                    } catch (NoSuchMethodException e) {
286                        throw new InvalidConfigurationException("Setter method not found " + getDescription(), e);
287                    }
288                } else {
289                    setInvoker = null;
290                }
291            }
292    
293            initializePersistentValue(null);
294        }
295    
296        private void initializePersistentValue(Object value) {
297            if (persistent || special) {
298                if (value == null && type.isPrimitive() && isConstructorArg) {
299                    if (type == Boolean.TYPE) {
300                        value = Boolean.FALSE;
301                    } else if (type == Byte.TYPE) {
302                        value = new Byte((byte) 0);
303                    } else if (type == Short.TYPE) {
304                        value = new Short((short) 0);
305                    } else if (type == Integer.TYPE) {
306                        value = new Integer(0);
307                    } else if (type == Long.TYPE) {
308                        value = new Long(0);
309                    } else if (type == Character.TYPE) {
310                        value = new Character((char) 0);
311                    } else if (type == Float.TYPE) {
312                        value = new Float(0);
313                    } else /** if (type == Double.TYPE) */ {
314                        value = new Double(0);
315                    }
316                }
317                persistentValue = value;
318            }
319        }
320    
321        public String getName() {
322            return name;
323        }
324    
325        public GAttributeInfo getAttributeInfo() {
326            return attributeInfo;
327        }
328    
329        public boolean isReadable() {
330            return readable;
331        }
332    
333        public boolean isWritable() {
334            return writable;
335        }
336    
337        public Class getType() {
338            return type;
339        }
340    
341        public boolean isFramework() {
342            return framework;
343        }
344    
345        public boolean isDynamic() {
346            return dynamic;
347        }
348    
349        public boolean isPersistent() {
350            return persistent;
351        }
352    
353        public boolean isManageable() {
354            return manageable;
355        }
356    
357        public boolean isEncrypted() {
358            return encrypted;
359        }
360    
361        public boolean isSpecial() {
362            return special;
363        }
364    
365        public void inject(Object target) throws Exception {
366            if ((persistent || special) && !isConstructorArg && writable && persistentValue != null) {
367                setValue(target, persistentValue);
368            }
369        }
370    
371        public Object getPersistentValue() {
372            if (!persistent && !special) {
373                throw new IllegalStateException("Attribute is not persistent " + getDescription());
374            }
375            return persistentValue;
376        }
377    
378        public void setPersistentValue(Object persistentValue) {
379            if (!persistent && !special) {
380                throw new IllegalStateException("Attribute is not persistent " + getDescription());
381            }
382    
383            if (persistentValue == null && type.isPrimitive()) {
384                throw new IllegalArgumentException("Cannot assign null to a primitive attribute. " + getDescription());
385            }
386    
387            // @todo actually check type
388            this.persistentValue = (encrypted && persistentValue != null) ? EncryptionManager
389                    .decrypt((String) persistentValue)
390                    : persistentValue;
391        }
392    
393        public Object getValue(Object target) throws Exception {
394            if (!readable) {
395                if (persistent) {
396                    return persistentValue;
397                } else {
398                    throw new IllegalStateException("This attribute is not readable. " + getDescription());
399                }
400            }
401    
402            if (special) {
403                return persistentValue;
404            }
405    
406            // get the target to invoke
407            if (target == null && !framework) {
408                throw new IllegalStateException("GBean does not have a target instance to invoke. " + getDescription());
409            }
410    
411            // call the getter
412            Object value = getInvoker.invoke(target, null);
413            return value;
414        }
415    
416        public void setValue(Object target, Object value) throws Exception {
417            if (!writable) {
418                if (persistent) {
419                    throw new IllegalStateException("This persistent attribute is not modifable while the gbean is running. " + getDescription());
420                } else {
421                    throw new IllegalStateException("This attribute is not writable. " + getDescription());
422                }
423            }
424    
425            // the value can not be null for primitives
426            if (value == null && type.isPrimitive()) {
427                throw new IllegalArgumentException("Cannot assign null to a primitive attribute. " + getDescription());
428            }
429    
430            // @todo actually check type
431    
432            // get the target to invoke
433            if (target == null && !framework) {
434                throw new IllegalStateException("GBean does not have a target instance to invoke. " + getDescription());
435            }
436    
437            if (encrypted && value != null) {
438                value = EncryptionManager.decrypt((String) value);
439            }
440            // call the setter
441            setInvoker.invoke(target, new Object[]{value});
442        }
443    
444        public String getDescription() {
445            return "Attribute Name: " + getName() + ", Type: " + getType() + ", GBeanInstance: " + gbeanInstance.getName();
446        }
447    
448        private static final class DynamicGetterMethodInvoker implements MethodInvoker {
449            private final String name;
450    
451            public DynamicGetterMethodInvoker(String name) {
452                this.name = name;
453            }
454    
455            public Object invoke(Object target, Object[] arguments) throws Exception {
456                return ((DynamicGBean) target).getAttribute(name);
457            }
458        }
459    
460        private static final class DynamicSetterMethodInvoker implements MethodInvoker {
461            private final String name;
462    
463            public DynamicSetterMethodInvoker(String name) {
464                this.name = name;
465            }
466    
467            public Object invoke(Object target, Object[] arguments) throws Exception {
468                ((DynamicGBean) target).setAttribute(name, arguments[0]);
469                return null;
470            }
471        }
472    }