org.apache.olingo.ext.proxy.commons.AbstractStructuredInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.olingo.ext.proxy.commons.AbstractStructuredInvocationHandler.java

Source

/*
 * 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.
 */
package org.apache.olingo.ext.proxy.commons;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.olingo.client.api.domain.ClientEntity;
import org.apache.olingo.client.api.domain.ClientInlineEntity;
import org.apache.olingo.client.api.domain.ClientInlineEntitySet;
import org.apache.olingo.client.api.domain.ClientLink;
import org.apache.olingo.client.api.domain.ClientLinked;
import org.apache.olingo.client.api.domain.ClientProperty;
import org.apache.olingo.client.api.domain.ClientValue;
import org.apache.olingo.client.api.uri.QueryOption;
import org.apache.olingo.client.api.uri.URIBuilder;
import org.apache.olingo.client.core.uri.URIUtils;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.ext.proxy.AbstractService;
import org.apache.olingo.ext.proxy.api.AbstractEntitySet;
import org.apache.olingo.ext.proxy.api.ComplexCollection;
import org.apache.olingo.ext.proxy.api.EdmStreamValue;
import org.apache.olingo.ext.proxy.api.EntityCollection;
import org.apache.olingo.ext.proxy.api.PrimitiveCollection;
import org.apache.olingo.ext.proxy.api.annotations.ComplexType;
import org.apache.olingo.ext.proxy.api.annotations.Namespace;
import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
import org.apache.olingo.ext.proxy.api.annotations.Property;
import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
import org.apache.olingo.ext.proxy.context.EntityContext;
import org.apache.olingo.ext.proxy.context.EntityUUID;
import org.apache.olingo.ext.proxy.utils.ClassUtils;
import org.apache.olingo.ext.proxy.utils.CoreUtils;
import org.apache.olingo.ext.proxy.utils.ProxyUtils;

public abstract class AbstractStructuredInvocationHandler extends AbstractInvocationHandler {

    protected URIBuilder uri;

    protected URI baseURI;

    protected final Class<?> typeRef;

    protected EntityInvocationHandler entityHandler;

    protected Object internal;

    private final Map<String, AnnotatableInvocationHandler> propAnnotatableHandlers = new HashMap<String, AnnotatableInvocationHandler>();

    private final Map<String, AnnotatableInvocationHandler> navPropAnnotatableHandlers = new HashMap<String, AnnotatableInvocationHandler>();

    protected final Map<String, Object> propertyChanges = new HashMap<String, Object>();

    protected final Map<String, Object> propertyCache = new HashMap<String, Object>();

    protected final Map<NavigationProperty, Object> linkChanges = new HashMap<NavigationProperty, Object>();

    protected final Map<NavigationProperty, Object> linkCache = new HashMap<NavigationProperty, Object>();

    protected final Map<String, EdmStreamValue> streamedPropertyChanges = new HashMap<String, EdmStreamValue>();

    protected final Map<String, EdmStreamValue> streamedPropertyCache = new HashMap<String, EdmStreamValue>();

    protected AbstractStructuredInvocationHandler(final Class<?> typeRef, final AbstractService<?> service) {

        super(service);
        this.internal = null;
        this.typeRef = typeRef;
        this.entityHandler = null;
    }

    protected AbstractStructuredInvocationHandler(final Class<?> typeRef, final Object internal,
            final AbstractService<?> service) {

        super(service);
        this.internal = internal;
        this.typeRef = typeRef;
        this.entityHandler = null;
    }

    protected AbstractStructuredInvocationHandler(final Class<?> typeRef, final Object internal,
            final EntityInvocationHandler entityHandler) {

        super(entityHandler == null ? null : entityHandler.service);
        this.internal = internal;
        this.typeRef = typeRef;
        // prevent memory leak
        this.entityHandler = entityHandler == this ? null : entityHandler;
    }

    public Object getInternal() {
        return internal;
    }

    public EntityInvocationHandler getEntityHandler() {
        return entityHandler == null
                ? this instanceof EntityInvocationHandler ? EntityInvocationHandler.class.cast(this) : null
                : entityHandler;
    }

    public void setEntityHandler(final EntityInvocationHandler entityHandler) {
        // prevents memory leak
        this.entityHandler = entityHandler == this ? null : entityHandler;
    }

