diff options
19 files changed, 2340 insertions, 24 deletions
diff --git a/gson/checkstyle-suppressions.xml b/gson/checkstyle-suppressions.xml new file mode 100644 index 00000000..0705e0e6 --- /dev/null +++ b/gson/checkstyle-suppressions.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- + ============LICENSE_START======================================================= + Copyright (C) 2019 AT&T Technologies. 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========================================================= +--> + +<!-- + NOTE: The sole purpose of this supression file is to allow "$" in field and method + names so that the gson class tests can verify that those fields are ignored + when doing serialization and de-serialization. + --> + +<!DOCTYPE suppressions PUBLIC + "-//Puppy Crawl//DTD Suppressions 1.0//EN" + "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd"> + +<suppressions> + <suppress checks="MemberName" + files="AdapterTest.java|ClassWalkerTest.java" + lines="1-9999"/> + <suppress checks="MethodName" + files="AdapterTest.java" + lines="1-9999"/> +</suppressions> diff --git a/gson/pom.xml b/gson/pom.xml new file mode 100644 index 00000000..3b9983f2 --- /dev/null +++ b/gson/pom.xml @@ -0,0 +1,143 @@ +<!-- + ============LICENSE_START======================================================= + ONAP Policy Engine - Common Modules + ================================================================================ + Copyright (C) 2018-2019 AT&T Intellectual Property. 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. + ============LICENSE_END========================================================= + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.policy.common</groupId> + <artifactId>common-modules</artifactId> + <version>1.4.0-SNAPSHOT</version> + </parent> + + <artifactId>gson</artifactId> + <description>Common Utilities</description> + <packaging>jar</packaging> + + <properties> + <!-- TODO move to top-level or parent --> + <jersey.version>2.25.1</jersey.version> + <jackson.version>2.9.5</jackson.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.glassfish.jersey.core</groupId> + <artifactId>jersey-server</artifactId> + <version>${jersey.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>3.11.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <pluginManagement> + <plugins> + <!--This plugin's configuration is used to store Eclipse m2e settings + only. It has no influence on the Maven build itself. --> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.jacoco</groupId> + <artifactId> + jacoco-maven-plugin + </artifactId> + <versionRange> + [0.7.1.201405082137,) + </versionRange> + <goals> + <goal>prepare-agent</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <artifactId>maven-checkstyle-plugin</artifactId> + <executions> + <execution> + <id>onap-java-style</id> + <goals> + <goal>check</goal> + </goals> + <phase>process-sources</phase> + <configuration> + <!-- Use Google Java Style Guide: + https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml + with minor changes --> + <configLocation>onap-checkstyle/onap-java-style.xml</configLocation> + <!-- <sourceDirectory> is needed so that checkstyle ignores the generated sources directory --> + <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory> + <includeResources>true</includeResources> + <includeTestSourceDirectory>true</includeTestSourceDirectory> + <includeTestResources>true</includeTestResources> + <excludes> + </excludes> + <suppressionsLocation>${project.basedir}/checkstyle-suppressions.xml</suppressionsLocation> + <consoleOutput>true</consoleOutput> + <failsOnViolation>true</failsOnViolation> + <violationSeverity>warning</violationSeverity> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.onap.oparent</groupId> + <artifactId>checkstyle</artifactId> + <version>${oparent.version}</version> + <scope>compile</scope> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> +</project> diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/GsonMessageBodyHandler.java b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java index a29afef4..2112c97c 100644 --- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/GsonMessageBodyHandler.java +++ b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java @@ -7,9 +7,9 @@ * 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. @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.policy.common.endpoints.http.server.internal; +package org.onap.policy.common.gson; import com.google.gson.Gson; import java.io.IOException; @@ -40,10 +40,6 @@ import javax.ws.rs.ext.Provider; /** * Provider that serializes and de-serializes JSON via gson. - * - * <p>Note: <i>jersey</i> will ignore this class if the maven artifact, - * <i>jersey-media-json-jackson</i>, is included, regardless of whether it's included - * directly or indirectly. */ @Provider @Consumes(MediaType.WILDCARD) @@ -64,7 +60,7 @@ public class GsonMessageBodyHandler implements MessageBodyReader<Object>, Messag /** * Constructs the object. - * + * * @param gson the Gson object to be used to serialize and de-serialize */ public GsonMessageBodyHandler(Gson gson) { @@ -99,7 +95,7 @@ public class GsonMessageBodyHandler implements MessageBodyReader<Object>, Messag /** * Determines if this provider can handle the given media type. - * + * * @param mediaType the media type of interest * @return {@code true} if this provider handles the given media type, {@code false} * otherwise diff --git a/utils/src/main/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategy.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java index 96213a65..cb959c43 100644 --- a/utils/src/main/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategy.java +++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.policy.common.utils.gson; +package org.onap.policy.common.gson; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java new file mode 100644 index 00000000..859f5386 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.annotation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Mimics Jackson JsonAnyGetter annotation, but used by gson. This requires the gson + * object to be configured with the jackson default behaviors (i.e., the associated + * JacksonXxx strategy and adapters must be registered with the gson object). + */ +@Retention(RUNTIME) +@Target(METHOD) +public @interface GsonJsonAnyGetter { + +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java new file mode 100644 index 00000000..87e0f330 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.annotation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Mimics Jackson JsonAnySetter annotation, but used by gson. This requires the gson + * object to be configured with the jackson default behaviors (i.e., the associated + * JacksonXxx strategy and adapters must be registered with the gson object). + */ +@Retention(RUNTIME) +@Target(METHOD) +public @interface GsonJsonAnySetter { + +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java new file mode 100644 index 00000000..cf2d4394 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java @@ -0,0 +1,39 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Mimics Jackson JsonIgnore annotation, but used by gson. This requires the gson object + * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx + * strategy and adapters must be registered with the gson object). + */ +@Retention(RUNTIME) +@Target({FIELD, METHOD}) +public @interface GsonJsonIgnore { + +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java new file mode 100644 index 00000000..c31c19bb --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java @@ -0,0 +1,44 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Mimics Jackson JsonProperty annotation, but used by gson. This requires the gson object + * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx + * strategy and adapters must be registered with the gson object). + */ +@Retention(RUNTIME) +@Target({FIELD, METHOD}) +public @interface GsonJsonProperty { + + /** + * Property name of this item when placed into a JsonObject. + * @return the item's serialized name + */ + String value() default ""; +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java new file mode 100644 index 00000000..b4ef53f7 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java @@ -0,0 +1,339 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; + +/** + * Super class of adapters used to serialize and de-serialize an item. + */ +public class Adapter { + + /** + * Pattern to match valid identifiers. + */ + private static final Pattern VALID_NAME_PAT = Pattern.compile("[a-zA-Z_]\\w*"); + + /** + * Name of the property within the json structure containing the item. + */ + private final String propName; + + /** + * Gson object that will provide the type converter. + */ + private final Gson gson; + + /** + * Converter used when reading. + */ + private final ConvInfo reader; + + /** + * Converter used when writing, allocated lazily, once an actual type is determined. + */ + private volatile ConvInfo writer = null; + + /** + * Name of the item being lifted - used when throwing exceptions. + */ + private final String fullName; + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param field field used to access the item from within an object + */ + public Adapter(Gson gson, Field field) { + this.propName = detmPropName(field); + this.reader = new ConvInfo(TypeToken.get(field.getGenericType())); + this.gson = gson; + this.fullName = getQualifiedName(field); + + field.setAccessible(true); + } + + /** + * Constructs the object. + * + * @param gson Gson object providing type adapters + * @param accessor method used to access the item from within an object + * @param forGetter {@code true} if the name is for a "getter" method, {@code false} + * if for a "setter" + * @param valueType the class of value on which this operates + */ + public Adapter(Gson gson, Method accessor, boolean forGetter, Type valueType) { + this.propName = (forGetter ? detmGetterPropName(accessor) : detmSetterPropName(accessor)); + this.reader = new ConvInfo(TypeToken.get(valueType)); + this.gson = gson; + this.fullName = getQualifiedName(accessor); + + accessor.setAccessible(true); + } + + /** + * Converts an object to a json tree. + * + * @param object the object to be converted + * @return a json tree representing the object + */ + @SuppressWarnings("unchecked") + public JsonElement toJsonTree(Object object) { + // always use a converter for the specific subclass + Class<? extends Object> clazz = object.getClass(); + + if (writer == null) { + // race condition here, but it's ok to overwrite a previous value + writer = new ConvInfo(TypeToken.get(clazz)); + } + + ConvInfo wtr = writer; + TypeAdapter<Object> conv = + (TypeAdapter<Object>) (wtr.clazz == clazz ? wtr.getConverter() : gson.getAdapter(clazz)); + + return conv.toJsonTree(object); + } + + /** + * Converts a json tree to an object. + * + * @param tree the tree to be converted + * @return the object represented by the tree + */ + public Object fromJsonTree(JsonElement tree) { + return reader.getConverter().fromJsonTree(tree); + } + + public final String getPropName() { + return propName; + } + + public final String getFullName() { + return fullName; + } + + /** + * Makes an error message, appending the item's full name to the message prefix. + * + * @param prefix the message prefix + * @return the error message + */ + public String makeError(String prefix) { + return (prefix + fullName); + } + + /** + * Determines if the field is managed by the walker. + * + * @param field the field to examine + * @return {@code true} if the field is managed by the walker, {@code false} otherwise + */ + public static boolean isManaged(Field field) { + return VALID_NAME_PAT.matcher(field.getName()).matches(); + } + + /** + * Determines if the method is managed by the walker. + * + * @param method the method to examine + * @return {@code true} if the method is managed by the walker, {@code false} + * otherwise + */ + public static boolean isManaged(Method method) { + return VALID_NAME_PAT.matcher(method.getName()).matches(); + } + + /** + * Determines the property name of an item within the json structure. + * + * @param field the item within the object + * @return the json property name for the item or {@code null} if the name is invalid + */ + public static String detmPropName(Field field) { + // use the serialized name, if specified + GsonJsonProperty prop = field.getAnnotation(GsonJsonProperty.class); + if (prop != null && !prop.value().isEmpty()) { + return prop.value(); + } + + // no name provided - use it as is + return (isManaged(field) ? field.getName() : null); + } + + /** + * Determines the property name of an item, within the json structure, associated with + * a "get" method. + * + * @param method method to be invoked to get the item within the object + * @return the json property name for the item, or {@code null} if the method name is + * not valid + */ + public static String detmGetterPropName(Method method) { + + return detmPropNameCommon(method, () -> { + + if (!isManaged(method)) { + return null; + } + + String name = method.getName(); + + if (name.startsWith("get")) { + return name.substring(3); + + } else if (name.startsWith("is")) { + Class<?> treturn = method.getReturnType(); + + if (treturn == boolean.class || treturn == Boolean.class) { + return name.substring(2); + } + } + + // not a valid name for a "getter" method + return null; + }); + } + + /** + * Determines the property name of an item, within the json structure, associated with + * a "set" method. + * + * @param method method to be invoked to set the item within the object + * @return the json property name for the item, or {@code null} if the method name is + * not valid + */ + public static String detmSetterPropName(Method method) { + + return detmPropNameCommon(method, () -> { + + if (!isManaged(method)) { + return null; + } + + String name = method.getName(); + + if (name.startsWith("set")) { + return name.substring(3); + } + + // not a valid name for a "setter" method + return null; + }); + } + + /** + * Determines the property name of an item within the json structure. + * + * @param method method to be invoked to get/set the item within the object + * @param extractor function to extract the name directly from the method name + * @return the json property name for the item, or {@code null} if the method name is + * not valid + */ + private static String detmPropNameCommon(Method method, Supplier<String> extractor) { + + // use the property name, if specified + GsonJsonProperty propName = method.getAnnotation(GsonJsonProperty.class); + if (propName != null && !propName.value().isEmpty()) { + return propName.value(); + } + + // no name provided - must compute it from the method name + String name = extractor.get(); + + if (name == null || name.isEmpty()) { + // nothing left after stripping the prefix - invalid name + return null; + } + + // translate the first letter to lower-case + return name.substring(0, 1).toLowerCase() + name.substring(1); + } + + /** + * Gets the fully qualified name of a field. + * + * @param field field whose name is desired + * @return the field fully qualified name + */ + public static String getQualifiedName(Field field) { + return (field.getDeclaringClass().getName() + "." + field.getName()); + } + + /** + * Gets the fully qualified name of a method. + * + * @param method method whose name is desired + * @return the method's fully qualified name + */ + public static String getQualifiedName(Method method) { + return (method.getDeclaringClass().getName() + "." + method.getName()); + } + + /** + * Converter info. + */ + private class ConvInfo { + + /** + * Type on which the converter works. + */ + private TypeToken<?> type; + + /** + * Class of object on which the converter works. + */ + private Class<?> clazz; + + /** + * Converter to use, initialized lazily. + */ + private volatile TypeAdapter<?> conv = null; + + /** + * Constructs the object. + * + * @param type type of object to be converted + */ + public ConvInfo(TypeToken<?> type) { + this.type = type; + this.clazz = type.getRawType(); + } + + public final TypeAdapter<?> getConverter() { + if (conv == null) { + // race condition here, but it's ok to overwrite a previous value + this.conv = gson.getAdapter(type); + } + + return conv; + } + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java new file mode 100644 index 00000000..e985d98a --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java @@ -0,0 +1,389 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.JsonParseException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.onap.policy.common.gson.annotation.GsonJsonAnyGetter; +import org.onap.policy.common.gson.annotation.GsonJsonAnySetter; +import org.onap.policy.common.gson.annotation.GsonJsonIgnore; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; + +/** + * Data populated while walking the hierarchy of a class. + */ +public class ClassWalker { + + public static final String ANY_GETTER_MISMATCH_ERR = + GsonJsonAnyGetter.class.getSimpleName() + " parameter mismatch for: "; + + public static final String ANY_SETTER_MISMATCH_ERR = + GsonJsonAnySetter.class.getSimpleName() + " parameter mismatch for: "; + + public static final String ANY_SETTER_TYPE_ERR = + GsonJsonAnySetter.class.getSimpleName() + " first parameter must be a string: "; + + /** + * Maps an input property name to an item within the class, where item is one of: + * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new + * items are added. + */ + private final Map<String, Object> inProps = new HashMap<>(); + + /** + * Maps an output property name to an item within the class, where item is one of: + * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new + * items are added. + */ + private final Map<String, Object> outProps = new HashMap<>(); + + /** + * Maps a method name to a "get" method. Used when overriding properties associated + * with a method. + */ + private final Map<String, Method> getters = new HashMap<>(); + + /** + * Maps a method name to a "set" method. Used when overriding properties associated + * with a method. + */ + private final Map<String, Method> setters = new HashMap<>(); + + /** + * Method having {@link GsonJsonAnyGetter} annotation. Overwritten as new "any-getters" + * are identified. + */ + private Method anyGetter = null; + + /** + * Method having {@link GsonJsonAnySetter} annotation. Overwritten as new "any-setters" + * are identified. + */ + private Method anySetter = null; + + + public Method getAnyGetter() { + return anyGetter; + } + + public Method getAnySetter() { + return anySetter; + } + + /** + * Gets the names of input properties that are not being ignored. + * + * @return the non-ignored input property names + */ + public List<String> getInNotIgnored() { + return getNonNull(inProps); + } + + /** + * Gets the names of output properties that are not being ignored. + * + * @return the non-ignored output property names + */ + public List<String> getOutNotIgnored() { + return getNonNull(outProps); + } + + /** + * Gets the property names, associated with a non-null value, from a set of + * properties. + * + * @param props set of properties from which to extract the names + * @return the property names having a non-null value + */ + private List<String> getNonNull(Map<String, Object> props) { + List<String> lst = new ArrayList<String>(props.size()); + + for (Entry<String, Object> ent : props.entrySet()) { + if (ent.getValue() != null) { + lst.add(ent.getKey()); + } + } + + return lst; + } + + /** + * Gets the input properties whose values are of the given class. + * + * @param clazz class of properties to get + * @return the input properties of the given class + */ + public <T> List<T> getInProps(Class<T> clazz) { + return getProps(clazz, inProps.values()); + } + + /** + * Gets the output properties whose values are of the given class. + * + * @param clazz class of properties to get + * @return the output properties of the given class + */ + public <T> List<T> getOutProps(Class<T> clazz) { + return getProps(clazz, outProps.values()); + } + + /** + * Gets the properties whose values are of the given class. + * + * @param clazz class of properties to get + * @param values values from which to select + * @return the output properties of the given class + */ + @SuppressWarnings("unchecked") + private <T> List<T> getProps(Class<T> clazz, Collection<Object> values) { + List<T> lst = new ArrayList<T>(values.size()); + + for (Object val : values) { + if (val != null && val.getClass() == clazz) { + lst.add((T) val); + } + } + + return lst; + } + + /** + * Recursively walks a class hierarchy, including super classes and interfaces, + * examining each class for various annotations. + * + * @param clazz class whose hierarchy is to be walked + */ + public void walkClassHierarchy(Class<?> clazz) { + if (clazz == Object.class) { + return; + } + + // walk interfaces first + for (Class<?> intfc : clazz.getInterfaces()) { + walkClassHierarchy(intfc); + } + + // walk superclass next, overwriting previous items + Class<?> sup = clazz.getSuperclass(); + if (sup != null) { + walkClassHierarchy(sup); + } + + // finally, examine this class, overwriting previous items + examine(clazz); + } + + /** + * Examines a class for annotations, examining fields and then methods. + * + * @param clazz class to be examined + */ + protected void examine(Class<?> clazz) { + for (Field field : clazz.getDeclaredFields()) { + examine(field); + } + + for (Method method : clazz.getDeclaredMethods()) { + examine(method); + } + } + + /** + * Examines a field for annotations. + * + * @param field field to be examined + */ + protected void examine(Field field) { + if (field.isSynthetic()) { + return; + } + + int mod = field.getModifiers(); + + if (Modifier.isStatic(mod)) { + // skip static fields + return; + } + + if (!Modifier.isPublic(mod) && field.getAnnotation(GsonJsonProperty.class) == null) { + // private/protected - skip it unless explicitly exposed + return; + } + + if (Modifier.isTransient(mod) && field.getAnnotation(GsonJsonProperty.class) == null) { + // transient - skip it unless explicitly exposed + return; + } + + String name = Adapter.detmPropName(field); + if (name == null) { + // invalid name + return; + } + + // if ignoring, then insert null into the map, otherwise insert the field + Field annotField = (field.getAnnotation(GsonJsonIgnore.class) != null ? null : field); + + // a field can be both an input and an output + + inProps.put(name, annotField); + outProps.put(name, annotField); + } + + /** + * Examines a method for annotations. + * + * @param method method to be examined + */ + protected void examine(Method method) { + if (method.isSynthetic()) { + return; + } + + int mod = method.getModifiers(); + + if (Modifier.isStatic(mod)) { + // static methods are not exposed + return; + } + + GsonJsonProperty prop = method.getAnnotation(GsonJsonProperty.class); + GsonJsonAnyGetter get = method.getAnnotation(GsonJsonAnyGetter.class); + GsonJsonAnySetter set = method.getAnnotation(GsonJsonAnySetter.class); + + if (!Modifier.isPublic(mod) && prop == null && get == null && set == null) { + // private/protected methods are not exposed, unless annotated + return; + } + + + if (method.getReturnType() == void.class) { + // "void" return type - must be a "setter" method + if (set == null) { + examineSetter(method); + + } else { + examineAnySetter(method); + } + + } else { + // must be a "getter" method + if (get == null) { + examineGetter(method); + + } else { + examineAnyGetter(method); + } + } + } + + /** + * Examines a "setter" method. + * + * @param method method to be examined + */ + private void examineSetter(Method method) { + String name = Adapter.detmSetterPropName(method); + if (name != null && method.getParameterCount() == 1) { + // remove old name mapping, if any + Method old = setters.get(method.getName()); + if (old != null) { + inProps.remove(Adapter.detmSetterPropName(old)); + } + + setters.put(method.getName(), method); + + // if ignoring, then insert null into the map, otherwise insert the method + inProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method)); + } + } + + /** + * Examines a "getter" method. + * + * @param method method to be examined + */ + private void examineGetter(Method method) { + String name = Adapter.detmGetterPropName(method); + if (name != null && method.getParameterCount() == 0) { + // remove old name mapping, if any + Method old = getters.get(method.getName()); + if (old != null) { + outProps.remove(Adapter.detmGetterPropName(old)); + } + + getters.put(method.getName(), method); + + // if ignoring, then insert null into the map, otherwise insert the method + outProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method)); + } + } + + /** + * Examines a method having a {@link GsonJsonAnySetter} annotation. + * + * @param method method to be examined + */ + private void examineAnySetter(Method method) { + if (method.getParameterCount() != 2) { + throw new JsonParseException(ANY_SETTER_MISMATCH_ERR + getFqdn(method)); + } + + if (method.getParameterTypes()[0] != String.class) { + throw new JsonParseException(ANY_SETTER_TYPE_ERR + getFqdn(method)); + } + + // if ignoring, then use null, otherwise use the method + anySetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method); + } + + /** + * Examines a method having a {@link GsonJsonAnyGetter} annotation. + * + * @param method method to be examined + */ + private void examineAnyGetter(Method method) { + if (method.getParameterCount() != 0) { + throw new JsonParseException(ANY_GETTER_MISMATCH_ERR + getFqdn(method)); + } + + // if ignoring, then use null, otherwise use the method + anyGetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method); + } + + /** + * Gets the fully qualified name of a method. + * + * @param method method whose name is desired + * @return the fully qualified method name + */ + private String getFqdn(Method method) { + return (method.getDeclaringClass().getName() + "." + method.getName()); + } +} diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java new file mode 100644 index 00000000..f2975860 --- /dev/null +++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java @@ -0,0 +1,39 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.JsonObject; + +/** + * Super class of all de-serializers. + */ +public interface Deserializer { + + String INVOKE_ERR = "cannot invoke method to deserialize: "; + + /** + * Gets an value from a tree, converts it, and puts it into a target object. + * + * @param source tree from which to get the value + * @param target where to place the converted value + */ + void getFromTree(JsonObject source, Object target); +} diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/GsonMessageBodyHandlerTest.java b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java index 9c6ec80d..85ecfea4 100644 --- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/GsonMessageBodyHandlerTest.java +++ b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.policy.common.endpoints.http.server.test; +package org.onap.policy.common.gson; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream; import javax.ws.rs.core.MediaType; import org.junit.Before; import org.junit.Test; -import org.onap.policy.common.endpoints.http.server.internal.GsonMessageBodyHandler; +import org.onap.policy.common.gson.GsonMessageBodyHandler; public class GsonMessageBodyHandlerTest { private static final String GEN_TYPE = "some-type"; diff --git a/utils/src/test/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategyTest.java b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java index e8927902..4b5473c5 100644 --- a/utils/src/test/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategyTest.java +++ b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.policy.common.utils.gson; +package org.onap.policy.common.gson; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -33,6 +33,7 @@ import java.util.LinkedList; import java.util.TreeMap; import org.junit.BeforeClass; import org.junit.Test; +import org.onap.policy.common.gson.JacksonExclusionStrategy; public class JacksonExclusionStrategyTest { diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java new file mode 100644 index 00000000..fcb0d9ad --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java @@ -0,0 +1,387 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import org.junit.Test; +import org.onap.policy.common.gson.JacksonExclusionStrategy; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; +import org.onap.policy.common.gson.internal.Adapter; +import org.onap.policy.common.gson.internal.DataAdapterFactory.Data; +import org.onap.policy.common.gson.internal.DataAdapterFactory.DerivedData; + +public class AdapterTest { + private static final String GET_VALUE_NAME = "getValue"; + private static final String VALUE_NAME = "value"; + private static final String MY_NAME = AdapterTest.class.getName(); + + private static DataAdapterFactory dataAdapter = new DataAdapterFactory(); + + private static Gson gson = new GsonBuilder().registerTypeAdapterFactory(dataAdapter) + .setExclusionStrategies(new JacksonExclusionStrategy()).create(); + + /* + * The remaining fields are just used within the tests. + */ + + private String value; + + // empty alias - should use field name + @GsonJsonProperty("") + protected String emptyAlias; + + @GsonJsonProperty("name-with-alias") + protected String nameWithAlias; + + protected String unaliased; + + protected String $invalidFieldName; + + private List<Data> listField; + + private Data dataField; + + + @Test + public void testIsManagedField() { + assertTrue(Adapter.isManaged(field(VALUE_NAME))); + + assertFalse(Adapter.isManaged(field("$invalidFieldName"))); + } + + @Test + public void testIsManagedMethod() { + assertTrue(Adapter.isManaged(mget(GET_VALUE_NAME))); + + assertFalse(Adapter.isManaged(mget("get$InvalidName"))); + assertFalse(Adapter.isManaged(mset("set$InvalidName"))); + } + + @Test + public void testAdapterField_Converter() { + Adapter adapter = new Adapter(gson, field("dataField")); + + // first, write something of type Data + dataAdapter.reset(); + dataField = new Data(300); + JsonElement tree = adapter.toJsonTree(dataField); + assertEquals("{'id':300}".replace('\'', '"'), tree.toString()); + + // now try a subclass + dataAdapter.reset(); + dataField = new DerivedData(300, "three"); + tree = adapter.toJsonTree(dataField); + assertEquals("{'id':300,'text':'three'}".replace('\'', '"'), tree.toString()); + } + + @Test + @SuppressWarnings("unchecked") + public void testAdapterField_Converter_List() { + listField = DataAdapterFactory.makeList(); + + Adapter adapter = new Adapter(gson, field("listField")); + + dataAdapter.reset(); + JsonElement tree = adapter.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + // encode it twice so it uses the cached converter + dataAdapter.reset(); + tree = adapter.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + dataAdapter.reset(); + List<Data> lst2 = (List<Data>) adapter.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + + // decode it twice so it uses the cached converter + dataAdapter.reset(); + lst2 = (List<Data>) adapter.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + } + + @Test + public void testAdapterMethod_Converter() throws Exception { + listField = DataAdapterFactory.makeList(); + + Method getter = mget("getMyList"); + + Adapter aget = new Adapter(gson, getter, true, getter.getReturnType()); + + dataAdapter.reset(); + JsonElement tree = aget.toJsonTree(listField); + assertTrue(dataAdapter.isDataWritten()); + assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString()); + + Method setter = AdapterTest.class.getDeclaredMethod("setMyList", List.class); + Adapter aset = new Adapter(gson, setter, true, setter.getGenericParameterTypes()[0]); + + dataAdapter.reset(); + @SuppressWarnings("unchecked") + List<Data> lst2 = (List<Data>) aset.fromJsonTree(tree); + assertTrue(dataAdapter.isDataRead()); + + assertEquals(listField.toString(), lst2.toString()); + } + + @Test + public void testGetPropName_testGetFullName_testMakeError() { + // test field + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".value", adapter.getFullName()); + + + // test getter + adapter = new Adapter(gson, mget(GET_VALUE_NAME), true, String.class); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".getValue", adapter.getFullName()); + + assertEquals("hello: " + MY_NAME + ".getValue", adapter.makeError("hello: ")); + + + // test setter + adapter = new Adapter(gson, mset("setValue"), false, String.class); + + assertEquals(VALUE_NAME, adapter.getPropName()); + assertEquals(MY_NAME + ".setValue", adapter.getFullName()); + } + + @Test + public void testToJsonTree() { + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + JsonElement tree = adapter.toJsonTree("hello"); + assertTrue(tree.isJsonPrimitive()); + assertEquals("hello", tree.getAsString()); + } + + @Test + public void testFromJsonTree() { + Adapter adapter = new Adapter(gson, field(VALUE_NAME)); + + assertEquals("world", adapter.fromJsonTree(new JsonPrimitive("world"))); + } + + @Test + public void testDetmPropName() { + assertEquals("emptyAlias", Adapter.detmPropName(field("emptyAlias"))); + assertEquals("name-with-alias", Adapter.detmPropName(field("nameWithAlias"))); + assertEquals("unaliased", Adapter.detmPropName(field("unaliased"))); + assertEquals(null, Adapter.detmPropName(field("$invalidFieldName"))); + } + + @Test + public void testDetmGetterPropName() { + assertEquals("emptyAlias", Adapter.detmGetterPropName(mget("getEmptyAlias"))); + assertEquals("get-with-alias", Adapter.detmGetterPropName(mget("getWithAlias"))); + assertEquals("plain", Adapter.detmGetterPropName(mget("getPlain"))); + assertEquals("primBool", Adapter.detmGetterPropName(mget("isPrimBool"))); + assertEquals("boxedBool", Adapter.detmGetterPropName(mget("isBoxedBool"))); + assertEquals(null, Adapter.detmGetterPropName(mget("isString"))); + assertEquals(null, Adapter.detmGetterPropName(mget("noGet"))); + assertEquals(null, Adapter.detmGetterPropName(mget("get"))); + assertEquals(null, Adapter.detmGetterPropName(mget("get$InvalidName"))); + } + + @Test + public void testDetmSetterPropName() { + assertEquals("emptyAlias", Adapter.detmSetterPropName(mset("setEmptyAlias"))); + assertEquals("set-with-alias", Adapter.detmSetterPropName(mset("setWithAlias"))); + assertEquals("plain", Adapter.detmSetterPropName(mset("setPlain"))); + assertEquals(null, Adapter.detmSetterPropName(mset("noSet"))); + assertEquals(null, Adapter.detmSetterPropName(mset("set"))); + assertEquals(null, Adapter.detmSetterPropName(mset("set$InvalidName"))); + } + + @Test + public void testGetQualifiedNameField() throws Exception { + assertEquals(MY_NAME + ".value", Adapter.getQualifiedName(AdapterTest.class.getDeclaredField(VALUE_NAME))); + } + + @Test + public void testGetQualifiedNameMethod() throws Exception { + assertEquals(MY_NAME + ".getValue", Adapter.getQualifiedName(mget(GET_VALUE_NAME))); + } + + /** + * Gets a field from this class, by name. + * + * @param name name of the field to get + * @return the field + */ + private Field field(String name) { + try { + return AdapterTest.class.getDeclaredField(name); + + } catch (SecurityException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets a "getter" method from this class, by name. + * + * @param name name of the method to get + * @return the method + */ + private Method mget(String name) { + try { + return AdapterTest.class.getDeclaredMethod(name); + + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets a "setter" method from this class, by name. + * + * @param name name of the method to get + * @return the method + */ + private Method mset(String name) { + try { + return AdapterTest.class.getDeclaredMethod(name, String.class); + + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + } + + /* + * The remaining methods are just used within the tests. + */ + + protected String getValue() { + return value; + } + + // empty alias - should use method name + @GsonJsonProperty("") + protected String getEmptyAlias() { + return ""; + } + + @GsonJsonProperty("get-with-alias") + protected String getWithAlias() { + return ""; + } + + // no alias, begins with "get" + protected String getPlain() { + return ""; + } + + // begins with "is", returns primitive boolean + protected boolean isPrimBool() { + return true; + } + + // begins with "is", returns boxed Boolean + protected Boolean isBoxedBool() { + return true; + } + + // begins with "is", but doesn't return a boolean + protected String isString() { + return ""; + } + + // doesn't begin with "get" + protected String noGet() { + return ""; + } + + // nothing after "get" + protected String get() { + return ""; + } + + // name has a bogus character + protected String get$InvalidName() { + return ""; + } + + + protected void setValue(String text) { + // do nothing + } + + // empty alias - should use method name + @GsonJsonProperty("") + protected void setEmptyAlias(String text) { + // do nothing + } + + @GsonJsonProperty("set-with-alias") + protected void setWithAlias(String text) { + // do nothing + } + + // no alias, begins with "set" + protected void setPlain(String text) { + // do nothing + } + + // doesn't begin with "set" + protected void noSet(String text) { + // do nothing + } + + // nothing after "get" + protected void set(String text) { + // do nothing + } + + // name has a bogus character + protected void set$InvalidName(String text) { + // do nothing + } + + // returns a list + protected List<Data> getMyList() { + return listField; + } + + // accepts a list + protected void setMyList(List<Data> newList) { + listField = newList; + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java new file mode 100644 index 00000000..1a15be09 --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java @@ -0,0 +1,507 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.JsonParseException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.gson.annotation.GsonJsonAnyGetter; +import org.onap.policy.common.gson.annotation.GsonJsonAnySetter; +import org.onap.policy.common.gson.annotation.GsonJsonIgnore; +import org.onap.policy.common.gson.annotation.GsonJsonProperty; +import org.onap.policy.common.gson.internal.Adapter; +import org.onap.policy.common.gson.internal.ClassWalker; + +public class ClassWalkerTest { + + private MyWalker walker; + + /** + * Set up. + */ + @Before + public void setUp() { + walker = new MyWalker(); + } + + @Test + public void testExamineClassOfQ_testExamineField_testExamineInField_testExamineOutField() { + walker.walkClassHierarchy(DerivedFromBottom.class); + + assertEquals("[Intfc1, Intfc2, Intfc1, Intfc3, Bottom, DerivedFromBottom]", walker.classes.toString()); + + List<String> inFields = walker.getInProps(Field.class).stream().map(field -> field.getName()) + .collect(Collectors.toList()); + Collections.sort(inFields); + assertEquals("[exposedField, overriddenValue, transField]", inFields.toString()); + + List<String> outFields = walker.getInProps(Field.class).stream().map(field -> field.getName()) + .collect(Collectors.toList()); + Collections.sort(outFields); + assertEquals("[exposedField, overriddenValue, transField]", outFields.toString()); + + // should work with interfaces without throwing an NPE + walker.walkClassHierarchy(Intfc1.class); + } + + @Test + public void testHasAnyGetter() { + walker.walkClassHierarchy(Object.class); + assertNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + + walker.walkClassHierarchy(AnyGetterIgnored.class); + assertNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + + walker.walkClassHierarchy(AnyGetterOnly.class); + assertNotNull(walker.getAnyGetter()); + assertNull(walker.getAnySetter()); + } + + @Test + public void testHasAnySetter() { + walker.walkClassHierarchy(Object.class); + assertNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + + walker.walkClassHierarchy(AnySetterIgnored.class); + assertNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + + walker.walkClassHierarchy(AnySetterOnly.class); + assertNotNull(walker.getAnySetter()); + assertNull(walker.getAnyGetter()); + } + + @Test + public void testExamineMethod() { + walker.walkClassHierarchy(DerivedFromData.class); + + assertEquals("[Data, DerivedFromData]", walker.classes.toString()); + + // ensure all methods were examined + Collections.sort(walker.methods); + List<String> lst = Arrays.asList("getId", "getValue", "getOnlyOut", "getStatic", "getText", "getTheMap", + "getUnserialized", "getValue", "getWithParams", "setExtraParams", "setId", "setMap", + "setMapValue", "setMissingParams", "setNonPublic", "setOnlyIn", "setText", "setUnserialized", + "setValue", "setValue", "wrongGetPrefix", "wrongSetPrefix"); + Collections.sort(lst); + assertEquals(lst.toString(), walker.methods.toString()); + + assertNotNull(walker.getAnyGetter()); + assertEquals("getTheMap", walker.getAnyGetter().getName()); + + List<String> getters = walker.getOutProps(Method.class).stream().map(method -> method.getName()) + .collect(Collectors.toList()); + Collections.sort(getters); + assertEquals("[getId, getOnlyOut, getValue]", getters.toString()); + + assertNotNull(walker.getAnySetter()); + assertEquals("setMapValue", walker.getAnySetter().getName()); + + List<String> setters = walker.getInProps(Method.class).stream().map(method -> method.getName()) + .collect(Collectors.toList()); + Collections.sort(setters); + assertEquals("[setId, setOnlyIn, setValue]", setters.toString()); + + // getter with invalid parameter count + assertThatThrownBy(() -> walker.walkClassHierarchy(AnyGetterMismatchParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_GETTER_MISMATCH_ERR + + AnyGetterMismatchParams.class.getName() + ".getTheMap"); + + // setter with too few parameters + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooFewParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR + + AnySetterTooFewParams.class.getName() + ".setOverride"); + + // setter with too many parameters + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooManyParams.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR + + AnySetterTooManyParams.class.getName() + ".setOverride"); + + // setter with invalid parameter type + assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterInvalidParam.class)) + .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_TYPE_ERR + + AnySetterInvalidParam.class.getName() + ".setOverride"); + } + + @Test + public void testExamineMethod_AnyGetter() { + walker.walkClassHierarchy(AnyGetterOverride.class); + + assertNotNull(walker.getAnyGetter()); + assertEquals("getOverride", walker.getAnyGetter().getName()); + } + + @Test + public void testExamineMethod_AnySetter() { + walker.walkClassHierarchy(AnySetterOverride.class); + + assertNotNull(walker.getAnySetter()); + assertEquals("setOverride", walker.getAnySetter().getName()); + } + + @Test + public void testGetInNotIgnored_testGetOutNotIgnored() { + walker.walkClassHierarchy(DerivedFromData.class); + + assertEquals("[id, onlyIn, text, value]", new TreeSet<>(walker.getInNotIgnored()).toString()); + assertEquals("[id, onlyOut, text, value]", new TreeSet<>(walker.getOutNotIgnored()).toString()); + } + + /** + * Walker subclass that records items that are examined. + */ + private static class MyWalker extends ClassWalker { + private List<String> classes = new ArrayList<>(); + private List<String> methods = new ArrayList<>(); + + @Override + protected void examine(Class<?> clazz) { + classes.add(clazz.getSimpleName()); + + super.examine(clazz); + } + + @Override + protected void examine(Method method) { + if (Adapter.isManaged(method)) { + methods.add(method.getName()); + } + + super.examine(method); + } + } + + protected static interface Intfc1 { + int id = 1000; + } + + protected static interface Intfc2 { + String text = "intfc2-text"; + } + + private static interface Intfc3 { + + } + + protected static class Bottom implements Intfc1, Intfc3 { + private int id; + public String value; + + public String invalid$fieldName; + + @GsonJsonProperty("exposed") + private String exposedField; + + @GsonJsonIgnore + public int ignored; + + public transient int ignoredTransField; + + @GsonJsonProperty("trans") + public transient int transField; + + @GsonJsonIgnore + public int getId() { + return id; + } + + @GsonJsonIgnore + public void setId(int id) { + this.id = id; + } + } + + protected static class DerivedFromBottom extends Bottom implements Intfc1, Intfc2 { + private String text; + protected String anotherValue; + + @GsonJsonProperty("value") + public String overriddenValue; + + @GsonJsonIgnore + public String getText() { + return text; + } + + @GsonJsonIgnore + public void setText(String text) { + this.text = text; + } + } + + protected static class Data { + private int id; + private String text; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + // not public, but property provided + @GsonJsonProperty("text") + protected String getText() { + return text; + } + + // this will be ignored, because there's already a field by this name + public void setText(String text) { + this.text = text; + } + + // should only show up in the output list + public int getOnlyOut() { + return 1100; + } + + // will be overridden by subclass + @GsonJsonProperty("super-value-getter") + public String getValue() { + return null; + } + + // will be overridden by subclass + @GsonJsonProperty("super-value-setter") + public void setValue(String value) { + // do nothing + } + } + + protected static class DerivedFromData extends Data { + // not serialized + private String unserialized; + + // overrides private field and public method from Data + public String text; + + private Map<String, String> map; + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @GsonJsonAnyGetter + public Map<String, String> getTheMap() { + return map; + } + + @GsonJsonIgnore + public void setMap(Map<String, String> map) { + this.map = map; + } + + @GsonJsonAnySetter + public void setMapValue(String key, String value) { + if (map == null) { + map = new TreeMap<>(); + } + + map.put(key, value); + } + + @GsonJsonIgnore + public String getUnserialized() { + return unserialized; + } + + @GsonJsonIgnore + public void setUnserialized(String unserialized) { + this.unserialized = unserialized; + } + + // should only show up in the input list + public void setOnlyIn(int value) { + // do nothing + } + + // has a param - shouldn't be serialized + public int getWithParams(String text) { + return 1000; + } + + // too few params - shouldn't be serialized + public void setMissingParams() { + // do nothing + } + + // too many params - shouldn't be serialized + public void setExtraParams(String text, String moreText) { + // do nothing + } + + // not public - shouldn't be serialized + protected void setNonPublic(String text) { + // do nothing + } + + // doesn't start with "get" + public String wrongGetPrefix() { + return null; + } + + // doesn't start with "set" + public void wrongSetPrefix(String text) { + // do nothing + } + + // static + public static String getStatic() { + return null; + } + } + + /** + * The "get" method has an incorrect argument count. + */ + private static class AnyGetterMismatchParams { + @GsonJsonAnyGetter + public Map<String, String> getTheMap(String arg) { + return new TreeMap<>(); + } + } + + /** + * Has {@link GsonJsonAnyGetter} method. + */ + private static class AnyGetterOnly { + @GsonJsonAnyGetter + private Map<String, Integer> getOverride() { + return null; + } + } + + /** + * Has {@link GsonJsonAnyGetter} method, but it's ignored. + */ + private static class AnyGetterIgnored { + @GsonJsonAnyGetter + @GsonJsonIgnore + private Map<String, Integer> getOverride() { + return null; + } + } + + /** + * Has {@link GsonJsonAnySetter} method. + */ + private static class AnySetterOnly { + @GsonJsonAnySetter + private void setOverride(String key, int value) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method, but it's ignored. + */ + private static class AnySetterIgnored { + @GsonJsonAnySetter + @GsonJsonIgnore + private void setOverride(String key, int value) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnyGetter} method that overrides the super class' method. + */ + private static class AnyGetterOverride extends DerivedFromData { + private Map<String, Integer> overMap; + + @GsonJsonAnyGetter + private Map<String, Integer> getOverride() { + return overMap; + } + } + + /** + * Has {@link GsonJsonAnySetter} method that overrides the super class' method. + */ + private static class AnySetterOverride extends DerivedFromData { + private Map<String, Integer> overMap; + + @GsonJsonAnySetter + private void setOverride(String key, int value) { + if (overMap == null) { + overMap = new TreeMap<>(); + } + + overMap.put(key, value); + } + } + + /** + * Has {@link GsonJsonAnySetter} method with too few parameters. + */ + private static class AnySetterTooFewParams extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(String key) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method with too few parameters. + */ + private static class AnySetterTooManyParams extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(String key, int value, String anotherValue) { + // do nothing + } + } + + /** + * Has {@link GsonJsonAnySetter} method whose first argument type is incorrect. + */ + private static class AnySetterInvalidParam extends DerivedFromData { + @GsonJsonAnySetter + public void setOverride(Integer key, String value) { + // do nothing + } + } +} diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java new file mode 100644 index 00000000..d0f0b1ec --- /dev/null +++ b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java @@ -0,0 +1,310 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.common.gson.internal; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Factory used with test Data. + */ +public class DataAdapterFactory implements TypeAdapterFactory { + + /** + * Output of {@link #makeList()}, encoded as json. + */ + public static final String ENCODED_LIST = "[{'id':100},{'id':101}]".replace('\'', '"'); + + /** + * Output of {@link #makeMap()}, encoded as json. + */ + public static final String ENCODED_MAP = "'data-100':{'id':100},'data-101':{'id':101}".replace('\'', '"'); + + /** + * Object handled by this factory. + */ + public static class Data { + private int id; + + public Data() { + super(); + } + + public Data(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public String toString() { + return "Data [id=" + id + "]"; + } + } + + /** + * Object derived from Data. + */ + public static class DerivedData extends Data { + private String text; + + public DerivedData() { + super(); + } + + public DerivedData(int id, String text) { + super(id); + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + return "DerivedData [text=" + text + ", toString()=" + super.toString() + "]"; + } + } + + /** + * Set to {@code true} when {@link #write(JsonWriter, Data)} has been invoked. + */ + private boolean dataWritten = false; + + /** + * Set to {@code true} when {@link #read(JsonReader)} has been invoked. + */ + private boolean dataRead = false; + + /** + * Clears the flags that indicate that "read" or "write" has been invoked. + */ + public void reset() { + dataWritten = true; + dataRead = true; + } + + public boolean isDataWritten() { + return dataWritten; + } + + public boolean isDataRead() { + return dataRead; + } + + /** + * Makes a list of Data. + * + * @return a new list of Data + */ + public static List<Data> makeList() { + List<Data> listField = new ArrayList<>(); + + listField.add(new Data(100)); + listField.add(new Data(101)); + + return listField; + } + + /** + * Makes an array of Data. + * + * @return a new array of Data + */ + public static JsonArray makeArray() { + JsonArray arr = new JsonArray(); + + for (Data data : makeList()) { + JsonObject json = new JsonObject(); + json.addProperty("id", data.getId()); + arr.add(json); + } + + return arr; + } + + /** + * Makes a map of Data. + * + * @return a new map of Data + */ + public static Map<String, List<Data>> makeMap() { + Map<String, List<Data>> map = new TreeMap<>(); + + for (Data data : makeList()) { + map.put("data-" + data.getId(), Arrays.asList(data)); + } + + return map; + } + + /** + * Adds Data objects to a tree, mirroring {@link #makeMap()}. + * + * @param tree tree into which objects are to be added + */ + public static void addToObject(JsonObject tree) { + for (JsonElement ent : makeArray()) { + JsonObject obj = ent.getAsJsonObject(); + JsonArray arr = new JsonArray(); + arr.add(obj); + tree.add("data-" + obj.get("id").getAsString(), arr); + } + } + + @SuppressWarnings("unchecked") + @Override + public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { + if (type.getRawType() == Data.class) { + return (TypeAdapter<T>) new DataTypeAdapter(gson.getDelegateAdapter(this, TypeToken.get(Data.class)), + gson.getAdapter(JsonElement.class)); + } + + if (type.getRawType() == DerivedData.class) { + return (TypeAdapter<T>) new DerivedDataTypeAdapter( + gson.getDelegateAdapter(this, TypeToken.get(DerivedData.class)), + gson.getAdapter(JsonElement.class)); + } + + return null; + } + + /** + * Adapter for "Data". + */ + private class DataTypeAdapter extends TypeAdapter<Data> { + private TypeAdapter<Data> delegate; + private TypeAdapter<JsonElement> elementAdapter; + + /** + * Constructs the object. + * + * @param delegate delegate adapter + * @param elementAdapter element adapter + */ + public DataTypeAdapter(TypeAdapter<Data> delegate, TypeAdapter<JsonElement> elementAdapter) { + this.delegate = delegate; + this.elementAdapter = elementAdapter; + } + + @Override + public void write(JsonWriter out, Data data) throws IOException { + dataWritten = true; + + JsonElement tree = delegate.toJsonTree(data); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + jsonObj.addProperty("id", data.getId()); + } + + elementAdapter.write(out, tree); + } + + @Override + public Data read(JsonReader in) throws IOException { + dataRead = true; + + JsonElement tree = elementAdapter.read(in); + Data data = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + data.setId(jsonObj.get("id").getAsInt()); + } + + return data; + } + } + /** + * Adapter for "DerivedData". + */ + private class DerivedDataTypeAdapter extends TypeAdapter<DerivedData> { + private TypeAdapter<DerivedData> delegate; + private TypeAdapter<JsonElement> elementAdapter; + + /** + * Constructs the object. + * + * @param delegate delegate adapter + * @param elementAdapter element adapter + */ + public DerivedDataTypeAdapter(TypeAdapter<DerivedData> delegate, TypeAdapter<JsonElement> elementAdapter) { + this.delegate = delegate; + this.elementAdapter = elementAdapter; + } + + @Override + public void write(JsonWriter out, DerivedData data) throws IOException { + dataWritten = true; + + JsonElement tree = delegate.toJsonTree(data); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + jsonObj.addProperty("id", data.getId()); + jsonObj.addProperty("text", data.getText()); + } + + elementAdapter.write(out, tree); + } + + @Override + public DerivedData read(JsonReader in) throws IOException { + dataRead = true; + + JsonElement tree = elementAdapter.read(in); + DerivedData data = delegate.fromJsonTree(tree); + + if (tree.isJsonObject()) { + JsonObject jsonObj = tree.getAsJsonObject(); + data.setId(jsonObj.get("id").getAsInt()); + data.setText(jsonObj.get("text").getAsString()); + } + + return data; + } + } +} diff --git a/policy-endpoints/pom.xml b/policy-endpoints/pom.xml index 41fa2f30..2c8b0d7f 100644 --- a/policy-endpoints/pom.xml +++ b/policy-endpoints/pom.xml @@ -53,6 +53,12 @@ <dependency> <groupId>org.onap.policy.common</groupId> + <artifactId>gson</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.onap.policy.common</groupId> <artifactId>utils</artifactId> <version>${project.version}</version> </dependency> diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java index 037f6c6f..286d73dc 100644 --- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java +++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java @@ -27,7 +27,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import org.onap.policy.common.endpoints.http.server.internal.GsonMessageBodyHandler; +import org.onap.policy.common.gson.GsonMessageBodyHandler; /** * GsonMessageBodyHandler that tracks activities. @@ -1,8 +1,8 @@ -<!-- - ============LICENSE_START======================================================= - ONAP policy +<!-- + ============LICENSE_START======================================================= + ONAP policy ================================================================================ - Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. + Copyright (C) 2017-2019 AT&T Intellectual Property. 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. @@ -15,7 +15,7 @@ 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. - ============LICENSE_END========================================================= + ============LICENSE_END========================================================= --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> @@ -51,7 +51,7 @@ <staging.path>content/repositories/staging/</staging.path> <!-- sonar/jacoco overrides --> - <!-- Overriding oparent default sonar/jacoco settings Combine all + <!-- Overriding oparent default sonar/jacoco settings Combine all our reports into one file shared across sub-modules --> <sonar.jacoco.reportPath>${project.basedir}/../target/code-coverage/jacoco-ut.exec</sonar.jacoco.reportPath> <sonar.jacoco.itReportPath>${project.basedir}/../target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPath> @@ -65,6 +65,7 @@ <module>capabilities</module> <module>utils-test</module> <module>utils</module> + <module>gson</module> <module>common-logging</module> <module>common-parameters</module> <module>integrity-audit</module> @@ -90,7 +91,7 @@ <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco.version}</version> <configuration> - <!-- Note: This exclusion list should match <sonar.exclusions> + <!-- Note: This exclusion list should match <sonar.exclusions> property above --> <excludes> <exclude>**/gen/**</exclude> @@ -100,8 +101,8 @@ </excludes> </configuration> <executions> - <!-- Prepares the property pointing to the JaCoCo - runtime agent which is passed as VM argument when Maven the Surefire plugin + <!-- Prepares the property pointing to the JaCoCo + runtime agent which is passed as VM argument when Maven the Surefire plugin is executed. --> <execution> <id>pre-unit-test</id> @@ -112,7 +113,7 @@ <destFile>${sonar.jacoco.reportPath}</destFile> </configuration> </execution> - <!-- Ensures that the code coverage report for unit + <!-- Ensures that the code coverage report for unit tests is created after unit tests have been run. --> <execution> <id>post-unit-test</id> |