package com.temenos.interaction.sdk.adapter.edmx; /* * #%L * interaction-sdk * %% * 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.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.odata4j.edm.EdmAssociation; import org.odata4j.edm.EdmAssociationEnd; import org.odata4j.edm.EdmDataServices; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmEntityType; import org.odata4j.edm.EdmMultiplicity; import org.odata4j.edm.EdmNavigationProperty; import org.odata4j.edm.EdmProperty; import org.odata4j.edm.EdmSimpleType; import org.odata4j.edm.EdmType; import org.odata4j.format.xml.EdmxFormatParser; import org.odata4j.stax2.XMLEventReader2; import org.odata4j.stax2.util.StaxUtil; import com.temenos.interaction.core.entity.vocabulary.terms.TermIdField; import com.temenos.interaction.sdk.EntityInfo; import com.temenos.interaction.sdk.FieldInfo; import com.temenos.interaction.sdk.JPAResponderGen; import com.temenos.interaction.sdk.JoinInfo; import com.temenos.interaction.sdk.adapter.InteractionAdapter; import com.temenos.interaction.sdk.command.Commands; import com.temenos.interaction.sdk.entity.EMEntity; import com.temenos.interaction.sdk.entity.EMProperty; import com.temenos.interaction.sdk.entity.EMTerm; import com.temenos.interaction.sdk.entity.EntityModel; import com.temenos.interaction.sdk.interaction.IMResourceStateMachine; import com.temenos.interaction.sdk.interaction.InteractionModel; import com.temenos.interaction.sdk.interaction.state.IMPseudoState; import com.temenos.interaction.sdk.util.ReferentialConstraintParser; public class EDMXAdapter implements InteractionAdapter { // Dodgy, we read the Edmx contents multiple times private ByteArrayOutputStream bufferedEdmx; // original EDMX input stream private InputStream isEdmx; boolean modelsInitialised = false; private InteractionModel interactionModel; private EntityModel entityModel; private Commands commands; private List<EntityInfo> entitiesInfo; public EDMXAdapter(String edmxFile) { try { isEdmx = new FileInputStream(edmxFile); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } public EDMXAdapter(InputStream metadata) { isEdmx = metadata; } private void initialise() { if (!modelsInitialised) { assert(isEdmx != null); // We need to read Edmx contents twice, once for odata4j parser and again for the sax parser to read ref.constraints try { bufferedEdmx = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int len; while ((len = isEdmx.read(buf)) > -1 ) { bufferedEdmx.write(buf, 0, len); } bufferedEdmx.flush(); } catch (IOException e) { throw new RuntimeException(e); } initialiseModels(); modelsInitialised = true; } } @Override public InteractionModel getInteractionModel() { initialise(); return interactionModel; } @Override public EntityModel getEntityModel() { initialise(); return entityModel; } @Override public Commands getCommands() { initialise(); return commands; } @Override public List<EntityInfo> getEntitiesInfo() { initialise(); return entitiesInfo; } /** * Generate entity and interaction models from an EDMX file. */ private void initialiseModels() { assert(bufferedEdmx != null); //Parse emdx file XMLEventReader2 reader = StaxUtil.newXMLEventReader(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bufferedEdmx.toByteArray())))); EdmDataServices edmDataServices = new EdmxFormatParser().parseMetadata(reader); //Make sure we have at least one entity container if(edmDataServices.getSchemas().size() == 0 || edmDataServices.getSchemas().get(0).getEntityContainers().size() == 0) { throw new RuntimeException("EDMX must contain at least one entity container"); } String entityContainerNamespace = edmDataServices.getSchemas().get(0).getEntityContainers().get(0).getName(); //Create interaction model Map<String, String> linkPropertyMap = buildLinkPropertyMap(edmDataServices); Map<String, String> linkPropertyOriginMap = buildLinkPropertyOriginMap(edmDataServices); interactionModel = buildInteractionModel(linkPropertyMap, linkPropertyOriginMap, edmDataServices); //Create the entity model entityModel = new EntityModel(entityContainerNamespace); for (EdmEntityType entityType : edmDataServices.getEntityTypes()) { List<String> keys = entityType.getKeys(); EMEntity emEntity = new EMEntity(entityType.getName()); for (EdmProperty prop : entityType.getProperties()) { EMProperty emProp = JPAResponderGen.createEMProperty(prop); if(keys.contains(prop.getName())) { emProp.addVocabularyTerm(new EMTerm(TermIdField.TERM_NAME, "true")); } emEntity.addProperty(emProp); } entityModel.addEntity(emEntity); } //Create commands commands = JPAResponderGen.getDefaultCommands(); //Obtain resource information entitiesInfo = new ArrayList<EntityInfo>(); for (EdmEntityType t : edmDataServices.getEntityTypes()) { EntityInfo entityInfo = createEntityInfoFromEdmEntityType(t, linkPropertyMap); JPAResponderGen.addNavPropertiesToEntityInfo(entityInfo, interactionModel); entitiesInfo.add(entityInfo); } } protected Map<String, String> buildLinkPropertyMap(EdmDataServices edmDataServices) { Map<String, String> linkPropertyMap = new HashMap<String, String>(); for (EdmEntitySet entitySet : edmDataServices.getEntitySets()) { EdmEntityType entityType = entitySet.getType(); //Use navigation properties to define state transitions if(entityType.getNavigationProperties() != null) { for (EdmNavigationProperty np : entityType.getNavigationProperties()) { EdmAssociation association = np.getRelationship(); String linkProperty = getLinkProperty(association.getName()); linkPropertyMap.put(association.getName(), linkProperty); } } } return linkPropertyMap; } protected Map<String, String> buildLinkPropertyOriginMap(EdmDataServices edmDataServices) { Map<String, String> linkPropertyOriginMap = new HashMap<String, String>(); for (EdmEntitySet entitySet : edmDataServices.getEntitySets()) { EdmEntityType entityType = entitySet.getType(); //Use navigation properties to define state transitions if(entityType.getNavigationProperties() != null) { for (EdmNavigationProperty np : entityType.getNavigationProperties()) { EdmAssociation association = np.getRelationship(); String linkProperty = getLinkPropertyOrigin(association.getName()); linkPropertyOriginMap.put(association.getName(), linkProperty); } } } return linkPropertyOriginMap; } /* * Build the interaction model from an EDMX model. */ protected InteractionModel buildInteractionModel(Map<String, String> linkPropertyMap, Map<String, String> linkPropertyOriginMap, EdmDataServices edmDataServices) { // this constructor creates all the resource state machines from the entity metadata InteractionModel interactionModel = new InteractionModel(edmDataServices); for (EdmEntitySet entitySet : edmDataServices.getEntitySets()) { EdmEntityType entityType = entitySet.getType(); String entityName = entityType.getName(); IMResourceStateMachine rsm = interactionModel.findResourceStateMachine(entityName); String collectionStateName = rsm.getCollectionState().getName(); String entityStateName = rsm.getEntityState().getName(); //Use navigation properties to define state transitions if(entityType.getNavigationProperties() != null) { for (EdmNavigationProperty np : entityType.getNavigationProperties()) { EdmAssociationEnd targetEnd = np.getToRole(); boolean isTargetCollection = targetEnd.getMultiplicity().equals(EdmMultiplicity.MANY); EdmEntityType targetEntityType = targetEnd.getType(); String targetEntityName = targetEntityType.getName(); IMResourceStateMachine targetRsm = interactionModel.findResourceStateMachine(targetEntityName); EdmAssociation association = np.getRelationship(); String linkProperty = linkPropertyMap.get(association.getName()); String linkTitle = np.getName(); String filter = null; if(isTargetCollection) { String linkPropertyOrigin = linkPropertyOriginMap.get(association.getName()); filter = linkProperty + " eq '{" + linkPropertyOrigin + "}'"; rsm.addTransitionToCollectionState(entityStateName, targetRsm, np.getName(), filter, linkProperty, linkTitle); } else { rsm.addTransitionToEntityState(entityStateName, targetRsm, np.getName(), linkProperty, linkTitle); } } } // add CRUD operations for each EntitySet IMPseudoState pseudoState = rsm.addPseudoStateTransition(collectionStateName, "created", collectionStateName, "POST", null, "CreateEntity", null, true); pseudoState.addAutoTransition(rsm.getResourceState(entityStateName), "GET"); rsm.addPseudoStateTransition(entityStateName, "updated", entityStateName, "PUT", null, "UpdateEntity", "edit", false); rsm.addPseudoStateTransition(entityStateName, "deleted", "DELETE", null, "DeleteEntity", "edit", false); } return interactionModel; } protected String getLinkProperty(String associationName) { return ReferentialConstraintParser.getDependent(associationName, new ByteArrayInputStream(bufferedEdmx.toByteArray())); } protected String getLinkPropertyOrigin(String associationName) { return ReferentialConstraintParser.getPrincipal(associationName, new ByteArrayInputStream(bufferedEdmx.toByteArray())); } public EntityInfo createEntityInfoFromEdmEntityType(EdmType type, Map<String, String> linkPropertyMap) { if (!(type instanceof EdmEntityType)) return null; EdmEntityType entityType = (EdmEntityType) type; // think OData4j only support single keys at the moment FieldInfo keyInfo = null; if (entityType.getKeys().size() > 0) { String keyName = entityType.getKeys().get(0); EdmType key = null; for (EdmProperty e : entityType.getProperties()) { if (e.getName().equals(keyName)) { key = e.getType(); } } keyInfo = new FieldInfo(keyName, JPAResponderGen.javaType(key), null); } List<FieldInfo> properties = new ArrayList<FieldInfo>(); for (EdmProperty property : entityType.getProperties()) { // add additional configuration by annotations List<String> annotations = new ArrayList<String>(); if (property.getType().equals(EdmSimpleType.DATETIME)) { annotations.add("@Temporal(TemporalType.TIMESTAMP)"); } else if (property.getType().equals(EdmSimpleType.TIME)) { annotations.add("@Temporal(TemporalType.TIME)"); } FieldInfo field = new FieldInfo(property.getName(), JPAResponderGen.javaType(property.getType()), annotations); properties.add(field); } List<JoinInfo> joins = new ArrayList<JoinInfo>(); for (EdmNavigationProperty navProperty : entityType.getNavigationProperties()) { // build the join annotations List<String> annotations = new ArrayList<String>(); // lets see if the target has a navproperty to this type boolean bidirectional = false; for (EdmNavigationProperty np : navProperty.getToRole().getType().getNavigationProperties()) { if (np.getRelationship().getName().equals(navProperty.getRelationship().getName())) { bidirectional = true;; } } String mappedBy = ""; if (bidirectional) { String linkProperty = linkPropertyMap.get(navProperty.getRelationship().getName()); mappedBy = "(mappedBy=\"" + linkProperty + "\")"; } EdmAssociationEnd from = navProperty.getFromRole(); if ((from.getMultiplicity().equals(EdmMultiplicity.ONE) || from.getMultiplicity().equals(EdmMultiplicity.ZERO_TO_ONE)) && navProperty.getToRole().getMultiplicity().equals(EdmMultiplicity.MANY)) { annotations.add("@OneToMany" + mappedBy); } else if (from.getMultiplicity().equals(EdmMultiplicity.MANY) && navProperty.getToRole().getMultiplicity().equals(EdmMultiplicity.MANY)) { annotations.add("@ManyToMany" + mappedBy); } if (annotations.size() > 0) { JoinInfo join = new JoinInfo(navProperty.getName(), navProperty.getToRole().getType().getName(), annotations); joins.add(join); } } //Check if user has specified the name of the JPA entities String jpaNamespace = System.getProperty("jpaNamespace"); boolean isJpaEntity = (jpaNamespace == null || jpaNamespace.equals(entityType.getNamespace())); return new EntityInfo(entityType.getName(), entityType.getNamespace(), keyInfo, properties, joins, isJpaEntity); } }