Java tutorial
/* Copyright (2012) Schibsted ASA * This file is part of Possom. * * Possom is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Possom is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Possom. If not, see <http://www.gnu.org/licenses/>. */ /* * BeanDataNodeInvocationHandler.java * * Created on 23 January 2007, 21:34 * */ package no.sesat.search.datamodel; import no.sesat.search.datamodel.generic.DataNode; import no.sesat.search.datamodel.generic.DataObject; import no.sesat.search.datamodel.generic.DataObject.Property; import no.sesat.search.datamodel.generic.MapDataObject; import org.apache.commons.beanutils.MappedPropertyDescriptor; import org.apache.log4j.Logger; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.beans.beancontext.BeanContext; //import java.beans.beancontext.BeanContextSupport; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.NotSerializableException; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import no.sesat.search.datamodel.BeanDataModelInvocationHandler.DataModelBeanContextSupport; /** * * @version <tt>$Id$</tt> */ class BeanDataNodeInvocationHandler<T> extends BeanDataObjectInvocationHandler<T> { // Constants ----------------------------------------------------- private static final Logger LOG = Logger.getLogger(BeanDataNodeInvocationHandler.class); // Attributes ---------------------------------------------------- private final BeanDataObjectInvocationHandler<T> dataObject; private final DataModelBeanContextSupport absoluteContext; // Static -------------------------------------------------------- static <T> BeanDataNodeInvocationHandler<T> instanceOf(final Class<T> cls, final DataModel datamodel, final Property... properties) throws IntrospectionException { return new BeanDataNodeInvocationHandler<T>(cls, datamodel, new PropertyInitialisor<T>(cls, properties)); } // Constructors -------------------------------------------------- /** Creates a new instance of ProxyBeanDataObject */ private BeanDataNodeInvocationHandler(final Class<T> cls, final DataModel datamodel, final PropertyInitialisor properties) throws IntrospectionException { this(cls, datamodel, new BeanContextSupport(), properties); } /** * Creates a new instance of ProxyBeanDataObject */ protected BeanDataNodeInvocationHandler(final Class<T> cls, final DataModel datamodel, final BeanContext context, final PropertyInitialisor properties) throws IntrospectionException { super(cls, context, properties.properties); // find the datamodel and use its context as the absoluteContext if (null != datamodel) { final BeanDataModelInvocationHandler handler = (BeanDataModelInvocationHandler) Proxy .getInvocationHandler(datamodel); absoluteContext = (DataModelBeanContextSupport) handler.getBeanContextChild(); } else { absoluteContext = (DataModelBeanContextSupport) context; } // make context to contextChild bindings for (PropertyDescriptor property : properties.childPropertyDescriptors) { for (Property p : properties.allProperties) { if (p.getName().equals(property.getName())) { if (p.getValue() instanceof MapDataObject) { for (Object obj : ((MapDataObject) p.getValue()).getValues().values()) { addChild(obj); } } else { addChild(p.getValue()); } break; } } } // delegate our own properties dataObject = new BeanDataObjectInvocationHandler<T>(cls, properties.delegatedProperties); } // Public -------------------------------------------------------- @Override public Object invoke(final Object obj, final Method method, final Object[] args) throws Throwable { assureAccessAllowed(method); // try our dataObject|dataNode delegated-properties try { return super.invoke(obj, method, args); } catch (IllegalArgumentException iae) { LOG.debug("property not one of our own. " + iae.getMessage()); } // try non-(dataObject|dataNode) delegated-properties try { return dataObject.invoke(obj, method, args); } catch (IllegalArgumentException iae) { LOG.debug("property not from delegate", iae); } // try pure self methods try { return method.invoke(this, args); } catch (IllegalAccessException iae) { LOG.info(iae.getMessage(), iae); } catch (IllegalArgumentException iae) { LOG.info(iae.getMessage(), iae); } catch (InvocationTargetException ite) { LOG.info(ite.getMessage(), ite); } throw new IllegalArgumentException("Method to invoke doesn't map to bean property"); } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- /** * obj may be null. */ @Override protected void addChild(final Object obj) { if (null != obj) { assert isDataObjectOrNode(obj) : "my own properties should only be Data(Object|Node)s"; assert isSerializable(obj) : "Object not serializable: " + obj; final BeanDataObjectInvocationHandler<?> childsNewHandler = (BeanDataObjectInvocationHandler<?>) Proxy .getInvocationHandler(obj); /// XXX Application bottleneck. See https://jira.sesam.no/jira/browse/SEARCH-3591 /// BeanContextSupport.add synchronises against the static variable BeanContext.globalHierarchyLock /// so every thread in the jvm must queue one at a time here :'( /// Worse is that this synchronisation is requested behavor by BeanContext.add /// We workaround it by instead locking against the datamodel we know we are restricted within. ((BeanContextSupport) context).add(childsNewHandler.getBeanContextChild(), absoluteContext.dataModelLock); } } private boolean isSerializable(final Object obj) { boolean correct = false; if (obj == null) { return true; } try { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream os = new ObjectOutputStream(baos); os.writeObject(obj); correct = true; } catch (NotSerializableException e) { /* Do nothing, return value already correct */ } catch (IOException e) { /* Do nothing, return value already correct */ } return correct; } /** * obj may be null. */ @Override protected void removeChild(final Object obj) { if (null != obj) { assert isDataObjectOrNode(obj) : "my own properties should only be Data(Object|Node)s"; final BeanDataObjectInvocationHandler<?> childsOldHandler = (BeanDataObjectInvocationHandler<?>) Proxy .getInvocationHandler(obj); context.remove(childsOldHandler.getBeanContextChild()); } } // Private ------------------------------------------------------- private boolean isDataObjectOrNode(final Object obj) { boolean dataObjectOrNode = false; final Class[] interfaces = obj.getClass().getInterfaces(); for (int i = 0; !dataObjectOrNode && i < interfaces.length; ++i) { dataObjectOrNode = null != interfaces[i].getAnnotation(DataObject.class); dataObjectOrNode |= null != interfaces[i].getAnnotation(DataNode.class); } return dataObjectOrNode; } // Inner classes ------------------------------------------------- static final class PropertyInitialisor<T> { final Property[] allProperties; final Property[] properties; final Property[] delegatedProperties; final PropertyDescriptor[] childPropertyDescriptors; PropertyInitialisor(final Class<T> cls, final Property... allProperties) throws IntrospectionException { this.allProperties = allProperties; final List<Property> properties = new ArrayList<Property>(); final List<Property> delegatedProperties = new ArrayList<Property>(); final List<PropertyDescriptor> descriptors = new ArrayList<PropertyDescriptor>(); final PropertyDescriptor[] allPropertyDescriptors = Introspector.getBeanInfo(cls) .getPropertyDescriptors(); // split properties between children dataObjects and our own properties final List<PropertyDescriptor> propertyDescriptors = new ArrayList<PropertyDescriptor>(); for (int i = 0; i < allPropertyDescriptors.length; ++i) { final PropertyDescriptor property = allPropertyDescriptors[i]; final Class<?> propCls; if (property instanceof MappedPropertyDescriptor) { propCls = ((MappedPropertyDescriptor) property).getMappedPropertyType(); ++i; // the next propertyDescriptor is the synonym to this mappedPropertyDescriptor } else { propCls = property.getPropertyType(); } // FIXME the following if-else only deals with normals properties (not mapped). if (null != propCls.getAnnotation(DataObject.class) || null != propCls.getAnnotation(DataNode.class)) { descriptors.add(property); for (Property p : allProperties) { final String name = p.getName(); if (name.equals(property.getName()) || name.equals(allPropertyDescriptors[i].getName())) { if (property instanceof MappedPropertyDescriptor) { // mappedPropertyDescriptor original & synonym properties.add(new Property(property.getName(), p.getValue())); properties.add(new Property(allPropertyDescriptors[i].getName(), p.getValue())); } else { properties.add(p); } break; } } } else { propertyDescriptors.add(property); for (Property p : allProperties) { if (p.getName().equals(property.getName())) { delegatedProperties.add(p); break; } } } } this.properties = properties.toArray(new Property[properties.size()]); this.delegatedProperties = delegatedProperties.toArray(new Property[delegatedProperties.size()]); this.childPropertyDescriptors = descriptors.toArray(new PropertyDescriptor[descriptors.size()]); } } }