/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Header:$ */ package org.apache.beehive.controls.runtime.bean; import java.beans.BeanInfo; import java.beans.DefaultPersistenceDelegate; import java.beans.Encoder; import java.beans.EventSetDescriptor; import java.beans.Expression; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PersistenceDelegate; import java.beans.PropertyDescriptor; import java.beans.Statement; import java.beans.XMLEncoder; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Iterator; import org.apache.beehive.controls.api.ControlException; import org.apache.beehive.controls.api.properties.AnnotatedElementMap; import org.apache.beehive.controls.api.properties.BeanPropertyMap; import org.apache.beehive.controls.api.properties.PropertyKey; import org.apache.beehive.controls.api.properties.PropertyMap; /** * The BeanPersistenceDelegate class supports the XML persistence of Control JavaBeans by * implementing the java.beans.PersistenceDelegate API, and overriding the default * persistence algorithm based upon the runtime structure for Controls. It selectively registers * other PersistenceDelegate instances for other nested entities, as required, to ensure that * runtime-defined state and object relationships are properly maintained. *

* For the BeanInfo of all generated ControlJavaBeans, a BeanPersistenceDelegate instance will * be registered as the "persistenceDelegate" attribute in the BeanDescriptor. The standard * java.beans.Encoder persistence delegate lookup mechanism recognizes this attribute * and will use the instance to persist an ControlBeans written to the encoding stream. *

* The BeanPersistence class implements optimized property persistence based upon the * fact that the ControlBean already has a map containing all non-default property state. Rather * than using the standard (and slower) algorithm of comparing the encoding instance against a * 'clean' instance, the delegate can simply retrieve the map and persist the values contained * within it. * * @see java.beans.XMLEncoder * @see java.beans.PersistenceDelegate */ public class BeanPersistenceDelegate extends DefaultPersistenceDelegate { /** * The FieldPersistencersistence is an XMLEncoder PersistenceDelegate for the * java.lang.reflect.Field claass. It is similar to the one that comes * bundled with the JDK with one key exception: it works for non-public fields as * well. */ class FieldPersistenceDelegate extends PersistenceDelegate { protected Expression instantiate(Object oldInstance, Encoder out) { Field f = (Field)oldInstance; return new Expression(oldInstance, f.getDeclaringClass(), "getDeclaredField", new Object[]{f.getName()}); } } /** * PersistenceDelegate.instantiate() */ protected Expression instantiate(Object oldInstance, Encoder out) { XMLEncoder xmlOut = (XMLEncoder)out; ControlBean control = (ControlBean)oldInstance; // // If processing a nested control, then use the parent bean's context as the // constructor context // ControlBeanContext cbc = null; if (xmlOut.getOwner() != null) cbc = ((ControlBean)xmlOut.getOwner()).getControlBeanContext(); // // See if the ControlBean has any associated PropertyMap in its delegation chain // that was derived from an AnnotatedElement so this relationship (and any associated // external config delegates) will be restored as part of the decoding process. // // BUGBUG: What about a user-created PropertyMap that was passed into the constructor? // AnnotatedElementMap aem = null; PropertyMap pMap = control.getPropertyMap(); while (pMap != null) { if (pMap instanceof AnnotatedElementMap) { aem = (AnnotatedElementMap)pMap; // // Ignore a class-valued AnnotationElementMap.. this just refers to the // Control type, and will be automatically reassociated at construction // time // if (aem.getAnnotatedElement() instanceof Class) aem = null; xmlOut.setPersistenceDelegate(AnnotatedElementMap.class, new AnnotatedElementMapPersistenceDelegate()); break; } pMap = pMap.getDelegateMap(); } // // Create a constructor that that uses the following form: // new (ControlBeanContext cbc, String id, PropertyMap map) // The context is set to null, so the current active container context will be // used, the id will be the ID of the original control and the map will be // any AnnotatedElementMap that was passed into the original constructor. // return new Expression(control, control.getClass(), "new", new Object [] {cbc, control.getLocalID(), aem}); } /** * PersistenceDelegate.initialize() */ protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { // // Get the bean and associated beanInfo for the source instance // ControlBean control = (ControlBean)oldInstance; BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(control.getClass()); } catch (IntrospectionException ie) { throw new ControlException("Unable to locate BeanInfo", ie); } // // Cast the encoding stream to an XMLEncoder (only encoding supported) and then set // the stream owner to the bean being persisted // XMLEncoder xmlOut = (XMLEncoder)out; Object owner = xmlOut.getOwner(); xmlOut.setOwner(control); try { // // The default implementation of property persistence will use BeanInfo to // incrementally compare oldInstance property values to newInstance property values. // Because the bean instance PropertyMap holds only the values that have been // modified, this process can be optimized by directly writing out only the properties // found in the map. // BeanPropertyMap beanMap = control.getPropertyMap(); PropertyDescriptor [] propDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyKey pk : beanMap.getPropertyKeys()) { // // Locate the PropertyDescriptor for the modified property, and use it to write // the property value to the encoder stream // String propName = pk.getPropertyName(); boolean found = false; for (int i = 0; i < propDescriptors.length; i++) { if (propName.equals(propDescriptors[i].getName())) { found = true; // Only write the property if it is not flagged as transient Object transientVal = propDescriptors[i].getValue("transient"); if (transientVal == null || transientVal.equals(Boolean.FALSE)) { xmlOut.writeStatement( new Statement(oldInstance, propDescriptors[i].getWriteMethod().getName(), new Object [] {beanMap.getProperty(pk)})); } } } if (found == false) { throw new ControlException("Unknown property in bean PropertyMap: " + pk); } } // // Get the bean context associated with the bean, and persist any nested controls // ControlBeanContext cbc = control.getControlBeanContext(); if (cbc.size() != 0) { xmlOut.setPersistenceDelegate(ControlBeanContext.class, new ContextPersistenceDelegate()); Iterator nestedIter = cbc.iterator(); while (nestedIter.hasNext()) { Object bean = nestedIter.next(); if (bean instanceof ControlBean) { xmlOut.writeStatement( new Statement(cbc, "add", new Object [] { bean } )); } } } // // Restore any listeners associated with the control // EventSetDescriptor [] eventSetDescriptors = beanInfo.getEventSetDescriptors(); for (int i = 0; i < eventSetDescriptors.length; i++) { EventSetDescriptor esd = eventSetDescriptors[i]; Method listenersMethod = esd.getGetListenerMethod(); String addListenerName = esd.getAddListenerMethod().getName(); if (listenersMethod != null) { // // Get the list of listeners, and then add statements to incrementally // add them in the same order // try { Object [] lstnrs = (Object [])listenersMethod.invoke(control, new Object []{}); for (int j = 0; j < lstnrs.length; j++) { // // If this is a generated EventAdaptor class, then set the delegate // explicitly // if (lstnrs[j] instanceof EventAdaptor) xmlOut.setPersistenceDelegate(lstnrs[j].getClass(), new AdaptorPersistenceDelegate()); xmlOut.writeStatement( new Statement(control, addListenerName, new Object [] {lstnrs[j]})); } } catch (Exception iae) { throw new ControlException("Unable to initialize listeners", iae); } } } // // See if the control holds an implementation instance, if so, we need to include // it (and any nested controls or state) in the encoding stream // Object impl = control.getImplementation(); if (impl != null) { // // Set the persistence delegate for the impl class to the Impl delegate, // set the current stream owner to the bean, and then write the implementation // Class implClass = impl.getClass(); if (xmlOut.getPersistenceDelegate(implClass) instanceof DefaultPersistenceDelegate) xmlOut.setPersistenceDelegate(implClass, new ImplPersistenceDelegate()); // // HACK: This bit of hackery pushes the impl into the persistence stream // w/out actually requiring it be used as an argument elsewhere, since there // is no public API on the bean that takes an impl instance as an argument. // xmlOut.writeStatement( new Statement(impl, "toString", null)); } } finally { // Restore the previous encoding stream owner xmlOut.setOwner(owner); } } /** * PersistenceDelegate.writeObject() */ public void writeObject(Object oldInstance, Encoder out) { // Override the default FieldPersistence algorithm for the encoder, so private fields // can also be encoded out.setPersistenceDelegate(Field.class, new FieldPersistenceDelegate()); super.writeObject(oldInstance, out); } }