b36e008992bd8d27cc2ea657805f82f1d91eb9ad
[policy/apex-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2016-2018 Ericsson. All rights reserved.
4  *  Modifications Copyright (C) 2019 Nordix Foundation.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.apex.context.impl.schema.java;
23
24 import com.google.gson.Gson;
25 import com.google.gson.GsonBuilder;
26 import com.google.gson.JsonElement;
27
28 import java.lang.reflect.Constructor;
29 import java.util.HashMap;
30 import java.util.Map;
31
32 import org.onap.policy.apex.context.ContextRuntimeException;
33 import org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper;
34 import org.onap.policy.apex.context.parameters.ContextParameterConstants;
35 import org.onap.policy.apex.context.parameters.SchemaParameters;
36 import org.onap.policy.apex.model.basicmodel.concepts.AxKey;
37 import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema;
38 import org.onap.policy.apex.model.utilities.typeutils.TypeBuilder;
39 import org.onap.policy.common.parameters.ParameterService;
40 import org.slf4j.ext.XLogger;
41 import org.slf4j.ext.XLoggerFactory;
42
43 /**
44  * This class implements translation to and from Apex distributed objects and Java objects when a Java schema is used.
45  * It creates schema items as Java objects and marshals and unmarshals these objects in various formats. All objects
46  * must be of the type of Java class defined in the schema.
47  *
48  * @author Liam Fallon (liam.fallon@ericsson.com)
49  */
50 public class JavaSchemaHelper extends AbstractSchemaHelper {
51     // Get a reference to the logger
52     private static final XLogger LOGGER = XLoggerFactory.getXLogger(JavaSchemaHelper.class);
53
54     // This map defines the built in types in types in Java
55     // @formatter:off
56     private static final Map<String, Class<?>> BUILT_IN_MAP = new HashMap<>();
57
58     static {
59         BUILT_IN_MAP.put("int",    Integer  .TYPE);
60         BUILT_IN_MAP.put("long",   Long     .TYPE);
61         BUILT_IN_MAP.put("double", Double   .TYPE);
62         BUILT_IN_MAP.put("float",  Float    .TYPE);
63         BUILT_IN_MAP.put("bool",   Boolean  .TYPE);
64         BUILT_IN_MAP.put("char",   Character.TYPE);
65         BUILT_IN_MAP.put("byte",   Byte     .TYPE);
66         BUILT_IN_MAP.put("void",   Void     .TYPE);
67         BUILT_IN_MAP.put("short",  Short    .TYPE);
68     }
69     // @formatter:on
70
71     /**
72      * {@inheritDoc}.
73      */
74     @Override
75     public void init(final AxKey userKey, final AxContextSchema schema) {
76         super.init(userKey, schema);
77
78         final String javatype = schema.getSchema();
79         // For Java, the schema is the Java class canonical path
80
81         try {
82             setSchemaClass(TypeBuilder.getJavaTypeClass(schema.getSchema()));
83         } catch (final IllegalArgumentException e) {
84
85             String resultSting = userKey.getId() + ": class/type " + schema.getSchema() + " for context schema \""
86                             + schema.getId() + "\" not found.";
87             if (JavaSchemaHelper.BUILT_IN_MAP.get(javatype) != null) {
88                 resultSting += " Primitive types are not supported. Use the appropriate Java boxing type instead.";
89             } else {
90                 resultSting += " Check the class path of the JVM";
91             }
92             LOGGER.warn(resultSting);
93             throw new ContextRuntimeException(resultSting, e);
94         }
95     }
96
97     /**
98      * {@inheritDoc}.
99      */
100     @Override
101     public Object createNewInstance(final Object incomingObject) {
102         if (incomingObject == null) {
103             return null;
104         }
105
106         if (getSchemaClass() == null) {
107             final String returnString = getUserKey().getId()
108                             + ": could not create an instance, schema class for the schema is null";
109             LOGGER.warn(returnString);
110             throw new ContextRuntimeException(returnString);
111         }
112
113         if (incomingObject instanceof JsonElement) {
114             final String elementJsonString = getGson().toJson((JsonElement) incomingObject);
115             return getGson().fromJson(elementJsonString, this.getSchemaClass());
116         }
117
118         if (getSchemaClass().isAssignableFrom(incomingObject.getClass())) {
119             return incomingObject;
120         }
121
122         final String returnString = getUserKey().getId() + ": the object \"" + incomingObject + "\" of type \""
123                         + incomingObject.getClass().getName()
124                         + "\" is not an instance of JsonObject and is not assignable to \"" + getSchemaClass().getName()
125                         + "\"";
126         LOGGER.warn(returnString);
127         throw new ContextRuntimeException(returnString);
128     }
129
130     /**
131      * {@inheritDoc}.
132      */
133     @Override
134     public Object unmarshal(final Object object) {
135         if (object == null) {
136             return null;
137         }
138
139         // If the object is an instance of the incoming object, carry on
140         if (object.getClass().equals(getSchemaClass())) {
141             return object;
142         }
143
144         // For numeric types, do a numeric conversion
145         if (Number.class.isAssignableFrom(getSchemaClass())) {
146             return numericConversion(object);
147         }
148
149         if (getSchemaClass().isAssignableFrom(object.getClass())) {
150             return object;
151         } else {
152             return stringConversion(object);
153         }
154     }
155
156     /**
157      * {@inheritDoc}.
158      */
159     @Override
160     public String marshal2String(final Object schemaObject) {
161         if (schemaObject == null) {
162             return "null";
163         }
164
165         // Check the incoming object is of a correct class
166         if (getSchemaClass().isAssignableFrom(schemaObject.getClass())) {
167             // Use Gson to translate the object
168             return getGson().toJson(schemaObject);
169         } else {
170             final String returnString = getUserKey().getId() + ": object \"" + schemaObject.toString()
171                             + "\" of class \"" + schemaObject.getClass().getName() + "\" not compatible with class \""
172                             + getSchemaClass().getName() + "\"";
173             LOGGER.warn(returnString);
174             throw new ContextRuntimeException(returnString);
175         }
176     }
177
178     /**
179      * {@inheritDoc}.
180      */
181     @Override
182     public Object marshal2Object(final Object schemaObject) {
183         // Use Gson to marshal the schema object into a Json element to return
184         return getGson().toJsonTree(schemaObject, getSchemaClass());
185     }
186
187     /**
188      * Do a numeric conversion between numeric types.
189      *
190      * @param object The incoming numeric object
191      * @return The converted object
192      */
193     private Object numericConversion(final Object object) {
194         // Check if the incoming object is a number, if not do a string conversion
195         if (object instanceof Number) {
196             if (getSchemaClass().isAssignableFrom(Byte.class)) {
197                 return ((Number) object).byteValue();
198             } else if (getSchemaClass().isAssignableFrom(Short.class)) {
199                 return ((Number) object).shortValue();
200             } else if (getSchemaClass().isAssignableFrom(Integer.class)) {
201                 return ((Number) object).intValue();
202             } else if (getSchemaClass().isAssignableFrom(Long.class)) {
203                 return ((Number) object).longValue();
204             } else if (getSchemaClass().isAssignableFrom(Float.class)) {
205                 return ((Number) object).floatValue();
206             } else if (getSchemaClass().isAssignableFrom(Double.class)) {
207                 return ((Number) object).doubleValue();
208             }
209         }
210
211         // OK, we'll try and convert from a string representation of the incoming object
212         return stringConversion(object);
213     }
214
215     /**
216      * Do a string conversion to the class type.
217      *
218      * @param object The incoming numeric object
219      * @return The converted object
220      */
221     private Object stringConversion(final Object object) {
222         // OK, we'll try and convert from a string representation of the incoming object
223         try {
224             final Constructor<?> stringConstructor = getSchemaClass().getConstructor(String.class);
225             return stringConstructor.newInstance(object.toString());
226         } catch (final Exception e) {
227             final String returnString = getUserKey().getId() + ": object \"" + object.toString() + "\" of class \""
228                             + object.getClass().getName() + "\" not compatible with class \""
229                             + getSchemaClass().getName() + "\"";
230             LOGGER.warn(returnString, e);
231             throw new ContextRuntimeException(returnString);
232         }
233     }
234
235     /**
236      * Get a GSON instance that has the correct adaptation included.
237      *
238      * @return the GSON instance
239      */
240     private Gson getGson() {
241         GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting();
242
243         // Get the Java schema helper parameters from the parameter service
244         SchemaParameters schemaParameters = ParameterService.get(ContextParameterConstants.SCHEMA_GROUP_NAME);
245
246         JavaSchemaHelperParameters javaSchemaHelperParmeters = (JavaSchemaHelperParameters) schemaParameters
247                         .getSchemaHelperParameterMap().get("Java");
248
249         if (javaSchemaHelperParmeters == null) {
250             javaSchemaHelperParmeters = new JavaSchemaHelperParameters();
251         }
252
253         for (JavaSchemaHelperJsonAdapterParameters jsonAdapterEntry : javaSchemaHelperParmeters.getJsonAdapters()
254                         .values()) {
255
256             Object adapterObject;
257             try {
258                 adapterObject = jsonAdapterEntry.getAdaptorClazz().newInstance();
259             } catch (InstantiationException | IllegalAccessException e) {
260                 final String returnString = getUserKey().getId() + ": instantiation of adapter class \""
261                                 + jsonAdapterEntry.getAdaptorClass() + "\"  to decode and encode class \""
262                                 + jsonAdapterEntry.getAdaptedClass() + "\" failed: " + e.getMessage();
263                 LOGGER.warn(returnString, e);
264                 throw new ContextRuntimeException(returnString);
265             }
266
267             gsonBuilder.registerTypeAdapter(jsonAdapterEntry.getAdaptedClazz(), adapterObject);
268         }
269
270         return gsonBuilder.create();
271     }
272 }