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