/* * 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.output.formatters; import org.wso2.carbon.mediator.datamapper.engine.core.exceptions.JSException; import org.wso2.carbon.mediator.datamapper.engine.core.exceptions.SchemaException; import org.wso2.carbon.mediator.datamapper.engine.core.exceptions.WriterException; import org.wso2.carbon.mediator.datamapper.engine.core.models.Model; import org.wso2.carbon.mediator.datamapper.engine.core.schemas.Schema; import org.wso2.carbon.mediator.datamapper.engine.input.readers.events.ReaderEvent; import org.wso2.carbon.mediator.datamapper.engine.input.readers.events.ReaderEventType; import org.wso2.carbon.mediator.datamapper.engine.output.OutputMessageBuilder; import org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineUtils; import java.util.ArrayList; import java.util.LinkedList; import java.util.Map; import java.util.Set; import static org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineConstants.SCHEMA_ATTRIBUTE_FIELD_PREFIX; import static org.wso2.carbon.mediator.datamapper.engine.utils.DataMapperEngineConstants.SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX; /** * This class implements {@link Formatter} interface to read {@link Map} model and trigger events * to read * by {@link OutputMessageBuilder} */ public class MapOutputFormatter implements Formatter { public static final String RHINO_NATIVE_ARRAY_FULL_QUALIFIED_CLASS_NAME = "sun.org.mozilla.javascript.internal.NativeArray"; private OutputMessageBuilder outputMessageBuilder; private Schema outputSchema; @Override public void format(Model model, OutputMessageBuilder outputMessageBuilder, Schema outputSchema) throws SchemaException, WriterException { if (model.getModel() instanceof Map) { this.outputMessageBuilder = outputMessageBuilder; this.outputSchema = outputSchema; Map<Object, Object> mapOutputModel = (Map<Object, Object>) model.getModel(); traverseMap(mapOutputModel); sendTerminateEvent(); } else { throw new IllegalArgumentException("Illegal model passed to MapOutputFormatter : " + model.getModel()); } } /** * This method traverse output variable represented as a map in a depth first traverse * recursively to trigger events * to build output message in {@link OutputMessageBuilder} * * @param outputMap */ private void traverseMap(Map<Object, Object> outputMap) throws SchemaException, WriterException { Set<Object> mapKeys = outputMap.keySet(); LinkedList<Object> orderedKeyList = new LinkedList<>(); boolean arrayType = false; if (isMapContainArray(mapKeys)) { sendArrayStartEvent(); arrayType = true; } ArrayList<Object> tempKeys = new ArrayList<>(); tempKeys.addAll(mapKeys); //Attributes should come first than other fields. So attribute should be listed first for (Object keyVal : mapKeys) { if(keyVal instanceof String) { String key= (String) keyVal; if (key.contains(SCHEMA_ATTRIBUTE_FIELD_PREFIX) && tempKeys.contains(key)) { orderedKeyList.addFirst(key); tempKeys.remove(key); } else { if (key.endsWith(SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX) && tempKeys.contains(key)) { String elementName = key.substring(0, key.lastIndexOf(SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX)); orderedKeyList.addLast(key); orderedKeyList.addLast(elementName); tempKeys.remove(key); tempKeys.remove(elementName); } else if (tempKeys.contains(key)) { if (tempKeys.contains(key + SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX)) { orderedKeyList.addLast(key + SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX); orderedKeyList.addLast(key); tempKeys.remove(key); tempKeys.remove(key + SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX); } else { orderedKeyList.addLast(key); tempKeys.remove(key); } } } }else if(keyVal instanceof Integer){ if (tempKeys.contains(keyVal)) { orderedKeyList.addLast(((Integer)keyVal).intValue()); tempKeys.remove(keyVal); } } } int mapKeyIndex = 0; for (Object keyVal : orderedKeyList) { Object value = outputMap.get(keyVal); String key = String.valueOf(keyVal); // When Data Mapper runs in Java 7 array element is given as a Native Array object. // This array object doesn't give values inside. That's why we used reflections in here if (value != null && value.getClass().toString().contains(RHINO_NATIVE_ARRAY_FULL_QUALIFIED_CLASS_NAME)) { try { value = DataMapperEngineUtils.getMapFromNativeArray(value); } catch (JSException e) { throw new WriterException(e.getMessage(),e); } } if (value instanceof Map) { // key value is a type of object or an array if (arrayType) { /*If it is array type we need to compensate the already created object start element. So avoid create another start element in first array element and endElement in the last */ if (mapKeyIndex != 0) { sendAnonymousObjectStartEvent(); } traverseMap((Map<Object, Object>) value); if (mapKeyIndex != mapKeys.size() - 1) { sendObjectEndEvent(key); } } else { sendObjectStartEvent(key); traverseMap((Map<Object, Object>) value); if (!key.endsWith(SCHEMA_ATTRIBUTE_PARENT_ELEMENT_POSTFIX)) { sendObjectEndEvent(key); } } } else { // Primitive value received to write if(arrayType){ // if it is an array of primitive values if (mapKeyIndex != 0) { sendAnonymousObjectStartEvent(); } sendPrimitiveEvent(key, value); if (mapKeyIndex != mapKeys.size() - 1) { sendObjectEndEvent(key); } } else { // if field value sendFieldEvent(key, value); } } mapKeyIndex++; } if (arrayType) { sendArrayEndEvent(); } } private void sendPrimitiveEvent(String key, Object value) throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.PRIMITIVE, key, value)); } private void sendAnonymousObjectStartEvent() throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.ANONYMOUS_OBJECT_START, null, null)); } private void sendArrayEndEvent() throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.ARRAY_END, null, null)); } private boolean isMapContainArray(Set<Object> mapKeys) { for (Object key : mapKeys) { try { if(key instanceof String) { Integer.parseInt((String) key); continue; }else if(key instanceof Integer){ continue; } } catch (NumberFormatException e) { return false; } } return true; } private void sendArrayStartEvent() throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.ARRAY_START, null, null)); } private void sendObjectStartEvent(String elementName) throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.OBJECT_START, elementName, null)); } private void sendObjectEndEvent(String objectName) throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.OBJECT_END, objectName, null)); } private void sendFieldEvent(String fieldName, Object value) throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.FIELD, fieldName, value)); } private void sendTerminateEvent() throws SchemaException, WriterException { getOutputMessageBuilder().notifyEvent(new ReaderEvent(ReaderEventType.TERMINATE, null, null)); } public OutputMessageBuilder getOutputMessageBuilder() { return outputMessageBuilder; } }