From 3db2feb37ac427a09790fef1ba637c16c3187ed6 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Wed, 26 Sep 2018 18:30:37 +0100 Subject: Allow custom JSON adapter specification In order to decode and encode complex Java POJOs with GSON, type adapters must be specified. This change allows specification of GSON type adapters for decoding and encoding of JSON in apex. Issue-ID: POLICY-954 Change-Id: Ib402d4e82c4f22fa4d532c016f77fb8e7bb568d3 Signed-off-by: liamfallon --- .../context/impl/schema/java/JavaSchemaHelper.java | 83 ++++++--- .../JavaSchemaHelperJsonAdapterParameters.java | 199 +++++++++++++++++++++ .../schema/java/JavaSchemaHelperParameters.java | 44 ++++- .../apex/context/parameters/ContextParameters.java | 44 +++-- .../apex/context/parameters/SchemaParameters.java | 21 ++- 5 files changed, 343 insertions(+), 48 deletions(-) create mode 100644 context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperJsonAdapterParameters.java (limited to 'context/context-management/src/main') diff --git a/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelper.java b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelper.java index 7a903e9d1..8b61f718a 100644 --- a/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelper.java +++ b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelper.java @@ -21,6 +21,7 @@ package org.onap.policy.apex.context.impl.schema.java; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import java.lang.reflect.Constructor; @@ -29,16 +30,19 @@ import java.util.Map; import org.onap.policy.apex.context.ContextRuntimeException; import org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.SchemaParameters; import org.onap.policy.apex.model.basicmodel.concepts.AxKey; import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema; import org.onap.policy.apex.model.utilities.typeutils.TypeBuilder; +import org.onap.policy.common.parameters.ParameterService; import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; /** - * This class implements translation to and from Apex distributed objects and Java objects when a - * Java schema is used. It creates schema items as Java objects and marshals and unmarshals these - * objects in various formats. All objects must be of the type of Java class defined in the schema. + * This class implements translation to and from Apex distributed objects and Java objects when a Java schema is used. + * It creates schema items as Java objects and marshals and unmarshals these objects in various formats. All objects + * must be of the type of Java class defined in the schema. * * @author Liam Fallon (liam.fallon@ericsson.com) */ @@ -66,10 +70,8 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { /* * (non-Javadoc) * - * @see - * org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper#init(org.onap.policy.apex.model - * .basicmodel. concepts. AxKey, - * org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema) + * @see org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper#init(org.onap.policy.apex.model .basicmodel. + * concepts. AxKey, org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema) */ @Override public void init(final AxKey userKey, final AxContextSchema schema) { @@ -83,7 +85,7 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { } catch (final IllegalArgumentException e) { String resultSting = userKey.getId() + ": class/type " + schema.getSchema() + " for context schema \"" - + schema.getId() + "\" not found."; + + schema.getId() + "\" not found."; if (JavaSchemaHelper.BUILT_IN_MAP.get(javatype) != null) { resultSting += " Primitive types are not supported. Use the appropriate Java boxing type instead."; } else { @@ -106,15 +108,15 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { } if (getSchemaClass() == null) { - final String returnString = - getUserKey().getId() + ": could not create an instance, schema class for the schema is null"; + final String returnString = getUserKey().getId() + + ": could not create an instance, schema class for the schema is null"; LOGGER.warn(returnString); throw new ContextRuntimeException(returnString); } if (incomingObject instanceof JsonElement) { - final String elementJsonString = new Gson().toJson((JsonElement) incomingObject); - return new Gson().fromJson(elementJsonString, this.getSchemaClass()); + final String elementJsonString = getGson().toJson((JsonElement) incomingObject); + return getGson().fromJson(elementJsonString, this.getSchemaClass()); } if (getSchemaClass().isAssignableFrom(incomingObject.getClass())) { @@ -122,9 +124,9 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { } final String returnString = getUserKey().getId() + ": the object \"" + incomingObject + "\" of type \"" - + incomingObject.getClass().getCanonicalName() - + "\" is not an instance of JsonObject and is not assignable to \"" - + getSchemaClass().getCanonicalName() + "\""; + + incomingObject.getClass().getCanonicalName() + + "\" is not an instance of JsonObject and is not assignable to \"" + + getSchemaClass().getCanonicalName() + "\""; LOGGER.warn(returnString); throw new ContextRuntimeException(returnString); } @@ -171,11 +173,11 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { // Check the incoming object is of a correct class if (getSchemaClass().isAssignableFrom(schemaObject.getClass())) { // Use Gson to translate the object - return new Gson().toJson(schemaObject); + return getGson().toJson(schemaObject); } else { final String returnString = getUserKey().getId() + ": object \"" + schemaObject.toString() - + "\" of class \"" + schemaObject.getClass().getCanonicalName() + "\" not compatible with class \"" - + getSchemaClass().getCanonicalName() + "\""; + + "\" of class \"" + schemaObject.getClass().getCanonicalName() + + "\" not compatible with class \"" + getSchemaClass().getCanonicalName() + "\""; LOGGER.warn(returnString); throw new ContextRuntimeException(returnString); } @@ -189,7 +191,7 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { @Override public Object marshal2Object(final Object schemaObject) { // Use Gson to marshal the schema object into a Json element to return - return new Gson().toJsonTree(schemaObject, getSchemaClass()); + return getGson().toJsonTree(schemaObject, getSchemaClass()); } /** @@ -233,10 +235,49 @@ public class JavaSchemaHelper extends AbstractSchemaHelper { return stringConstructor.newInstance(object.toString()); } catch (final Exception e) { final String returnString = getUserKey().getId() + ": object \"" + object.toString() + "\" of class \"" - + object.getClass().getCanonicalName() + "\" not compatible with class \"" - + getSchemaClass().getCanonicalName() + "\""; + + object.getClass().getCanonicalName() + "\" not compatible with class \"" + + getSchemaClass().getCanonicalName() + "\""; LOGGER.warn(returnString, e); throw new ContextRuntimeException(returnString); } } + + /** + * Get a GSON instance that has the correct adaptation included. + * + * @return the GSON instance + */ + private Gson getGson() { + GsonBuilder gsonBuilder = new GsonBuilder(); + + // Get the Java schema helper parameters from the parameter service + SchemaParameters schemaParameters = ParameterService.get(ContextParameterConstants.SCHEMA_GROUP_NAME); + + JavaSchemaHelperParameters javaSchemaHelperParmeters = (JavaSchemaHelperParameters) schemaParameters + .getSchemaHelperParameterMap().get("Java"); + + if (javaSchemaHelperParmeters == null) { + javaSchemaHelperParmeters = new JavaSchemaHelperParameters(); + } + + for (JavaSchemaHelperJsonAdapterParameters jsonAdapterEntry : javaSchemaHelperParmeters.getJsonAdapters() + .values()) { + + Object adapterObject; + try { + adapterObject = jsonAdapterEntry.getAdaptorClazz().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + final String returnString = getUserKey().getId() + ": instantiation of adapter class \"" + + jsonAdapterEntry.getAdaptorClass() + "\" to decode and encode class \"" + + jsonAdapterEntry.getAdaptedClass() + "\" failed: " + e.getMessage(); + LOGGER.warn(returnString, e); + throw new ContextRuntimeException(returnString); + } + + gsonBuilder.registerTypeAdapter(jsonAdapterEntry.getAdaptedClazz(), adapterObject); + } + + return gsonBuilder.create(); + } + } diff --git a/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperJsonAdapterParameters.java b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperJsonAdapterParameters.java new file mode 100644 index 000000000..ddd14cf21 --- /dev/null +++ b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperJsonAdapterParameters.java @@ -0,0 +1,199 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.context.impl.schema.java; + +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializer; + +import org.onap.policy.common.parameters.GroupValidationResult; +import org.onap.policy.common.parameters.ParameterGroup; +import org.onap.policy.common.parameters.ValidationStatus; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; + +//@formatter:off +/** + * Event protocol parameters for JSON as an event protocol. + * + *

The parameters for this plugin are: + *

    + *
  1. adaptedClass: The name of the class being adapted. + *
  2. adapterClass: the JSON adapter class to use for the adapted class. + *
+ * + * @author Liam Fallon (liam.fallon@ericsson.com) + */ +//@formatter:on +public class JavaSchemaHelperJsonAdapterParameters implements ParameterGroup { + private static final XLogger LOGGER = XLoggerFactory.getXLogger(JavaSchemaHelperJsonAdapterParameters.class); + + // Recurring string constants + private static final String ADAPTED_CLASS = "adaptedClass"; + private static final String ADAPTOR_CLASS = "adaptorClass"; + + private String adaptedClass; + private String adaptorClass; + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return getAdaptedClass(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setName(String adaptedClass) { + setAdaptedClass(adaptedClass); + } + + /** + * Gets the adapted class. + * + * @return the adapted class + */ + public String getAdaptedClass() { + return adaptedClass; + } + + /** + * Gets the adapted class. + * + * @return the adapted class + */ + public Class getAdaptedClazz() { + if (adaptedClass == null) { + return null; + } + + try { + return Class.forName(adaptedClass); + } catch (final ClassNotFoundException e) { + LOGGER.warn("class \"" + adaptedClass + "\" not found: ", e); + return null; + } + } + + /** + * Sets the adapted class. + * + * @param adaptedClass the new adapted class + */ + public void setAdaptedClass(String adaptedClass) { + this.adaptedClass = adaptedClass; + } + + /** + * Gets the adaptor class. + * + * @return the adaptor class + */ + public String getAdaptorClass() { + return adaptorClass; + } + + /** + * Gets the adaptor class. + * + * @return the adaptor class + */ + public Class getAdaptorClazz() { + if (adaptorClass == null) { + return null; + } + + try { + return Class.forName(adaptorClass); + } catch (final ClassNotFoundException e) { + LOGGER.warn("class \"" + adaptorClass + "\" not found: ", e); + return null; + } + } + + /** + * Sets the adaptor class. + * + * @param adaptorClass the new adaptor class + */ + public void setAdaptorClass(String adaptorClass) { + this.adaptorClass = adaptorClass; + } + + /** + * {@inheritDoc} + */ + @Override + public GroupValidationResult validate() { + final GroupValidationResult result = new GroupValidationResult(this); + + getClass(ADAPTED_CLASS, adaptedClass, result); + + Class adaptorClazz = getClass(ADAPTOR_CLASS, adaptorClass, result); + if (adaptorClazz != null) { + String errorMessage = null; + + if (!JsonSerializer.class.isAssignableFrom(adaptorClazz)) { + errorMessage = "class is not a JsonSerializer"; + } + + if (!JsonDeserializer.class.isAssignableFrom(adaptorClazz)) { + if (errorMessage == null) { + errorMessage = "class is not a JsonDeserializer"; + } + else { + errorMessage = "class is not a JsonSerializer or JsonDeserializer"; + } + } + + if (errorMessage != null) { + result.setResult(ADAPTOR_CLASS, ValidationStatus.INVALID, errorMessage); + } + } + + return result; + } + + /** + * Check a class exists. + * + * @param parameterName the parameter name of the class to check for existence + * @param classToCheck the class to check for existence + * @param result the result of the check + */ + private Class getClass(String parameterName, String classToCheck, final GroupValidationResult result) { + if (classToCheck == null || classToCheck.trim().length() == 0) { + result.setResult(parameterName, ValidationStatus.INVALID, "parameter is null or blank"); + return null; + } + + // Get the class for the event protocol + try { + return Class.forName(classToCheck); + } catch (final ClassNotFoundException e) { + result.setResult(parameterName, ValidationStatus.INVALID, "class not found: " + e.getMessage()); + LOGGER.warn("class not found: ", e); + return null; + } + } +} diff --git a/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperParameters.java b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperParameters.java index 18339d8db..01a5df20b 100644 --- a/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperParameters.java +++ b/context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperParameters.java @@ -20,7 +20,12 @@ package org.onap.policy.apex.context.impl.schema.java; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + import org.onap.policy.apex.context.parameters.SchemaHelperParameters; +import org.onap.policy.common.parameters.GroupValidationResult; /** * The Schema helper parameter class for the Java schema helper is an empty parameter class that acts as a placeholder. @@ -28,11 +33,48 @@ import org.onap.policy.apex.context.parameters.SchemaHelperParameters; * @author Liam Fallon (liam.fallon@ericsson.com) */ public class JavaSchemaHelperParameters extends SchemaHelperParameters { + // Map of specific type adapters for this event + private Map jsonAdapters = new LinkedHashMap<>(); /** - * The Constructor. + * Constructor for Java schema helper parameters. */ public JavaSchemaHelperParameters() { + this.setName("Java"); this.setSchemaHelperPluginClass(JavaSchemaHelper.class.getCanonicalName()); } + + /** + * Get the JSON adapters. + * + * @return the JSON adapters + */ + public Map getJsonAdapters() { + return jsonAdapters; + } + + /** + * Set JSON adapters for the schema helper. + * + * @param jsonAdapters the JSON adapters + */ + public void setJsonAdapters(Map jsonAdapters) { + this.jsonAdapters = jsonAdapters; + } + + /* + * (non-Javadoc) + * + * @see org.onap.policy.apex.service.parameters.ApexParameterValidator#validate() + */ + @Override + public GroupValidationResult validate() { + final GroupValidationResult result = new GroupValidationResult(this); + + for (Entry typeAdapterEntry : jsonAdapters.entrySet()) { + result.setResult("jsonAdapters", typeAdapterEntry.getKey(), typeAdapterEntry.getValue().validate()); + } + return result; + } + } diff --git a/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/ContextParameters.java b/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/ContextParameters.java index 80ec0eb2d..21b3a5eaf 100644 --- a/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/ContextParameters.java +++ b/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/ContextParameters.java @@ -23,26 +23,26 @@ package org.onap.policy.apex.context.parameters; import org.onap.policy.common.parameters.GroupValidationResult; import org.onap.policy.common.parameters.ParameterGroup; +// @formatter:off /** - * Bean class to hold parameters for context handling in Apex. This class contains all the context - * parameters for schema handling, distribution, locking, and persistence of context albums. + * Bean class to hold parameters for context handling in Apex. This class contains all the context parameters for schema + * handling, distribution, locking, and persistence of context albums. * *

The following parameters are defined: *

    - *
  1. flushPeriod: Context is flushed to any persistor plugin that is defined periodically, and the - * period for flushing is the flush period. - *
  2. distributorParameters: The parameters (a {@link DistributorParameters} instance) for the - * distributor plugin that is being used for context album distribution - *
  3. schemaParameters: The parameters (a {@link SchemaParameters} instance) for the schema plugin - * that is being used for context album schemas - *
  4. lockManagerParameters: The parameters (a {@link LockManagerParameters} instance) for the - * locking mechanism plugin that is being used for context album locking - *
  5. persistorParameters: The parameters (a {@link PersistorParameters} instance) for the - * persistence plugin that is being used for context album persistence + *
  6. flushPeriod: Context is flushed to any persistor plugin that is defined periodically, and the period for flushing + * is the flush period. + *
  7. distributorParameters: The parameters (a {@link DistributorParameters} instance) for the distributor plugin that + * is being used for context album distribution + *
  8. schemaParameters: The parameters (a {@link SchemaParameters} instance) for the schema plugin that is being used + * for context album schemas + *
  9. lockManagerParameters: The parameters (a {@link LockManagerParameters} instance) for the locking mechanism plugin + * that is being used for context album locking + *
  10. persistorParameters: The parameters (a {@link PersistorParameters} instance) for the persistence plugin that is + * being used for context album persistence *
- * - * @author Liam Fallon (liam.fallon@ericsson.com) */ +// @formatter:on public class ContextParameters implements ParameterGroup { // @formatter:off // Plugin Parameters @@ -54,8 +54,7 @@ public class ContextParameters implements ParameterGroup { // @formatter:on /** - * Constructor to create a context parameters instance and register the instance with the - * parameter service. + * Constructor to create a context parameters instance and register the instance with the parameter service. */ public ContextParameters() { super(); @@ -135,7 +134,7 @@ public class ContextParameters implements ParameterGroup { public void setPersistorParameters(final PersistorParameters persistorParameters) { this.persistorParameters = persistorParameters; } - + @Override public String toString() { return "ContextParameters [name=" + name + ", distributorParameters=" + distributorParameters @@ -155,6 +154,15 @@ public class ContextParameters implements ParameterGroup { @Override public GroupValidationResult validate() { - return new GroupValidationResult(this); + GroupValidationResult result = new GroupValidationResult(this); + + // @formatter:off + result.setResult("distributorParameters", distributorParameters.validate()); + result.setResult("schemaParameters", schemaParameters.validate()); + result.setResult("lockManagerParameters", lockManagerParameters.validate()); + result.setResult("persistorParameters", persistorParameters.validate()); + // @formatter:on + + return result; } } diff --git a/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/SchemaParameters.java b/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/SchemaParameters.java index 9992b9f3c..fb1713776 100644 --- a/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/SchemaParameters.java +++ b/context/context-management/src/main/java/org/onap/policy/apex/context/parameters/SchemaParameters.java @@ -21,6 +21,7 @@ package org.onap.policy.apex.context.parameters; import java.util.Map; +import java.util.Map.Entry; import java.util.TreeMap; import org.onap.policy.apex.context.impl.schema.java.JavaSchemaHelperParameters; @@ -28,10 +29,9 @@ import org.onap.policy.common.parameters.GroupValidationResult; import org.onap.policy.common.parameters.ParameterGroup; /** - * Bean class holding schema parameters for schemas and their helpers. As more than one schema can - * be used in Apex simultaneously, this class is used to hold the schemas that are defined in a - * given Apex system and to get the schema helper plugin parameters {@link SchemaHelperParameters} - * for each schema. + * Bean class holding schema parameters for schemas and their helpers. As more than one schema can be used in Apex + * simultaneously, this class is used to hold the schemas that are defined in a given Apex system and to get the schema + * helper plugin parameters {@link SchemaHelperParameters} for each schema. * *

The default {@code Java} schema is always defined and its parameters are held in a * {@link JavaSchemaHelperParameters} instance. @@ -48,8 +48,7 @@ public class SchemaParameters implements ParameterGroup { private Map schemaHelperParameterMap; /** - * Constructor to create a distributor parameters instance and register the instance with the - * parameter service. + * Constructor to create a distributor parameters instance and register the instance with the parameter service. */ public SchemaParameters() { super(); @@ -90,7 +89,7 @@ public class SchemaParameters implements ParameterGroup { public SchemaHelperParameters getSchemaHelperParameters(final String schemaFlavour) { return schemaHelperParameterMap.get(schemaFlavour); } - + @Override public String getName() { return name; @@ -103,6 +102,12 @@ public class SchemaParameters implements ParameterGroup { @Override public GroupValidationResult validate() { - return new GroupValidationResult(this); + final GroupValidationResult result = new GroupValidationResult(this); + + for (Entry schemaHelperEntry : schemaHelperParameterMap.entrySet()) { + result.setResult("schemaHelperParameterMap", schemaHelperEntry.getKey(), + schemaHelperEntry.getValue().validate()); + } + return result; } } -- cgit 1.2.3-korg