summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/onap/schema
diff options
context:
space:
mode:
authorBansal, Nitin (nb121v) <nitin.bansal@amdocs.com>2017-11-20 16:02:06 -0500
committerBansal, Nitin (nb121v) <nitin.bansal@amdocs.com>2017-11-20 16:34:16 -0500
commit908b4693e5a0a4c2f323dbf518b35e4620f183a1 (patch)
treecd057c3b4e91f696e116db94d7a3e08c800421a0 /src/main/java/org/onap/schema
parent2dd042556f9142fcd525c0277ea21ac3bd828e4f (diff)
Refactor to move from openecomp to onap
Refactor to move from openecomp to onap IssueID: AAI-486 Change-Id: I1d5634739514acf11d5fbdf5e2c7865aaccd864e Signed-off-by: Bansal, Nitin (nb121v) <nitin.bansal@amdocs.com>
Diffstat (limited to 'src/main/java/org/onap/schema')
-rw-r--r--src/main/java/org/onap/schema/OxmModelValidator.java331
-rw-r--r--src/main/java/org/onap/schema/RelationshipSchema.java108
-rw-r--r--src/main/java/org/onap/schema/RelationshipSchemaLoader.java263
-rw-r--r--src/main/java/org/onap/schema/RelationshipSchemaValidator.java352
4 files changed, 1054 insertions, 0 deletions
diff --git a/src/main/java/org/onap/schema/OxmModelValidator.java b/src/main/java/org/onap/schema/OxmModelValidator.java
new file mode 100644
index 0000000..d23804c
--- /dev/null
+++ b/src/main/java/org/onap/schema/OxmModelValidator.java
@@ -0,0 +1,331 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Gizmo
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.schema;
+
+import com.google.common.base.CaseFormat;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+
+import org.eclipse.persistence.dynamic.DynamicType;
+import org.eclipse.persistence.internal.helper.DatabaseField;
+import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
+import org.eclipse.persistence.mappings.DatabaseMapping;
+import org.eclipse.persistence.oxm.XMLField;
+import org.onap.aaiutils.oxm.OxmModelLoader;
+import org.onap.crud.entity.Vertex;
+import org.onap.crud.exception.CrudException;
+import org.onap.crud.util.CrudServiceUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.core.Response.Status;
+
+public class OxmModelValidator {
+ public enum Metadata {
+ NODE_TYPE("aai-node-type"), URI("aai-uri"), CREATED_TS("aai-created-ts"), SOT("source-of-truth"), LAST_MOD_SOT(
+ "last-mod-source-of-truth");
+
+ private final String propName;
+
+ Metadata(String propName) {
+ this.propName = propName;
+ }
+
+ public String propertyName() {
+ return propName;
+ }
+
+ public static boolean isProperty(String property) {
+ for (Metadata meta : Metadata.values()) {
+ if (meta.propName.equals(property)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public static Map<String, Object> resolveCollectionfilter(String version, String type, Map<String, String> filter)
+ throws CrudException {
+
+ DynamicJAXBContext jaxbContext = null;
+ try {
+ jaxbContext = OxmModelLoader.getContextForVersion(version);
+ } catch (Exception e) {
+ throw new CrudException(e);
+ }
+
+ Map<String, Object> result = new HashMap<String, Object>();
+ if (jaxbContext == null) {
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+ final DynamicType modelObjectType = jaxbContext.getDynamicType(
+ CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type)));
+
+ for (String key : filter.keySet()) {
+ String keyJavaName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, key);
+ if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) != null) {
+ try {
+ DatabaseMapping mapping = modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName);
+ Object value = CrudServiceUtil.validateFieldType(filter.get(key), mapping.getField().getType());
+ result.put(key, value);
+ } catch (Exception ex) {
+ // Skip any exceptions thrown while validating the filter
+ // key value
+ continue;
+ }
+ }
+ }
+
+ return result;
+
+ }
+
+ public static String resolveCollectionType(String version, String type) throws CrudException {
+
+ DynamicJAXBContext jaxbContext = null;
+ try {
+ jaxbContext = OxmModelLoader.getContextForVersion(version);
+ } catch (Exception e) {
+ throw new CrudException(e);
+ }
+
+ if (jaxbContext == null) {
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+ // Determine if the Object part is a collection type in the model
+ // definition
+ final DynamicType modelObjectType = jaxbContext.getDynamicType(
+ CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type)));
+
+ if (modelObjectType == null) {
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+
+ if (modelObjectType.getDescriptor().getMappings().size() == 1
+ && modelObjectType.getDescriptor().getMappings().get(0).isCollectionMapping()) {
+ String childJavaObjectName = modelObjectType.getDescriptor().getMappings().get(0).getAttributeName();
+ childJavaObjectName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, childJavaObjectName);
+ final DynamicType childObjectType = jaxbContext.getDynamicType(childJavaObjectName);
+ if (childObjectType == null) {
+ // Should not happen as child object is defined in oxm model
+ // itself
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+ return childObjectType.getDescriptor().getTableName();
+ } else {
+ return modelObjectType.getDescriptor().getTableName();
+ }
+
+ }
+
+ public static Vertex validateIncomingUpsertPayload(String id, String version, String type, JsonElement properties)
+ throws CrudException {
+
+ try {
+ type = resolveCollectionType(version, type);
+ DynamicJAXBContext jaxbContext = OxmModelLoader.getContextForVersion(version);
+ String modelObjectClass = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
+ CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type));
+
+ final DynamicType modelObjectType = jaxbContext.getDynamicType(modelObjectClass);
+ final DynamicType reservedType = jaxbContext.getDynamicType("ReservedPropNames");
+
+ Set<Map.Entry<String, JsonElement>> payloadEntriesSet = properties.getAsJsonObject().entrySet();
+
+ // loop through input to validate against schema
+ for (Map.Entry<String, JsonElement> entry : payloadEntriesSet) {
+ String keyJavaName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, entry.getKey());
+
+ // check for valid field
+ if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) == null) {
+ if (reservedType.getDescriptor().getMappingForAttributeName(keyJavaName) == null) {
+ throw new CrudException("Invalid field: " + entry.getKey(), Status.BAD_REQUEST);
+ }
+ }
+
+ }
+
+ Map<String, JsonElement> entriesMap = new HashMap<String, JsonElement>();
+ for (Map.Entry<String, JsonElement> entry : payloadEntriesSet) {
+ entriesMap.put(entry.getKey(), entry.getValue());
+ }
+
+ Vertex.Builder modelVertexBuilder = new Vertex.Builder(type);
+ if (id != null) {
+ modelVertexBuilder.id(id);
+ }
+ for (DatabaseMapping mapping : modelObjectType.getDescriptor().getMappings()) {
+ if (mapping.isAbstractDirectMapping()) {
+ DatabaseField field = mapping.getField();
+ String defaultValue = mapping.getProperties().get("defaultValue") == null ? ""
+ : mapping.getProperties().get("defaultValue").toString();
+
+ String keyName = field.getName().substring(0, field.getName().indexOf("/"));
+
+ if (((XMLField) field).isRequired() && !entriesMap.containsKey(keyName) && !defaultValue.isEmpty()) {
+ modelVertexBuilder.property(keyName, CrudServiceUtil.validateFieldType(defaultValue, field.getType()));
+ }
+ // if schema field is required and not set then reject
+ if (((XMLField) field).isRequired() && !entriesMap.containsKey(keyName) && defaultValue.isEmpty()) {
+ throw new CrudException("Missing required field: " + keyName, Status.BAD_REQUEST);
+ }
+ // If invalid field then reject
+ if (entriesMap.containsKey(keyName)) {
+ Object value = CrudServiceUtil.validateFieldType(entriesMap.get(keyName).getAsString(), field.getType());
+ modelVertexBuilder.property(keyName, value);
+ }
+
+ // Set defaults
+ if (!defaultValue.isEmpty() && !entriesMap.containsKey(keyName)) {
+ modelVertexBuilder.property(keyName, CrudServiceUtil.validateFieldType(defaultValue, field.getType()));
+ }
+ }
+ }
+
+ // Handle reserved properties
+ for (DatabaseMapping mapping : reservedType.getDescriptor().getMappings()) {
+ if (mapping.isAbstractDirectMapping()) {
+ DatabaseField field = mapping.getField();
+ String keyName = field.getName().substring(0, field.getName().indexOf("/"));
+
+ if (entriesMap.containsKey(keyName)) {
+ Object value = CrudServiceUtil.validateFieldType(entriesMap.get(keyName).getAsString(), field.getType());
+ modelVertexBuilder.property(keyName, value);
+ }
+ }
+ }
+
+ return modelVertexBuilder.build();
+ } catch (Exception e) {
+ throw new CrudException(e.getMessage(), Status.BAD_REQUEST);
+ }
+ }
+
+ public static Vertex validateIncomingPatchPayload(String id, String version, String type, JsonElement properties,
+ Vertex existingVertex) throws CrudException {
+
+ try {
+ type = resolveCollectionType(version, type);
+ DynamicJAXBContext jaxbContext = OxmModelLoader.getContextForVersion(version);
+ String modelObjectClass = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
+ CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type));
+
+ final DynamicType modelObjectType = jaxbContext.getDynamicType(modelObjectClass);
+ final DynamicType reservedType = jaxbContext.getDynamicType("ReservedPropNames");
+
+ Set<Map.Entry<String, JsonElement>> payloadEntriesSet = properties.getAsJsonObject().entrySet();
+
+ // Loop through the payload properties and merge with existing
+ // vertex props
+ for (Map.Entry<String, JsonElement> entry : payloadEntriesSet) {
+
+ String keyJavaName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, entry.getKey());
+
+ DatabaseField field = null;
+ String defaultValue = null;
+
+ if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) != null) {
+ field = modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName).getField();
+ defaultValue = modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName).getProperties()
+ .get("defaultValue") == null ? ""
+ : modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName).getProperties()
+ .get("defaultValue").toString();
+ } else if (reservedType.getDescriptor().getMappingForAttributeName(keyJavaName) != null) {
+ field = reservedType.getDescriptor().getMappingForAttributeName(keyJavaName).getField();
+ defaultValue = "";
+ }
+
+ if (field == null) {
+ throw new CrudException("Invalid field: " + entry.getKey(), Status.BAD_REQUEST);
+ }
+
+ // check if mandatory field is not set to null
+ if (((XMLField) field).isRequired() && entry.getValue() instanceof JsonNull && !defaultValue.isEmpty()) {
+ existingVertex.getProperties().put(entry.getKey(),
+ CrudServiceUtil.validateFieldType(defaultValue, field.getType()));
+ } else if (((XMLField) field).isRequired() && entry.getValue() instanceof JsonNull && defaultValue.isEmpty()) {
+ throw new CrudException("Mandatory field: " + entry.getKey() + " can't be set to null", Status.BAD_REQUEST);
+ } else if (!((XMLField) field).isRequired() && entry.getValue() instanceof JsonNull
+ && existingVertex.getProperties().containsKey(entry.getKey())) {
+ existingVertex.getProperties().remove(entry.getKey());
+ } else if (!(entry.getValue() instanceof JsonNull)) {
+ // add/update the value if found in existing vertex
+ Object value = CrudServiceUtil.validateFieldType(entry.getValue().getAsString(), field.getType());
+ existingVertex.getProperties().put(entry.getKey(), value);
+ }
+
+ }
+
+ return existingVertex;
+ } catch (Exception e) {
+ throw new CrudException(e.getMessage(), Status.BAD_REQUEST);
+ }
+
+ }
+
+ private static DatabaseField getDatabaseField(String fieldName, DynamicType modelObjectType) {
+ for (DatabaseField field : modelObjectType.getDescriptor().getAllFields()) {
+ int ix = field.getName().indexOf("/");
+ if (ix <= 0) {
+ ix = field.getName().length();
+ }
+
+ String keyName = field.getName().substring(0, ix);
+ if (fieldName.equals(keyName)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ public static Vertex validateOutgoingPayload(String version, Vertex vertex) {
+
+ Vertex.Builder modelVertexBuilder = new Vertex.Builder(vertex.getType()).id(vertex.getId().get());
+
+ try {
+ DynamicJAXBContext jaxbContext = OxmModelLoader.getContextForVersion(version);
+ String modelObjectClass = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
+ CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL,
+ vertex.getProperties().get(Metadata.NODE_TYPE.propertyName()) != null
+ ? vertex.getProperties().get(Metadata.NODE_TYPE.propertyName()).toString() : vertex.getType()));
+ final DynamicType modelObjectType = jaxbContext.getDynamicType(modelObjectClass);
+
+ for (String key : vertex.getProperties().keySet()) {
+ DatabaseField field = getDatabaseField(key, modelObjectType);
+ if (field != null) {
+ if (!Metadata.isProperty(key)) {
+ modelVertexBuilder.property(key, vertex.getProperties().get(key));
+ }
+ }
+ }
+ return modelVertexBuilder.build();
+ } catch (Exception ex) {
+ return vertex;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/onap/schema/RelationshipSchema.java b/src/main/java/org/onap/schema/RelationshipSchema.java
new file mode 100644
index 0000000..e6a40b5
--- /dev/null
+++ b/src/main/java/org/onap/schema/RelationshipSchema.java
@@ -0,0 +1,108 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Gizmo
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.schema;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.onap.crud.exception.CrudException;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+import javax.ws.rs.core.Response.Status;
+
+
+public class RelationshipSchema {
+ private static final Gson gson = new GsonBuilder().create();
+
+ public static final String SCHEMA_SOURCE_NODE_TYPE = "from";
+ public static final String SCHEMA_TARGET_NODE_TYPE = "to";
+ public static final String SCHEMA_RELATIONSHIP_TYPE = "label";
+ public static final String SCHEMA_RULES_ARRAY = "rules";
+
+
+ private Map<String, Map<String, Class<?>>> relations = new HashMap<>();
+ /**
+ * Hashmap of valid relationship types along with properties.
+ */
+ private Map<String, Map<String, Class<?>>> relationTypes = new HashMap<>();
+
+
+ public RelationshipSchema(List<String> jsonStrings) throws CrudException, IOException {
+ String edgeRules = jsonStrings.get(0);
+ String props = jsonStrings.get(1);
+
+ HashMap<String, ArrayList<LinkedHashMap<String, String>>> rules = new ObjectMapper().readValue(edgeRules, HashMap.class);
+ HashMap<String, String> properties = new ObjectMapper().readValue(props, HashMap.class);
+ Map<String, Class<?>> edgeProps = properties.entrySet().stream().collect(Collectors.toMap(p -> p.getKey(), p -> {
+ try {
+ return resolveClass(p.getValue());
+ } catch (CrudException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }));
+
+ rules.get(SCHEMA_RULES_ARRAY).forEach(l -> {
+ relationTypes.put(l.get(SCHEMA_RELATIONSHIP_TYPE), edgeProps);
+ relations.put(buildRelation(l.get(SCHEMA_SOURCE_NODE_TYPE), l.get(SCHEMA_TARGET_NODE_TYPE), l.get(SCHEMA_RELATIONSHIP_TYPE)), edgeProps);
+ });
+ }
+
+
+
+ public Map<String, Class<?>> lookupRelation(String key) {
+ return this.relations.get(key);
+ }
+
+ public Map<String, Class<?>> lookupRelationType(String type) {
+ return this.relationTypes.get(type);
+ }
+
+ public boolean isValidType(String type) {
+ return relationTypes.containsKey(type);
+ }
+
+
+ private String buildRelation(String source, String target, String relation){
+ return source + ":" + target + ":" + relation;
+ }
+
+ private Class<?> resolveClass(String type) throws CrudException, ClassNotFoundException {
+ Class<?> clazz = Class.forName(type);
+ validateClassTypes(clazz);
+ return clazz;
+ }
+
+ private void validateClassTypes(Class<?> clazz) throws CrudException {
+ if (!clazz.isAssignableFrom(Integer.class) && !clazz.isAssignableFrom(Double.class)
+ && !clazz.isAssignableFrom(Boolean.class) && !clazz.isAssignableFrom(String.class)) {
+ throw new CrudException("", Status.BAD_REQUEST);
+ }
+ }
+}
+
+
diff --git a/src/main/java/org/onap/schema/RelationshipSchemaLoader.java b/src/main/java/org/onap/schema/RelationshipSchemaLoader.java
new file mode 100644
index 0000000..48727a0
--- /dev/null
+++ b/src/main/java/org/onap/schema/RelationshipSchemaLoader.java
@@ -0,0 +1,263 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Gizmo
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.schema;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.commons.io.IOUtils;
+import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.crud.exception.CrudException;
+import org.onap.crud.logging.CrudServiceMsgs;
+import org.onap.crud.util.CrudServiceConstants;
+import org.onap.crud.util.FileWatcher;
+import org.springframework.core.io.UrlResource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+public class RelationshipSchemaLoader {
+
+ private static Map<String, RelationshipSchema> versionContextMap = new ConcurrentHashMap<>();
+ private static SortedSet<Integer> versions = new TreeSet<Integer>();
+ private static Map<String, Timer> timers = new ConcurrentHashMap<String, Timer>();
+ final static String edgePropsFiles = "edge_properties_";
+ final static String fileExt = ".json";
+ final static Pattern rulesFilePattern = Pattern.compile("DbEdgeRules(.*)" + fileExt);
+ final static Pattern propsFilePattern = Pattern.compile(edgePropsFiles + "(.*)" + fileExt);
+ final static Pattern versionPattern = Pattern.compile(".*(v\\d+)" + fileExt);
+
+ private static org.onap.aai.cl.api.Logger logger = LoggerFactory.getInstance()
+ .getLogger(RelationshipSchemaLoader.class.getName());
+
+ public synchronized static void loadModels() throws CrudException {
+ load(rulesFilePattern, propsFilePattern);
+ }
+
+ public synchronized static void loadModels(String version) throws CrudException {
+ String pattern = String.format(".*(%s)" + fileExt, version);
+ load(Pattern.compile(pattern), Pattern.compile(edgePropsFiles + version + fileExt));
+ }
+
+ public static RelationshipSchema getSchemaForVersion(String version) throws CrudException {
+ if (versionContextMap == null || versionContextMap.isEmpty()) {
+ loadModels();
+ } else if (!versionContextMap.containsKey(version)) {
+ try {
+ loadModels(version);
+ } catch (Exception e) {
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+ }
+ RelationshipSchema schema = versionContextMap.get(version);
+ if (schema == null) {
+ throw new CrudException("", Status.NOT_FOUND);
+ } else
+ return schema;
+ }
+
+ public static String getLatestSchemaVersion() throws CrudException {
+ return "v" + versions.last();
+ }
+
+ public static Map<String, RelationshipSchema> getVersionContextMap() {
+ return versionContextMap;
+ }
+
+ public static void setVersionContextMap(Map<String, RelationshipSchema> versionContextMap) {
+ RelationshipSchemaLoader.versionContextMap = versionContextMap;
+ }
+
+ public static void resetVersionContextMap() {
+ RelationshipSchemaLoader.versionContextMap = new ConcurrentHashMap<>();
+ }
+
+ private static void load(Pattern rulesPattern, Pattern edgePropsPattern) throws CrudException {
+ ClassLoader cl = RelationshipSchemaLoader.class.getClassLoader();
+ ResourcePatternResolver rulesResolver = new PathMatchingResourcePatternResolver(cl);
+ List<Object> rulesFiles;
+ String rulesDir = CrudServiceConstants.CRD_HOME_MODEL;
+ try {
+
+ // getResources method returns objects of type "Resource"
+ // 1. We are getting all the objects from the classpath which has
+ // "DbEdgeRules" in the name.
+ // 2. We run them through a filter and return only the objects which match
+ // the supplied pattern "p"
+ // 3. We then collect the objects in a list. At this point we have a list
+ // of the kind of files we require.
+ rulesFiles = Arrays.stream(rulesResolver.getResources("classpath*:/dbedgerules/DbEdgeRules*" + fileExt))
+ .filter(r -> !myMatcher(rulesPattern, r.getFilename()).isEmpty()).collect(Collectors.toList());
+
+ // This gets all the objects of type "File" from external directory (not
+ // on the classpath)
+ // 1. From an external directory (one not on the classpath) we get all the
+ // objects of type "File"
+ // 2. We only return the files whose names matched the supplied pattern
+ // "p2".
+ // 3. We then collect all the objects in a list and add the contents of
+ // this list
+ // to the previous collection (rulesFiles)
+ rulesFiles
+ .addAll(Arrays.stream(new File(rulesDir).listFiles((d, name) -> edgePropsPattern.matcher(name).matches()))
+ .collect(Collectors.toList()));
+
+ if (rulesFiles.isEmpty()) {
+ logger.error(CrudServiceMsgs.INVALID_OXM_DIR, rulesDir);
+ throw new FileNotFoundException("DbEdgeRules and edge_properties files were not found.");
+ }
+
+ // Sort and then group the files with their versions, convert them to the
+ // schema, and add them to versionContextMap
+ // 1. Sort the files. We need the DbEdgeRules files to be before the
+ // edgeProperties files.
+ // 2. Group the files with their versions. ie. v11 ->
+ // ["DbEdgeRule_v11.json", "edgeProperties_v11.json"].
+ // The "group method" returns a HashMap whose key is the version and the
+ // value is a list of objects.
+ // 3. Go through each version and map the files into one schema using the
+ // "jsonFilesLoader" method.
+ // Also update the "versionContextMap" with the version and it's schema.
+ rulesFiles.stream().sorted(Comparator.comparing(RelationshipSchemaLoader::filename))
+ .collect(Collectors.groupingBy(f -> myMatcher(versionPattern, filename(f))))
+ .forEach((version, resourceAndFile) -> {
+ if (resourceAndFile.size() == 2) {
+ versionContextMap.put(version, jsonFilesLoader(version, resourceAndFile));
+ } else {
+ String filenames = resourceAndFile.stream().map(f -> filename(f)).collect(Collectors.toList()).toString();
+ String errorMsg = "Expecting a rules and a edge_properties files for " + version + ". Found: "
+ + filenames;
+ logger.warn(CrudServiceMsgs.INVALID_OXM_FILE, errorMsg);
+ }
+ });
+ logger.info(CrudServiceMsgs.LOADED_OXM_FILE, "Relationship Schema and Properties files: "
+ + rulesFiles.stream().map(f -> filename(f)).collect(Collectors.toList()));
+ } catch (IOException e) {
+ logger.error(CrudServiceMsgs.INVALID_OXM_DIR, rulesDir);
+ throw new CrudException("DbEdgeRules or edge_properties files were not found.", new FileNotFoundException());
+ }
+ }
+
+ private static String filename(Object k) throws ClassCastException {
+ if (k instanceof UrlResource) {
+ return ((UrlResource) k).getFilename();
+ } else if (k instanceof File) {
+ return ((File) k).getName();
+ } else {
+ throw new ClassCastException();
+ }
+ }
+
+ private static RelationshipSchema jsonFilesLoader(String version, List<Object> files) {
+ List<String> fileContents = new ArrayList<>();
+ RelationshipSchema rsSchema = null;
+ if (files.size() == 2) {
+ for (Object file : files) {
+ fileContents.add(jsonToRelationSchema(version, file));
+ versions.add(Integer.parseInt(version.substring(1)));
+ }
+
+ try {
+ rsSchema = new RelationshipSchema(fileContents);
+ } catch (CrudException | IOException e) {
+ e.printStackTrace();
+ logger.error(CrudServiceMsgs.INVALID_OXM_FILE,
+ files.stream().map(f -> filename(f)).collect(Collectors.toList()).toString(), e.getMessage());
+ }
+ return rsSchema;
+ }
+ return rsSchema;
+ }
+
+ private synchronized static void updateVersionContext(String version, RelationshipSchema rs) {
+ versionContextMap.put(version, rs);
+ }
+
+ private synchronized static String jsonToRelationSchema(String version, Object file) {
+ InputStream inputStream = null;
+ String content = null;
+
+ try {
+ if (file instanceof UrlResource) {
+ inputStream = ((UrlResource) file).getInputStream();
+ } else {
+ inputStream = new FileInputStream((File) file);
+ addtimer(version, file);
+ }
+ content = IOUtils.toString(inputStream, "UTF-8");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return content;
+ }
+
+ private static void addtimer(String version, Object file) {
+ TimerTask task = null;
+ task = new FileWatcher((File) file) {
+ protected void onChange(File file) {
+ // here we implement the onChange
+ logger.info(CrudServiceMsgs.OXM_FILE_CHANGED, file.getName());
+
+ try {
+ // Cannot use the file object here because we also have to get the
+ // edge properties associated with that version.
+ // The properties are stored in a different file.
+ RelationshipSchemaLoader.loadModels(version);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ if (!timers.containsKey(version)) {
+ Timer timer = new Timer("db_edge_rules_" + version);
+ timer.schedule(task, new Date(), 10000);
+ timers.put(version, timer);
+
+ }
+ }
+
+ private static String myMatcher(Pattern p, String s) {
+ Matcher m = p.matcher(s);
+ return m.matches() ? m.group(1) : "";
+ }
+}
diff --git a/src/main/java/org/onap/schema/RelationshipSchemaValidator.java b/src/main/java/org/onap/schema/RelationshipSchemaValidator.java
new file mode 100644
index 0000000..53048be
--- /dev/null
+++ b/src/main/java/org/onap/schema/RelationshipSchemaValidator.java
@@ -0,0 +1,352 @@
+/**
+ * ============LICENSE_START=======================================================
+ * Gizmo
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.onap.schema;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+
+import org.onap.crud.entity.Edge;
+import org.onap.crud.entity.Vertex;
+import org.onap.crud.exception.CrudException;
+import org.onap.crud.service.EdgePayload;
+import org.onap.crud.util.CrudServiceUtil;
+import org.radeox.util.logging.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.ws.rs.core.Response.Status;
+
+public class RelationshipSchemaValidator {
+
+ public static final String SOURCE_NODE = "source";
+ public static final String TARGET_NODE = "target";
+
+ final static Pattern urlPattern = Pattern.compile("services/inventory/(.*)/(.*)/(.*)");
+
+ public static Map<String, Object> resolveCollectionfilter(String version, String type,
+ Map<String, String> filter)
+ throws CrudException {
+
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+ if (schema == null) {
+ throw new CrudException("", Status.NOT_FOUND);
+ }
+
+ Map<String, Class<?>> props = schema.lookupRelationType(type);
+ Map<String, Object> result = new HashMap<String, Object>();
+
+ for (String key : filter.keySet()) {
+
+ if (props.containsKey(key)) {
+ try {
+ Object value = CrudServiceUtil.validateFieldType(filter.get(key), props.get(key));
+ result.put(key, value);
+ } catch (Exception ex) {
+ // Skip any exceptions thrown while validating the filter
+ // key value
+ continue;
+ }
+ }
+ }
+
+ return result;
+
+ }
+
+ public static void validateType(String version, String type) throws CrudException {
+
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+ if (!schema.isValidType(type)) {
+ throw new CrudException("Invalid " + RelationshipSchema.SCHEMA_RELATIONSHIP_TYPE
+ + ": " + type,
+ Status.BAD_REQUEST);
+ }
+
+ }
+
+ public static Edge validateIncomingAddPayload(String version, String type, Vertex sourceNode,
+ Vertex targetNode, JsonElement properties)
+ throws CrudException {
+ EdgePayload payload = new EdgePayload();
+ payload.setSource("services/inventory/" + version + "/" + sourceNode.getType()
+ + "/" + sourceNode.getId().get());
+ payload.setTarget("services/inventory/" + version + "/" + targetNode.getType()
+ + "/" + targetNode.getId().get());
+ payload.setType(type);
+ payload.setProperties(properties);
+ return validateIncomingAddPayload(version, type, payload);
+ }
+
+ public static Edge validateIncomingAddPayload(String version, String type, EdgePayload payload)
+ throws CrudException {
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+
+ try {
+
+ if (payload.getSource() == null || payload.getTarget() == null) {
+ throw new CrudException("Source/Target not specified", Status.BAD_REQUEST);
+ }
+
+ Matcher sourceMatcher = urlPattern.matcher(payload.getSource());
+ Matcher targetMatcher = urlPattern.matcher(payload.getTarget());
+
+ if (!sourceMatcher.matches() || !targetMatcher.matches()) {
+ throw new CrudException("Invalid Source/Target Urls", Status.BAD_REQUEST);
+ }
+
+ // create key based on source:target:relationshipType
+ String sourceNodeType = sourceMatcher.group(2);
+ String targetNodeType = targetMatcher.group(2);
+
+ String sourceNodeId = sourceMatcher.group(3);
+ String targetNodeId = targetMatcher.group(3);
+
+ String key = sourceNodeType + ":" + targetNodeType + ":" + type;
+
+ // find the validate the key from the schema
+ Map<String, Class<?>> schemaObject = schema.lookupRelation(key);
+
+ if (schemaObject == null) {
+ throw new CrudException("Invalid source/target/relationship type: " + key,
+ Status.BAD_REQUEST);
+ }
+
+ Edge.Builder modelEdgeBuilder = new Edge.Builder(type);
+
+ modelEdgeBuilder.source(new Vertex.Builder(sourceNodeType).id(sourceNodeId).build());
+ modelEdgeBuilder.target(new Vertex.Builder(targetNodeType).id(targetNodeId).build());
+
+ // validate it properties
+ validateEdgeProps(modelEdgeBuilder, payload.getProperties(), schemaObject);
+
+ return modelEdgeBuilder.build();
+ } catch (Exception ex) {
+
+ throw new CrudException(ex.getMessage(), Status.BAD_REQUEST);
+ }
+
+ }
+
+ public static Edge validateIncomingPatchPayload(Edge edge, String version, EdgePayload payload)
+ throws CrudException {
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+
+ try {
+ if (payload.getSource() != null) {
+ Matcher sourceMatcher = urlPattern.matcher(payload.getSource());
+
+ if (!sourceMatcher.matches()) {
+ throw new CrudException("Invalid Target Urls", Status.BAD_REQUEST);
+ }
+ String sourceNodeId = sourceMatcher.group(3);
+ if (!sourceNodeId.equals(edge.getSource().getId().get())) {
+ throw new CrudException("Source can't be updated", Status.BAD_REQUEST);
+ }
+ }
+
+ if (payload.getTarget() != null) {
+ Matcher targetMatcher = urlPattern.matcher(payload.getTarget());
+
+ if (!targetMatcher.matches()) {
+ throw new CrudException("Invalid Target Urls", Status.BAD_REQUEST);
+ }
+ String sourceNodeId = targetMatcher.group(3);
+ if (!sourceNodeId.equals(edge.getTarget().getId().get())) {
+ throw new CrudException("Target can't be updated", Status.BAD_REQUEST);
+ }
+ }
+ // create key based on source:target:relationshipType
+
+ String key = edge.getSource().getType() + ":" + edge.getTarget().getType()
+ + ":" + edge.getType();
+
+ // find the validate the key from the schema
+ Map<String, Class<?>> schemaObject = schema.lookupRelation(key);
+
+ if (schemaObject == null) {
+ Logger.warn("key :" + key
+ + " not found in relationship schema . Skipping the schema validation");
+ return edge;
+ }
+
+ Set<Map.Entry<String, JsonElement>> entries = payload.getProperties()
+ .getAsJsonObject().entrySet();
+
+ for (Map.Entry<String, JsonElement> entry : entries) {
+
+ if (!schemaObject.containsKey(entry.getKey())) {
+ throw new CrudException("Invalid property: " + entry.getKey(), Status.BAD_REQUEST);
+ } else if (entry.getValue() instanceof JsonNull && edge.getProperties()
+ .containsKey(entry.getKey())) {
+ edge.getProperties().remove(entry.getKey());
+ } else if (!(entry.getValue() instanceof JsonNull)) {
+ Object value = CrudServiceUtil.validateFieldType(entry.getValue().getAsString(),
+ schemaObject.get(entry.getKey()));
+ edge.getProperties().put(entry.getKey(), value);
+ }
+
+ }
+
+ return edge;
+
+ } catch (Exception ex) {
+
+ throw new CrudException(ex.getMessage(), Status.BAD_REQUEST);
+ }
+ }
+
+ public static Edge validateIncomingUpdatePayload(Edge edge, String version, Vertex sourceNode,
+ Vertex targetNode, JsonElement properties)
+ throws CrudException {
+ EdgePayload payload = new EdgePayload();
+ payload.setSource("services/inventory/" + version + "/" + sourceNode.getType()
+ + "/" + sourceNode.getId().get());
+ payload.setTarget("services/inventory/" + version + "/" + targetNode.getType()
+ + "/" + targetNode.getId().get());
+ payload.setType(edge.getType());
+ payload.setProperties(properties);
+ return validateIncomingUpdatePayload(edge, version, payload);
+ }
+
+ public static Edge validateIncomingUpdatePayload(Edge edge, String version, EdgePayload payload)
+ throws CrudException {
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+
+ try {
+
+ if (payload.getSource() != null) {
+ Matcher sourceMatcher = urlPattern.matcher(payload.getSource());
+
+ if (!sourceMatcher.matches()) {
+ throw new CrudException("Invalid Target Urls", Status.BAD_REQUEST);
+ }
+ String sourceNodeId = sourceMatcher.group(3);
+ if (!sourceNodeId.equals(edge.getSource().getId().get())) {
+ throw new CrudException("Source can't be updated", Status.BAD_REQUEST);
+ }
+ }
+
+ if (payload.getTarget() != null) {
+ Matcher targetMatcher = urlPattern.matcher(payload.getTarget());
+
+ if (!targetMatcher.matches()) {
+ throw new CrudException("Invalid Target Urls", Status.BAD_REQUEST);
+ }
+ String sourceNodeId = targetMatcher.group(3);
+ if (!sourceNodeId.equals(edge.getTarget().getId().get())) {
+ throw new CrudException("Target can't be updated", Status.BAD_REQUEST);
+ }
+ }
+ // create key based on source:target:relationshipType
+
+ String key = edge.getSource().getType() + ":" + edge.getTarget().getType()
+ + ":" + edge.getType();
+
+ // find the validate the key from the schema
+ Map<String, Class<?>> schemaObject = schema.lookupRelation(key);
+
+ if (schemaObject == null) {
+ Logger.warn("key :" + key
+ + " not found in relationship schema . Skipping the schema validation");
+ return edge;
+ }
+
+ Edge.Builder updatedEdgeBuilder = new Edge.Builder(edge.getType()).id(edge.getId().get());
+
+ updatedEdgeBuilder
+ .source(new Vertex.Builder(edge.getSource().getType()).id(edge.getSource().getId()
+ .get()).build());
+ updatedEdgeBuilder
+ .target(new Vertex.Builder(edge.getTarget().getType()).id(edge.getTarget().getId()
+ .get()).build());
+
+ validateEdgeProps(updatedEdgeBuilder, payload.getProperties(), schemaObject);
+
+ return updatedEdgeBuilder.build();
+ } catch (Exception ex) {
+
+ throw new CrudException(ex.getMessage(), Status.BAD_REQUEST);
+ }
+ }
+
+
+ private static void validateEdgeProps(Edge.Builder builder, JsonElement props,
+ Map<String, Class<?>> schemaObject)
+ throws CrudException {
+ Set<Map.Entry<String, JsonElement>> entries = props.getAsJsonObject().entrySet();
+
+ for (Map.Entry<String, JsonElement> entry : entries) {
+
+ if (!schemaObject.containsKey(entry.getKey())) {
+ throw new CrudException("Invalid property: " + entry.getKey(), Status.BAD_REQUEST);
+ } else {
+ Object value = CrudServiceUtil.validateFieldType(entry.getValue().getAsString(),
+ schemaObject.get(entry.getKey()));
+ builder.property(entry.getKey(), value);
+ }
+
+ }
+
+ }
+
+ public static Edge validateOutgoingPayload(String version, Edge edge) throws CrudException {
+
+ Edge.Builder modelEdgeBuilder = new Edge.Builder(edge.getType()).id(edge.getId()
+ .get()).source(edge.getSource())
+ .target(edge.getTarget());
+
+ RelationshipSchema schema = RelationshipSchemaLoader.getSchemaForVersion(version);
+
+ String key = edge.getSource().getType() + ":" + edge.getTarget().getType()
+ + ":" + edge.getType();
+ Map<String, Class<?>> schemaObject = schema.lookupRelation(key);
+
+ if (schemaObject == null || schemaObject.isEmpty()) {
+ return edge;
+ }
+
+ for (String prop : edge.getProperties().keySet()) {
+ if (schemaObject.containsKey(prop)) {
+ modelEdgeBuilder.property(prop, edge.getProperties().get(prop));
+ }
+
+ }
+ return modelEdgeBuilder.build();
+ }
+
+
+ public static String vertexTypeFromUri(String uri) throws CrudException {
+
+ Matcher matcher = urlPattern.matcher(uri);
+
+ if (!matcher.matches()) {
+ throw new CrudException("Invalid Source/Target Urls", Status.BAD_REQUEST);
+ }
+
+ return matcher.group(2);
+ }
+}