no.sesat.search.datamodel.BeanDataNodeInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for no.sesat.search.datamodel.BeanDataNodeInvocationHandler.java

Source

/* 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()]);
        }
    }
}