You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/05/12 10:38:39 UTC
[04/50] [abbrv] [OLINGO-260] providing proxy merge from ODataJClient.
Still missing integration tests
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
new file mode 100644
index 0000000..389f791
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
@@ -0,0 +1,564 @@
+/*
+ * 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.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataMediaRequest;
+import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataProperty;
+import org.apache.olingo.commons.api.domain.ODataInlineEntity;
+import org.apache.olingo.commons.api.domain.ODataInlineEntitySet;
+import org.apache.olingo.commons.api.domain.ODataLink;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ODataMediaFormat;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+import org.apache.olingo.ext.proxy.api.annotations.EntityType;
+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.EngineUtils;
+
+public class EntityTypeInvocationHandler<C extends CommonEdmEnabledODataClient<?>>
+ extends AbstractInvocationHandler<C> {
+
+ private static final long serialVersionUID = 2629912294765040037L;
+
+ private CommonODataEntity entity;
+
+ private final Class<?> typeRef;
+
+ private Map<String, Object> propertyChanges = new HashMap<String, Object>();
+
+ private Map<String, InputStream> streamedPropertyChanges = new HashMap<String, InputStream>();
+
+ private Map<NavigationProperty, Object> linkChanges = new HashMap<NavigationProperty, Object>();
+
+ private InputStream stream;
+
+ private EntityUUID uuid;
+
+ private final EntityContext entityContext = EntityContainerFactory.getContext().entityContext();
+
+ private int propertiesTag;
+
+ private int linksTag;
+
+ static EntityTypeInvocationHandler<?> getInstance(
+ final CommonODataEntity entity,
+ final EntitySetInvocationHandler<?, ?, ?, ?> entitySet,
+ final Class<?> typeRef) {
+
+ return getInstance(
+ entity,
+ entitySet.getEntitySetName(),
+ typeRef,
+ entitySet.containerHandler);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ static EntityTypeInvocationHandler<?> getInstance(
+ final CommonODataEntity entity,
+ final String entitySetName,
+ final Class<?> typeRef,
+ final EntityContainerInvocationHandler<?> containerHandler) {
+
+ return new EntityTypeInvocationHandler(
+ entity, entitySetName, typeRef, containerHandler);
+ }
+
+ private EntityTypeInvocationHandler(
+ final CommonODataEntity entity,
+ final String entitySetName,
+ final Class<?> typeRef,
+ final EntityContainerInvocationHandler<C> containerHandler) {
+
+ super(containerHandler.getClient(), containerHandler);
+ this.typeRef = typeRef;
+
+ this.entity = entity;
+ this.entity.setMediaEntity(typeRef.getAnnotation(EntityType.class).hasStream());
+
+ this.uuid = new EntityUUID(
+ containerHandler.getEntityContainerName(),
+ entitySetName,
+ entity.getTypeName(),
+ EngineUtils.getKey(client.getCachedEdm(), typeRef, entity));
+
+ this.stream = null;
+ this.propertiesTag = 0;
+ this.linksTag = 0;
+ }
+
+ public void setEntity(final CommonODataEntity entity) {
+ this.entity = entity;
+ this.entity.setMediaEntity(typeRef.getAnnotation(EntityType.class).hasStream());
+
+ this.uuid = new EntityUUID(
+ getUUID().getContainerName(),
+ getUUID().getEntitySetName(),
+ getUUID().getName(),
+ EngineUtils.getKey(client.getCachedEdm(), typeRef, entity));
+
+ this.propertyChanges.clear();
+ this.linkChanges.clear();
+ this.streamedPropertyChanges.clear();
+ this.propertiesTag = 0;
+ this.linksTag = 0;
+ this.stream = null;
+ }
+
+ public EntityUUID getUUID() {
+ return uuid;
+ }
+
+ public FullQualifiedName getName() {
+ return this.entity.getTypeName();
+ }
+
+ public String getEntityContainerName() {
+ return uuid.getContainerName();
+ }
+
+ public String getEntitySetName() {
+ return uuid.getEntitySetName();
+ }
+
+ public Class<?> getTypeRef() {
+ return typeRef;
+ }
+
+ public CommonODataEntity getEntity() {
+ return entity;
+ }
+
+ public Map<String, Object> getPropertyChanges() {
+ return propertyChanges;
+ }
+
+ public Map<NavigationProperty, Object> getLinkChanges() {
+ return linkChanges;
+ }
+
+ /**
+ * Gets the current ETag defined into the wrapped entity.
+ *
+ * @return
+ */
+ public String getETag() {
+ return this.entity.getETag();
+ }
+
+ /**
+ * Overrides ETag value defined into the wrapped entity.
+ *
+ * @param eTag ETag.
+ */
+ public void setETag(final String eTag) {
+ this.entity.setETag(eTag);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (isSelfMethod(method, args)) {
+ return invokeSelfMethod(method, args);
+ } 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(this));
+ } else if (method.getName().startsWith("get")) {
+ // 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, getter.getGenericReturnType());
+ }
+
+ // attach the current handler
+ attach();
+
+ 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 {
+ throw new UnsupportedOperationException("Method not found: " + method);
+ }
+ }
+
+ private Object getNavigationPropertyValue(final NavigationProperty property, final Method getter) {
+ final Class<?> type = getter.getReturnType();
+ final Class<?> collItemType;
+ if (AbstractEntityCollection.class.isAssignableFrom(type)) {
+ final Type[] entityCollectionParams =
+ ((ParameterizedType) type.getGenericInterfaces()[0]).getActualTypeArguments();
+ collItemType = (Class<?>) entityCollectionParams[0];
+ } else {
+ collItemType = type;
+ }
+
+ final Object navPropValue;
+
+ if (linkChanges.containsKey(property)) {
+ navPropValue = linkChanges.get(property);
+ } else {
+ final ODataLink link = EngineUtils.getNavigationLink(property.name(), entity);
+ if (link instanceof ODataInlineEntity) {
+ // return entity
+ navPropValue = getEntityProxy(
+ ((ODataInlineEntity) link).getEntity(),
+ property.targetContainer(),
+ property.targetEntitySet(),
+ type,
+ false);
+ } else if (link instanceof ODataInlineEntitySet) {
+ // return entity set
+ navPropValue = getEntityCollection(
+ collItemType,
+ type,
+ property.targetContainer(),
+ ((ODataInlineEntitySet) link).getEntitySet(),
+ link.getLink(),
+ false);
+ } else {
+ // navigate
+ final URI uri = URIUtils.getURI(
+ containerHandler.getFactory().getServiceRoot(), link.getLink().toASCIIString());
+
+ if (AbstractEntityCollection.class.isAssignableFrom(type)) {
+ navPropValue = getEntityCollection(
+ collItemType,
+ type,
+ property.targetContainer(),
+ client.getRetrieveRequestFactory().getEntitySetRequest(uri).execute().getBody(),
+ uri,
+ true);
+ } else {
+ final ODataRetrieveResponse<CommonODataEntity> res =
+ client.getRetrieveRequestFactory().getEntityRequest(uri).execute();
+
+ navPropValue = getEntityProxy(
+ res.getBody(),
+ property.targetContainer(),
+ property.targetEntitySet(),
+ type,
+ res.getETag(),
+ true);
+ }
+ }
+
+ if (navPropValue != null) {
+ int checkpoint = linkChanges.hashCode();
+ linkChanges.put(property, navPropValue);
+ updateLinksTag(checkpoint);
+ }
+ }
+
+ return navPropValue;
+ }
+
+ private Object getPropertyValue(final String name, final Type type) {
+ try {
+ final Object res;
+
+ if (propertyChanges.containsKey(name)) {
+ res = propertyChanges.get(name);
+ } else {
+
+ res = type == null
+ ? EngineUtils.getValueFromProperty(
+ client.getCachedEdm(), entity.getProperty(name))
+ : EngineUtils.getValueFromProperty(
+ client.getCachedEdm(), entity.getProperty(name), type);
+
+ if (res != null) {
+ int checkpoint = propertyChanges.hashCode();
+ propertyChanges.put(name, res);
+ updatePropertiesTag(checkpoint);
+ }
+ }
+
+ return res;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Error getting value for property '" + name + "'", e);
+ }
+ }
+
+ private Object getPropertyValue(final Property property, final Type type) {
+ if (!(type instanceof ParameterizedType) && (Class<?>) type == InputStream.class) {
+ return getStreamedProperty(property);
+ } else {
+ return getPropertyValue(property.name(), type);
+ }
+ }
+
+ public Object getAdditionalProperty(final String name) {
+ return getPropertyValue(name, null);
+ }
+
+ public Collection<String> getAdditionalPropertyNames() {
+ 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 (CommonODataProperty property : entity.getProperties()) {
+ if (!propertyNames.contains(property.getName())) {
+ res.add(property.getName());
+ }
+ }
+
+ return res;
+ }
+
+ private void setNavigationPropertyValue(final NavigationProperty property, final Object value) {
+ // 1) attach source entity
+ attach(AttachedEntityStatus.CHANGED, false);
+
+ // 2) attach the target entity handlers
+ for (Object link : AbstractEntityCollection.class.isAssignableFrom(value.getClass())
+ ? (AbstractEntityCollection) value : Collections.singleton(value)) {
+
+ final InvocationHandler etih = Proxy.getInvocationHandler(link);
+ if (!(etih instanceof EntityTypeInvocationHandler)) {
+ throw new IllegalArgumentException("Invalid argument type");
+ }
+
+ @SuppressWarnings("unchecked")
+ final EntityTypeInvocationHandler<C> handler = (EntityTypeInvocationHandler<C>) etih;
+ if (!handler.getTypeRef().isAnnotationPresent(EntityType.class)) {
+ throw new IllegalArgumentException(
+ "Invalid argument type " + handler.getTypeRef().getSimpleName());
+ }
+
+ if (!entityContext.isAttached(handler)) {
+ entityContext.attach(handler, AttachedEntityStatus.LINKED);
+ }
+ }
+
+ // 3) add links
+ linkChanges.put(property, value);
+ }
+
+ private void setPropertyValue(final Property property, final Object value) {
+ if (property.type().equalsIgnoreCase("Edm.Stream")) {
+ setStreamedProperty(property, (InputStream) value);
+ } else {
+ propertyChanges.put(property.name(), value);
+ }
+
+ attach(AttachedEntityStatus.CHANGED);
+ }
+
+ public void addAdditionalProperty(final String name, final Object value) {
+ propertyChanges.put(name, value);
+ attach(AttachedEntityStatus.CHANGED);
+ }
+
+ private void updatePropertiesTag(final int checkpoint) {
+ if (checkpoint == propertiesTag) {
+ propertiesTag = propertyChanges.hashCode();
+ }
+ }
+
+ private void updateLinksTag(final int checkpoint) {
+ if (checkpoint == linksTag) {
+ linksTag = linkChanges.hashCode();
+ }
+ }
+
+ public boolean isChanged() {
+ return this.linkChanges.hashCode() != this.linksTag
+ || this.propertyChanges.hashCode() != this.propertiesTag
+ || this.stream != null
+ || !this.streamedPropertyChanges.isEmpty();
+ }
+
+ public void setStream(final InputStream stream) {
+ if (typeRef.getAnnotation(EntityType.class).hasStream()) {
+ IOUtils.closeQuietly(this.stream);
+ this.stream = stream;
+ attach(AttachedEntityStatus.CHANGED);
+ }
+ }
+
+ public InputStream getStreamChanges() {
+ return this.stream;
+ }
+
+ public Map<String, InputStream> getStreamedPropertyChanges() {
+ return streamedPropertyChanges;
+ }
+
+ public InputStream getStream() {
+
+ final String contentSource = entity.getMediaContentSource();
+
+ if (this.stream == null
+ && typeRef.getAnnotation(EntityType.class).hasStream()
+ && StringUtils.isNotBlank(contentSource)) {
+
+ final String comntentType =
+ StringUtils.isBlank(entity.getMediaContentType()) ? "*/*" : entity.getMediaContentType();
+
+ final URI contentSourceURI = URIUtils.getURI(containerHandler.getFactory().getServiceRoot(), contentSource);
+
+ final ODataMediaRequest retrieveReq = client.getRetrieveRequestFactory().getMediaRequest(contentSourceURI);
+ retrieveReq.setFormat(ODataMediaFormat.fromFormat(comntentType));
+
+ this.stream = retrieveReq.execute().getBody();
+ }
+
+ return this.stream;
+ }
+
+ public Object getStreamedProperty(final Property property) {
+
+ InputStream res = streamedPropertyChanges.get(property.name());
+
+ try {
+ if (res == null) {
+ final URI link = URIUtils.getURI(
+ containerHandler.getFactory().getServiceRoot(),
+ EngineUtils.getEditMediaLink(property.name(), this.entity).toASCIIString());
+
+ final ODataMediaRequest req = client.getRetrieveRequestFactory().getMediaRequest(link);
+ res = req.execute().getBody();
+
+ }
+ } catch (Exception e) {
+ res = null;
+ }
+
+ return res;
+
+ }
+
+ private void setStreamedProperty(final Property property, final InputStream input) {
+ final Object obj = propertyChanges.get(property.name());
+ if (obj != null && obj instanceof InputStream) {
+ IOUtils.closeQuietly((InputStream) obj);
+ }
+
+ streamedPropertyChanges.put(property.name(), input);
+ }
+
+ private void attach() {
+ if (!entityContext.isAttached(this)) {
+ entityContext.attach(this, AttachedEntityStatus.ATTACHED);
+ }
+ }
+
+ private void attach(final AttachedEntityStatus status) {
+ attach(status, true);
+ }
+
+ private void attach(final AttachedEntityStatus status, final boolean override) {
+ if (entityContext.isAttached(this)) {
+ if (override) {
+ entityContext.setStatus(this, status);
+ }
+ } else {
+ entityContext.attach(this, status);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return uuid.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof EntityTypeInvocationHandler
+ && ((EntityTypeInvocationHandler) obj).getUUID().equals(uuid);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
new file mode 100644
index 0000000..0f61208
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
@@ -0,0 +1,218 @@
+/*
+ * 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.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.ODataOperation;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmEntityContainer;
+import org.apache.olingo.commons.api.edm.EdmOperation;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.ext.proxy.api.OperationExecutor;
+import org.apache.olingo.ext.proxy.api.OperationType;
+import org.apache.olingo.ext.proxy.api.annotations.Operation;
+import org.apache.olingo.ext.proxy.api.annotations.Parameter;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class OperationInvocationHandler<C extends CommonEdmEnabledODataClient<?>> extends AbstractInvocationHandler<C>
+ implements OperationExecutor {
+
+ private static final long serialVersionUID = 2629912294765040027L;
+
+ private final InvocationHandler target;
+
+ private final FullQualifiedName targetFQN;
+
+ private final String serviceRoot;
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(OperationInvocationHandler.class);
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ static OperationInvocationHandler<?> getInstance(final EntityContainerInvocationHandler<?> containerHandler) {
+ return new OperationInvocationHandler(containerHandler);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ static OperationInvocationHandler<?> getInstance(final EntityTypeInvocationHandler<?> entityHandler) {
+ return new OperationInvocationHandler(entityHandler);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ static OperationInvocationHandler<?> getInstance(final EntityCollectionInvocationHandler<?, ?> collectionHandler) {
+ return new OperationInvocationHandler(collectionHandler);
+ }
+
+ @SuppressWarnings("unchecked")
+ private OperationInvocationHandler(final EntityContainerInvocationHandler<C> containerHandler) {
+
+ super(containerHandler.getClient(), containerHandler);
+
+ this.target = containerHandler;
+
+ this.targetFQN =
+ new FullQualifiedName(containerHandler.getSchemaName(), containerHandler.getEntityContainerName());
+
+ this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+ }
+
+ @SuppressWarnings("unchecked")
+ private OperationInvocationHandler(final EntityTypeInvocationHandler<C> entityHandler) {
+ super(entityHandler.getClient(), entityHandler.containerHandler);
+
+ this.target = entityHandler;
+ this.targetFQN = entityHandler.getEntity().getTypeName();
+ this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+ }
+
+ @SuppressWarnings("unchecked")
+ private OperationInvocationHandler(final EntityCollectionInvocationHandler<?, C> collectionHandler) {
+ super(collectionHandler.getClient(), collectionHandler.containerHandler);
+
+ this.target = collectionHandler;
+
+ final String typeName = ClassUtils.getEntityTypeName(collectionHandler.getEntityReference());
+ final String typeNamespace = ClassUtils.getNamespace(collectionHandler.getEntityReference());
+
+ this.targetFQN = new FullQualifiedName(typeNamespace, typeName);
+ this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (isSelfMethod(method, args)) {
+ return invokeSelfMethod(method, args);
+ } else {
+ final Annotation[] methodAnnots = method.getAnnotations();
+ if (methodAnnots[0] instanceof Operation) {
+ final Operation operation = (Operation) methodAnnots[0];
+
+ final Annotation[][] annotations = method.getParameterAnnotations();
+ final List<String> parameterNames;
+ final LinkedHashMap<Parameter, Object> parameters = new LinkedHashMap<Parameter, Object>();
+ if (annotations == null || annotations.length == 0) {
+ parameterNames = null;
+ } else {
+ parameterNames = new ArrayList<String>();
+ for (int i = 0; i < args.length; i++) {
+ for (Annotation paramAnnotation : annotations[i]) {
+ if (paramAnnotation instanceof Parameter) {
+ parameterNames.add(((Parameter) paramAnnotation).name());
+ parameters.put((Parameter) paramAnnotation, args[i]);
+ }
+ }
+
+ if (parameters.size() <= i) {
+ throw new IllegalArgumentException("Paramter " + i + " is not annotated as @Parameter");
+ }
+ }
+ }
+
+ final Map.Entry<URI, EdmOperation> edmOperation;
+ if (target instanceof EntityContainerInvocationHandler) {
+ edmOperation = getUnboundOperation(operation, parameterNames);
+ } else if (target instanceof EntityTypeInvocationHandler) {
+ edmOperation = getBoundOperation(operation, parameterNames);
+ } else if (target instanceof EntityCollectionInvocationHandler) {
+ edmOperation = getCollectionBoundOperation(operation, parameterNames);
+ } else {
+ throw new IllegalStateException("Invalid target invocation");
+ }
+
+ return invokeOperation(operation, method, parameters, edmOperation.getKey(), edmOperation.getValue());
+ } else {
+ throw new UnsupportedOperationException("Method not found: " + method);
+ }
+ }
+ }
+
+ private Map.Entry<URI, EdmOperation> getUnboundOperation(
+ final Operation operation, final List<String> parameterNames) {
+ final EdmEntityContainer container = client.getCachedEdm().getEntityContainer(targetFQN);
+ final EdmOperation edmOperation;
+
+ if (operation.type() == OperationType.FUNCTION) {
+ edmOperation = container.getFunctionImport(operation.name()).getUnboundFunction(parameterNames);
+ } else {
+ edmOperation = container.getActionImport(operation.name()).getUnboundAction();
+ }
+
+ final CommonURIBuilder<?> uriBuilder = getClient().getURIBuilder(this.serviceRoot).
+ appendOperationCallSegment(URIUtils.operationImportURISegment(container, edmOperation.getName()));
+
+ return new AbstractMap.SimpleEntry<URI, EdmOperation>(uriBuilder.build(), edmOperation);
+ }
+
+ private Map.Entry<URI, EdmOperation> getBoundOperation(
+ final Operation operation, final List<String> parameterNames) {
+ final CommonODataEntity entity = ((EntityTypeInvocationHandler<?>) target).getEntity();
+
+ final ODataOperation boundOp =
+ entity.getOperation(new FullQualifiedName(targetFQN.getNamespace(), operation.name()).toString());
+
+ final EdmOperation edmOperation;
+
+ if (operation.type() == OperationType.FUNCTION) {
+ edmOperation = client.getCachedEdm().getBoundFunction(
+ new FullQualifiedName(boundOp.getTitle()), entity.getTypeName(), false, parameterNames);
+ } else {
+ edmOperation = client.getCachedEdm().getBoundAction(
+ new FullQualifiedName(boundOp.getTitle()), entity.getTypeName(), false);
+ }
+
+ return new AbstractMap.SimpleEntry<URI, EdmOperation>(boundOp.getTarget(), edmOperation);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map.Entry<URI, EdmOperation> getCollectionBoundOperation(
+ final Operation operation, final List<String> parameterNames) {
+
+ final Edm edm = client.getCachedEdm();
+
+ final EdmOperation edmOperation;
+
+ if (operation.type() == OperationType.FUNCTION) {
+ edmOperation = client.getCachedEdm().getBoundFunction(
+ new FullQualifiedName(targetFQN.getNamespace(), operation.name()), targetFQN, true, parameterNames);
+ } else {
+ edmOperation = client.getCachedEdm().getBoundAction(
+ new FullQualifiedName(targetFQN.getNamespace(), operation.name()), targetFQN, true);
+ }
+
+ return new AbstractMap.SimpleEntry<URI, EdmOperation>(
+ ((EntityCollectionInvocationHandler<?, C>) target).getURI(), edmOperation);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
new file mode 100644
index 0000000..00c3f73
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
@@ -0,0 +1,173 @@
+/*
+ * 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.Serializable;
+import java.net.URI;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.CommonODataClient;
+import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.client.api.uri.URIFilter;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+import org.apache.olingo.ext.proxy.api.NoResultException;
+import org.apache.olingo.ext.proxy.api.NonUniqueResultException;
+import org.apache.olingo.ext.proxy.api.Query;
+import org.apache.olingo.ext.proxy.api.Sort;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+
+public class QueryImpl<T extends Serializable, EC extends AbstractEntityCollection<T>> implements Query<T, EC> {
+
+ private static final long serialVersionUID = -300830736753191114L;
+
+ private final CommonODataClient client;
+
+ private final Class<T> typeRef;
+
+ private final Class<EC> collTypeRef;
+
+ private final EntitySetInvocationHandler handler;
+
+ private final URI baseURI;
+
+ private String filter;
+
+ private String orderBy;
+
+ private Integer maxResults;
+
+ private Integer firstResult;
+
+ @SuppressWarnings("unchecked")
+ QueryImpl(final CommonODataClient client,
+ final Class<EC> collTypeRef, final URI baseURI, final EntitySetInvocationHandler handler) {
+
+ this.client = client;
+ this.typeRef = (Class<T>) ClassUtils.extractTypeArg(collTypeRef);
+ this.collTypeRef = collTypeRef;
+ this.baseURI = baseURI;
+ this.handler = handler;
+ }
+
+ @Override
+ public Query<T, EC> setFilter(final String filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ @Override
+ public Query<T, EC> setFilter(final URIFilter filter) {
+ this.filter = filter.build();
+ return this;
+ }
+
+ @Override
+ public String getFilter() {
+ return filter;
+ }
+
+ @Override
+ public Query<T, EC> setOrderBy(final Sort... sort) {
+ final StringBuilder builder = new StringBuilder();
+ for (Sort sortClause : sort) {
+ builder.append(sortClause.getKey()).append(' ').append(sortClause.getValue()).append(',');
+ }
+ builder.deleteCharAt(builder.length() - 1);
+
+ this.orderBy = builder.toString();
+ return this;
+ }
+
+ @Override
+ public Query<T, EC> setOrderBy(final String orderBy) {
+ this.orderBy = orderBy;
+ return this;
+ }
+
+ @Override
+ public String getOrderBy() {
+ return orderBy;
+ }
+
+ @Override
+ public Query<T, EC> setMaxResults(final int maxResults) throws IllegalArgumentException {
+ if (maxResults <= 0) {
+ throw new IllegalArgumentException("maxResults must be positive");
+ }
+
+ this.maxResults = maxResults;
+ return this;
+ }
+
+ @Override
+ public int getMaxResults() {
+ return maxResults;
+ }
+
+ @Override
+ public Query<T, EC> setFirstResult(final int firstResult) throws IllegalArgumentException {
+ if (firstResult <= 0) {
+ throw new IllegalArgumentException("firstResult must be positive");
+ }
+
+ this.firstResult = firstResult;
+ return this;
+ }
+
+ @Override
+ public int getFirstResult() {
+ return firstResult;
+ }
+
+ @Override
+ public T getSingleResult() throws NoResultException, NonUniqueResultException {
+ final EC result = getResult();
+ if (result.isEmpty()) {
+ throw new NoResultException();
+ }
+ if (result.size() > 1) {
+ throw new NonUniqueResultException();
+ }
+
+ return result.iterator().next();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public EC getResult() {
+ final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(this.baseURI.toASCIIString()).
+ appendNavigationSegment(new FullQualifiedName(
+ ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)).toString());
+
+ if (StringUtils.isNotBlank(filter)) {
+ uriBuilder.filter(filter);
+ }
+ if (StringUtils.isNotBlank(orderBy)) {
+ uriBuilder.orderBy(orderBy);
+ }
+ if (maxResults != null) {
+ uriBuilder.top(maxResults);
+ }
+ if (firstResult != null) {
+ uriBuilder.skip(firstResult);
+ }
+
+ return (EC) handler.fetchWholeEntitySet(uriBuilder.build(), typeRef, collTypeRef);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
new file mode 100644
index 0000000..a30be64
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.context;
+
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+public class AttachedEntity {
+
+ private final EntityTypeInvocationHandler<?> entity;
+
+ private final AttachedEntityStatus status;
+
+ public AttachedEntity(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+ this.entity = entity;
+ this.status = status;
+ }
+
+ public EntityTypeInvocationHandler<?> getEntity() {
+ return entity;
+ }
+
+ public AttachedEntityStatus getStatus() {
+ return status;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
new file mode 100644
index 0000000..6d0d986
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
@@ -0,0 +1,44 @@
+/*
+ * 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.context;
+
+public enum AttachedEntityStatus {
+
+ /**
+ * Explicitely attached.
+ */
+ ATTACHED,
+ /**
+ * New object.
+ */
+ NEW,
+ /**
+ * Modified object.
+ */
+ CHANGED,
+ /**
+ * Deleted object.
+ */
+ DELETED,
+ /**
+ * Attached because explicitely liked to another object.
+ */
+ LINKED
+
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
new file mode 100644
index 0000000..c77ce08
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
@@ -0,0 +1,37 @@
+/*
+ * 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.context;
+
+public class Context {
+
+ private final EntityContext entities;
+
+ public Context() {
+ this.entities = new EntityContext();
+
+ }
+
+ public EntityContext entityContext() {
+ return entities;
+ }
+
+ public void detachAll() {
+ entities.detachAll();
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
new file mode 100644
index 0000000..753f33e
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
@@ -0,0 +1,199 @@
+/*
+ * 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.context;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+/**
+ * Entity context.
+ */
+public class EntityContext implements Iterable<AttachedEntity> {
+
+ /**
+ * Attached entities with not null key.
+ * <p>
+ * This map have to be used to search for entities by key.
+ */
+ private final Map<EntityUUID, EntityTypeInvocationHandler<?>> searchableEntities =
+ new HashMap<EntityUUID, EntityTypeInvocationHandler<?>>();
+
+ /**
+ * All attached entities (new entities included).
+ * <p>
+ * Attachment order will be maintained.
+ */
+ private final Map<EntityTypeInvocationHandler<?>, AttachedEntityStatus> allAttachedEntities =
+ new LinkedHashMap<EntityTypeInvocationHandler<?>, AttachedEntityStatus>();
+
+ /**
+ * Attaches an entity with status <tt>NEW</tt>.
+ * <p>
+ * Use this method to attach a new created entity.
+ *
+ * @see AttachedEntityStatus
+ * @param entity entity to be attached.
+ */
+ public void attachNew(final EntityTypeInvocationHandler<?> entity) {
+ if (allAttachedEntities.containsKey(entity)) {
+ throw new IllegalStateException("An entity with the same key has already been attached");
+ }
+ allAttachedEntities.put(entity, AttachedEntityStatus.NEW);
+ }
+
+ /**
+ * Attaches an existing entity with status <tt>ATTACHED</tt>.
+ * <p>
+ * Use this method to attach an existing entity.
+ *
+ * @see AttachedEntityStatus
+ * @param entity entity to be attached.
+ */
+ public void attach(final EntityTypeInvocationHandler<?> entity) {
+ attach(entity, AttachedEntityStatus.ATTACHED);
+ }
+
+ /**
+ * Attaches an entity with specified status.
+ * <p>
+ * Use this method to attach an existing entity.
+ *
+ * @see AttachedEntityStatus
+ * @param entity entity to be attached.
+ * @param status status.
+ */
+ public void attach(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+ if (isAttached(entity)) {
+ throw new IllegalStateException("An entity with the same profile has already been attached");
+ }
+
+ allAttachedEntities.put(entity, status);
+
+ if (entity.getUUID().getKey() != null) {
+ searchableEntities.put(entity.getUUID(), entity);
+ }
+ }
+
+ /**
+ * Detaches entity.
+ *
+ * @param entity entity to be detached.
+ */
+ public void detach(final EntityTypeInvocationHandler<?> entity) {
+ if (searchableEntities.containsKey(entity.getUUID())) {
+ searchableEntities.remove(entity.getUUID());
+ }
+ allAttachedEntities.remove(entity);
+ }
+
+ /**
+ * Detaches all attached entities.
+ * <p>
+ * Use this method to clears the entity context.
+ */
+ public void detachAll() {
+ allAttachedEntities.clear();
+ searchableEntities.clear();
+ }
+
+ /**
+ * Searches an entity with the specified key.
+ *
+ * @param uuid entity key.
+ * @return retrieved entity.
+ */
+ public EntityTypeInvocationHandler<?> getEntity(final EntityUUID uuid) {
+ return searchableEntities.get(uuid);
+ }
+
+ /**
+ * Gets entity status.
+ *
+ * @param entity entity to be retrieved.
+ * @return attached entity status.
+ */
+ public AttachedEntityStatus getStatus(final EntityTypeInvocationHandler<?> entity) {
+ if (!isAttached(entity)) {
+ throw new IllegalStateException("Entity is not in the context");
+ }
+
+ return allAttachedEntities.get(entity);
+ }
+
+ /**
+ * Changes attached entity status.
+ *
+ * @param entity attached entity to be modified.
+ * @param status new status.
+ */
+ public void setStatus(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+ if (!isAttached(entity)) {
+ throw new IllegalStateException("Entity is not in the context");
+ }
+
+ final AttachedEntityStatus current = allAttachedEntities.get(entity);
+
+ // Previously deleted object cannot be modified anymore.
+ if (current == AttachedEntityStatus.DELETED) {
+ throw new IllegalStateException("Entity has been previously deleted");
+ }
+
+ if (status == AttachedEntityStatus.NEW || status == AttachedEntityStatus.ATTACHED) {
+ throw new IllegalStateException("Entity status has already been initialized");
+ }
+
+ if ((status == AttachedEntityStatus.LINKED && current == AttachedEntityStatus.ATTACHED)
+ || (status == AttachedEntityStatus.CHANGED && current == AttachedEntityStatus.ATTACHED)
+ || (status == AttachedEntityStatus.CHANGED && current == AttachedEntityStatus.LINKED)
+ || (status == AttachedEntityStatus.DELETED)) {
+ allAttachedEntities.put(entity, status);
+ }
+ }
+
+ /**
+ * Checks if an entity is already attached.
+ *
+ * @param entity entity.
+ * @return <tt>true</tt> if is attached; <tt>false</tt> otherwise.
+ */
+ public boolean isAttached(final EntityTypeInvocationHandler<?> entity) {
+ return allAttachedEntities.containsKey(entity)
+ || (entity.getUUID().getKey() != null && searchableEntities.containsKey(entity.getUUID()));
+ }
+
+ /**
+ * Iterator.
+ *
+ * @return attached entities iterator.
+ */
+ @Override
+ public Iterator<AttachedEntity> iterator() {
+ final List<AttachedEntity> res = new ArrayList<AttachedEntity>();
+ for (Map.Entry<EntityTypeInvocationHandler<?>, AttachedEntityStatus> attachedEntity : allAttachedEntities.
+ entrySet()) {
+ res.add(new AttachedEntity(attachedEntity.getKey(), attachedEntity.getValue()));
+ }
+ return res.iterator();
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
new file mode 100644
index 0000000..e83b2c3
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
@@ -0,0 +1,104 @@
+/*
+ * 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.context;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.olingo.commons.api.domain.ODataLinkType;
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+public class EntityLinkDesc implements Serializable {
+
+ private static final long serialVersionUID = 704670372070370762L;
+
+ private final String sourceName;
+
+ private final EntityTypeInvocationHandler<?> source;
+
+ private final Collection<EntityTypeInvocationHandler<?>> targets;
+
+ private final ODataLinkType type;
+
+ public EntityLinkDesc(
+ final String sourceName,
+ final EntityTypeInvocationHandler<?> source,
+ final Collection<EntityTypeInvocationHandler<?>> target,
+ final ODataLinkType type) {
+ this.sourceName = sourceName;
+ this.source = source;
+ this.targets = target;
+ this.type = type;
+ }
+
+ public EntityLinkDesc(
+ final String sourceName,
+ final EntityTypeInvocationHandler<?> source,
+ final EntityTypeInvocationHandler<?> target,
+ final ODataLinkType type) {
+ this.sourceName = sourceName;
+ this.source = source;
+ this.targets = Collections.<EntityTypeInvocationHandler<?>>singleton(target);
+ this.type = type;
+ }
+
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ public EntityTypeInvocationHandler<?> getSource() {
+ return source;
+ }
+
+ public Collection<EntityTypeInvocationHandler<?>> getTargets() {
+ return targets;
+ }
+
+ public ODataLinkType getType() {
+ return type;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java
new file mode 100644
index 0000000..de7a6f1
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java
@@ -0,0 +1,102 @@
+/*
+ * 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.context;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+
+public class EntityUUID implements Serializable {
+
+ private static final long serialVersionUID = 4855025769803086495L;
+
+ // needed by equals and hashcode
+ private final int tempKey;
+
+ private final String containerName;
+
+ private final String entitySetName;
+
+ private final FullQualifiedName name;
+
+ private final Object key;
+
+ public EntityUUID(
+ final String containerName,
+ final String entitySetName,
+ final FullQualifiedName name) {
+ this(containerName, entitySetName, name, null);
+ }
+
+ public EntityUUID(
+ final String containerName,
+ final String entitySetName,
+ final FullQualifiedName name,
+ final Object key) {
+ this.containerName = containerName;
+ this.entitySetName = entitySetName;
+ this.name = name;
+ this.key = key;
+ this.tempKey = (int) (Math.random() * 1000000);
+ }
+
+ public String getContainerName() {
+ return containerName;
+ }
+
+ public String getEntitySetName() {
+ return entitySetName;
+ }
+
+ public FullQualifiedName getName() {
+ return name;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return key == null
+ ? EqualsBuilder.reflectionEquals(this, obj)
+ : EqualsBuilder.reflectionEquals(this, obj, "tempKey");
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this, "tempKey");
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public String toString() {
+ return name.getNamespace() + ":" + containerName + ":" + entitySetName + ":" + name.getName()
+ + "(" + (key == null ? null : key.toString()) + ")";
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
new file mode 100644
index 0000000..c4f325f
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
@@ -0,0 +1,142 @@
+/*
+ * 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.utils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.olingo.ext.proxy.api.annotations.CompoundKey;
+import org.apache.olingo.ext.proxy.api.annotations.EntityType;
+import org.apache.olingo.ext.proxy.api.annotations.Key;
+import org.apache.olingo.ext.proxy.api.annotations.KeyRef;
+import org.apache.olingo.ext.proxy.api.annotations.Namespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class ClassUtils {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(ClassUtils.class);
+
+ private ClassUtils() {
+ // Empty private constructor for static utility classes
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Class<?> extractTypeArg(final Class<?> paramType) {
+ final Type[] params = ((ParameterizedType) paramType.getGenericInterfaces()[0]).getActualTypeArguments();
+ return (Class<?>) params[0];
+ }
+
+ public static Method findGetterByAnnotatedName(
+ final Class<?> clazz, final Class<? extends Annotation> ann, final String name) {
+ final Method[] methods = clazz.getMethods();
+
+ Method result = null;
+ for (int i = 0; i < methods.length && result == null; i++) {
+ final Annotation annotation = methods[i].getAnnotation(ann);
+ try {
+ if ((annotation != null)
+ && methods[i].getName().startsWith("get") // Assumption: getter is always prefixed by 'get' word
+ && name.equals(ann.getMethod("name").invoke(annotation))) {
+ result = methods[i];
+ }
+ } catch (Exception e) {
+ LOG.warn("Error retrieving value annotation name for {}.{}", clazz.getName(), methods[i].getName());
+ }
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <ANN extends Annotation> ANN getAnnotation(final Class<ANN> reference, final AccessibleObject obj) {
+ final Annotation ann = obj.getAnnotation(reference);
+ return ann == null ? null : (ANN) ann;
+ }
+
+ public static Class<?> getCompoundKeyRef(final Class<?> entityTypeRef) {
+ if (entityTypeRef.getAnnotation(EntityType.class) == null) {
+ throw new IllegalArgumentException("Invalid annotation for entity type " + entityTypeRef);
+ }
+
+ final Annotation ann = entityTypeRef.getAnnotation(KeyRef.class);
+
+ return ann == null || ((KeyRef) ann).value().getAnnotation(CompoundKey.class) == null
+ ? null
+ : ((KeyRef) ann).value();
+ }
+
+ public static Class<?> getKeyRef(final Class<?> entityTypeRef) {
+ Class<?> res = getCompoundKeyRef(entityTypeRef);
+
+ if (res == null) {
+ final Set<Method> keyGetters = new HashSet<Method>();
+
+ for (Method method : entityTypeRef.getDeclaredMethods()) {
+ if (method.getName().startsWith("get") && method.getAnnotation(Key.class) != null) {
+ keyGetters.add(method);
+ }
+ }
+
+ if (keyGetters.size() == 1) {
+ res = keyGetters.iterator().next().getReturnType();
+ } else {
+ throw new IllegalStateException(entityTypeRef.getSimpleName() + "'s key reference not found");
+ }
+ }
+
+ return res;
+ }
+
+ public static String getEntityTypeName(final Class<?> ref) {
+ final Annotation annotation = ref.getAnnotation(EntityType.class);
+ if (!(annotation instanceof EntityType)) {
+ throw new IllegalArgumentException(ref.getPackage().getName()
+ + " is not annotated as @" + EntityType.class.getSimpleName());
+ }
+ return ((EntityType) annotation).name();
+ }
+
+ public static String getNamespace(final Class<?> ref) {
+ final Annotation annotation = ref.getAnnotation(Namespace.class);
+ if (!(annotation instanceof Namespace)) {
+ throw new IllegalArgumentException(ref.getName()
+ + " is not annotated as @" + Namespace.class.getSimpleName());
+ }
+ return ((Namespace) annotation).value();
+ }
+
+ public static Void returnVoid()
+ throws NoSuchMethodException, InstantiationException, IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException {
+
+ final Constructor<Void> voidConstructor = Void.class.getDeclaredConstructor();
+ voidConstructor.setAccessible(true);
+ return voidConstructor.newInstance();
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
new file mode 100644
index 0000000..d625c80
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
@@ -0,0 +1,423 @@
+/*
+ * 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.utils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.v3.UnsupportedInV3Exception;
+import org.apache.olingo.client.core.edm.xml.AbstractComplexType;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataProperty;
+import org.apache.olingo.commons.api.domain.ODataLink;
+import org.apache.olingo.commons.api.domain.ODataValue;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.apache.olingo.commons.core.edm.EdmTypeInfo;
+import org.apache.olingo.ext.proxy.api.annotations.ComplexType;
+import org.apache.olingo.ext.proxy.api.annotations.CompoundKeyElement;
+import org.apache.olingo.ext.proxy.api.annotations.Key;
+import org.apache.olingo.ext.proxy.api.annotations.Property;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class EngineUtils {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(EngineUtils.class);
+
+ private EngineUtils() {
+ // Empty private constructor for static utility classes
+ }
+
+ public static ODataLink getNavigationLink(final String name, final CommonODataEntity entity) {
+ ODataLink res = null;
+ final List<ODataLink> links = entity.getNavigationLinks();
+
+ for (int i = 0; i < links.size() && res == null; i++) {
+ if (links.get(i).getName().equalsIgnoreCase(name)) {
+ res = links.get(i);
+ }
+ }
+ return res;
+ }
+
+ public static ODataValue getODataValue(
+ final CommonEdmEnabledODataClient<?> client, final EdmTypeInfo type, final Object obj) {
+
+ final ODataValue value;
+
+ if (type.isCollection()) {
+ value = client.getObjectFactory().newCollectionValue(type.getFullQualifiedName().toString());
+
+ final EdmTypeInfo intType = new EdmTypeInfo.Builder().
+ setEdm(client.getCachedEdm()).setTypeExpression(type.getFullQualifiedName().toString()).build();
+
+ for (Object collectionItem : (Collection) obj) {
+ if (intType.isPrimitiveType()) {
+ value.asCollection().add(getODataValue(client, intType, collectionItem).asPrimitive());
+ } else if (intType.isComplexType()) {
+ value.asCollection().add(getODataValue(client, intType, collectionItem).asComplex());
+ } else if (intType.isEnumType()) {
+ if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+ throw new UnsupportedInV3Exception();
+ } else {
+ value.asCollection().add(((org.apache.olingo.commons.api.domain.v4.ODataValue) getODataValue(
+ client, intType, collectionItem)).asEnum());
+ }
+
+ } else {
+ throw new UnsupportedOperationException("Usupported object type " + intType.getFullQualifiedName());
+ }
+ }
+ } else if (type.isComplexType()) {
+ value = client.getObjectFactory().newComplexValue(type.getFullQualifiedName().toString());
+
+ if (obj.getClass().isAnnotationPresent(ComplexType.class)) {
+ for (Method method : obj.getClass().getMethods()) {
+ final Property complexPropertyAnn = method.getAnnotation(Property.class);
+ try {
+ if (complexPropertyAnn != null) {
+ value.asComplex().add(getODataComplexProperty(
+ client, type.getFullQualifiedName(), complexPropertyAnn.name(), method.invoke(obj)));
+ }
+ } catch (Exception ignore) {
+ // ignore value
+ LOG.warn("Error attaching complex field '{}'", complexPropertyAnn.name(), ignore);
+ }
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Object '" + obj.getClass().getSimpleName() + "' is not a complex value");
+ }
+ } else if (type.isEnumType()) {
+ if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+ throw new UnsupportedInV3Exception();
+ } else {
+ value = ((org.apache.olingo.commons.api.domain.v4.ODataObjectFactory) client.getObjectFactory()).
+ newEnumValue(type.getFullQualifiedName().toString(), ((Enum) obj).name());
+ }
+ } else {
+ value = client.getObjectFactory().newPrimitiveValueBuilder().setType(type.getPrimitiveTypeKind()).setValue(obj).
+ build();
+ }
+
+ return value;
+ }
+
+ private static CommonODataProperty getODataEntityProperty(
+ final CommonEdmEnabledODataClient<?> client,
+ final FullQualifiedName entity,
+ final String property,
+ final Object obj) {
+ final EdmType edmType = client.getCachedEdm().getEntityType(entity).getProperty(property).getType();
+ final EdmTypeInfo type = new EdmTypeInfo.Builder().
+ setEdm(client.getCachedEdm()).setTypeExpression(edmType.getFullQualifiedName().toString()).build();
+
+ return getODataProperty(client, property, type, obj);
+ }
+
+ private static CommonODataProperty getODataComplexProperty(
+ final CommonEdmEnabledODataClient<?> client,
+ final FullQualifiedName complex,
+ final String property,
+ final Object obj) {
+ final EdmType edmType = client.getCachedEdm().getComplexType(complex).getProperty(property).getType();
+ final EdmTypeInfo type = new EdmTypeInfo.Builder().
+ setEdm(client.getCachedEdm()).setTypeExpression(edmType.getFullQualifiedName().toString()).build();
+
+ return getODataProperty(client, property, type, obj);
+ }
+
+ private static CommonODataProperty getODataProperty(
+ final CommonEdmEnabledODataClient<?> client, final String name, final EdmTypeInfo type, final Object obj) {
+ final CommonODataProperty oprop;
+
+ try {
+ if (type == null || obj == null) {
+ oprop = client.getObjectFactory().newPrimitiveProperty(name, null);
+ } else if (type.isCollection()) {
+ // create collection property
+ oprop = client.getObjectFactory().newCollectionProperty(name, getODataValue(client, type, obj).asCollection());
+ } else if (type.isPrimitiveType()) {
+ // create a primitive property
+ oprop = client.getObjectFactory().newPrimitiveProperty(name, getODataValue(client, type, obj).asPrimitive());
+ } else if (type.isComplexType()) {
+ // create a complex property
+ oprop = client.getObjectFactory().newComplexProperty(name, getODataValue(client, type, obj).asComplex());
+ } else if (type.isEnumType()) {
+ if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+ throw new UnsupportedInV3Exception();
+ } else {
+ oprop = ((org.apache.olingo.commons.api.domain.v4.ODataObjectFactory) client.getObjectFactory()).
+ newEnumProperty(name,
+ ((org.apache.olingo.commons.api.domain.v4.ODataValue) getODataValue(client, type, obj)).asEnum());
+ }
+ } else {
+ throw new UnsupportedOperationException("Usupported object type " + type.getFullQualifiedName());
+ }
+
+ return oprop;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void addProperties(
+ final CommonEdmEnabledODataClient<?> client,
+ final Map<String, Object> changes,
+ final CommonODataEntity entity) {
+
+ for (Map.Entry<String, Object> property : changes.entrySet()) {
+ // if the getter exists and it is annotated as expected then get value/value and add a new property
+ final CommonODataProperty odataProperty = entity.getProperty(property.getKey());
+ if (odataProperty != null) {
+ entity.getProperties().remove(odataProperty);
+ }
+
+ ((List<CommonODataProperty>) entity.getProperties()).add(
+ getODataEntityProperty(client, entity.getTypeName(), property.getKey(), property.getValue()));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void setPropertyValue(final Object bean, final Method getter, final Object value)
+ throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ // Assumption: setter is always prefixed by 'set' word
+ final String setterName = getter.getName().replaceFirst("get", "set");
+ bean.getClass().getMethod(setterName, getter.getReturnType()).invoke(bean, value);
+ }
+
+ public static Object getKey(
+ final Edm metadata, final Class<?> entityTypeRef, final CommonODataEntity entity) {
+ final Object res;
+
+ if (entity.getProperties().isEmpty()) {
+ res = null;
+ } else {
+ final Class<?> keyRef = ClassUtils.getCompoundKeyRef(entityTypeRef);
+ if (keyRef == null) {
+ final CommonODataProperty property = entity.getProperty(firstValidEntityKey(entityTypeRef));
+ res = property == null || !property.hasPrimitiveValue()
+ ? null
+ : property.getPrimitiveValue().toValue();
+
+ } else {
+ try {
+ res = keyRef.newInstance();
+ populate(metadata, res, CompoundKeyElement.class, entity.getProperties().iterator());
+ } catch (Exception e) {
+ LOG.error("Error population compound key {}", keyRef.getSimpleName(), e);
+ throw new IllegalArgumentException("Cannot populate compound key");
+ }
+ }
+ }
+
+ return res;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ public static void populate(
+ final Edm metadata,
+ final Object bean,
+ final Class<? extends Annotation> getterAnn,
+ final Iterator<? extends CommonODataProperty> propItor) {
+
+ if (bean != null) {
+ while (propItor.hasNext()) {
+ final CommonODataProperty property = propItor.next();
+
+ final Method getter =
+ ClassUtils.findGetterByAnnotatedName(bean.getClass(), getterAnn, property.getName());
+
+ if (getter == null) {
+ LOG.warn("Could not find any property annotated as {} in {}",
+ property.getName(), bean.getClass().getName());
+ } else {
+ try {
+ if (property.hasNullValue()) {
+ setPropertyValue(bean, getter, null);
+ }
+ if (property.hasPrimitiveValue()) {
+ setPropertyValue(bean, getter, property.getPrimitiveValue().toValue());
+ }
+ if (property.hasComplexValue()) {
+ final Object complex = getter.getReturnType().newInstance();
+ populate(metadata, complex, Property.class, property.getValue().asComplex().iterator());
+ setPropertyValue(bean, getter, complex);
+ }
+ if (property.hasCollectionValue()) {
+ final ParameterizedType collType = (ParameterizedType) getter.getGenericReturnType();
+ final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0];
+
+ Collection<Object> collection = (Collection<Object>) getter.invoke(bean);
+ if (collection == null) {
+ collection = new ArrayList<Object>();
+ setPropertyValue(bean, getter, collection);
+ }
+
+ final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+ while (collPropItor.hasNext()) {
+ final ODataValue value = collPropItor.next();
+ if (value.isPrimitive()) {
+ collection.add(value.asPrimitive().toValue());
+ }
+ if (value.isComplex()) {
+ final Object collItem = collItemClass.newInstance();
+ populate(metadata, collItem, Property.class, value.asComplex().iterator());
+ collection.add(collItem);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Could not set property {} on {}", getter, bean, e);
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Object getValueFromProperty(final Edm metadata, final CommonODataProperty property)
+ throws InstantiationException, IllegalAccessException {
+
+ final Object value;
+
+ if (property == null || property.hasNullValue()) {
+ value = null;
+ } else if (property.hasCollectionValue()) {
+ value = new ArrayList<Object>();
+
+ final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+ while (collPropItor.hasNext()) {
+ final ODataValue odataValue = collPropItor.next();
+ if (odataValue.isPrimitive()) {
+ ((Collection) value).add(odataValue.asPrimitive().toValue());
+ }
+ if (odataValue.isComplex()) {
+ final Object collItem =
+ buildComplexInstance(metadata, property.getName(), odataValue.asComplex().iterator());
+ ((Collection) value).add(collItem);
+ }
+ }
+ } else if (property.hasPrimitiveValue()) {
+ value = property.getPrimitiveValue().toValue();
+ } else if (property.hasComplexValue()) {
+ value = buildComplexInstance(
+ metadata, property.getValue().asComplex().getTypeName(), property.getValue().asComplex().iterator());
+ } else {
+ throw new IllegalArgumentException("Invalid property " + property);
+ }
+
+ return value;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <C extends AbstractComplexType> C buildComplexInstance(
+ final Edm metadata, final String name, final Iterator<CommonODataProperty> properties) {
+
+ for (C complex : (Iterable<C>) ServiceLoader.load(AbstractComplexType.class)) {
+ final ComplexType ann = complex.getClass().getAnnotation(ComplexType.class);
+ final String fn = ann == null ? null : ClassUtils.getNamespace(complex.getClass()) + "." + ann.name();
+
+ if (name.equals(fn)) {
+ populate(metadata, complex, Property.class, properties);
+ return complex;
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static Object getValueFromProperty(final Edm metadata, final CommonODataProperty property, final Type type)
+ throws InstantiationException, IllegalAccessException {
+
+ final Object value;
+
+ if (property == null || property.hasNullValue()) {
+ value = null;
+ } else if (property.hasCollectionValue()) {
+ value = new ArrayList();
+
+ final ParameterizedType collType = (ParameterizedType) type;
+ final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0];
+
+ final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+ while (collPropItor.hasNext()) {
+ final ODataValue odataValue = collPropItor.next();
+ if (odataValue.isPrimitive()) {
+ ((Collection) value).add(odataValue.asPrimitive().toValue());
+ }
+ if (odataValue.isComplex()) {
+ final Object collItem = collItemClass.newInstance();
+ populate(metadata, collItem, Property.class, odataValue.asComplex().iterator());
+ ((Collection) value).add(collItem);
+ }
+ }
+ } else if (property.hasPrimitiveValue()) {
+ value = property.getPrimitiveValue().toValue();
+ } else if (property.hasComplexValue()) {
+ value = ((Class<?>) type).newInstance();
+ populate(metadata, value, Property.class, property.getValue().asComplex().iterator());
+ } else {
+ throw new IllegalArgumentException("Invalid property " + property);
+ }
+
+ return value;
+ }
+
+ private static String firstValidEntityKey(final Class<?> entityTypeRef) {
+ for (Method method : entityTypeRef.getDeclaredMethods()) {
+ if (method.getAnnotation(Key.class) != null) {
+ final Annotation ann = method.getAnnotation(Property.class);
+ if (ann != null) {
+ return ((Property) ann).name();
+ }
+ }
+ }
+ return null;
+ }
+
+ public static URI getEditMediaLink(final String name, final CommonODataEntity entity) {
+ for (ODataLink editMediaLink : entity.getEditMediaLinks()) {
+ if (name.equalsIgnoreCase(editMediaLink.getName())) {
+ return editMediaLink.getLink();
+ }
+ }
+
+ throw new IllegalArgumentException("Invalid streamed property " + name);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
----------------------------------------------------------------------
diff --git a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
index e52d278..fab1893 100644
--- a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
+++ b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
@@ -389,6 +389,10 @@ public abstract class AbstractUtility {
return StringUtils.uncapitalize(str);
}
+ public String join(final Object[] array, String sep) {
+ return StringUtils.join(array, sep);
+ }
+
public Map<String, String> getFcProperties(final EdmProperty property) {
return Collections.<String, String>emptyMap();
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
----------------------------------------------------------------------
diff --git a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
index acd9f75..28bcdd0 100644
--- a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
+++ b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
@@ -156,6 +156,7 @@ public class V3MetadataMojo extends AbstractMetadataMojo {
for (EdmEntityContainer container : schema.getEntityContainers()) {
objs.clear();
objs.put("container", container);
+ objs.put("namespace", schema.getNamespace());
parseObj(base, pkg, "container",
utility.capitalize(container.getName()) + ".java", objs);