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