/*******************************************************************************
* Copyright 2013 SAP AG
*
* Licensed 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 com.sap.core.odata.core.ep.aggregator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sap.core.odata.api.edm.EdmComplexType;
import com.sap.core.odata.api.edm.EdmConcurrencyMode;
import com.sap.core.odata.api.edm.EdmCustomizableFeedMappings;
import com.sap.core.odata.api.edm.EdmEntitySet;
import com.sap.core.odata.api.edm.EdmEntityType;
import com.sap.core.odata.api.edm.EdmException;
import com.sap.core.odata.api.edm.EdmFunctionImport;
import com.sap.core.odata.api.edm.EdmNavigationProperty;
import com.sap.core.odata.api.edm.EdmProperty;
import com.sap.core.odata.api.edm.EdmSimpleType;
import com.sap.core.odata.api.edm.EdmStructuralType;
import com.sap.core.odata.api.edm.EdmTargetPath;
import com.sap.core.odata.api.edm.EdmType;
import com.sap.core.odata.api.edm.EdmTypeKind;
import com.sap.core.odata.api.ep.EntityProviderException;
import com.sap.core.odata.api.uri.ExpandSelectTreeNode;
/**
* Aggregator to get easy and fast access to all for serialization and de-serialization necessary {@link EdmEntitySet} informations.
*
* @author SAP AG
*/
public class EntityInfoAggregator {
private static final Set<String> SYN_TARGET_PATHS = new HashSet<String>(Arrays.asList(
EdmTargetPath.SYNDICATION_AUTHORNAME,
EdmTargetPath.SYNDICATION_AUTHOREMAIL,
EdmTargetPath.SYNDICATION_AUTHORURI,
EdmTargetPath.SYNDICATION_PUBLISHED,
EdmTargetPath.SYNDICATION_RIGHTS,
EdmTargetPath.SYNDICATION_TITLE,
EdmTargetPath.SYNDICATION_UPDATED,
EdmTargetPath.SYNDICATION_CONTRIBUTORNAME,
EdmTargetPath.SYNDICATION_CONTRIBUTOREMAIL,
EdmTargetPath.SYNDICATION_CONTRIBUTORURI,
EdmTargetPath.SYNDICATION_SOURCE,
EdmTargetPath.SYNDICATION_SUMMARY));
private Map<String, EntityPropertyInfo> propertyInfo = new HashMap<String, EntityPropertyInfo>();
private Map<String, NavigationPropertyInfo> navigationPropertyInfos = new HashMap<String, NavigationPropertyInfo>();
private List<EntityPropertyInfo> keyPropertyInfos;
/*
* list with all property names in the order based on order in {@link EdmProperty} (normally [key, entity,
* navigation])
*/
private List<String> etagPropertyNames = new ArrayList<String>();
private List<String> propertyNames;
private List<String> navigationPropertyNames;
private List<String> selectedPropertyNames;
private List<String> selectedNavigationPropertyNames;
private List<String> expandedNavigationPropertyNames;
private Map<String, EntityPropertyInfo> targetPath2EntityPropertyInfo = new HashMap<String, EntityPropertyInfo>();
private List<String> noneSyndicationTargetPaths = new ArrayList<String>();
private boolean isDefaultEntityContainer;
private String entitySetName;
private String entityContainerName;
private EdmEntityType entityType;
private EdmEntitySet entitySet;
/**
* Constructor is private to force creation over {@link #create(EdmEntitySet)} method.
*/
private EntityInfoAggregator() {}
/**
* Create an {@link EntityInfoAggregator} based on given {@link EdmEntitySet}
*
* @param entitySet
* with which the {@link EntityInfoAggregator} is initialized.
* @param expandSelectTree
* @return created and initialized {@link EntityInfoAggregator}
* @throws EntityProviderException
* if during initialization of {@link EntityInfoAggregator} something goes wrong (e.g. exceptions during
* access
* of {@link EdmEntitySet}).
*/
public static EntityInfoAggregator create(final EdmEntitySet entitySet, final ExpandSelectTreeNode expandSelectTree) throws EntityProviderException {
EntityInfoAggregator eia = new EntityInfoAggregator();
eia.initialize(entitySet, expandSelectTree);
return eia;
}
/**
* Create an {@link EntityInfoAggregator} based on given {@link EdmEntitySet}
*
* @param entitySet
* with which the {@link EntityInfoAggregator} is initialized.
* @return created and initialized {@link EntityInfoAggregator}
* @throws EntityProviderException
* if during initialization of {@link EntityInfoAggregator} something goes wrong (e.g. exceptions during
* access
* of {@link EdmEntitySet}).
*/
public static EntityInfoAggregator create(final EdmEntitySet entitySet) throws EntityProviderException {
EntityInfoAggregator eia = new EntityInfoAggregator();
eia.initialize(entitySet, null);
return eia;
}
/**
* Create an {@link EntityPropertyInfo} based on given {@link EdmProperty}
*
* @param property
* for which the {@link EntityPropertyInfo} is created.
* @return created {@link EntityPropertyInfo}
* @throws EntityProviderException
* if create of {@link EntityPropertyInfo} something goes wrong (e.g. exceptions during
* access of {@link EdmProperty}).
*/
public static EntityPropertyInfo create(final EdmProperty property) throws EntityProviderException {
try {
EntityInfoAggregator eia = new EntityInfoAggregator();
return eia.createEntityPropertyInfo(property);
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
/**
* Create an map of <code>complex type property name</code> to {@link EntityPropertyInfo} based on given {@link EdmComplexType}
*
* @param complexType
* for which the {@link EntityPropertyInfo} is created.
* @return created map of <code>complex type property name</code> to {@link EntityPropertyInfo}
* @throws EntityProviderException
* if create of {@link EntityPropertyInfo} something goes wrong (e.g. exceptions during
* access of {@link EntityPropertyInfo}).
*/
public static Map<String, EntityPropertyInfo> create(final EdmComplexType complexType) throws EntityProviderException {
try {
EntityInfoAggregator entityInfo = new EntityInfoAggregator();
return entityInfo.createPropertyInfoObjects(complexType, complexType.getPropertyNames());
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
/**
* Create an {@link EntityPropertyInfo} based on given {@link EdmFunctionImport}
*
* @param functionImport
* for which the {@link EntityPropertyInfo} is created.
* @return created {@link EntityPropertyInfo}
* @throws EntityProviderException
* if create of {@link EntityPropertyInfo} something goes wrong (e.g. exceptions during
* access of {@link EdmFunctionImport}).
*/
public static EntityPropertyInfo create(final EdmFunctionImport functionImport) throws EntityProviderException {
try {
EntityInfoAggregator eia = new EntityInfoAggregator();
return eia.createEntityPropertyInfo(functionImport, functionImport.getReturnType().getType());
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
/**
* @return the edm entity set which was used to build this entity info aggregator object
*/
public EdmEntitySet getEntitySet() {
return entitySet;
}
/**
* @return entity set name.
*/
public String getEntitySetName() {
return entitySetName;
}
/**
* @return <code>true</code> if the entity container of {@link EdmEntitySet} is the default container,
* otherwise <code>false</code>.
*/
public boolean isDefaultEntityContainer() {
return isDefaultEntityContainer;
}
public EntityPropertyInfo getTargetPathInfo(final String targetPath) {
return targetPath2EntityPropertyInfo.get(targetPath);
}
public EdmEntityType getEntityType() {
return entityType;
}
public String getEntityContainerName() {
return entityContainerName;
}
/**
* @return unmodifiable set of all found target path names.
*/
public Collection<String> getTargetPathNames() {
return Collections.unmodifiableCollection(targetPath2EntityPropertyInfo.keySet());
}
/**
* @return unmodifiable set of found <code>none syndication target path names</code> (all target path names which are
* not pre-defined).
*/
public List<String> getNoneSyndicationTargetPathNames() {
return Collections.unmodifiableList(noneSyndicationTargetPaths);
}
/**
* @return unmodifiable set of all found navigation property names.
*/
public List<String> getNavigationPropertyNames() throws EntityProviderException {
return Collections.unmodifiableList(navigationPropertyNames);
}
/**
* @return unmodifiable set of all property names.
*/
public List<String> getPropertyNames() throws EntityProviderException {
return Collections.unmodifiableList(propertyNames);
}
/**
* @return unmodifiable set of selected property names.
*/
public List<String> getSelectedPropertyNames() throws EntityProviderException {
return Collections.unmodifiableList(selectedPropertyNames);
}
/**
* @return unmodifiable set of selected property names.
*/
public List<String> getSelectedNavigationPropertyNames() throws EntityProviderException {
return Collections.unmodifiableList(selectedNavigationPropertyNames);
}
public Collection<EntityPropertyInfo> getPropertyInfos() {
return Collections.unmodifiableCollection(propertyInfo.values());
}
public EntityPropertyInfo getPropertyInfo(final String name) {
return propertyInfo.get(name);
}
public Collection<EntityPropertyInfo> getETagPropertyInfos() {
List<EntityPropertyInfo> keyProperties = new ArrayList<EntityPropertyInfo>();
for (String etagPropertyName : etagPropertyNames) {
EntityPropertyInfo e = propertyInfo.get(etagPropertyName);
keyProperties.add(e);
}
return keyProperties;
}
/**
* @return list of all key property infos
* @throws EntityProviderException
*/
public List<EntityPropertyInfo> getKeyPropertyInfos() throws EntityProviderException {
if (keyPropertyInfos == null) {
try {
keyPropertyInfos = new ArrayList<EntityPropertyInfo>();
for (String keyPropertyName : entityType.getKeyPropertyNames()) {
keyPropertyInfos.add(propertyInfo.get(keyPropertyName));
}
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
return keyPropertyInfos;
}
public NavigationPropertyInfo getNavigationPropertyInfo(final String name) {
return navigationPropertyInfos.get(name);
}
private void initialize(final EdmEntitySet entitySet, final ExpandSelectTreeNode expandSelectTree) throws EntityProviderException {
try {
this.entitySet = entitySet;
entityType = entitySet.getEntityType();
entitySetName = entitySet.getName();
isDefaultEntityContainer = entitySet.getEntityContainer().isDefaultEntityContainer();
entityContainerName = entitySet.getEntityContainer().getName();
propertyNames = entityType.getPropertyNames();
navigationPropertyNames = entityType.getNavigationPropertyNames();
propertyInfo = createPropertyInfoObjects(entityType, propertyNames);
navigationPropertyInfos = createNavigationInfoObjects(entityType, navigationPropertyNames);
selectedPropertyNames = propertyNames;
selectedNavigationPropertyNames = navigationPropertyNames;
expandedNavigationPropertyNames = new ArrayList<String>();
if (expandSelectTree != null && !expandSelectTree.isAll()) {
selectedPropertyNames = new ArrayList<String>();
selectedNavigationPropertyNames = new ArrayList<String>();
for (EdmProperty property : expandSelectTree.getProperties()) {
selectedPropertyNames.add(property.getName());
}
for (String property : expandSelectTree.getLinks().keySet()) {
selectedNavigationPropertyNames.add(property);
if (expandSelectTree.getLinks().get(property) != null) {
expandedNavigationPropertyNames.add(property);
}
}
} else if (expandSelectTree != null) {
for (String property : expandSelectTree.getLinks().keySet()) {
if (expandSelectTree.getLinks().get(property) != null) {
expandedNavigationPropertyNames.add(property);
}
}
}
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
private Map<String, EntityPropertyInfo> createPropertyInfoObjects(final EdmStructuralType type, final List<String> propertyNames) throws EntityProviderException {
try {
Map<String, EntityPropertyInfo> infos = new HashMap<String, EntityPropertyInfo>();
for (String propertyName : propertyNames) {
EdmProperty property = (EdmProperty) type.getProperty(propertyName);
checkETagRelevant(property);
EntityPropertyInfo info = createEntityPropertyInfo(property);
infos.put(info.getName(), info);
checkTargetPathInfo(property, info);
}
return infos;
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
private Map<String, NavigationPropertyInfo> createNavigationInfoObjects(final EdmStructuralType type, final List<String> propertyNames) throws EntityProviderException {
try {
Map<String, NavigationPropertyInfo> infos = new HashMap<String, NavigationPropertyInfo>();
for (String propertyName : propertyNames) {
EdmNavigationProperty navProperty = (EdmNavigationProperty) type.getProperty(propertyName);
NavigationPropertyInfo info = NavigationPropertyInfo.create(navProperty);
infos.put(propertyName, info);
}
return infos;
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
private EntityPropertyInfo createEntityPropertyInfo(final EdmProperty property) throws EdmException, EntityProviderException {
EdmType type = property.getType();
if (type instanceof EdmSimpleType) {
return EntityPropertyInfo.create(property);
} else if (type instanceof EdmComplexType) {
EdmComplexType complex = (EdmComplexType) type;
Map<String, EntityPropertyInfo> recursiveInfos = createPropertyInfoObjects(complex, complex.getPropertyNames());
return EntityComplexPropertyInfo.create(property, complex.getPropertyNames(), recursiveInfos);
} else {
throw new EntityProviderException(EntityProviderException.UNSUPPORTED_PROPERTY_TYPE);
}
}
private EntityPropertyInfo createEntityPropertyInfo(final EdmFunctionImport functionImport, final EdmType type) throws EdmException, EntityProviderException {
EntityPropertyInfo epi;
if (type.getKind() == EdmTypeKind.COMPLEX) {
EdmComplexType complex = (EdmComplexType) type;
Map<String, EntityPropertyInfo> eia = EntityInfoAggregator.create(complex);
List<EntityPropertyInfo> childEntityInfoList = new ArrayList<EntityPropertyInfo>();
for (String propertyName : complex.getPropertyNames()) {
childEntityInfoList.add(eia.get(propertyName));
}
epi = new EntityComplexPropertyInfo(functionImport.getName(), type, null, null, childEntityInfoList);
} else if (type.getKind() == EdmTypeKind.SIMPLE) {
epi = new EntityPropertyInfo(functionImport.getName(), type, null, null, null, null);
} else {
throw new EntityProviderException(EntityProviderException.UNSUPPORTED_PROPERTY_TYPE.addContent(type.getKind()));
}
return epi;
}
private void checkETagRelevant(final EdmProperty edmProperty) throws EntityProviderException {
try {
if (edmProperty.getFacets() != null && edmProperty.getFacets().getConcurrencyMode() == EdmConcurrencyMode.Fixed) {
etagPropertyNames.add(edmProperty.getName());
}
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
private void checkTargetPathInfo(final EdmProperty property, final EntityPropertyInfo propertyInfo) throws EntityProviderException {
try {
EdmCustomizableFeedMappings customizableFeedMappings = property.getCustomizableFeedMappings();
if (customizableFeedMappings != null) {
String targetPath = customizableFeedMappings.getFcTargetPath();
targetPath2EntityPropertyInfo.put(targetPath, propertyInfo);
if (!SYN_TARGET_PATHS.contains(targetPath)) {
noneSyndicationTargetPaths.add(targetPath);
}
}
} catch (EdmException e) {
throw new EntityProviderException(EntityProviderException.COMMON, e);
}
}
public List<String> getExpandedNavigationPropertyNames() {
return expandedNavigationPropertyNames;
}
}