2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.plugins.context.schema.avro;
23 import java.io.ByteArrayOutputStream;
25 import org.apache.avro.Schema;
26 import org.apache.avro.generic.GenericDatumReader;
27 import org.apache.avro.generic.GenericDatumWriter;
28 import org.apache.avro.generic.GenericRecord;
29 import org.apache.avro.io.DatumWriter;
30 import org.apache.avro.io.DecoderFactory;
31 import org.apache.avro.io.EncoderFactory;
32 import org.apache.avro.io.JsonDecoder;
33 import org.apache.avro.io.JsonEncoder;
34 import org.onap.policy.apex.context.ContextRuntimeException;
35 import org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper;
36 import org.onap.policy.apex.model.basicmodel.concepts.AxKey;
37 import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema;
38 import org.slf4j.ext.XLogger;
39 import org.slf4j.ext.XLoggerFactory;
41 import com.google.gson.Gson;
42 import com.google.gson.GsonBuilder;
43 import com.google.gson.JsonElement;
46 * This class is the implementation of the {@link org.onap.policy.apex.context.SchemaHelper}
47 * interface for Avro schemas.
49 * @author Liam Fallon (liam.fallon@ericsson.com)
51 public class AvroSchemaHelper extends AbstractSchemaHelper {
52 // Get a reference to the logger
53 private static final XLogger LOGGER = XLoggerFactory.getXLogger(AvroSchemaHelper.class);
55 // The Avro schema for this context schema
56 private Schema avroSchema;
58 // The mapper that translates between Java and Avro objects
59 private AvroObjectMapper avroObjectMapper;
62 public void init(final AxKey userKey, final AxContextSchema schema) throws ContextRuntimeException {
63 super.init(userKey, schema);
65 // Configure the Avro schema
67 avroSchema = new Schema.Parser().parse(schema.getSchema());
68 } catch (final Exception e) {
69 final String resultSting = userKey.getID() + ": avro context schema \"" + schema.getID()
70 + "\" schema is invalid: " + e.getMessage() + ", schema: " + schema.getSchema();
71 LOGGER.warn(resultSting);
72 throw new ContextRuntimeException(resultSting);
75 // Get the object mapper for the schema type to a Java class
76 avroObjectMapper = new AvroObjectMapperFactory().get(userKey, avroSchema);
78 // Get the Java type for this schema, if it is a primitive type then we can do direct
80 setSchemaClass(avroObjectMapper.getJavaClass());
84 * Getter to get the Avro schema.
86 * @return the Avro schema
88 public Schema getAvroSchema() {
93 public Object getSchemaObject() {
98 public Object createNewInstance() {
99 // Create a new instance using the Avro object mapper
100 final Object newInstance = avroObjectMapper.createNewInstance(avroSchema);
102 // If no new instance is created, use default schema handler behavior
103 if (newInstance != null) {
106 return super.createNewInstance();
111 public Object createNewInstance(final String stringValue) {
112 return unmarshal(stringValue);
116 public Object createNewInstance(final JsonElement jsonElement) {
117 final Gson gson = new GsonBuilder().serializeNulls().create();
118 final String elementJsonString = gson.toJson(jsonElement);
120 return createNewInstance(elementJsonString);
124 public Object unmarshal(final Object object) {
125 // If an object is already in the correct format, just carry on
126 if (passThroughObject(object)) {
130 // Check that the incoming object is a string, the incoming object must be a string
134 if (object == null) {
137 if (object != null && avroSchema.getType().equals(Schema.Type.STRING)) {
138 objectString = object.toString().trim();
139 if (objectString.length() == 0) {
140 objectString = "\"\"";
141 } else if (objectString.length() == 1) {
142 objectString = "\"" + objectString + "\"";
144 // All strings must be quoted for decoding
145 if (objectString.charAt(0) != '"') {
146 objectString = '"' + objectString;
148 if (objectString.charAt(objectString.length() - 1) != '"') {
153 objectString = (String) object;
155 } catch (final ClassCastException e) {
156 final String returnString = getUserKey().getID() + ": object \"" + object.toString() + "\" of type \""
157 + object.getClass().getCanonicalName() + "\" must be assignable to \""
158 + getSchemaClass().getCanonicalName()
159 + "\" or be a Json string representation of it for Avro unmarshalling";
160 LOGGER.warn(returnString);
161 throw new ContextRuntimeException(returnString);
164 // Translate illegal characters in incoming JSON keys to legal Avro values
165 objectString = AvroSchemaKeyTranslationUtilities.translateIllegalKeys(objectString, false);
168 Object decodedObject;
170 final JsonDecoder jsonDecoder = DecoderFactory.get().jsonDecoder(avroSchema, objectString);
171 decodedObject = new GenericDatumReader<GenericRecord>(avroSchema).read(null, jsonDecoder);
172 } catch (final Exception e) {
173 final String returnString = getUserKey().getID() + ": object \"" + objectString
174 + "\" Avro unmarshalling failed: " + e.getMessage();
175 LOGGER.warn(returnString, e);
176 throw new ContextRuntimeException(returnString, e);
179 // Now map the decoded object into something we can handle
180 return avroObjectMapper.mapFromAvro(decodedObject);
184 public String marshal2Json(final Object object) {
185 // Condition the object for Avro encoding
186 final Object conditionedObject = avroObjectMapper.mapToAvro(object);
188 final ByteArrayOutputStream output = new ByteArrayOutputStream();
190 final DatumWriter<Object> writer = new GenericDatumWriter<>(avroSchema);
191 final JsonEncoder jsonEncoder = EncoderFactory.get().jsonEncoder(avroSchema, output, true);
192 writer.write(conditionedObject, jsonEncoder);
195 } catch (final Exception e) {
196 final String returnString =
197 getUserKey().getID() + ": object \"" + object + "\" Avro marshalling failed: " + e.getMessage();
198 LOGGER.warn(returnString);
199 throw new ContextRuntimeException(returnString, e);
202 return AvroSchemaKeyTranslationUtilities.translateIllegalKeys(new String(output.toByteArray()), true);
206 public JsonElement marshal2JsonElement(final Object schemaObject) {
207 // Get the object as a Json string
208 final String schemaObjectAsString = marshal2Json(schemaObject);
210 // Get a Gson instance to convert the Json string to an object created by Json
211 final Gson gson = new Gson();
213 // Convert the Json string into an object
214 final Object schemaObjectAsObject = gson.fromJson(schemaObjectAsString, Object.class);
216 return gson.toJsonTree(schemaObjectAsObject);
220 * Check if we can pass this object straight through encoding or decoding, is it an object
221 * native to the schema.
223 * @param object the object to check
224 * @return true if it's a straight pass through
226 private boolean passThroughObject(final Object object) {
227 if (object == null || getSchemaClass() == null) {
231 // All strings must be mapped
232 if (object instanceof String) {
236 // Now, check if the object is native
237 return getSchemaClass().isAssignableFrom(object.getClass());