package com.temenos.interaction.commands.odata;
/*
* #%L
* interaction-commands-odata
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import org.odata4j.core.OEntities;
import org.odata4j.core.OEntity;
import org.odata4j.core.OEntityKey;
import org.odata4j.core.OProperties;
import org.odata4j.core.OProperty;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.producer.ODataProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.temenos.interaction.core.command.InteractionContext;
import com.temenos.interaction.core.command.InteractionException;
import com.temenos.interaction.core.entity.Entity;
import com.temenos.interaction.core.entity.EntityProperties;
import com.temenos.interaction.core.entity.EntityProperty;
import com.temenos.interaction.core.hypermedia.ActionPropertyReference;
import com.temenos.interaction.core.resource.CollectionResource;
import com.temenos.interaction.core.resource.EntityResource;
import com.temenos.interaction.core.resource.MetaDataResource;
import com.temenos.interaction.core.resource.ResourceTypeHelper;
import com.temenos.interaction.odataext.ODataHelper;
public class CommandHelper {
private final static Logger logger = LoggerFactory.getLogger(CommandHelper.class);
private static Pattern parameterPattern = Pattern.compile("\\{(.*?)\\}");
/**
* To handle generic case where user do not know the type, here we will be looking for a type
* and then generate EntityResource<E>
* @param entity
* @return EntityResource<E>
*/
public static<E> EntityResource<E> createEntityResource(E entity) {
GenericEntity<E> ge = new GenericEntity<E>(entity) {};
Type t = com.temenos.interaction.core.command.CommandHelper.getEffectiveGenericType(ge.getType(), entity);
if(ResourceTypeHelper.isType(ge.getRawType(), t, OEntity.class, OEntity.class)) {
OEntity te = (OEntity) entity;
String entityName = te != null && te.getEntityType() != null ? te.getEntityType().getName() : null;
return com.temenos.interaction.core.command.CommandHelper.createEntityResource(entityName, entity, OEntity.class);
} else if(ResourceTypeHelper.isType(ge.getRawType(), t, Entity.class, Entity.class)) {
Entity te = (Entity) entity;
String entityName = te != null ? te.getName() : null;
return com.temenos.interaction.core.command.CommandHelper.createEntityResource(entityName, entity, Entity.class);
} else {
// Call the generic and lets see what happens
return com.temenos.interaction.core.command.CommandHelper.createEntityResource(entity, null);
}
}
/**
* To help converting Entity object into EntityResource as we are no longer extending the core CommandHelper
* @param entity
* @return EntityResource<Entity> EntityResource of parameter type Entity
*/
public static EntityResource<Entity> createEntityResource(Entity entity) {
return com.temenos.interaction.core.command.CommandHelper.createEntityResource(entity);
}
/**
* Create an OData entity resource (entry)
* @param e OEntity
* @return entity resource EntityResource of parameter type OEntity
*/
public static EntityResource<OEntity> createEntityResource(OEntity e) {
String entityName = e != null && e.getEntityType() != null ? e.getEntityType().getName() : null;
return com.temenos.interaction.core.command.CommandHelper.createEntityResource(entityName, e, OEntity.class);
}
/**
* Create an OData collection resource (feed)
* @param entitySetName Entity set name
* @param entities List of OData entities
* @return collection resource
*/
public static CollectionResource<OEntity> createCollectionResource(String entitySetName, List<OEntity> entities) {
List<EntityResource<OEntity>> subResources = new ArrayList<EntityResource<OEntity>>();
for (OEntity entity : entities)
subResources.add(createEntityResource(entity));
return new CollectionResource<OEntity>(entitySetName, subResources) {};
}
/**
* Create an OData service document (atomsvc)
* @param metadata Edmx
* @return Service document
*/
public static EntityResource<EdmDataServices> createServiceDocumentResource(EdmDataServices metadata) {
return new EntityResource<EdmDataServices>(metadata) {};
}
/**
* Create an OData metadata document (edmx)
* @param metadata Edmx
* @return metadata resource
*/
public static MetaDataResource<EdmDataServices> createMetaDataResource(EdmDataServices metadata) {
return new MetaDataResource<EdmDataServices>(metadata) {};
}
/**
* Create an OEntityKey instance for the specified entity id
* @param edmDataServices edmDataServices
* @param entity Entity set name
* @param id Id
* @return An OEntityKey instance
* @throws Exception Error creating key
*/
public static OEntityKey createEntityKey(EdmDataServices edmDataServices, String entitySetName, String id) throws Exception {
return createEntityKey(edmDataServices.getEdmEntitySet(entitySetName), id);
}
/**
* Create an OEntityKey instance for the specified entity id
* @param entitySet entitySet
* @param id Id
* @return An OEntityKey instance
* @throws Exception Error creating key
*/
public static OEntityKey createEntityKey(EdmEntitySet entitySet, String id) throws Exception {
//Lookup type of entity key (simple keys only)
String keyType = null;
if(entitySet != null) {
EdmEntityType entityType = entitySet.getType();
List<String> keys = entityType.getKeys();
if(keys.size() == 1) {
EdmProperty prop = entityType.findDeclaredProperty(keys.get(0));
if(prop != null && prop.getType() != null) {
keyType = prop.getType().getFullyQualifiedTypeName();
}
}
}
assert(keyType != null) : "Should not be possible to get this far and find no key type";
//Create an entity key
OEntityKey key = null;
try {
if (keyType.equals("Edm.Int64")) {
key = OEntityKey.parse(id);
} else if(keyType.equals("Edm.Int32")) {
key = OEntityKey.parse(id);
} else if(keyType.equals("Edm.DateTime")) {
key = OEntityKey.parse(id);
} else if(keyType.equals("Edm.Time")) {
key = OEntityKey.parse(id);
} else if(keyType.equals("Edm.String")) {
key = OEntityKey.parse(id);
}
} catch (Exception e) {
logger.warn("Entity key type " + keyType + " is not supported by CommandHelper, trying OEntityKey.parse.",e);
}
// could not parse the key, have one last attempt with OEntityKey create
if (key == null) {
try {
if (keyType.equals("Edm.Int64")) {
key = OEntityKey.create(Long.parseLong(id));
} else if(keyType.equals("Edm.Int32")) {
key = OEntityKey.create(Integer.parseInt(id));
} else {
key = OEntityKey.create(id);
}
} catch (Exception e) {
logger.error("OEntityKey.parse failed to parse id [" + id + "]", e);
}
}
if (key == null)
throw new Exception("Entity key type " + id + " is not supported.");
return key;
}
/**
* Returns the entity set holding the specified entity (type) name
* @param entityName entity type name
* @param edmDataServices metadata
* @return entity set
* @throws Exception if entity set cannot be found
*/
public static EdmEntitySet getEntitySet(String entityName, EdmDataServices edmDataServices) throws Exception {
return ODataHelper.getEntitySet(entityName, edmDataServices);
}
/**
* Returns a view action property
* This method will try to replace template parameters "{param}"
* with query parameters if available
* @param ctx interaction context
* @param property action property
* @return property value
*/
public static String getViewActionProperty(InteractionContext ctx, String property) {
String prop = null;
if(ctx.getCurrentState().getViewAction() != null) {
Properties properties = ctx.getCurrentState().getViewAction().getProperties();
if(properties != null && properties.containsKey(property)) {
//Get the specified action property
prop = getActionProperty(property, properties, ctx.getQueryParameters());
//Fill in template parameters
if(prop != null) {
Matcher m = parameterPattern.matcher(prop);
while(m.find()) {
String templateParam = m.group(1); //e.g. code
if(ctx.getQueryParameters().containsKey(templateParam)) {
prop = prop.replace("{" + templateParam + "}", ctx.getQueryParameters().getFirst(templateParam));
}
else if(ctx.getPathParameters().containsKey(templateParam)) {
prop = prop.replace("{" + templateParam + "}", ctx.getPathParameters().getFirst(templateParam));
}
}
}
}
}
return prop != null && !prop.equals(property) ? prop : null;
}
/**
* Obtain the specified action property.
* An action property can either contain a simple value or reference a link property.
* If it is the latter it will obtain the referenced value stored in the link property.
* e.g. GetEntities filter=myfilter and myfilter="CreditAcctNo eq '{Acc}'", Acc="CreditAcctNo"
* => filter=CreditAcctNo eq '{CreditAcctNo}'
* @param propertyName Action property name
* @param actionProperties Action properties
* @param queryParameters Query parameters (keys)
* @return Action property string
*/
protected static String getActionProperty(String propertyName, Properties actionProperties, MultivaluedMap<String, String> queryParameters) {
Object propObj = actionProperties.get(propertyName);
if(propObj != null && propObj instanceof ActionPropertyReference) {
ActionPropertyReference propRef = (ActionPropertyReference) propObj;
Set<String> queryParamKeys = queryParameters.keySet();
if (queryParameters.containsKey(propRef.getKey())) {
return queryParameters.getFirst(propRef.getKey());
}
String key = "_";
for(String queryParamKey : queryParamKeys) {
if(!queryParamKey.startsWith("$")) { //Do not consider $filter, $select, etc.
key += "_" + queryParamKey;
}
}
return propRef.getProperty(key);
}
else {
return actionProperties.get(propertyName).toString(); //e.g. fld eq '{code}'
}
}
/**
* Create a map containing the values we wish to pass as custom options to the OData producer
*
* @param ctx interaction context
*
* @return Map containing the values we wish to pass as custom options to the OData producer
*/
static Map<String, String> populateCustomOptionsMap(InteractionContext ctx) {
Map<String, String> customOptions = new HashMap<String,String>();
// Capture all path parameters
MultivaluedMap<String, String> pathParams = ctx.getPathParameters();
if(pathParams != null) {
for(Map.Entry<String,List<String>> entry: pathParams.entrySet()) {
String parmName = entry.getKey();
List<String> paramValues = entry.getValue();
if(!paramValues.isEmpty()) {
customOptions.put(parmName, paramValues.get(0));
}
}
}
// Capture all query parameters
MultivaluedMap<String, String> queryParams = ctx.getQueryParameters();
if(queryParams != null) {
for(Map.Entry<String,List<String>> entry: queryParams.entrySet()) {
String parmName = entry.getKey();
List<String> paramValues = entry.getValue();
if(!paramValues.isEmpty()) {
customOptions.put(parmName, paramValues.get(0));
}
}
}
return customOptions;
}
// TODO move this transformation up to where we have all the metadata, note the hacked hardcoded "Id"
protected static OEntity createOEntityFromEntity(AbstractODataCommand command, ODataProducer producer, Entity entity, OEntityKey key) throws InteractionException {
try {
assert(entity != null);
assert(entity.getName() != null);
EdmEntitySet entitySet = command.getEdmEntitySet(entity.getName());
EdmEntityType entityType = entitySet.getType();
String id = null;
EntityProperties entityProps = entity.getProperties();
List<OProperty<?>> eProps = new ArrayList<OProperty<?>>();
for (String propKey : entityProps.getProperties().keySet()) {
EntityProperty prop = entityProps.getProperty(propKey);
if (prop.getName().equals("Id")) {
id = prop.getValue().toString();
}
if (entityType.findProperty(prop.getName()) != null) {
EdmProperty eProp = entityType.findProperty(prop.getName());
if (eProp.getType().equals(EdmSimpleType.STRING)) {
eProps.add(OProperties.string(prop.getName(), prop.getValue().toString()));
} else if (eProp.getType().equals(EdmSimpleType.INT32)) {
eProps.add(OProperties.int32(prop.getName(), new Integer(prop.getValue().toString())));
}
}
}
if (id != null) {
OEntityKey eKey = CommandHelper.createEntityKey(producer.getMetadata(), entity.getName(), id);
return OEntities.create(entitySet, eKey, eProps, null);
} else if (key != null) {
return OEntities.create(entitySet, key, eProps, null);
} else {
return OEntities.createRequest(entitySet, eProps, null);
}
} catch (Exception e) {
throw new InteractionException(Status.INTERNAL_SERVER_ERROR, e);
}
}
}