    public Class<?> getTypeRef() {
        return typeRef;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        if (method.getName().startsWith("get")) {
            // Here need check "get"/"set" first for better get-/set- performance because
            // the below if-statements are really time-consuming, even twice slower than "get" body.

            // Assumption: for each getter will always exist a setter and viceversa.
            // get method annotation and check if it exists as expected

            final Object res;
            final Method getter = typeRef.getMethod(method.getName());

            final Property property = ClassUtils.getAnnotation(Property.class, getter);
            if (property == null) {
                final NavigationProperty navProp = ClassUtils.getAnnotation(NavigationProperty.class, getter);
                if (navProp == null) {
                    throw new UnsupportedOperationException("Unsupported method " + method.getName());
                } else {
                    // if the getter refers to a navigation property ... navigate and follow link if necessary
                    res = getNavigationPropertyValue(navProp, getter);
                }
            } else {
                // if the getter refers to a property .... get property from wrapped entity
                res = getPropertyValue(property.name(), getter.getGenericReturnType());
            }

            return res;
        } else if (method.getName().startsWith("set")) {
            // get the corresponding getter method (see assumption above)
            final String getterName = method.getName().replaceFirst("set", "get");
            final Method getter = typeRef.getMethod(getterName);

            final Property property = ClassUtils.getAnnotation(Property.class, getter);
            if (property == null) {
                final NavigationProperty navProp = ClassUtils.getAnnotation(NavigationProperty.class, getter);
                if (navProp == null) {
                    throw new UnsupportedOperationException("Unsupported method " + method.getName());
                } else {
                    // if the getter refers to a navigation property ... 
                    if (ArrayUtils.isEmpty(args) || args.length != 1) {
                        throw new IllegalArgumentException("Invalid argument");
                    }

                    setNavigationPropertyValue(navProp, args[0]);
                }
            } else {
                setPropertyValue(property, args[0]);
            }

            return ClassUtils.returnVoid();
        } else if ("expand".equals(method.getName()) || "select".equals(method.getName())
                || "refs".equals(method.getName())) {
            invokeSelfMethod(method, args);
            return proxy;
        } else if (isSelfMethod(method)) {
            return invokeSelfMethod(method, args);
        } else if ("load".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
            load();
            return proxy;
        } else if ("loadAsync".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
            return service.getClient().getConfiguration().getExecutor().submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    load();
                    return proxy;
                }
            });
        } else if ("operations".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
            final Class<?> returnType = method.getReturnType();

            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { returnType }, OperationInvocationHandler.getInstance(getEntityHandler()));
        } else if ("annotations".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
            final Class<?> returnType = method.getReturnType();

            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { returnType },
                    AnnotatationsInvocationHandler.getInstance(getEntityHandler(), this));
        } else {
            throw new NoSuchMethodException(method.getName());
        }
    }

    public void delete(final String name) {
        if (baseURI != null) {
            getContext().entityContext().addFurtherDeletes(getClient().newURIBuilder(baseURI.toASCIIString())
                    .appendPropertySegment(name).appendValueSegment().build());
        }
    }

    public void delete() {
        final EntityContext entityContext = getContext().entityContext();

        if (this instanceof EntityInvocationHandler) {
            deleteEntity(EntityInvocationHandler.class.cast(this), null);
        } else if (baseURI != null) {
            entityContext.addFurtherDeletes(
                    getClient().newURIBuilder(baseURI.toASCIIString()).appendValueSegment().build());
        }
    }

    protected void attach() {
        attach(AttachedEntityStatus.ATTACHED, false);
    }

    protected void attach(final AttachedEntityStatus status) {
        attach(status, true);
    }

    protected void attach(final AttachedEntityStatus status, final boolean override) {
        if (getContext().entityContext().isAttached(getEntityHandler())) {
            if (override) {
                getContext().entityContext().setStatus(getEntityHandler(), status);
            }
        } else {
            getContext().entityContext().attach(getEntityHandler(), status);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected Object getPropertyValue(final String name, final Type type) {
        try {
            Object res;
            final Class<?> ref = ClassUtils.getTypeClass(type);

            if (ref == EdmStreamValue.class) {
                if (streamedPropertyCache.containsKey(name)) {
                    res = streamedPropertyCache.get(name);
                } else if (streamedPropertyChanges.containsKey(name)) {
                    res = streamedPropertyChanges.get(name);
                } else {
                    res = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                            new Class<?>[] { EdmStreamValue.class },
                            new EdmStreamValueHandler(baseURI == null ? null
                                    : getClient().newURIBuilder(baseURI.toASCIIString()).appendPropertySegment(name)
                                            .build(),
                                    service));

                    streamedPropertyCache.put(name, EdmStreamValue.class.cast(res));
                }

                return res;
            } else {
                if (propertyChanges.containsKey(name)) {
                    res = propertyChanges.get(name);
                } else if (propertyCache.containsKey(name)) {
                    res = propertyCache.get(name);
                } else {
                    final ClientProperty property = getInternalProperty(name);

                    if (ref != null && ClassUtils.getTypeClass(type).isAnnotationPresent(ComplexType.class)) {
                        res = getComplex(name,
                                property == null || property.hasNullValue() ? null : property.getValue(), ref,
                                getEntityHandler(), baseURI, false);
                    } else if (ref != null && ComplexCollection.class.isAssignableFrom(ref)) {
                        final ComplexCollectionInvocationHandler<?> collectionHandler;
                        final Class<?> itemRef = ClassUtils.extractTypeArg(ref, ComplexCollection.class);

                        if (property == null || property.hasNullValue()) {
                            collectionHandler = new ComplexCollectionInvocationHandler(itemRef, service,
                                    baseURI == null ? null
                                            : getClient().newURIBuilder(baseURI.toASCIIString())
                                                    .appendPropertySegment(name));
                        } else {
                            List items = new ArrayList();

                            for (ClientValue item : property.getValue().asCollection()) {
                                items.add(getComplex(name, item, itemRef, getEntityHandler(), null, true));
                            }

                            collectionHandler = new ComplexCollectionInvocationHandler(service, items, itemRef,
                                    baseURI == null ? null
                                            : getClient().newURIBuilder(baseURI.toASCIIString())
                                                    .appendPropertySegment(name));
                        }

                        res = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                new Class<?>[] { ref }, collectionHandler);

                    } else if (ref != null && PrimitiveCollection.class.isAssignableFrom(ref)) {
                        PrimitiveCollectionInvocationHandler collectionHandler;
                        if (property == null || property.hasNullValue()) {
                            collectionHandler = new PrimitiveCollectionInvocationHandler(service, null,
                                    baseURI == null ? null
                                            : getClient().newURIBuilder(baseURI.toASCIIString())
                                                    .appendPropertySegment(name));
                        } else {
                            List items = new ArrayList();
                            for (ClientValue item : property.getValue().asCollection()) {
                                items.add(item.asPrimitive().toValue());
                            }
                            collectionHandler = new PrimitiveCollectionInvocationHandler(service, items, null,
                                    baseURI == null ? null
                                            : getClient().newURIBuilder(baseURI.toASCIIString())
                                                    .appendPropertySegment(name));
                        }

                        res = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                new Class<?>[] { PrimitiveCollection.class }, collectionHandler);
                    } else {
                        res = property == null || property.hasNullValue() ? null
                                : CoreUtils.getObjectFromODataValue(property.getValue(), type, service);
                    }
                }

                if (res != null) {
                    propertyCache.put(name, res);
                }

                return res;
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Error getting value for property '" + name + "'", e);
        }
    }

    protected void setPropertyValue(final Property property, final Object value) {
        if (EdmPrimitiveTypeKind.Stream.getFullQualifiedName().toString().equalsIgnoreCase(property.type())) {
            setStreamedProperty(property, (EdmStreamValue) value);
        } else {
            addPropertyChanges(property.name(), value);

            if (value != null) {
                Collection<?> coll;
                if (Collection.class.isAssignableFrom(value.getClass())) {
                    coll = Collection.class.cast(value);
                } else {
                    coll = Collections.singleton(value);
                }

                for (Object item : coll) {
                    if (item instanceof Proxy) {
                        final InvocationHandler handler = Proxy.getInvocationHandler(item);
                        if ((handler instanceof ComplexInvocationHandler)
                                && ((ComplexInvocationHandler) handler).getEntityHandler() == null) {
                            ((ComplexInvocationHandler) handler).setEntityHandler(getEntityHandler());
                        }
                    }
                }
            }
        }

        attach(AttachedEntityStatus.CHANGED);
    }

    private void setStreamedProperty(final Property property, final EdmStreamValue input) {
        final Object obj = streamedPropertyChanges.get(property.name());
        if (obj instanceof InputStream) {
            IOUtils.closeQuietly((InputStream) obj);
        }

        streamedPropertyCache.remove(property.name());
        streamedPropertyChanges.put(property.name(), input.load());
    }

    protected abstract Object getNavigationPropertyValue(final NavigationProperty property, final Method getter);

    protected Object retrieveNavigationProperty(final NavigationProperty property, final Method getter) {
        final Class<?> type = getter.getReturnType();
        final Class<?> collItemType;
        if (EntityCollection.class.isAssignableFrom(type)) {
            collItemType = ClassUtils.extractTypeArg(type, EntityCollection.class, ComplexCollection.class);
        } else {
            collItemType = type;
        }

        final Object navPropValue;

        URI targetEntitySetURI = CoreUtils.getTargetEntitySetURI(getClient(), property);
        final ClientLink link = ((ClientLinked) internal).getNavigationLink(property.name());

        if (link instanceof ClientInlineEntity) {
            // return entity
            navPropValue = ProxyUtils.getEntityProxy(service, ((ClientInlineEntity) link).getEntity(),
                    targetEntitySetURI, type, null, false);
        } else if (link instanceof ClientInlineEntitySet) {
            if (AbstractEntitySet.class.isAssignableFrom(type)) {
                navPropValue = ProxyUtils.getEntitySetProxy(service, type,
                        ((ClientInlineEntitySet) link).getEntitySet(), targetEntitySetURI, false);
            } else {
                // return entity set
                navPropValue = ProxyUtils.getEntityCollectionProxy(service, collItemType, type, targetEntitySetURI,
                        ((ClientInlineEntitySet) link).getEntitySet(), targetEntitySetURI, false);
            }
        } else {
            // navigate
            final URI targetURI = URIUtils.getURI(getEntityHandler().getEntityURI(), property.name());

            if (EntityCollection.class.isAssignableFrom(type)) {
                navPropValue = ProxyUtils.getEntityCollectionProxy(service, collItemType, type, targetEntitySetURI,
                        null, targetURI, true);
            } else if (AbstractEntitySet.class.isAssignableFrom(type)) {
                navPropValue = ProxyUtils.getEntitySetProxy(service, type, targetURI); // cannot be used standard target entity set URI
            } else {
                final EntityUUID uuid = new EntityUUID(targetEntitySetURI, collItemType, null);
                LOG.debug("Ask for '{}({})'", collItemType.getSimpleName(), null);

                EntityInvocationHandler handler = getContext().entityContext().getEntity(uuid);

                if (handler == null) {
                    final ClientEntity entity = getClient().getObjectFactory()
                            .newEntity(new FullQualifiedName(collItemType.getAnnotation(Namespace.class).value(),
                                    ClassUtils.getEntityTypeName(collItemType)));

                    handler = EntityInvocationHandler.getInstance(entity,
                            URIUtils.getURI(this.uri.build(), property.name()), targetEntitySetURI, collItemType,
                            service);

                } else if (getContext().entityContext().getStatus(handler) == AttachedEntityStatus.DELETED) {
                    // object deleted
                    LOG.debug("Object '{}({})' has been deleted", collItemType.getSimpleName(), uuid);
                    handler = null;
                }

                navPropValue = handler == null ? null
                        : Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                new Class<?>[] { collItemType }, handler);
            }
        }

        return navPropValue;
    }

    // use read- instead of get- for .invoke() to distinguish it from entity property getter.
    public Object readAdditionalProperty(final String name) {
        return getPropertyValue(name, null);
    }

    public Map<String, Object> getPropertyChanges() {
        Map<String, Object> changedProperties = new HashMap<String, Object>();
        changedProperties.putAll(propertyChanges);

        for (Map.Entry<String, Object> propertyCacheEntry : propertyCache.entrySet()) {
            if (hasCachedPropertyChanged(propertyCacheEntry.getValue())) {
                changedProperties.put(propertyCacheEntry.getKey(), propertyCacheEntry.getValue());
            }
        }

        return changedProperties;
    }

    protected boolean hasCachedPropertyChanged(final Object cachedValue) {
        AbstractStructuredInvocationHandler structuredInvocationHandler = getStructuredInvocationHandler(
                cachedValue);
        if (structuredInvocationHandler != null) {
            return structuredInvocationHandler.isChanged();
        }

        return false;
    }

    public boolean isChanged() {
        return !linkChanges.isEmpty() || hasPropertyChanges();
    }

    protected boolean hasPropertyChanges() {
        return !propertyChanges.isEmpty() || hasDeepPropertyChanges();
    }

    protected boolean hasDeepPropertyChanges() {
        for (Object propertyValue : propertyCache.values()) {
            if (hasCachedPropertyChanged(propertyValue)) {
                return true;
            }
        }

        return false;
    }

    public void applyChanges() {
        streamedPropertyCache.putAll(streamedPropertyChanges);
        streamedPropertyChanges.clear();
        propertyCache.putAll(propertyChanges);
        propertyChanges.clear();
        linkCache.putAll(linkChanges);
        linkChanges.clear();

        applyChangesOnChildren();
    }

    protected void applyChangesOnChildren() {
        for (Object propertyValue : propertyCache.values()) {
            applyChanges(propertyValue);
        }
    }

    protected void applyChanges(final Object cachedValue) {
        AbstractStructuredInvocationHandler structuredInvocationHandler = getStructuredInvocationHandler(
                cachedValue);
        if (structuredInvocationHandler != null) {
            structuredInvocationHandler.applyChanges();
        }
    }

    protected AbstractStructuredInvocationHandler getStructuredInvocationHandler(final Object value) {
        if (value != null && Proxy.isProxyClass(value.getClass())) {
            InvocationHandler invocationHandler = Proxy.getInvocationHandler(value);
            if (invocationHandler instanceof AbstractStructuredInvocationHandler) {
                return (AbstractStructuredInvocationHandler) invocationHandler;
            }
        }

        return null;
    }

    public Collection<String> readAdditionalPropertyNames() {
        final Set<String> res = new HashSet<String>(propertyChanges.keySet());
        final Set<String> propertyNames = new HashSet<String>();
        for (Method method : typeRef.getMethods()) {
            final Annotation ann = method.getAnnotation(Property.class);
            if (ann != null) {
                final String property = ((Property) ann).name();
                propertyNames.add(property);

                // maybe someone could add a normal attribute to the additional set
                res.remove(property);
            }
        }

        for (ClientProperty property : getInternalProperties()) {
            if (!propertyNames.contains(property.getName())) {
                res.add(property.getName());
            }
        }

        return res;
    }

    public void addAdditionalProperty(final String name, final Object value) {
        propertyChanges.put(name, value);
        attach(AttachedEntityStatus.CHANGED);
    }

    public Map<NavigationProperty, Object> getLinkChanges() {
        return linkChanges;
    }

    public void removeAdditionalProperty(final String name) {
        propertyChanges.remove(name);
        attach(AttachedEntityStatus.CHANGED);
    }

    protected void addPropertyChanges(final String name, final Object value) {
        propertyCache.remove(name);
        propertyChanges.put(name, value);
    }

    protected void addLinkChanges(final NavigationProperty navProp, final Object value) {
        linkChanges.put(navProp, value);

        if (linkCache.containsKey(navProp)) {
            linkCache.remove(navProp);
        }
    }

    public Map<String, EdmStreamValue> getStreamedPropertyChanges() {
        return streamedPropertyChanges;
    }

    private void setNavigationPropertyValue(final NavigationProperty property, final Object value) {
        // 1) attach source entity
        if (!getContext().entityContext().isAttached(getEntityHandler())) {
            getContext().entityContext().attach(getEntityHandler(), AttachedEntityStatus.CHANGED);
        }

        // 2) add links
        addLinkChanges(property, value);
    }

    public Map<String, AnnotatableInvocationHandler> getPropAnnotatableHandlers() {
        return propAnnotatableHandlers;
    }

    public void putPropAnnotatableHandler(final String propName, final AnnotatableInvocationHandler handler) {
        propAnnotatableHandlers.put(propName, handler);
    }

    public Map<String, AnnotatableInvocationHandler> getNavPropAnnotatableHandlers() {
        return navPropAnnotatableHandlers;
    }

    public void putNavPropAnnotatableHandler(final String navPropName, final AnnotatableInvocationHandler handler) {
        navPropAnnotatableHandlers.put(navPropName, handler);
    }

    public void expand(final String... expand) {
        this.uri.replaceQueryOption(QueryOption.EXPAND, StringUtils.join(expand, ","));
    }

    public void select(final String... select) {
        this.uri.replaceQueryOption(QueryOption.SELECT, StringUtils.join(select, ","));
    }

    public void refs() {
        this.uri.appendRefSegment();
    }

    public void clearQueryOptions() {
        this.uri = baseURI == null ? null : getClient().newURIBuilder(baseURI.toASCIIString());
    }

    protected abstract void load();

    protected abstract <T extends ClientProperty> List<T> getInternalProperties();

    protected abstract ClientProperty getInternalProperty(final String name);
}