/* * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.wso2.carbon.mediator.datamapper.engine.core.schemas; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.mediator.datamapper.engine.core.exceptions.InvalidPayloadException; import org.wso2.carbon.mediator.datamapper.engine.core.exceptions.SchemaException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineConstants.ARRAY_ELEMENT_TYPE; import static org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineConstants.OBJECT_ELEMENT_TYPE; import static org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineConstants.*; /** * This class implements {@link Schema} interface using Jackson JSON library to hold JSON schema */ public class JacksonJSONSchema implements Schema { private static final Log log = LogFactory.getLog(JacksonJSONSchema.class); private static final String EMPTY_STRING = ""; private static final String NAMESPACE_NAME_CONCAT_STRING = ":"; private Map jsonSchemaMap; private static final String PROPERTIES_KEY = "properties"; private static final String ATTRIBUTES_KEY = "attributes"; private static final String NAMESPACE_KEY = "namespaces"; private static final String PREFIX_KEY = "prefix"; private static final String URL_KEY = "url"; private static final String TYPE_KEY = "type"; private static final String TITLE_KEY = "title"; private static final String ITEMS_KEY = "items"; private Map<String, String> namespaceMap; private Map<String, String> prefixMap; private boolean currentArrayIsPrimitive; public JacksonJSONSchema(InputStream inputSchema) throws SchemaException { ObjectMapper objectMapper = new ObjectMapper(); try { jsonSchemaMap = objectMapper.readValue(inputSchema, Map.class); } catch (IOException e) { throw new SchemaException("Error while reading input stream. " + e.getMessage()); } initNamespaceMap(); } /** * populating name-space value map */ private void initNamespaceMap() { namespaceMap = new HashMap<>(); prefixMap = new HashMap<>(); ArrayList<Map> namespaceElementArray = (ArrayList<Map>) jsonSchemaMap.get(NAMESPACE_KEY); if (namespaceElementArray != null) { for (Map namespaceObject : namespaceElementArray) { String urlValue = (String) namespaceObject.get(URL_KEY); if (!namespaceMap.containsKey(urlValue)) { namespaceMap.put(urlValue, (String) namespaceObject.get(PREFIX_KEY)); } else { //multiple prefixes exists for this namespace URI. hence adding all prefixes that related to same // namespace separating by commas //adding comma separated prefix string is looks UGLY :-/, BUT can't help .... !, trying to do with //minimum impact existing mechanism namespaceMap.put(urlValue, (namespaceMap.get(urlValue) + PREFIX_LIST_SEPERATOR + namespaceObject.get(PREFIX_KEY))); } if (!prefixMap.containsKey((String) namespaceObject.get(PREFIX_KEY))) { prefixMap.put((String) namespaceObject.get(PREFIX_KEY), urlValue); } } } } @Override public String getName() throws SchemaException { String schemaName = (String) jsonSchemaMap.get(TITLE_KEY); if (schemaName != null) { return schemaName; } else { throw new SchemaException("Invalid WSO2 Data Mapper JSON input schema, schema name not found."); } } @Override public String getElementTypeByName(List<SchemaElement> elementStack) throws InvalidPayloadException, SchemaException { Map<String, Object> schema = jsonSchemaMap; String elementType = null; boolean elementFound = false; for (SchemaElement element : elementStack) { elementFound = false; String elementName = element.getElementName(); String elementNamespace = element.getNamespace(); if (elementName.equals(getName())) { schema = (Map<String, Object>) jsonSchemaMap.get(PROPERTIES_KEY); elementType = (String) jsonSchemaMap.get(TYPE_KEY); if (ARRAY_ELEMENT_TYPE.equals(elementType)) { setCurrentArrayType(schema, elementName); } elementFound = true; } else if (schema.containsKey(elementName)) { elementType = (String) ((Map<String, Object>) schema.get(elementName)).get(TYPE_KEY); if (ARRAY_ELEMENT_TYPE.equals(elementType)) { setCurrentArrayType(schema, elementName); schema = getSchemaItems((Map<String, Object>) schema.get(elementName)); schema = getSchemaProperties(schema); } else if (OBJECT_ELEMENT_TYPE.equals(elementType)) { schema = getSchemaProperties((Map<String, Object>) schema.get(elementName)); } else { schema = getSchemaProperties((Map<String, Object>) schema.get(elementName)); } elementFound = true; } if (!elementFound) { elementType = NULL_ELEMENT_TYPE; log.warn("Element name not found : " + elementName); } } return elementType; } private void setCurrentArrayType(Map<String, Object> schema, String elementName) { Map<String, Object> tempSchema = getSchemaItems((Map<String, Object>) schema.get(elementName)); if (OBJECT_ELEMENT_TYPE.equals(tempSchema.get(TYPE_KEY))) { currentArrayIsPrimitive = false; } else if (STRING_ELEMENT_TYPE.equals(tempSchema.get(TYPE_KEY)) || BOOLEAN_ELEMENT_TYPE .equals(tempSchema.get(TYPE_KEY)) || NUMBER_ELEMENT_TYPE.equals(tempSchema.get(TYPE_KEY)) || INTEGER_ELEMENT_TYPE.equals(tempSchema.get(TYPE_KEY))) { currentArrayIsPrimitive = true; } } public String getElementTypeByName(String elementName) throws SchemaException { String elementType = null; Map<String, Object> properties = getSchemaProperties(jsonSchemaMap); if (getName().equals(elementName)) { return (String) jsonSchemaMap.get(TYPE_KEY); } else if (properties.containsKey(elementName)) { return getSchemaType((Map<String, Object>) properties.get(elementName)); } else { Iterator<Map.Entry<String, Object>> entryIterator = properties.entrySet().iterator(); while (entryIterator.hasNext()) { Map<String, Object> subSchema = (Map<String, Object>) entryIterator.next().getValue(); String schemaType = getSchemaType(subSchema); if (OBJECT_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementTypeByName(elementName, subSchema); } else if (ARRAY_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementTypeByName(elementName, getSchemaItems(subSchema)); } if (elementType != null) { return elementType; } } return null; } } @Override public boolean isChildElement(String elementName, String childElementName) { Map<String, Object> elementSchema = getElementSchemaByName(elementName, jsonSchemaMap); if (elementSchema.containsKey(PROPERTIES_KEY)) { if (getSchemaProperties(elementSchema).containsKey(childElementName)) { return true; } } else { if (((Map<String, Object>) getSchemaItems(elementSchema).get(PROPERTIES_KEY)) .containsKey(childElementName)) { return true; } } return false; } @Override public boolean isChildElement(List<SchemaElement> elementStack, String childElementName) throws InvalidPayloadException, SchemaException { Map<String, Object> elementSchema = getElementSchemaByName(elementStack, jsonSchemaMap); if (elementSchema.containsKey(PROPERTIES_KEY)) { if (getSchemaProperties(elementSchema).containsKey(childElementName)) { return true; } } else if (elementSchema.containsKey(ITEMS_KEY)) { if (((Map<String, Object>) getSchemaItems(elementSchema).get(PROPERTIES_KEY)) .containsKey(childElementName)) { return true; } } else { if (elementSchema.containsKey(childElementName)) { return true; } } return false; } public Map<String, String> getNamespaceMap() { return namespaceMap; } public Map<String, String> getPrefixMap() { return prefixMap; } private Map<String, Object> getElementSchemaByName(List<SchemaElement> elementStack, Map<String, Object> schema) throws InvalidPayloadException, SchemaException { Map<String, Object> tempSchema = schema; String elementType = null; for (SchemaElement element : elementStack) { String elementName = element.getElementName(); String elementNamespace = element.getNamespace(); elementName = getNamespaceAddedFieldName(elementNamespace, elementName); if (elementName.equals(getName())) { tempSchema = (Map<String, Object>) jsonSchemaMap.get(PROPERTIES_KEY); elementType = (String) jsonSchemaMap.get(TYPE_KEY); } else if (tempSchema.containsKey(elementName)) { elementType = (String) ((Map<String, Object>) tempSchema.get(elementName)).get(TYPE_KEY); if (ARRAY_ELEMENT_TYPE.equals(elementType)) { tempSchema = getSchemaItems((Map<String, Object>) tempSchema.get(elementName)); tempSchema = getSchemaProperties(tempSchema); } else if (OBJECT_ELEMENT_TYPE.equals(elementType)) { tempSchema = getSchemaProperties((Map<String, Object>) tempSchema.get(elementName)); } else { tempSchema = getSchemaProperties((Map<String, Object>) tempSchema.get(elementName)); } } } return tempSchema; } private Map<String, Object> getElementSchemaByName(String elementName, Map<String, Object> schema) { Map<String, Object> elementType = null; Map<String, Object> properties = getSchemaProperties(schema); if (properties.containsKey(elementName)) { return (Map<String, Object>) properties.get(elementName); } else { Iterator<Map.Entry<String, Object>> entryIterator = properties.entrySet().iterator(); while (entryIterator.hasNext()) { Map<String, Object> subSchema = (Map<String, Object>) entryIterator.next().getValue(); String schemaType = getSchemaType(subSchema); if (OBJECT_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementSchemaByName(elementName, subSchema); } else if (ARRAY_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementSchemaByName(elementName, getSchemaItems(subSchema)); } if (elementType != null) { return elementType; } } return null; } } private String getElementTypeByName(String elementName, Map<String, Object> schema) { String elementType = null; Map<String, Object> properties = getSchemaProperties(schema); if (properties.containsKey(elementName)) { return getSchemaType((Map<String, Object>) properties.get(elementName)); } else { Iterator<Map.Entry<String, Object>> entryIterator = properties.entrySet().iterator(); while (entryIterator.hasNext()) { Map<String, Object> subSchema = (Map<String, Object>) entryIterator.next().getValue(); String schemaType = getSchemaType(subSchema); if (OBJECT_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementTypeByName(elementName, subSchema); } else if (ARRAY_ELEMENT_TYPE.equals(schemaType)) { elementType = getElementTypeByName(elementName, getSchemaItems(subSchema)); } else { elementType = getElementTypeByName(elementName, subSchema); } if (elementType != null) { return elementType; } } } return null; } private Map<String, Object> getSchemaProperties(Map<String, Object> schema) { Map<String, Object> nextSchema = new HashMap<>(); if (schema.containsKey(PROPERTIES_KEY)) { nextSchema.putAll((Map<? extends String, Object>) schema.get(PROPERTIES_KEY)); } if (schema.containsKey(ATTRIBUTES_KEY)) { nextSchema.putAll((Map<? extends String, Object>) schema.get(ATTRIBUTES_KEY)); } return nextSchema; } public Map<String, Object> getSchemaItems(Map<String, Object> schema) { Map<String, Object> nextSchema = new HashMap<>(); if (schema.containsKey(ITEMS_KEY)) { Object propertyList = schema.get(ITEMS_KEY); if (propertyList instanceof Map) { nextSchema.putAll((Map<? extends String, Object>) propertyList); if (nextSchema.containsKey(ATTRIBUTES_KEY)) { nextSchema.putAll((Map<? extends String, Object>) nextSchema.get(ATTRIBUTES_KEY)); } } else { nextSchema.putAll((Map<? extends String, Object>) ((ArrayList) propertyList).get(0)); } } else { throw new IllegalArgumentException("Given schema does not contain value under key : " + ITEMS_KEY); } if (schema.containsKey(ATTRIBUTES_KEY)) { nextSchema.putAll((Map<? extends String, Object>) schema.get(ATTRIBUTES_KEY)); } return nextSchema; } private String getSchemaType(Map<String, Object> schema) { if (schema.containsKey(TYPE_KEY)) { Object type = schema.get(TYPE_KEY); if (type instanceof String) { return (String) type; } else { throw new IllegalArgumentException("Illegal format " + type.getClass() + " value found under key : " + TYPE_KEY); } } else { throw new IllegalArgumentException("Given schema does not contain value under key : " + TYPE_KEY); } } @Override public String getPrefixForNamespace(String url) { if (EMPTY_STRING.equals(url)) { return EMPTY_STRING; } else if (namespaceMap.containsKey(url)) { return namespaceMap.get(url); } else { return null; } } @Override public boolean isCurrentArrayIsPrimitive() { return currentArrayIsPrimitive; } private String getNamespaceAddedFieldName(String uri, String localName) throws InvalidPayloadException { if (uri != null) { String prefix = getPrefixForNamespace(uri); if (StringUtils.isNotEmpty(prefix)) { return prefix + NAMESPACE_NAME_CONCAT_STRING + localName; } else if (prefix != null) { return localName; } else { throw new InvalidPayloadException(uri + " name-space is not defined in the schema with element " + localName); } } return localName; } @Override public Map getSchemaMap() { return jsonSchemaMap; } }