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: * $Header:$
018: */
019: package org.apache.beehive.controls.runtime.bean;
020:
021: import java.beans.BeanInfo;
022: import java.beans.DefaultPersistenceDelegate;
023: import java.beans.Encoder;
024: import java.beans.EventSetDescriptor;
025: import java.beans.Expression;
026: import java.beans.IntrospectionException;
027: import java.beans.Introspector;
028: import java.beans.PersistenceDelegate;
029: import java.beans.PropertyDescriptor;
030: import java.beans.Statement;
031: import java.beans.XMLEncoder;
032: import java.lang.reflect.Field;
033: import java.lang.reflect.Method;
034: import java.util.Iterator;
035:
036: import org.apache.beehive.controls.api.ControlException;
037: import org.apache.beehive.controls.api.properties.AnnotatedElementMap;
038: import org.apache.beehive.controls.api.properties.BeanPropertyMap;
039: import org.apache.beehive.controls.api.properties.PropertyKey;
040: import org.apache.beehive.controls.api.properties.PropertyMap;
041:
042: /**
043: * The BeanPersistenceDelegate class supports the XML persistence of Control JavaBeans by
044: * implementing the <code>java.beans.PersistenceDelegate</b> API, and overriding the default
045: * persistence algorithm based upon the runtime structure for Controls. It selectively registers
046: * other PersistenceDelegate instances for other nested entities, as required, to ensure that
047: * runtime-defined state and object relationships are properly maintained.
048: * <p>
049: * For the BeanInfo of all generated ControlJavaBeans, a BeanPersistenceDelegate instance will
050: * be registered as the "persistenceDelegate" attribute in the BeanDescriptor. The standard
051: * <code>java.beans.Encoder</code> persistence delegate lookup mechanism recognizes this attribute
052: * and will use the instance to persist an ControlBeans written to the encoding stream.
053: * <p>
054: * The BeanPersistence class implements optimized property persistence based upon the
055: * fact that the ControlBean already has a map containing all non-default property state. Rather
056: * than using the standard (and slower) algorithm of comparing the encoding instance against a
057: * 'clean' instance, the delegate can simply retrieve the map and persist the values contained
058: * within it.
059: *
060: * @see java.beans.XMLEncoder
061: * @see java.beans.PersistenceDelegate
062: */
063: public class BeanPersistenceDelegate extends DefaultPersistenceDelegate {
064: /**
065: * The FieldPersistencersistence is an XMLEncoder PersistenceDelegate for the
066: * <code>java.lang.reflect.Field</code> claass. It is similar to the one that comes
067: * bundled with the JDK with one key exception: it works for non-public fields as
068: * well.
069: */
070: class FieldPersistenceDelegate extends PersistenceDelegate {
071: protected Expression instantiate(Object oldInstance, Encoder out) {
072: Field f = (Field) oldInstance;
073: return new Expression(oldInstance, f.getDeclaringClass(),
074: "getDeclaredField", new Object[] { f.getName() });
075: }
076: }
077:
078: /**
079: * PersistenceDelegate.instantiate()
080: */
081: protected Expression instantiate(Object oldInstance, Encoder out) {
082: XMLEncoder xmlOut = (XMLEncoder) out;
083: ControlBean control = (ControlBean) oldInstance;
084:
085: //
086: // If processing a nested control, then use the parent bean's context as the
087: // constructor context
088: //
089: ControlBeanContext cbc = null;
090: if (xmlOut.getOwner() != null)
091: cbc = ((ControlBean) xmlOut.getOwner())
092: .getControlBeanContext();
093:
094: //
095: // See if the ControlBean has any associated PropertyMap in its delegation chain
096: // that was derived from an AnnotatedElement so this relationship (and any associated
097: // external config delegates) will be restored as part of the decoding process.
098: //
099: // BUGBUG: What about a user-created PropertyMap that was passed into the constructor?
100: //
101: AnnotatedElementMap aem = null;
102: PropertyMap pMap = control.getPropertyMap();
103: while (pMap != null) {
104: if (pMap instanceof AnnotatedElementMap) {
105: aem = (AnnotatedElementMap) pMap;
106:
107: //
108: // Ignore a class-valued AnnotationElementMap.. this just refers to the
109: // Control type, and will be automatically reassociated at construction
110: // time
111: //
112: if (aem.getAnnotatedElement() instanceof Class)
113: aem = null;
114:
115: xmlOut.setPersistenceDelegate(
116: AnnotatedElementMap.class,
117: new AnnotatedElementMapPersistenceDelegate());
118:
119: break;
120: }
121:
122: pMap = pMap.getDelegateMap();
123: }
124:
125: //
126: // Create a constructor that that uses the following form:
127: // new <BeanClass>(ControlBeanContext cbc, String id, PropertyMap map)
128: // The context is set to null, so the current active container context will be
129: // used, the id will be the ID of the original control and the map will be
130: // any AnnotatedElementMap that was passed into the original constructor.
131: //
132: return new Expression(control, control.getClass(), "new",
133: new Object[] { cbc, control.getLocalID(), aem });
134: }
135:
136: /**
137: * PersistenceDelegate.initialize()
138: */
139: protected void initialize(Class<?> type, Object oldInstance,
140: Object newInstance, Encoder out) {
141: //
142: // Get the bean and associated beanInfo for the source instance
143: //
144: ControlBean control = (ControlBean) oldInstance;
145: BeanInfo beanInfo;
146: try {
147: beanInfo = Introspector.getBeanInfo(control.getClass());
148: } catch (IntrospectionException ie) {
149: throw new ControlException("Unable to locate BeanInfo", ie);
150: }
151:
152: //
153: // Cast the encoding stream to an XMLEncoder (only encoding supported) and then set
154: // the stream owner to the bean being persisted
155: //
156: XMLEncoder xmlOut = (XMLEncoder) out;
157: Object owner = xmlOut.getOwner();
158: xmlOut.setOwner(control);
159: try {
160:
161: //
162: // The default implementation of property persistence will use BeanInfo to
163: // incrementally compare oldInstance property values to newInstance property values.
164: // Because the bean instance PropertyMap holds only the values that have been
165: // modified, this process can be optimized by directly writing out only the properties
166: // found in the map.
167: //
168: BeanPropertyMap beanMap = control.getPropertyMap();
169: PropertyDescriptor[] propDescriptors = beanInfo
170: .getPropertyDescriptors();
171: for (PropertyKey pk : beanMap.getPropertyKeys()) {
172: //
173: // Locate the PropertyDescriptor for the modified property, and use it to write
174: // the property value to the encoder stream
175: //
176: String propName = pk.getPropertyName();
177: boolean found = false;
178: for (int i = 0; i < propDescriptors.length; i++) {
179: if (propName.equals(propDescriptors[i].getName())) {
180: found = true;
181:
182: // Only write the property if it is not flagged as transient
183: Object transientVal = propDescriptors[i]
184: .getValue("transient");
185: if (transientVal == null
186: || transientVal.equals(Boolean.FALSE)) {
187: xmlOut.writeStatement(new Statement(
188: oldInstance,
189: propDescriptors[i].getWriteMethod()
190: .getName(),
191: new Object[] { beanMap
192: .getProperty(pk) }));
193: }
194: }
195: }
196: if (found == false) {
197: throw new ControlException(
198: "Unknown property in bean PropertyMap: "
199: + pk);
200: }
201: }
202:
203: //
204: // Get the bean context associated with the bean, and persist any nested controls
205: //
206: ControlBeanContext cbc = control.getControlBeanContext();
207: if (cbc.size() != 0) {
208: xmlOut.setPersistenceDelegate(ControlBeanContext.class,
209: new ContextPersistenceDelegate());
210:
211: Iterator nestedIter = cbc.iterator();
212: while (nestedIter.hasNext()) {
213: Object bean = nestedIter.next();
214: if (bean instanceof ControlBean) {
215: xmlOut.writeStatement(new Statement(cbc, "add",
216: new Object[] { bean }));
217: }
218: }
219: }
220:
221: //
222: // Restore any listeners associated with the control
223: //
224: EventSetDescriptor[] eventSetDescriptors = beanInfo
225: .getEventSetDescriptors();
226: for (int i = 0; i < eventSetDescriptors.length; i++) {
227: EventSetDescriptor esd = eventSetDescriptors[i];
228: Method listenersMethod = esd.getGetListenerMethod();
229: String addListenerName = esd.getAddListenerMethod()
230: .getName();
231: if (listenersMethod != null) {
232: //
233: // Get the list of listeners, and then add statements to incrementally
234: // add them in the same order
235: //
236: try {
237: Object[] lstnrs = (Object[]) listenersMethod
238: .invoke(control, new Object[] {});
239: for (int j = 0; j < lstnrs.length; j++) {
240: //
241: // If this is a generated EventAdaptor class, then set the delegate
242: // explicitly
243: //
244: if (lstnrs[j] instanceof EventAdaptor)
245: xmlOut
246: .setPersistenceDelegate(
247: lstnrs[j].getClass(),
248: new AdaptorPersistenceDelegate());
249: xmlOut.writeStatement(new Statement(
250: control, addListenerName,
251: new Object[] { lstnrs[j] }));
252: }
253: } catch (Exception iae) {
254: throw new ControlException(
255: "Unable to initialize listeners", iae);
256: }
257: }
258: }
259:
260: //
261: // See if the control holds an implementation instance, if so, we need to include
262: // it (and any nested controls or state) in the encoding stream
263: //
264: Object impl = control.getImplementation();
265: if (impl != null) {
266:
267: //
268: // Set the persistence delegate for the impl class to the Impl delegate,
269: // set the current stream owner to the bean, and then write the implementation
270: //
271: Class implClass = impl.getClass();
272: if (xmlOut.getPersistenceDelegate(implClass) instanceof DefaultPersistenceDelegate)
273: xmlOut.setPersistenceDelegate(implClass,
274: new ImplPersistenceDelegate());
275:
276: //
277: // HACK: This bit of hackery pushes the impl into the persistence stream
278: // w/out actually requiring it be used as an argument elsewhere, since there
279: // is no public API on the bean that takes an impl instance as an argument.
280: //
281: xmlOut.writeStatement(new Statement(impl, "toString",
282: null));
283: }
284: } finally {
285: // Restore the previous encoding stream owner
286: xmlOut.setOwner(owner);
287: }
288: }
289:
290: /**
291: * PersistenceDelegate.writeObject()
292: */
293: public void writeObject(Object oldInstance, Encoder out) {
294: // Override the default FieldPersistence algorithm for the encoder, so private fields
295: // can also be encoded
296: out.setPersistenceDelegate(Field.class,
297: new FieldPersistenceDelegate());
298: super.writeObject(oldInstance, out);
299: }
300: }
|