From 98e3bbefc7966df6fb70b4448360772a198841eb Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 14 Feb 2026 13:47:20 +0100 Subject: [PATCH 1/2] feat: replace Wink JSON and Jackson with Jakarta JSON Processing (JSON-P) --- bom/lyo-bom/pom.xml | 54 +- client/oslc-client/pom.xml | 5 +- .../lyo/client/RootServicesHelper.java | 9 +- core/oslc4j-core-build/pom.xml | 1 - core/oslc4j-core-wink/pom.xml | 48 - .../oslc4j/provider/json4j/JsonHelper.java | 2348 ----------------- core/oslc4j-json4j-provider/pom.xml | 15 +- .../json4j/AbstractOslcRdfJsonProvider.java | 555 ++-- .../json4j/Json4JProvidersRegistry.java | 44 +- .../json4j/Json4JSimpleProvidersRegistry.java | 44 +- .../Json4JUpdatedProvidersRegistry.java | 44 +- .../oslc4j/provider/json4j/JsonHelper.java | 68 + .../json4j/OslcCompactJsonProvider.java | 144 +- .../json4j/OslcRdfJsonArrayProvider.java | 127 +- .../json4j/OslcRdfJsonCollectionProvider.java | 314 +-- .../provider/json4j/OslcRdfJsonProvider.java | 374 +-- .../OslcSimpleRdfJsonArrayProvider.java | 59 +- .../OslcSimpleRdfJsonCollectionProvider.java | 59 +- .../json4j/internal/AbstractBuilder.java | 63 + .../provider/json4j/internal/JSONArray.java | 118 + .../json4j/internal/JSONModelBuilder.java | 645 +++++ .../provider/json4j/internal/JSONObject.java | 164 ++ .../json4j/internal/ResourceBuilder.java | 826 ++++++ .../OslcJsonProviderCollectionTest.java | 8 +- .../json4j/internal/JSONArrayTest.java | 39 + .../json4j/internal/JSONObjectTest.java | 67 + .../json4j/internal/ServiceProviderTest.java | 46 + .../json4j/test/JsonOslcNameTest.java | 17 +- .../src/test/resources/provider.json | 66 + core/oslc4j-utils/pom.xml | 5 - .../utils/marshallers/OSLC4JMarshaller.java | 4 +- pom.xml | 8 +- server/oauth-webapp/pom.xml | 5 +- .../webapp/services/ConsumersService.java | 5 +- .../oauth/webapp/services/OAuthService.java | 13 +- server/oslc-ui-model/pom.xml | 19 +- .../org/eclipse/lyo/server/ui/model/Link.java | 20 +- .../eclipse/lyo/server/ui/model/Preview.java | 14 +- .../lyo/server/ui/model/PreviewFactory.java | 15 +- .../eclipse/lyo/server/ui/model/Property.java | 20 +- .../server/ui/model/PropertyDefintion.java | 41 +- .../lyo/server/ui/model/PropertyValue.java | 26 +- server/pom.xml | 11 +- 43 files changed, 2906 insertions(+), 3671 deletions(-) delete mode 100644 core/oslc4j-core-wink/pom.xml delete mode 100644 core/oslc4j-core-wink/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java create mode 100644 core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java create mode 100644 core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java create mode 100644 core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java create mode 100644 core/oslc4j-json4j-provider/src/test/resources/provider.json diff --git a/bom/lyo-bom/pom.xml b/bom/lyo-bom/pom.xml index aacc2b9e3..dae3b9a06 100644 --- a/bom/lyo-bom/pom.xml +++ b/bom/lyo-bom/pom.xml @@ -38,11 +38,6 @@ oslc4j-json4j-provider ${v.lyo} - - org.eclipse.lyo.oslc4j.core - oslc4j-core-wink - ${v.lyo} - org.eclipse.lyo.oslc4j.core oslc4j-utils @@ -341,54 +336,7 @@ jakarta.validation-api 3.1.1 - - com.fasterxml.jackson - jackson-bom - ${v.jackson} - pom - import - - - - com.fasterxml.jackson.core - jackson-core - ${v.jackson} - - - com.fasterxml.jackson.core - jackson-databind - ${v.jackson} - - - com.fasterxml.jackson.core - jackson-annotations - ${v.jackson} - - - com.fasterxml.jackson.jakarta.rs - jackson-jakarta-rs-json-provider - ${v.jackson} - - - com.fasterxml.jackson.jakarta.rs - jackson-jakarta-rs-base - ${v.jackson} - - - com.fasterxml.jackson.module - jackson-module-jakarta-xmlbind-annotations - ${v.jackson} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${v.jackson} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${v.jackson} - + diff --git a/client/oslc-client/pom.xml b/client/oslc-client/pom.xml index 7ad312285..a21d2f6e1 100644 --- a/client/oslc-client/pom.xml +++ b/client/oslc-client/pom.xml @@ -112,8 +112,9 @@ - com.fasterxml.jackson.core - jackson-databind + org.eclipse.lyo.oslc4j.core + oslc4j-json4j-provider + ${v.lyo} diff --git a/client/oslc-client/src/main/java/org/eclipse/lyo/client/RootServicesHelper.java b/client/oslc-client/src/main/java/org/eclipse/lyo/client/RootServicesHelper.java index d6edafdc0..5039cf6a9 100644 --- a/client/oslc-client/src/main/java/org/eclipse/lyo/client/RootServicesHelper.java +++ b/client/oslc-client/src/main/java/org/eclipse/lyo/client/RootServicesHelper.java @@ -13,7 +13,8 @@ */ package org.eclipse.lyo.client; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; +import jakarta.json.Json; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; @@ -343,10 +344,8 @@ public String requestConsumerKey(String consumerName, String consumerSecret) // trying to be liberal with all possible JSON content types throw new IllegalStateException("Server returned something else than JSON in the response"); } - ObjectMapper mapper = new ObjectMapper(); - Map jsonData = new HashMap<>(); - jsonData = mapper.readValue(content, Map.class); - String consumerKey = (String) jsonData.get("key"); + JSONObject jsonData = new JSONObject(Json.createReader(content).readObject()); + String consumerKey = jsonData.getString("key"); logger.debug( "Consumer should redirect user to this approval URL, to approve the OAuth consumer: " + getConsumerApprovalUrl(consumerKey)); diff --git a/core/oslc4j-core-build/pom.xml b/core/oslc4j-core-build/pom.xml index 604f23376..a3597ecfa 100644 --- a/core/oslc4j-core-build/pom.xml +++ b/core/oslc4j-core-build/pom.xml @@ -22,7 +22,6 @@ ../oslc-trs ../oslc4j-jena-provider ../shacl - ../oslc4j-core-wink ../oslc4j-json4j-provider ../oslc4j-utils ../lyo-core-settings diff --git a/core/oslc4j-core-wink/pom.xml b/core/oslc4j-core-wink/pom.xml deleted file mode 100644 index e9ca31a69..000000000 --- a/core/oslc4j-core-wink/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - org.eclipse.lyo.oslc4j.core - oslc4j-core-build - 7.0.0-SNAPSHOT - ../oslc4j-core-build/pom.xml - - oslc4j-core-wink - Lyo :: Core :: OSLC Wink ext - Core components of the Eclipse Lyo OSLC4J SDK that rely on Wink. - - - - - - - - org.eclipse.lyo.oslc4j.core - oslc4j-core - - - org.apache.wink - wink-json4j - 1.4 - - - - - - - org.apache.maven.plugins - maven-source-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - org.apache.maven.plugins - maven-resources-plugin - - - - diff --git a/core/oslc4j-core-wink/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java b/core/oslc4j-core-wink/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java deleted file mode 100644 index cdee7a04b..000000000 --- a/core/oslc4j-core-wink/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java +++ /dev/null @@ -1,2348 +0,0 @@ -/* - * Copyright (c) 2020 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 - * which is available at http://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause - */ -package org.eclipse.lyo.oslc4j.provider.json4j; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.AbstractCollection; -import java.util.AbstractList; -import java.util.AbstractSequentialList; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.Deque; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Queue; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.namespace.QName; - -import org.apache.wink.json4j.JSONArray; -import org.apache.wink.json4j.JSONException; -import org.apache.wink.json4j.JSONObject; -import org.eclipse.lyo.oslc4j.core.NestedWildcardProperties; -import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; -import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; -import org.eclipse.lyo.oslc4j.core.OslcGlobalNamespaceProvider; -import org.eclipse.lyo.oslc4j.core.SingletonWildcardProperties; -import org.eclipse.lyo.oslc4j.core.annotation.OslcName; -import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespaceDefinition; -import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; -import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; -import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; -import org.eclipse.lyo.oslc4j.core.annotation.OslcSchema; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreInvalidPropertyDefinitionException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreInvalidPropertyTypeException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingNamespaceDeclarationException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingNamespacePrefixException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingSetMethodException; -import org.eclipse.lyo.oslc4j.core.exception.OslcCoreRelativeURIException; -import org.eclipse.lyo.oslc4j.core.model.AbstractResource; -import org.eclipse.lyo.oslc4j.core.model.AnyResource; -import org.eclipse.lyo.oslc4j.core.model.IExtendedResource; -import org.eclipse.lyo.oslc4j.core.model.IOslcCustomNamespaceProvider; -import org.eclipse.lyo.oslc4j.core.model.IReifiedResource; -import org.eclipse.lyo.oslc4j.core.model.IResource; -import org.eclipse.lyo.oslc4j.core.model.InheritedMethodAnnotationHelper; -import org.eclipse.lyo.oslc4j.core.model.OslcConstants; -import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; -import org.eclipse.lyo.oslc4j.core.model.TypeFactory; -import org.eclipse.lyo.oslc4j.core.model.XMLLiteral; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated -public final class JsonHelper -{ - private static final String JSON_PROPERTY_DELIMITER = ":"; - private static final String JSON_PROPERTY_PREFIXES = "prefixes"; - private static final String JSON_PROPERTY_SUFFIX_ABOUT = "about"; - private static final String JSON_PROPERTY_SUFFIX_MEMBER = "member"; - private static final String JSON_PROPERTY_SUFFIX_RESOURCE = "resource"; - private static final String JSON_PROPERTY_SUFFIX_RESPONSE_INFO = "responseInfo"; - private static final String JSON_PROPERTY_SUFFIX_RESULTS = "results"; - private static final String JSON_PROPERTY_SUFFIX_TOTAL_COUNT = "totalCount"; - private static final String JSON_PROPERTY_SUFFIX_NEXT_PAGE = "nextPage"; - private static final String JSON_PROPERTY_SUFFIX_TYPE = "type"; - private static final String JSON_PROPERTY_SUFFIX_FIRST = "first"; - private static final String JSON_PROPERTY_SUFFIX_REST = "rest"; - private static final String JSON_PROPERTY_SUFFIX_NIL = "nil"; - private static final String JSON_PROPERTY_SUFFIX_LIST = "List"; - private static final String JSON_PROPERTY_SUFFIX_ALT = "Alt"; - private static final String JSON_PROPERTY_SUFFIX_BAG = "Bag"; - private static final String JSON_PROPERTY_SUFFIX_SEQ = "Seq"; - - private static final String RDF_ABOUT_URI = OslcConstants.RDF_NAMESPACE + JSON_PROPERTY_SUFFIX_ABOUT; - private static final String RDF_TYPE_URI = OslcConstants.RDF_NAMESPACE + JSON_PROPERTY_SUFFIX_TYPE; - private static final String RDF_NIL_URI = OslcConstants.RDF_NAMESPACE + JSON_PROPERTY_SUFFIX_NIL; - private static final String RDF_RESOURCE_URI = OslcConstants.RDF_NAMESPACE + JSON_PROPERTY_SUFFIX_RESOURCE; - - private static final String METHOD_NAME_START_GET = "get"; - private static final String METHOD_NAME_START_IS = "is"; - private static final String METHOD_NAME_START_SET = "set"; - - private static final int METHOD_NAME_START_GET_LENGTH = METHOD_NAME_START_GET.length(); - private static final int METHOD_NAME_START_IS_LENGTH = METHOD_NAME_START_IS.length(); - - private static final String POSITIVE_INF = "INF"; - private static final String NEGATIVE_INF = "-INF"; - private static final String NOT_A_NUMBER = "NaN"; - - /** - * System property {@value} : When "true", write "INF", "-INF", and "NaN" - * strings for Infinity, -Infinity, and NaN float and double values, - * respectively. Enabled by default. - * - * @see #OSLC4J_READ_SPECIAL_NUMS - */ - public static final String OSLC4J_WRITE_SPECIAL_NUMS = "org.eclipse.lyo.oslc4j.writeSpecialNumberValues"; - - /** - * System property {@value} : When "true", read "INF", "-INF", and "NaN" - * strings for Infinity, -Infinity, and NaN float and double values, - * respectively. Enabled by default. - * - * @see #OSLC4J_WRITE_SPECIAL_NUMS - */ - public static final String OSLC4J_READ_SPECIAL_NUMS = "org.eclipse.lyo.oslc4j.readSpecialNumberValues"; - - private static final Logger logger = LoggerFactory.getLogger(JsonHelper.class.getName()); - - private JsonHelper() - { - super(); - } - - public static JSONObject createJSON(final String descriptionAbout, - final String responseInfoAbout, - final ResponseInfo responseInfo, - final Object[] objects, - final Map properties) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException, - JSONException, - OslcCoreApplicationException - { - final JSONObject resultJSONObject = new JSONObject(); - - final Map namespaceMappings = new TreeMap<>(); - final Map reverseNamespaceMappings = new HashMap<>(); - - // Add all global namespace mappings, since they have lower precedence - Map globalPrefixDefinitionMap = OslcGlobalNamespaceProvider.getInstance().getPrefixDefinitionMap(); - for(Map.Entry prefixDefinitionEntry : globalPrefixDefinitionMap.entrySet()) { - namespaceMappings.put(prefixDefinitionEntry.getKey(), prefixDefinitionEntry.getValue()); - reverseNamespaceMappings.put(prefixDefinitionEntry.getValue(), prefixDefinitionEntry.getKey()); - } - - if (descriptionAbout != null) - { - final JSONArray jsonArray = new JSONArray(); - - for (final Object object : objects) - { - HashMap visitedObjects = new HashMap<>(); - final JSONObject jsonObject = handleSingleResource(object, - new JSONObject(), - namespaceMappings, - reverseNamespaceMappings, - properties, - visitedObjects); - - if (jsonObject != null) - { - jsonArray.add(jsonObject); - } - } - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - // Ensure we have an rdfs prefix - final String rdfsPrefix = ensureNamespacePrefix(OslcConstants.RDFS_NAMESPACE_PREFIX, - OslcConstants.RDFS_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - resultJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_ABOUT, - descriptionAbout); - - /* Support for Container rdf:type */ - if(OSLC4JUtils.isQueryResultListAsContainer()){ - final JSONArray containerTypesJSONArray = new JSONArray(); - - final JSONObject containerTypeJSONObject = new JSONObject(); - - - containerTypeJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - OslcConstants.TYPE_CONTAINER); - - - containerTypesJSONArray.add(containerTypeJSONObject); - - resultJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE, - containerTypesJSONArray); - - Map visitedObjects = new HashMap<>(); - addExtendedProperties(namespaceMappings, - reverseNamespaceMappings, - resultJSONObject, - (IExtendedResource) responseInfo.getContainer(), - properties, - visitedObjects); - } - - - resultJSONObject.put(rdfsPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_MEMBER, - jsonArray); - - if (responseInfoAbout != null) - { - // Ensure we have an oslc prefix - final String oslcPrefix = ensureNamespacePrefix(OslcConstants.OSLC_CORE_NAMESPACE_PREFIX, - OslcConstants.OSLC_CORE_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - final JSONObject responseInfoJSONObject = new JSONObject(); - - responseInfoJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_ABOUT, - responseInfoAbout); - - - - if (responseInfo != null) - { - responseInfoJSONObject.put(oslcPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TOTAL_COUNT, - responseInfo.totalCount() == null ? objects.length : responseInfo.totalCount()); - - if (responseInfo.nextPage() != null) - { - final JSONObject nextPageJSONObject = new JSONObject(); - nextPageJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - responseInfo.nextPage()); - responseInfoJSONObject.put(oslcPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_NEXT_PAGE, - nextPageJSONObject); - } - - final JSONArray responseInfoTypesJSONArray = new JSONArray(); - - final JSONObject responseInfoTypeJSONObject = new JSONObject(); - - responseInfoTypeJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - OslcConstants.TYPE_RESPONSE_INFO); - - responseInfoTypesJSONArray.add(responseInfoTypeJSONObject); - - responseInfoJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE, - responseInfoTypesJSONArray); - - resultJSONObject.put(oslcPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESPONSE_INFO, - responseInfoJSONObject); - - Map visitedObjects = new HashMap<>(); - addExtendedProperties(namespaceMappings, - reverseNamespaceMappings, - responseInfoJSONObject, - (IExtendedResource) responseInfo, - properties, - visitedObjects); - } - } - } - else if (objects.length == 1) - { - HashMap visitedObjects = new HashMap<>(); - handleSingleResource(objects[0], - resultJSONObject, - namespaceMappings, - reverseNamespaceMappings, - properties, - visitedObjects); - } - - // Set the namespace prefixes - final JSONObject namespaces = new JSONObject(); - for (final Map.Entry namespaceMapping : namespaceMappings.entrySet()) - { - namespaces.put(namespaceMapping.getKey(), - namespaceMapping.getValue()); - } - - if (namespaces.size() > 0) - { - resultJSONObject.put(JSON_PROPERTY_PREFIXES, - namespaces); - } - - return resultJSONObject; - } - - public static Object[] fromJSON(final JSONObject jsonObject, - final Class beanClass) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InstantiationException, - InvocationTargetException, - OslcCoreApplicationException, - URISyntaxException - { - final List beans = new ArrayList<>(); - final Map namespaceMappings = new HashMap<>(); - final Map reverseNamespaceMappings = new HashMap<>(); - - // First read the prefixes and set up maps so we can create full property definition values later - final Object prefixes = jsonObject.opt(JSON_PROPERTY_PREFIXES); - - if (prefixes instanceof JSONObject) - { - final JSONObject prefixesJSONObject = (JSONObject) prefixes; - - @SuppressWarnings({"unchecked", "cast"}) - final Set> prefixesEntrySet = (Set>) prefixesJSONObject.entrySet(); - for (final Map.Entry prefixEntry : prefixesEntrySet) - { - final String prefix = prefixEntry.getKey(); - final Object namespace = prefixEntry.getValue(); - - if (namespace instanceof String) - { - namespaceMappings.put(prefix, - namespace.toString()); - - reverseNamespaceMappings.put(namespace.toString(), - prefix.toString()); - } - } - } - - // We have to know the reverse mapping for the rdf namespace - final String rdfPrefix = reverseNamespaceMappings.get(OslcConstants.RDF_NAMESPACE); - - if (rdfPrefix == null) - { - throw new OslcCoreMissingNamespaceDeclarationException(OslcConstants.RDF_NAMESPACE); - } - - final Map, Map> classPropertyDefinitionsToSetMethods = new HashMap<>(); - - JSONArray jsonArray = null; - - // Look for rdfs:member - final String rdfsPrefix = reverseNamespaceMappings.get(OslcConstants.RDFS_NAMESPACE); - - if (rdfsPrefix != null) - { - final Object members = jsonObject.opt(rdfsPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_MEMBER); - - if (members instanceof JSONArray) - { - jsonArray = (JSONArray) members; - } - } - - if (jsonArray == null) - { - // Look for oslc:results. Seen in ChangeManagement. - final String oslcPrefix = reverseNamespaceMappings.get(OslcConstants.OSLC_CORE_NAMESPACE); - - if (oslcPrefix != null) - { - final Object results = jsonObject.opt(oslcPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESULTS); - - if (results instanceof JSONArray) - { - jsonArray = (JSONArray) results; - } - } - } - - if (jsonArray != null) - { - for (final Object object : jsonArray) - { - if (object instanceof JSONObject) - { - final JSONObject resourceJSONObject = (JSONObject) object; - - if (URI.class.equals(beanClass)) { - String uri = resourceJSONObject.optString(rdfPrefix + JSON_PROPERTY_DELIMITER - + JSON_PROPERTY_SUFFIX_RESOURCE); - - beans.add(URI.create(uri)); - } - else - { - final Object bean = beanClass.newInstance(); - HashSet rdfTypes = new HashSet<>(); - - fromJSON(rdfPrefix, - namespaceMappings, - classPropertyDefinitionsToSetMethods, - resourceJSONObject, - beanClass, - bean, - rdfTypes); - - beans.add(bean); - } - } - } - } - else - { - final Object bean = beanClass.newInstance(); - HashSet rdfTypes = new HashSet<>(); - - fromJSON(rdfPrefix, - namespaceMappings, - classPropertyDefinitionsToSetMethods, - jsonObject, - beanClass, - bean, - rdfTypes); - - beans.add(bean); - } - - return beans.toArray((Object[]) Array.newInstance(beanClass, - beans.size())); - } - - private static void buildAttributeResource(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class resourceClass, - final Method method, - final OslcPropertyDefinition propertyDefinitionAnnotation, - final JSONObject jsonObject, - final Object value, - final Map nestedProperties, - final boolean onlyNested) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException, - JSONException, - OslcCoreApplicationException - { - final String propertyDefinition = propertyDefinitionAnnotation.value(); - - String name; - final OslcName nameAnnotation = InheritedMethodAnnotationHelper.getAnnotation(method, - OslcName.class); - - if (nameAnnotation != null) - { - name = nameAnnotation.value(); - } - else - { - name = getDefaultPropertyName(method); - } - - if (!propertyDefinition.endsWith(name)) - { - throw new OslcCoreInvalidPropertyDefinitionException(resourceClass, - method, - propertyDefinitionAnnotation); - } - - final boolean isRdfContainer; - - final OslcRdfCollectionType collectionType = - InheritedMethodAnnotationHelper.getAnnotation(method, - OslcRdfCollectionType.class); - - if (collectionType != null && - OslcConstants.RDF_NAMESPACE.equals(collectionType.namespaceURI()) && - (JSON_PROPERTY_SUFFIX_LIST.equals(collectionType.collectionType()) - || JSON_PROPERTY_SUFFIX_ALT.equals(collectionType.collectionType()) - || JSON_PROPERTY_SUFFIX_BAG.equals(collectionType.collectionType()) - || JSON_PROPERTY_SUFFIX_SEQ.equals(collectionType.collectionType()))) - { - isRdfContainer = true; - } - else - { - isRdfContainer = false; - } - - final Object localResourceValue; - - final Class returnType = method.getReturnType(); - - if (returnType.isArray()) - { - final JSONArray jsonArray = new JSONArray(); - - // We cannot cast to Object[] in case this is an array of primitives. We will use Array reflection instead. - // Strange case about primitive arrays: they cannot be cast to Object[], but retrieving their individual elements - // does not return primitives, but the primitive object wrapping counterparts like Integer, Byte, Double, etc. - final int length = Array.getLength(value); - for (int index = 0; - index < length; - index++) - { - final Object object = Array.get(value, - index); - - final Object localResource = handleLocalResource(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - method, - object, - nestedProperties, - onlyNested); - if (localResource != null) - { - jsonArray.add(localResource); - } - } - - if (isRdfContainer) - { - localResourceValue = buildContainer(namespaceMappings, - reverseNamespaceMappings, - collectionType, jsonArray); - } - else - { - if (jsonArray.size() > 0) - { - localResourceValue = jsonArray; - } - else - { - localResourceValue = null; - } - } - } - else if (Collection.class.isAssignableFrom(returnType)) - { - final JSONArray jsonArray = new JSONArray(); - - @SuppressWarnings("unchecked") - final Collection collection = (Collection) value; - - for (final Object object : collection) - { - final Object localResource = handleLocalResource(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - method, - object, - nestedProperties, - onlyNested); - if (localResource != null) - { - jsonArray.add(localResource); - } - } - - if (isRdfContainer) - { - localResourceValue = buildContainer(namespaceMappings, - reverseNamespaceMappings, - collectionType, jsonArray); - } - else - { - if (jsonArray.size() > 0) - { - localResourceValue = jsonArray; - } - else - { - localResourceValue = null; - } - } - } - else - { - localResourceValue = handleLocalResource(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - method, - value, - nestedProperties, - onlyNested); - } - - if (localResourceValue != null) - { - final String namespace = propertyDefinition.substring(0, - propertyDefinition.length() - name.length()); - - final String prefix = reverseNamespaceMappings.get(namespace); - - if (prefix == null) - { - throw new OslcCoreMissingNamespaceDeclarationException(namespace); - } - - jsonObject.put(prefix + JSON_PROPERTY_DELIMITER + name, - localResourceValue); - } - } - - private static Object buildContainer(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final OslcRdfCollectionType collectionType, - final JSONArray jsonArray) - throws JSONException - { - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - if (JSON_PROPERTY_SUFFIX_LIST.equals(collectionType.collectionType())) - { - JSONObject listObject = new JSONObject(); - - listObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - OslcConstants.RDF_NAMESPACE + JSON_PROPERTY_SUFFIX_NIL); - - for (int i = jsonArray.size() - 1; i >= 0; i --) - { - Object o = jsonArray.get(i); - - JSONObject newListObject = new JSONObject(); - newListObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_FIRST, o); - newListObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_REST, listObject); - - listObject = newListObject; - } - - return listObject; - } - - JSONObject container = new JSONObject(); - - container.put(rdfPrefix + JSON_PROPERTY_DELIMITER + collectionType.collectionType(), - jsonArray); - - return container; - } - - private static void buildResource(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Object object, - final Class objectClass, - final JSONObject jsonObject, - final Map properties, - final Map visitedObjects) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException, - JSONException, - OslcCoreApplicationException - { - visitedObjects.put(object, jsonObject); - buildResourceAttributes(namespaceMappings, - reverseNamespaceMappings, - object, - objectClass, - jsonObject, - properties, - visitedObjects); - - // For JSON, we have to save array of rdf:type - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - if (rdfPrefix != null) - { - final JSONArray rdfTypesJSONArray = new JSONArray(); - - final String qualifiedName; - if (objectClass.getAnnotation(OslcResourceShape.class) != null) - { - qualifiedName = TypeFactory.getQualifiedName(objectClass); - if (qualifiedName != null) - { - addType(rdfPrefix, - rdfTypesJSONArray, - qualifiedName); - } - } - else - { - qualifiedName = null; - } - - if (object instanceof IExtendedResource) - { - final IExtendedResource extendedResource = (IExtendedResource) object; - for (final URI type : extendedResource.getTypes()) - { - final String typeString = type.toString(); - if (!typeString.equals(qualifiedName)) - { - addType(rdfPrefix, - rdfTypesJSONArray, - typeString); - } - } - } - - jsonObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE, - rdfTypesJSONArray); - } - } - - private static void addType(final String rdfPrefix, - final JSONArray rdfTypesJSONArray, - final String typeURI) - throws JSONException - { - final JSONObject rdfTypeJSONObject = new JSONObject(); - rdfTypeJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - typeURI); - rdfTypesJSONArray.add(rdfTypeJSONObject); - } - - private static void buildResourceAttributes(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Object object, - final Class objectClass, - final JSONObject jsonObject, - final Map properties, - final Map visitedObjects) - throws IllegalAccessException, - InvocationTargetException, - DatatypeConfigurationException, - JSONException, - OslcCoreApplicationException - { - if (properties == OSLC4JConstants.OSL4J_PROPERTY_SINGLETON) - { - return; - } - - for (final Method method : objectClass.getMethods()) - { - if (method.getParameterTypes().length == 0) - { - final String methodName = method.getName(); - if (((methodName.startsWith(METHOD_NAME_START_GET)) && - (methodName.length() > METHOD_NAME_START_GET_LENGTH)) || - ((methodName.startsWith(METHOD_NAME_START_IS)) && - (methodName.length() > METHOD_NAME_START_IS_LENGTH))) - { - final OslcPropertyDefinition oslcPropertyDefinitionAnnotation = InheritedMethodAnnotationHelper.getAnnotation(method, - OslcPropertyDefinition.class); - - if (oslcPropertyDefinitionAnnotation != null) - { - final Object value = method.invoke(object); - - if (value != null) - { - Map nestedProperties = null; - boolean onlyNested = false; - - if (properties != null) - { - @SuppressWarnings("unchecked") - final Map map = (Map)properties.get(oslcPropertyDefinitionAnnotation.value()); - - if (map != null) - { - nestedProperties = map; - } - else if (properties instanceof SingletonWildcardProperties && - ! (properties instanceof NestedWildcardProperties)) - { - nestedProperties = OSLC4JConstants.OSL4J_PROPERTY_SINGLETON; - } - else if (properties instanceof NestedWildcardProperties) - { - nestedProperties = ((NestedWildcardProperties)properties).commonNestedProperties(); - onlyNested = ! (properties instanceof SingletonWildcardProperties); - } - else - { - continue; - } - } - - buildAttributeResource(namespaceMappings, - reverseNamespaceMappings, - objectClass, - method, - oslcPropertyDefinitionAnnotation, - jsonObject, - value, - nestedProperties, - onlyNested); - } - } - } - } - } - - if (object instanceof IExtendedResource) - { - final IExtendedResource extendedResource = (IExtendedResource) object; - - addExtendedProperties(namespaceMappings, - reverseNamespaceMappings, - jsonObject, - extendedResource, - properties, - visitedObjects); - } - } - - protected static void addExtendedProperties(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final JSONObject jsonObject, - final IExtendedResource extendedResource, - final Map properties, - final Map visitedObjects) - throws JSONException, - DatatypeConfigurationException, - IllegalAccessException, - InvocationTargetException, - OslcCoreApplicationException - { - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - String rdfTypeKey = rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE; - JSONArray typesJSONArray; - if (jsonObject.containsKey(rdfTypeKey)) - { - typesJSONArray = (JSONArray) jsonObject.get(rdfTypeKey); - } else { - typesJSONArray = new JSONArray(); - } - - final JSONObject typeJSONObject = new JSONObject(); - - for (final URI type : extendedResource.getTypes()) - { - final String propertyName = type.toString(); - - if (properties != null && - properties.get(propertyName) == null && - ! (properties instanceof NestedWildcardProperties) && - ! (properties instanceof SingletonWildcardProperties)) - { - continue; - } - - typeJSONObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - propertyName); - - typesJSONArray.add(typeJSONObject); - } - - if (typesJSONArray.size() > 0) - { - jsonObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE, typesJSONArray); - } - - for (Map.Entry extendedProperty : extendedResource.getExtendedProperties().entrySet()) - { - final String namespace = extendedProperty.getKey().getNamespaceURI(); - final String localName = extendedProperty.getKey().getLocalPart(); - Map nestedProperties = null; - boolean onlyNested = false; - - if (properties != null) - { - @SuppressWarnings("unchecked") - final Map map = (Map)properties.get(namespace + localName); - - if (map != null) - { - nestedProperties = map; - } - else if (properties instanceof SingletonWildcardProperties && - ! (properties instanceof NestedWildcardProperties)) - { - nestedProperties = OSLC4JConstants.OSL4J_PROPERTY_SINGLETON; - } - else if (properties instanceof NestedWildcardProperties) - { - nestedProperties = ((NestedWildcardProperties)properties).commonNestedProperties(); - onlyNested = ! (properties instanceof SingletonWildcardProperties); - } - else - { - continue; - } - } - - final Object value = getExtendedPropertyJsonValue(namespaceMappings, - reverseNamespaceMappings, - extendedProperty.getValue(), - nestedProperties, - onlyNested, - visitedObjects); - - if (value == null && ! onlyNested) - { - logger.warn("Could not add extended property " + extendedProperty.getKey() + " for resource " + extendedResource.getAbout()); - } - else - { - String prefix = reverseNamespaceMappings.get(namespace); - - if (prefix == null) - { - prefix = extendedProperty.getKey().getPrefix(); - - // Add the prefix to the JSON namespace mappings. - namespaceMappings.put(prefix, namespace); - reverseNamespaceMappings.put(namespace, prefix); - } - - // Add the value to the JSON object. - jsonObject.put(prefix + JSON_PROPERTY_DELIMITER + localName, value); - } - } - } - - private static Object getExtendedPropertyJsonValue(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Object object, - final Map nestedProperties, - final boolean onlyNested, - final Map visitedObjects) - throws JSONException, - DatatypeConfigurationException, - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException, - OslcCoreApplicationException - { - final Class resourceClass = object.getClass(); - if (object instanceof Collection) - { - final JSONArray jsonArray = new JSONArray(); - @SuppressWarnings("unchecked") - final Collection c = (Collection) object; - for (final Object next : c) - { - final Object nextJson = getExtendedPropertyJsonValue(namespaceMappings, - reverseNamespaceMappings, - next, - nestedProperties, - onlyNested, - visitedObjects); - if (nextJson != null) - { - jsonArray.add(nextJson); - } - } - - return jsonArray; - } - else if ((object instanceof String) || - (object instanceof Boolean) || - (object instanceof Number)) - { - if (onlyNested) - { - return null; - } - - return object; - } - else if (object instanceof XMLLiteral) - { - if (onlyNested) - { - return null; - } - - // XMLLiterals are treated as strings in the OSLC 2.0 JSON format. - final XMLLiteral xmlLiteral = (XMLLiteral) object; - return xmlLiteral.getValue(); - } - else if (object instanceof Date) - { - if (onlyNested) - { - return null; - } - - final GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTime((Date) object); - - return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar).toString(); - } - else if (object instanceof URI) - { - if (onlyNested) - { - return null; - } - - return handleResourceReference(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - null, - (URI) object); - } - else if (object instanceof IResource && !visitedObjects.containsKey(object)) - { - return handleSingleResource(object, - new JSONObject(), - namespaceMappings, - reverseNamespaceMappings, - nestedProperties, - visitedObjects); - } - else if (visitedObjects.containsKey(object)) - { - JSONObject returnObject = visitedObjects.get(object); - if (!returnObject.isEmpty()) - return returnObject; - } - - return null; - } - - private static String getDefaultPropertyName(final Method method) - { - final String methodName = method.getName(); - final int startingIndex = methodName.startsWith(METHOD_NAME_START_GET) ? METHOD_NAME_START_GET_LENGTH : METHOD_NAME_START_IS_LENGTH; - final int endingIndex = startingIndex + 1; - - // We want the name to start with a lower-case letter - final String lowercasedFirstCharacter = methodName.substring(startingIndex, - endingIndex).toLowerCase(Locale.ENGLISH); - - if (methodName.length() == endingIndex) - { - return lowercasedFirstCharacter; - } - - return lowercasedFirstCharacter + - methodName.substring(endingIndex); - } - - private static Object handleLocalResource(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class resourceClass, - final Method method, - final Object object, - final Map nestedProperties, - final boolean onlyNested) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException, - JSONException, - OslcCoreApplicationException - { - // Handle special float values. - if ((object instanceof Float) && - writeSpecialNumberValues()) - { - if (onlyNested) - { - return null; - } - - final Float f = (Float) object; - if (f.compareTo(Float.POSITIVE_INFINITY) == 0) - { - return POSITIVE_INF; - } - - if (f.compareTo(Float.NEGATIVE_INFINITY) == 0) - { - return NEGATIVE_INF; - } - - if (f.isNaN()) - { - return NOT_A_NUMBER; - } - } - - // Handle special double values. - if ((object instanceof Double) && - writeSpecialNumberValues()) - { - if (onlyNested) - { - return null; - } - - final Double d = (Double) object; - if (d.compareTo(Double.POSITIVE_INFINITY) == 0) - { - return POSITIVE_INF; - } - - if (d.compareTo(Double.NEGATIVE_INFINITY) == 0) - { - return NEGATIVE_INF; - } - - if (d.isNaN()) - { - return NOT_A_NUMBER; - } - } - - if ((object instanceof String) || - (object instanceof Boolean) || - (object instanceof Number)) - { - if (onlyNested) - { - return null; - } - - return object; - } - else if (object instanceof URI) - { - if (onlyNested) - { - return null; - } - - return handleResourceReference(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - method, - (URI) object); - } - else if (object instanceof Date) - { - if (onlyNested) - { - return null; - } - - final GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTime((Date) object); - - return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar).toString(); - } - else if (object instanceof IReifiedResource) - { - return handleReifiedResource(namespaceMappings, - reverseNamespaceMappings, - object.getClass(), - method, - (IReifiedResource) object, - nestedProperties); - } - - Map visitedObjects = new HashMap<>(); - return handleSingleResource(object, - new JSONObject(), - namespaceMappings, - reverseNamespaceMappings, - nestedProperties, - visitedObjects); - } - - private static Object handleReifiedResource(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class resourceClass, - final Method method, - final IReifiedResource reifiedResource, - final Map properties) - throws OslcCoreInvalidPropertyTypeException, - OslcCoreRelativeURIException, - JSONException, - IllegalAccessException, - InvocationTargetException, - DatatypeConfigurationException, - OslcCoreApplicationException - { - final Object value = reifiedResource.getValue(); - if (value == null) - { - return null; - } - - if (!(value instanceof URI)) - { - // The OSLC JSON serialization doesn't support reification on anything except - // resources by reference (typically links with labels). Throw an exception - // if the value isn't a URI. - // See http://open-services.net/bin/view/Main/OslcCoreSpecAppendixLinks - throw new OslcCoreInvalidPropertyTypeException(resourceClass, - method, - method.getReturnType()); - } - - // Add the resource reference value. - final JSONObject jsonObject = handleResourceReference(namespaceMappings, - reverseNamespaceMappings, - resourceClass, - method, - (URI) value); - - // Add any reified statements. - Map visitedObjects = new HashMap<>(); - buildResourceAttributes(namespaceMappings, - reverseNamespaceMappings, - reifiedResource, - resourceClass, - jsonObject, - properties, - visitedObjects); - - return jsonObject; - } - - protected static JSONObject handleResourceReference(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class resourceClass, - final Method method, - final URI uri) - throws OslcCoreRelativeURIException, - JSONException - { - if (OSLC4JUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) - { - throw new OslcCoreRelativeURIException(resourceClass, - (method == null) ? "" : method.getName(), - uri); - } - - // Special nested JSONObject for URI - final JSONObject jsonObject = new JSONObject(); - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - jsonObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - uri.toString()); - - return jsonObject; - } - - private static JSONObject handleSingleResource(final Object object, - final JSONObject jsonObject, - final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Map properties, - final Map visitedObjects) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException, - JSONException, - OslcCoreApplicationException - { - final Class objectClass = object.getClass(); - - if (object instanceof URI) { - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - jsonObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE, - ((URI) object).toASCIIString()); - - visitedObjects.put(object, jsonObject); - - } - else { - - // Collect the namespace prefix -> namespace mappings - recursivelyCollectNamespaceMappings(namespaceMappings, - reverseNamespaceMappings, - objectClass); - - if (object instanceof IResource) - { - final URI aboutURI = ((IResource) object).getAbout(); - addAboutURI(jsonObject, - namespaceMappings, - reverseNamespaceMappings, - objectClass, - aboutURI); - } - - buildResource(namespaceMappings, - reverseNamespaceMappings, - object, - objectClass, - jsonObject, - properties, - visitedObjects); - } - - return jsonObject; - } - - protected static void addAboutURI(final JSONObject jsonObject, - final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class objectClass, - final URI aboutURI) - throws OslcCoreRelativeURIException, - JSONException - { - if (aboutURI != null) - { - if (OSLC4JUtils.relativeURIsAreDisabled() && !aboutURI.isAbsolute()) - { - throw new OslcCoreRelativeURIException(objectClass, - "getAbout", - aboutURI); - } - - // Ensure we have an rdf prefix - final String rdfPrefix = ensureNamespacePrefix(OslcConstants.RDF_NAMESPACE_PREFIX, - OslcConstants.RDF_NAMESPACE, - namespaceMappings, - reverseNamespaceMappings); - - jsonObject.put(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_ABOUT, - aboutURI.toString()); - } - } - - private static String ensureNamespacePrefix(final String prefix, - final String namespace, - final Map namespaceMappings, - final Map reverseNamespaceMappings) - { - final String existingPrefix = reverseNamespaceMappings.get(namespace); - - if (existingPrefix != null) - { - return existingPrefix; - } - - final String existingNamespace = namespaceMappings.get(prefix); - - if (existingNamespace == null) - { - namespaceMappings.put(prefix, - namespace); - - reverseNamespaceMappings.put(namespace, - prefix); - - return prefix; - } - - // There is already a namespace for this prefix. We need to generate a new unique prefix. - int index = 1; - - while (true) - { - final String newPrefix = prefix + - index; - - if (!namespaceMappings.containsKey(newPrefix)) - { - namespaceMappings.put(newPrefix, - namespace); - - reverseNamespaceMappings.put(namespace, - newPrefix); - - return newPrefix; - } - - index++; - } - } - - private static void recursivelyCollectNamespaceMappings(final Map namespaceMappings, - final Map reverseNamespaceMappings, - final Class objectClass) - { - final OslcSchema oslcSchemaAnnotation = objectClass.getPackage().getAnnotation(OslcSchema.class); - if (oslcSchemaAnnotation != null) - { - final OslcNamespaceDefinition[] oslcNamespaceDefinitionAnnotations = oslcSchemaAnnotation.value(); - for (final OslcNamespaceDefinition oslcNamespaceDefinitionAnnotation : oslcNamespaceDefinitionAnnotations) - { - final String prefix = oslcNamespaceDefinitionAnnotation.prefix(); - final String namespaceURI = oslcNamespaceDefinitionAnnotation.namespaceURI(); - - namespaceMappings.put(prefix, - namespaceURI); - - reverseNamespaceMappings.put(namespaceURI, - prefix); - } - //Adding custom prefixes obtained from an implementation, if there is an implementation. - Class customNamespaceProvider = oslcSchemaAnnotation.customNamespaceProvider(); - if(!customNamespaceProvider.isInterface()) - { - try { - IOslcCustomNamespaceProvider customNamespaceProviderImpl = customNamespaceProvider.newInstance(); - Map customNamespacePrefixes = customNamespaceProviderImpl.getCustomNamespacePrefixes(); - if(null != customNamespacePrefixes) - { - for(Map.Entry namespaceEntry : customNamespacePrefixes.entrySet()) - { - namespaceMappings.put(namespaceEntry.getKey(), namespaceEntry.getValue()); - reverseNamespaceMappings.put(namespaceEntry.getValue(), namespaceEntry.getKey()); - } - } - } catch (IllegalAccessException e) { - throw new RuntimeException("The custom namespace provider implementation: "+ - customNamespaceProvider.getClass().getName() + - ", must have a public no args construtor", e); - } catch (InstantiationException e) { - throw new RuntimeException("The custom namespace provider must not be a abstract, nor interface class and " + - "must have a public no args constructor", e); - } - } - } - - final Class superClass = objectClass.getSuperclass(); - if (superClass != null) - { - recursivelyCollectNamespaceMappings(namespaceMappings, - reverseNamespaceMappings, - superClass); - } - - final Class[] interfaces = objectClass.getInterfaces(); - if (interfaces != null) - { - for (final Class interfac : interfaces) - { - recursivelyCollectNamespaceMappings(namespaceMappings, - reverseNamespaceMappings, - interfac); - } - } - } - - /** - * Returns a list of rdf:types for a given json object. If the list was - * populated before, returns the given list. This list will only be - * populated if the property inferTypeFromShape is set to true. - * - * @param jsonObject - * @param rdfPrefix - * @param types - * @return List of rdf:types - */ - private static HashSet getRdfTypesFromJsonObject(JSONObject jsonObject, String rdfPrefix, HashSet types) { - // The list of rdf:types will be populated only if the property - // inferTypeFromShape is set and if the list was not populated before. - // This is necessary because for an inline object, the retuned - // rdf:type is not from the parent object, it is from the actual - // resource. - if (OSLC4JUtils.inferTypeFromShape() && types.isEmpty()) { - final String typeProperty = rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE; - if (jsonObject.has(typeProperty)) { - try { - JSONArray array = jsonObject.getJSONArray(typeProperty); - for (int i = 0; i < array.size(); ++i) { - final JSONObject typeObj = array.getJSONObject(i); - String resTypePropertyValue = typeObj.getString(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE); - types.add(resTypePropertyValue); - } - } - catch (JSONException e) - { - throw new IllegalArgumentException(e); - } - } - } - return types; - } - - private static void fromJSON(final String rdfPrefix, - final Map jsonNamespaceMappings, - final Map, Map> classPropertyDefinitionsToSetMethods, - final JSONObject jsonObject, - final Class beanClass, - final Object bean, - HashSet rdfTypes) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InstantiationException, - InvocationTargetException, - OslcCoreApplicationException, - URISyntaxException - { - Map setMethodMap = classPropertyDefinitionsToSetMethods.get(beanClass); - if (setMethodMap == null) - { - setMethodMap = createPropertyDefinitionToSetMethods(beanClass); - - classPropertyDefinitionsToSetMethods.put(beanClass, - setMethodMap); - } - - boolean isIReifiedResource = false; - - if (bean instanceof IResource) - { - final Object aboutURIObject = jsonObject.opt(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_ABOUT); - - if (aboutURIObject instanceof String) - { - final URI aboutURI = new URI(aboutURIObject.toString()); - - if (OSLC4JUtils.relativeURIsAreDisabled() && !aboutURI.isAbsolute()) - { - throw new OslcCoreRelativeURIException(beanClass, - "setAbout", - aboutURI); - } - - ((IResource) bean).setAbout(aboutURI); - } - } - else if (bean instanceof IReifiedResource) - { - isIReifiedResource = true; - - @SuppressWarnings("unchecked") - final IReifiedResource reifiedResource = (IReifiedResource) bean; - String resourceReference; - try - { - resourceReference = jsonObject.getString(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE); - } - catch (JSONException e) - { - throw new IllegalArgumentException(e); - } - - try - { - reifiedResource.setValue(new URI(resourceReference)); - } - catch (ClassCastException e) - { - throw new IllegalArgumentException(e); - } - } - - final IExtendedResource extendedResource; - final Map extendedProperties; - if (bean instanceof IExtendedResource) - { - extendedResource = (IExtendedResource) bean; - extendedProperties = new HashMap<>(); - extendedResource.setExtendedProperties(extendedProperties); - } - else - { - extendedResource = null; - extendedProperties = null; - } - - // get the list of rdf types - rdfTypes = getRdfTypesFromJsonObject(jsonObject, rdfPrefix, rdfTypes); - - @SuppressWarnings("unchecked") - final Set> entrySet = jsonObject.entrySet(); - - for (final Map.Entry entry : entrySet) - { - final String prefixedName = entry.getKey(); - final Object jsonValue = entry.getValue(); - - final String[] split = prefixedName.split(JSON_PROPERTY_DELIMITER); - - if (split.length != 2) - { - if (!JSON_PROPERTY_PREFIXES.equals(prefixedName)) - { - logger.warn("Ignored JSON property '" + - prefixedName + - "'."); - } - } - else - { - final String namespacePrefix = split[0]; - final String name = split[1]; - - final String namespace = jsonNamespaceMappings.get(namespacePrefix); - - if (namespace == null) - { - throw new OslcCoreMissingNamespacePrefixException(namespacePrefix); - } - - final String propertyDefinition = namespace + - name; - - final Method setMethod = setMethodMap.get(propertyDefinition); - if (setMethod == null) - { - if (RDF_ABOUT_URI.equals(propertyDefinition) || - (isIReifiedResource && RDF_RESOURCE_URI.equals(propertyDefinition))) - { - // Ignore missing property definitions for rdf:about, rdf:types and - // rdf:resource for IReifiedResources. - } - else if (RDF_TYPE_URI.equals(propertyDefinition)) - { - if (extendedResource != null) - { - fillInRdfType(rdfPrefix, jsonObject, extendedResource); - } - // Otherwise ignore missing propertyDefinition for rdf:type. - } - else - { - if (extendedProperties == null) - { - logger.debug("Set method not found for object type: " + - beanClass.getName() + - ", propertyDefinition: " + - propertyDefinition); - } - else - { - final QName qName = new QName(namespace, - name, - namespacePrefix); - - final Object value = fromExtendedJSONValue(jsonValue, - rdfPrefix, - jsonNamespaceMappings, - beanClass, - qName, - rdfTypes); - extendedProperties.put(qName, value); - } - } - } - else - { - final Class setMethodParameterClass = setMethod.getParameterTypes()[0]; - Class setMethodComponentParameterClass = setMethodParameterClass; - - if (setMethodComponentParameterClass.isArray()) - { - setMethodComponentParameterClass = setMethodComponentParameterClass.getComponentType(); - } - else if (Collection.class.isAssignableFrom(setMethodComponentParameterClass)) - { - final Type genericParameterType = setMethod.getGenericParameterTypes()[0]; - - if (genericParameterType instanceof ParameterizedType) - { - final ParameterizedType parameterizedType = (ParameterizedType) genericParameterType; - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments.length == 1) - { - final Type actualTypeArgument = actualTypeArguments[0]; - if (actualTypeArgument instanceof Class) - { - setMethodComponentParameterClass = (Class) actualTypeArgument; - } - } - } - } - - final Object parameter = fromJSONValue(rdfPrefix, - jsonNamespaceMappings, - classPropertyDefinitionsToSetMethods, - beanClass, - setMethod, - setMethodParameterClass, - setMethodComponentParameterClass, - jsonValue, - rdfTypes); - - if (parameter != null) - { - try { - setMethod.invoke(bean, - new Object[] {parameter}); - } catch (IllegalAccessException | IllegalArgumentException | - InvocationTargetException e) { - logger.warn("Failed to set property {}='{}' via '{}'", prefixedName, parameter, setMethod.getName()); - throw e; - } - } - } - } - } - } - - /* - * Infer the appropriate bean value from the JSON value. We can't rely on - * the setter parameter type since this is an extended value that has no - * setter in the bean. - */ - private static Object fromExtendedJSONValue(final Object jsonValue, - final String rdfPrefix, - final Map jsonNamespaceMappings, - final Class beanClass, - final QName propertyQName, - HashSet rdfTypes) - throws DatatypeConfigurationException, - URISyntaxException, - IllegalArgumentException, - IllegalAccessException, - InstantiationException, - InvocationTargetException, - OslcCoreApplicationException - { - if (jsonValue instanceof JSONArray) - { - final JSONArray jsonArray = (JSONArray) jsonValue; - final ArrayList collection = new ArrayList<>(); - for (Object o : jsonArray) { - collection.add(fromExtendedJSONValue(o, rdfPrefix, jsonNamespaceMappings, beanClass, propertyQName, rdfTypes)); - } - - return collection; - } - else if (jsonValue instanceof JSONObject) - { - final JSONObject o = (JSONObject) jsonValue; - - // Is it a resource reference? - final Object resourceURIValue = o.opt(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE); - if (resourceURIValue != null) - { - final URI uri = new URI((String) resourceURIValue); - if (OSLC4JUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) - { - throw new OslcCoreRelativeURIException(beanClass, - "", - uri); - } - - return new URI((String) resourceURIValue); - } - - // Handle an inline resource. - final AbstractResource any = new AnyResource(); - fromJSON(rdfPrefix, - jsonNamespaceMappings, - new HashMap<>(), - o, - AnyResource.class, - any, - rdfTypes); - - return any; - } - else if (jsonValue instanceof String) - { - - // fix for Bug 412789 - // try to infer the data type from resource shapes for Strings - if (OSLC4JUtils.inferTypeFromShape()) { - - Object newObject = OSLC4JUtils.getValueBasedOnResourceShapeType(rdfTypes, propertyQName, jsonValue); - - // return the value only if the type was really inferred from - // the resource shape, otherwise keep the same behavior - if (null != newObject) { - - // return the new value only for ambiguous case - if ((newObject instanceof String) - || (newObject instanceof XMLLiteral) - || (newObject instanceof Date)) { - return newObject; - } - } - } - - // Check if it's in the OSLC date format. - try - { - return DatatypeFactory.newInstance() - .newXMLGregorianCalendar((String) jsonValue) - .toGregorianCalendar().getTime(); - } - catch (IllegalArgumentException e) - { - // It's not a date. Treat it as a string. - return jsonValue; - } - } - else if (jsonValue instanceof Integer) { - - // fix for Bug 412789 - // There is no need to infer data type from resource shapes as - // integer values do not have ambiguity cases - return jsonValue; - - } - else if (jsonValue instanceof Double) { - - // fix for Bug 412789 - // try to infer data type from resource shapes for Double - if (OSLC4JUtils.inferTypeFromShape()) { - Object newObject = OSLC4JUtils.getValueBasedOnResourceShapeType(rdfTypes, propertyQName, jsonValue); - - // return the value only if the type was really inferred from - // the resource shape, otherwise keep the same behavior - if (null != newObject) { - - // return the new value only for ambiguous case - if ((newObject instanceof Double) - || (newObject instanceof Float) - || (newObject instanceof BigDecimal)) { - return newObject; - } - } - } - - } - - return jsonValue; - } - - protected static void fillInRdfType(final String rdfPrefix, - final JSONObject jsonObject, - final IExtendedResource resource) - throws URISyntaxException - { - final String typeProperty = rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_TYPE; - if (jsonObject.has(typeProperty)) - { - try - { - JSONArray array = jsonObject.getJSONArray(typeProperty); - for (int i = 0; i < array.size(); ++i) - { - final JSONObject typeObj = array.getJSONObject(i); - resource.addType(new URI(typeObj.getString(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE))); - } - } - catch (JSONException e) - { - throw new IllegalArgumentException(e); - } - } - } - - private static boolean isRdfListNode(final String rdfPrefix, - final Class beanClass, - final Method setMethod, - final Object jsonValue) - { - if (!(jsonValue instanceof JSONObject)) - { - return false; - } - - final JSONObject jsonObject = (JSONObject)jsonValue; - - final boolean isListNode = - jsonObject.has(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_FIRST) - && jsonObject.has(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_REST); - if (isListNode) - { - return true; - } - - final boolean isNilResource = RDF_NIL_URI.equals( - jsonObject.optString(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE)); - if (!isNilResource) - { - return false; - } - - final String setMethodName = setMethod.getName(); - if (setMethodName.startsWith(METHOD_NAME_START_SET)) { - String getMethodName = METHOD_NAME_START_GET + setMethodName.substring(METHOD_NAME_START_GET_LENGTH); - Method getMethod; - try { - getMethod = beanClass.getMethod(getMethodName, new Class[0]); - } catch (NoSuchMethodException e) { - - String isMethodName = METHOD_NAME_START_IS + setMethodName.substring(METHOD_NAME_START_GET_LENGTH); - try { - getMethod = beanClass.getMethod(isMethodName, new Class[0]); - } catch (NoSuchMethodException e1) { - return false; - } - } - - final OslcRdfCollectionType collectionType = - InheritedMethodAnnotationHelper.getAnnotation(getMethod, - OslcRdfCollectionType.class); - - if (collectionType != null && - OslcConstants.RDF_NAMESPACE.equals(collectionType.namespaceURI()) && - "List".equals(collectionType.collectionType())) - { - return true; - } - } - - return false; - } - - private static Object fromJSONValue(final String rdfPrefix, - final Map jsonNamespaceMappings, - final Map, Map> classPropertyDefinitionsToSetMethods, - final Class beanClass, - final Method setMethod, - final Class setMethodParameterClass, - final Class setMethodComponentParameterClass, - final Object jsonValue, - HashSet rdfTypes) - throws DatatypeConfigurationException, - IllegalAccessException, - IllegalArgumentException, - InstantiationException, - InvocationTargetException, - OslcCoreApplicationException, - URISyntaxException - { - boolean isRdfContainerNode = isRdfListNode(rdfPrefix, beanClass, setMethod, jsonValue); - JSONArray container = null; - - if (! isRdfContainerNode && jsonValue instanceof JSONObject) { - - JSONObject parent = (JSONObject)jsonValue; - - try - { - container = parent.optJSONArray(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_ALT, - null); - - if (container == null) - { - container = parent.optJSONArray(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_BAG, - null); - } - - if (container == null) - { - container = parent.optJSONArray(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_SEQ, - null); - } - } - catch (JSONException e) - { - throw new IllegalArgumentException(e); - } - - isRdfContainerNode = container != null; - } - - if (!isRdfContainerNode && jsonValue instanceof JSONObject) - { - final JSONObject nestedJSONObject = (JSONObject) jsonValue; - - if (!IReifiedResource.class.isAssignableFrom(setMethodComponentParameterClass)) - { - // If this is the special case for an rdf:resource? - final Object uriObject = nestedJSONObject.opt(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE); - - if (uriObject instanceof String) - { - final URI uri = new URI(uriObject.toString()); - - if (OSLC4JUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) - { - throw new OslcCoreRelativeURIException(beanClass, - setMethod.getName(), - uri); - } - - return uri; - } - } - - final Object nestedBean = setMethodComponentParameterClass.newInstance(); - - fromJSON(rdfPrefix, - jsonNamespaceMappings, - classPropertyDefinitionsToSetMethods, - nestedJSONObject, - setMethodComponentParameterClass, - nestedBean, - rdfTypes); - - return nestedBean; - } - else if (jsonValue instanceof JSONArray || isRdfContainerNode) - { - final Collection jsonArray; - - if (isRdfContainerNode && container == null) - { - jsonArray = new ArrayList<>(); - - JSONObject listNode = (JSONObject) jsonValue; - while (listNode != null - && !RDF_NIL_URI.equals( - listNode.opt(rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_RESOURCE))) { - - Object o = listNode.opt( - rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_FIRST); - jsonArray.add(o); - - listNode = listNode.optJSONObject( - rdfPrefix + JSON_PROPERTY_DELIMITER + JSON_PROPERTY_SUFFIX_REST); - } - } - else if (isRdfContainerNode) - { - @SuppressWarnings("unchecked") - final Collection array = container; - - jsonArray = array; - } - else - { - @SuppressWarnings("unchecked") - final Collection array = (JSONArray)jsonValue; - - jsonArray = array; - } - - final ArrayList tempList = new ArrayList<>(); - - for (final Object jsonArrayEntryObject : jsonArray) - { - final Object parameterArrayObject = fromJSONValue(rdfPrefix, - jsonNamespaceMappings, - classPropertyDefinitionsToSetMethods, - beanClass, - setMethod, - setMethodComponentParameterClass, - setMethodComponentParameterClass, - jsonArrayEntryObject, - rdfTypes); - - tempList.add(parameterArrayObject); - } - - if (setMethodParameterClass.isArray()) - { - // To support primitive arrays, we have to use Array reflection to set individual elements. We cannot use Collection.toArray. - // Array.set will unwrap objects to their corresponding primitives. - final Object array = Array.newInstance(setMethodComponentParameterClass, - jsonArray.size()); - - int index = 0; - - for (final Object parameterArrayObject : tempList) - { - Array.set(array, - index, - parameterArrayObject); - - index++; - } - - return array; - } - - // This has to be a Collection - - final Collection collection; - - // Handle the Collection, List, Deque, Queue interfaces. - // Handle the AbstractCollection, AbstractList, AbstractSequentialList classes - if ((Collection.class == setMethodParameterClass) || - (List.class == setMethodParameterClass) || - (Deque.class == setMethodParameterClass) || - (Queue.class == setMethodParameterClass) || - (AbstractCollection.class == setMethodParameterClass) || - (AbstractList.class == setMethodParameterClass) || - (AbstractSequentialList.class == setMethodParameterClass)) - { - collection = new LinkedList<>(); - } - // Handle the Set interface - // Handle the AbstractSet class - else if ((Set.class == setMethodParameterClass) || - (AbstractSet.class == setMethodParameterClass)) - { - collection = new HashSet<>(); - } - // Handle the SortedSet and NavigableSet interfaces - else if ((SortedSet.class == setMethodParameterClass) || - (NavigableSet.class == setMethodParameterClass)) - { - collection = new TreeSet<>(); - } - // Not handled above. Let's try newInstance with possible failure. - else - { - @SuppressWarnings("unchecked") - final Collection tempCollection = ((Collection) setMethodParameterClass.newInstance()); - collection = tempCollection; - } - - collection.addAll(tempList); - - return collection; - } - else if (jsonValue == null) - { - if (Boolean.class == setMethodComponentParameterClass || Boolean.TYPE == setMethodComponentParameterClass) - { - throw new IllegalArgumentException("Boolean cannot be null."); - } - - if (Double.TYPE == setMethodComponentParameterClass) - { - if (readSpecialNumberValues()) - { - logger.warn("Null double value treated as NaN."); - return Double.NaN; - } - else - { - throw new IllegalArgumentException("Null double value not allowed. You can change this behavior by setting system property of " - + OSLC4J_READ_SPECIAL_NUMS + " to true."); - } - } - - if (Float.TYPE == setMethodComponentParameterClass) - { - if (readSpecialNumberValues()) - { - logger.warn("Null float value treated as NaN."); - return Float.NaN; - } - else - { - throw new IllegalArgumentException("Null float value not allowed. You can change this behavior by setting system property of " - + OSLC4J_READ_SPECIAL_NUMS + " to true."); - } - } - - if (Short.TYPE == setMethodComponentParameterClass || - Integer.TYPE == setMethodComponentParameterClass || - Long.TYPE == setMethodComponentParameterClass) - { - throw new IllegalArgumentException("Null values not allowed for type " + setMethodComponentParameterClass); - } - - return null; - } - else - { - final String stringValue = jsonValue.toString(); - - if (String.class == setMethodComponentParameterClass) - { - return stringValue; - } - else if ((Boolean.class == setMethodComponentParameterClass) || (Boolean.TYPE == setMethodComponentParameterClass)) - { - // Cannot use Boolean.parseBoolean since it supports case-insensitive TRUE. - if (Boolean.TRUE.toString().equals(stringValue)) - { - return Boolean.TRUE; - } - else if (Boolean.FALSE.toString().equals(stringValue)) - { - return Boolean.FALSE; - } - else - { - throw new IllegalArgumentException("'" + stringValue + "' has wrong format for Boolean."); - } - } - else if ((Byte.class == setMethodComponentParameterClass) || (Byte.TYPE == setMethodComponentParameterClass)) - { - return Byte.valueOf(stringValue); - } - else if ((Short.class == setMethodComponentParameterClass) || (Short.TYPE == setMethodComponentParameterClass)) - { - return Short.valueOf(stringValue); - } - else if ((Integer.class == setMethodComponentParameterClass) || (Integer.TYPE == setMethodComponentParameterClass)) - { - return Integer.valueOf(stringValue); - } - else if ((Long.class == setMethodComponentParameterClass) || (Long.TYPE == setMethodComponentParameterClass)) - { - return Long.valueOf(stringValue); - } - else if (BigInteger.class == setMethodComponentParameterClass) - { - return new BigInteger(stringValue); - } - else if ((Float.class == setMethodComponentParameterClass) || (Float.TYPE == setMethodComponentParameterClass)) - { - if (readSpecialNumberValues()) - { - if (POSITIVE_INF.equals(stringValue) || "Infinity".equals(stringValue)) - { - return Float.POSITIVE_INFINITY; - } - if (NEGATIVE_INF.equals(stringValue) || "-Infinity".equals(stringValue)) - { - return Float.NEGATIVE_INFINITY; - } - if (NOT_A_NUMBER.equals(stringValue)) - { - return Float.NaN; - } - } - - return Float.valueOf(stringValue); - } - else if ((Double.class == setMethodComponentParameterClass) || (Double.TYPE == setMethodComponentParameterClass)) - { - if (readSpecialNumberValues()) - { - if (POSITIVE_INF.equals(stringValue) || "Infinity".equals(stringValue)) - { - return Double.POSITIVE_INFINITY; - } - if (NEGATIVE_INF.equals(stringValue) || "-Infinity".equals(stringValue)) - { - return Double.NEGATIVE_INFINITY; - } - if (NOT_A_NUMBER.equals(stringValue)) - { - return Double.NaN; - } - } - - return Double.valueOf(stringValue); - } - else if (Date.class == setMethodComponentParameterClass) - { - return DatatypeFactory.newInstance().newXMLGregorianCalendar(stringValue).toGregorianCalendar().getTime(); - } - } - - return null; - } - - private static boolean readSpecialNumberValues() - { - return "true".equals(System.getProperty(OSLC4J_READ_SPECIAL_NUMS, "true")); - } - - private static boolean writeSpecialNumberValues() - { - return "true".equals(System.getProperty(OSLC4J_WRITE_SPECIAL_NUMS, "true")); - } - - private static Map createPropertyDefinitionToSetMethods(final Class beanClass) - throws OslcCoreApplicationException - { - final Map result = new HashMap<>(); - final Method[] methods = beanClass.getMethods(); - for (final Method method : methods) - { - if (method.getParameterTypes().length == 0) - { - final String getMethodName = method.getName(); - if (((getMethodName.startsWith(METHOD_NAME_START_GET)) && - (getMethodName.length() > METHOD_NAME_START_GET_LENGTH)) || - ((getMethodName.startsWith(METHOD_NAME_START_IS)) && - (getMethodName.length() > METHOD_NAME_START_IS_LENGTH))) - { - final OslcPropertyDefinition oslcPropertyDefinitionAnnotation = InheritedMethodAnnotationHelper.getAnnotation(method, - OslcPropertyDefinition.class); - - if (oslcPropertyDefinitionAnnotation != null) - { - // We need to find the set companion setMethod - final String setMethodName; - if (getMethodName.startsWith(METHOD_NAME_START_GET)) - { - setMethodName = METHOD_NAME_START_SET + - getMethodName.substring(METHOD_NAME_START_GET_LENGTH); - } - else - { - setMethodName = METHOD_NAME_START_SET + - getMethodName.substring(METHOD_NAME_START_IS_LENGTH); - } - - final Class getMethodReturnType = method.getReturnType(); - try - { - final Method setMethod = beanClass.getMethod(setMethodName, - getMethodReturnType); - - result.put(oslcPropertyDefinitionAnnotation.value(), - setMethod); - } - catch (final NoSuchMethodException exception) - { - throw new OslcCoreMissingSetMethodException(beanClass, - method, - exception); - } - } - } - } - } - - return result; - } - -} diff --git a/core/oslc4j-json4j-provider/pom.xml b/core/oslc4j-json4j-provider/pom.xml index 19e74042f..755b6c058 100644 --- a/core/oslc4j-json4j-provider/pom.xml +++ b/core/oslc4j-json4j-provider/pom.xml @@ -19,14 +19,19 @@ org.eclipse.lyo.oslc4j.core - oslc4j-core-wink - ${v.lyo} + oslc4j-core + ${v.lyo} - org.apache.wink - wink-json4j - 1.4 + jakarta.json + jakarta.json-api + 2.1.3 + + + org.eclipse.parsson + parsson + 1.1.7 jakarta.servlet diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java index 6a86a530c..a9de79d77 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,307 +16,270 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.apache.wink.json4j.JSONObject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.ext.Providers; + import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; -import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; import org.eclipse.lyo.oslc4j.core.exception.MessageExtractor; import org.eclipse.lyo.oslc4j.core.model.Error; -import org.eclipse.lyo.oslc4j.core.model.IResource; import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; import org.eclipse.lyo.oslc4j.core.model.ResponseInfoArray; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.ResponseBuilder; -import jakarta.ws.rs.ext.Providers; - -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated -public abstract class AbstractOslcRdfJsonProvider -{ - private static final Logger logger = Logger.getLogger(AbstractOslcRdfJsonProvider.class.getName()); - - private static final Annotation[] ANNOTATIONS_EMPTY_ARRAY = new Annotation[0]; - private static final Class CLASS_OSLC_ERROR = Error.class; - - private @Context HttpHeaders httpHeaders; // Available only on the server - protected @Context HttpServletRequest httpServletRequest; // Available only on the server - private @Context Providers providers; // Available on both client and server - - protected AbstractOslcRdfJsonProvider() - { - super(); - } - - protected static boolean isWriteable(final Class klass, - final Annotation[] annotations, - final MediaType requiredMediaType, - final MediaType actualMediaType) - { - // we leave media type and annotation checks to Jersey &c. - boolean hasOslcResourceShapeAnnot = klass.getAnnotation(OslcResourceShape.class) != null; - boolean subclassOfIResource = IResource.class.isAssignableFrom(klass); - return hasOslcResourceShapeAnnot || subclassOfIResource; - } - - protected void writeTo(final boolean queryResult, - final Object[] objects, - final MediaType errorMediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws WebApplicationException - { - boolean isClientSide = false; - - try { - httpServletRequest.getMethod(); - } catch (RuntimeException e) { - isClientSide = true; - } - - String descriptionURI = null; - String responseInfoURI = null; - - if (queryResult && ! isClientSide) - { - - final String method = httpServletRequest.getMethod(); - if ("GET".equals(method)) - { - descriptionURI = OSLC4JUtils.resolveURI(httpServletRequest,true); - responseInfoURI = descriptionURI; - - final String queryString = httpServletRequest.getQueryString(); - if ((queryString != null) && - (isOslcQuery(queryString))) - { - responseInfoURI += "?" + queryString; - } - } - - } - - final JSONObject jsonObject; - - @SuppressWarnings("unchecked") - final Map properties = isClientSide ? - null : - (Map)httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_SELECTED_PROPERTIES); - final String nextPageURI = isClientSide ? - null : - (String)httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_NEXT_PAGE); - final Integer totalCount = isClientSide ? - null : - (Integer)httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_TOTAL_COUNT); - - ResponseInfo responseInfo = new ResponseInfoArray<>(null, properties, totalCount, nextPageURI); - - try - { - jsonObject = JsonHelper.createJSON(descriptionURI, - responseInfoURI, - responseInfo, - objects, - properties); - - jsonObject.write(outputStream, - true); - } - catch (final Exception exception) - { - logger.log(Level.FINE, MessageExtractor.getMessage("ErrorSerializingResource"), exception); - throw new WebApplicationException(exception); - } - } - - protected void writeTo(final Object[] objects, - final MediaType errorMediaType, - final MultivaluedMap map, - final OutputStream outputStream, - final Map properties, - final String descriptionURI, - final String responseInfoURI, - final ResponseInfo responseInfo) - throws WebApplicationException - { - final JSONObject jsonObject; - - try - { - jsonObject = JsonHelper.createJSON(descriptionURI, - responseInfoURI, - responseInfo, - objects, - properties); - - jsonObject.write(outputStream, true); - } - catch (final Exception exception) - { - logger.log(Level.FINE, MessageExtractor.getMessage("ErrorSerializingResource"), exception); - throw new WebApplicationException(exception); - } - } - - protected static boolean isReadable(final Class type, - final MediaType requiredMediaType, - final MediaType actualMediaType) - { - return (type.getAnnotation(OslcResourceShape.class) != null) && - (requiredMediaType.isCompatible(actualMediaType)); - } - - protected Object[] readFrom(final Class type, - final MediaType errorMediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws WebApplicationException - { - try - { - final JSONObject jsonObject = new JSONObject(inputStream); - - return JsonHelper.fromJSON(jsonObject, - type); - } - catch (final Exception exception) - { - throw new WebApplicationException(exception, - buildBadRequestResponse(exception, - errorMediaType, - map)); - } - } - - protected Response buildBadRequestResponse(final Exception exception, - final MediaType errorMediaType, - final MultivaluedMap map) - { - final MediaType determinedErrorMediaType = determineErrorMediaType(errorMediaType, - map); - - final Error error = new Error(); - - error.setStatusCode(String.valueOf(Response.Status.BAD_REQUEST.getStatusCode())); - error.setMessage(exception.getMessage()); - - final ResponseBuilder responseBuilder = Response.status(Response.Status.BAD_REQUEST); - return responseBuilder.type(determinedErrorMediaType).entity(error).build(); - } - - /** - * We handle the case where a client requests an accept type different than their content type. - * This will work correctly in the successful case. But, in the error case where our - * MessageBodyReader/MessageBodyWriter fails internally, we respect the client's requested accept type. - */ - private MediaType determineErrorMediaType(final MediaType initialErrorMediaType, - final MultivaluedMap map) - { - try - { - // HttpHeaders will not be available on the client side and will throw a NullPointerException - final List acceptableMediaTypes = httpHeaders.getAcceptableMediaTypes(); - - // Since we got here, we know we are executing on the server - - if (acceptableMediaTypes != null) - { - for (final MediaType acceptableMediaType : acceptableMediaTypes) - { - // If a concrete media type with a MessageBodyWriter - if (isAcceptableMediaType(acceptableMediaType)) - { - return acceptableMediaType; - } - } - } - } - catch (final NullPointerException exception) - { - // Ignore NullPointerException since this means the context has not been set since we are on the client - - // See if the MultivaluedMap for headers is available - if (map != null) - { - final Object acceptObject = map.getFirst("Accept"); - - if (acceptObject instanceof String) - { - final String[] accepts = acceptObject.toString().split(","); - - double winningQ = 0.0D; - MediaType winningMediaType = null; - - for (final String accept : accepts) - { - final MediaType acceptMediaType = MediaType.valueOf(accept); - - // If a concrete media type with a MessageBodyWriter - if (isAcceptableMediaType(acceptMediaType)) - { - final String stringQ = acceptMediaType.getParameters().get("q"); - - final double q = stringQ == null ? 1.0D : Double.parseDouble(stringQ); - - // Make sure and exclude blackballed media type - if (Double.compare(q, 0.0D) > 0) - { - if ((winningMediaType == null) || - (Double.compare(q, winningQ) > 0)) - { - winningMediaType = acceptMediaType; - winningQ = q; - } - } - } - } - - if (winningMediaType != null) - { - return winningMediaType; - } - } - } - } - - return initialErrorMediaType; - } - - /** - * Only allow media types that are not wildcards and have a related MessageBodyWriter for OSLC Error. - */ - private boolean isAcceptableMediaType(final MediaType mediaType) - { - return (!mediaType.isWildcardType()) && - (!mediaType.isWildcardSubtype()) && - (providers.getMessageBodyWriter(CLASS_OSLC_ERROR, - CLASS_OSLC_ERROR, - ANNOTATIONS_EMPTY_ARRAY, - mediaType) != null); - } - protected static boolean isOslcQuery(final String parmString) - { - boolean containsOslcParm = false; - - final String [] uriParts = parmString.toLowerCase().split("oslc\\.",2); - if (uriParts.length > 1) - { - containsOslcParm = true; - } - - return containsOslcParm; - } -} +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sodius.oslc.core.provider.internal.LyoProviderUtils; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.JsonWriter; +import jakarta.json.stream.JsonGenerator; + +public abstract class AbstractOslcRdfJsonProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOslcRdfJsonProvider.class); + + private static final Annotation[] ANNOTATIONS_EMPTY_ARRAY = new Annotation[0]; + private static final Class CLASS_OSLC_ERROR = Error.class; + + private @Context HttpHeaders httpHeaders; // Available only on the server + protected @Context HttpServletRequest httpServletRequest; // Available only on the server + private @Context Providers providers; // Available on both client and server + + protected AbstractOslcRdfJsonProvider() { + super(); + } + + @SuppressWarnings("java:S3776") // Complex legacy method + protected static boolean isWriteable(final Class type, final Annotation[] annotations, final MediaType requiredMediaType, + final MediaType actualMediaType) { + if (type.getAnnotation(OslcResourceShape.class) != null) { + // When handling "recursive" writing of an OSLC Error object, we get a zero-length array of annotations + if ((annotations != null) && ((annotations.length > 0) || (Error.class != type))) { + for (final Annotation annotation : annotations) { + if (annotation instanceof Produces) { + final Produces producesAnnotation = (Produces) annotation; + + for (final String value : producesAnnotation.value()) { + if (requiredMediaType.isCompatible(MediaType.valueOf(value))) { + return true; + } + } + } + } + + return false; + } + + // We do not have annotations when running from the non-web client. + return requiredMediaType.isCompatible(actualMediaType); + } + + return false; + } + + protected void writeTo(final boolean queryResult, final Object[] objects, final MediaType errorMediaType, + final MultivaluedMap map, final OutputStream outputStream) throws WebApplicationException { + boolean isClientSide = false; + + try { + httpServletRequest.getMethod(); + } catch (RuntimeException e) { + isClientSide = true; + } + + String descriptionURI = null; + String responseInfoURI = null; + + if (queryResult && !isClientSide) { + + final String method = httpServletRequest.getMethod(); + if ("GET".equals(method)) { + descriptionURI = LyoProviderUtils.resolveURI(httpServletRequest); + responseInfoURI = descriptionURI; + + final String queryString = httpServletRequest.getQueryString(); + if ((queryString != null) && (isOslcQuery(queryString))) { + responseInfoURI += "?" + queryString; + } + } + + } + + final JsonObject jsonObject; + + @SuppressWarnings("unchecked") + final Map properties = isClientSide ? null + : (Map) httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_SELECTED_PROPERTIES); + final String nextPageURI = isClientSide ? null : (String) httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_NEXT_PAGE); + final Integer totalCount = isClientSide ? null : (Integer) httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_TOTAL_COUNT); + + ResponseInfo responseInfo = new ResponseInfoArray<>(null, properties, totalCount, nextPageURI); + + try { + jsonObject = JsonHelper.createJSON(descriptionURI, responseInfoURI, responseInfo, objects, properties); + + createPrettyPrintingWriter(outputStream).writeObject(jsonObject); + } catch (final Exception exception) { // NOSONAR + LOGGER.warn(MessageExtractor.getMessage("ErrorSerializingResource"), exception); + throw new WebApplicationException(exception, buildServerErrorResponse(exception, errorMediaType, map)); + } + } + + @SuppressWarnings("java:S107") // too many params on legacy method + protected void writeTo(final Object[] objects, final MediaType errorMediaType, final MultivaluedMap map, + final OutputStream outputStream, final Map properties, final String descriptionURI, final String responseInfoURI, + final ResponseInfo responseInfo) throws WebApplicationException { + final JsonObject jsonObject; + + try { + jsonObject = JsonHelper.createJSON(descriptionURI, responseInfoURI, responseInfo, objects, properties); + + createPrettyPrintingWriter(outputStream).writeObject(jsonObject); + } catch (final Exception exception) {// NOSONAR + LOGGER.warn(MessageExtractor.getMessage("ErrorSerializingResource"), exception); + throw new WebApplicationException(exception, buildServerErrorResponse(exception, errorMediaType, map)); + } + } + + private static JsonWriter createPrettyPrintingWriter(OutputStream outputStream) { + Map properties = Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true); + return Json.createWriterFactory(properties).createWriter(outputStream); + } + + protected static boolean isReadable(final Class type, final MediaType requiredMediaType, final MediaType actualMediaType) { + return (type.getAnnotation(OslcResourceShape.class) != null) && (requiredMediaType.isCompatible(actualMediaType)); + } + + protected Object[] readFrom(final Class type, final MediaType errorMediaType, final MultivaluedMap map, + final InputStream inputStream) throws WebApplicationException { + try (JsonReader reader = Json.createReader(inputStream)) { + final JsonObject jsonObject = reader.readObject(); + return JsonHelper.fromJSON(jsonObject, type); + } catch (final Exception exception) { + throw new WebApplicationException(exception, buildBadRequestResponse(exception, errorMediaType, map)); + } + } + + protected Response buildBadRequestResponse(final Exception exception, final MediaType errorMediaType, final MultivaluedMap map) { + return buildResponse(exception, errorMediaType, map, Response.Status.BAD_REQUEST); + } + + protected Response buildServerErrorResponse(final Exception exception, final MediaType errorMediaType, final MultivaluedMap map) { + return buildResponse(exception, errorMediaType, map, Response.Status.INTERNAL_SERVER_ERROR); + } + + private Response buildResponse(final Exception exception, final MediaType errorMediaType, final MultivaluedMap map, + Response.Status status) { + try { + final MediaType determinedErrorMediaType = determineErrorMediaType(errorMediaType, map); + + final Error error = new Error(); + + error.setStatusCode(String.valueOf(status.getStatusCode())); + error.setMessage(exception.getMessage()); + + final ResponseBuilder responseBuilder = Response.status(status); + return responseBuilder.type(determinedErrorMediaType).entity(error).build(); + } catch (UndeclaredThrowableException e) { + // determineErrorMediaType may raise an UndeclaredThrowableException with an infinite stack trace + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * We handle the case where a client requests an accept type different than their content type. + * This will work correctly in the successful case. But, in the error case where our + * MessageBodyReader/MessageBodyWriter fails internally, we respect the client's requested accept type. + */ + @SuppressWarnings("java:S3776") // Complex legacy method + private MediaType determineErrorMediaType(final MediaType initialErrorMediaType, final MultivaluedMap map) { + try { + // HttpHeaders will not be available on the client side and will throw a NullPointerException + final List acceptableMediaTypes = httpHeaders.getAcceptableMediaTypes(); + + // Since we got here, we know we are executing on the server + + if (acceptableMediaTypes != null) { + for (final MediaType acceptableMediaType : acceptableMediaTypes) { + // If a concrete media type with a MessageBodyWriter + if (isAcceptableMediaType(acceptableMediaType)) { + return acceptableMediaType; + } + } + } + } catch (final NullPointerException exception) { + // Ignore NullPointerException since this means the context has not been set since we are on the client + + // See if the MultivaluedMap for headers is available + if (map != null) { + final Object acceptObject = map.getFirst("Accept"); + + if (acceptObject instanceof String) { + final String[] accepts = acceptObject.toString().split(","); + + double winningQ = 0.0D; + MediaType winningMediaType = null; + + for (final String accept : accepts) { + final MediaType acceptMediaType = MediaType.valueOf(accept); + + // If a concrete media type with a MessageBodyWriter + if (isAcceptableMediaType(acceptMediaType)) { + final String stringQ = acceptMediaType.getParameters().get("q"); + + final double q = stringQ == null ? 1.0D : Double.parseDouble(stringQ); + + // Make sure and exclude blackballed media type + if (Double.compare(q, 0.0D) > 0) { + if ((winningMediaType == null) || (Double.compare(q, winningQ) > 0)) { // NOSONAR + winningMediaType = acceptMediaType; + winningQ = q; + } + } + } + } + + if (winningMediaType != null) { + return winningMediaType; + } + } + } + } + + return initialErrorMediaType; + } + + /** + * Only allow media types that are not wildcards and have a related MessageBodyWriter for OSLC Error. + */ + private boolean isAcceptableMediaType(final MediaType mediaType) { + return (!mediaType.isWildcardType()) && (!mediaType.isWildcardSubtype()) + && (providers.getMessageBodyWriter(CLASS_OSLC_ERROR, CLASS_OSLC_ERROR, ANNOTATIONS_EMPTY_ARRAY, mediaType) != null); + } + + protected static boolean isOslcQuery(final String parmString) { + boolean containsOslcParm = false; + + final String[] uriParts = parmString.toLowerCase(Locale.getDefault()).split("oslc\\.", 2); + if (uriParts.length > 1) { + containsOslcParm = true; + } + + return containsOslcParm; + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JProvidersRegistry.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JProvidersRegistry.java index b48b73bcb..5773f74c8 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JProvidersRegistry.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JProvidersRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,32 +16,24 @@ import java.util.HashSet; import java.util.Set; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated -public final class Json4JProvidersRegistry -{ - private static final Set> PROVIDERS = new HashSet<>(); +public final class Json4JProvidersRegistry { + private static final Set> PROVIDERS = new HashSet<>(); - static - { - PROVIDERS.add(OslcCompactJsonProvider.class); - PROVIDERS.add(OslcRdfJsonArrayProvider.class); - PROVIDERS.add(OslcRdfJsonCollectionProvider.class); - PROVIDERS.add(OslcRdfJsonProvider.class); - } + static { + PROVIDERS.add(OslcCompactJsonProvider.class); + PROVIDERS.add(OslcRdfJsonArrayProvider.class); + PROVIDERS.add(OslcRdfJsonCollectionProvider.class); + PROVIDERS.add(OslcRdfJsonProvider.class); + } - private Json4JProvidersRegistry() - { - super(); - } + private Json4JProvidersRegistry() { + super(); + } - /** - * Returns a mutable set of provider classes. Each request returns a new Set. - */ - public static final Set> getProviders() - { - return new HashSet<>(PROVIDERS); - } + /** + * Returns a mutable set of provider classes. Each request returns a new Set. + */ + public static final Set> getProviders() { + return new HashSet<>(PROVIDERS); + } } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JSimpleProvidersRegistry.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JSimpleProvidersRegistry.java index 813eb3c0a..00043ae72 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JSimpleProvidersRegistry.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JSimpleProvidersRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,32 +16,24 @@ import java.util.HashSet; import java.util.Set; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated -public final class Json4JSimpleProvidersRegistry -{ - private static final Set> PROVIDERS = new HashSet<>(); +public final class Json4JSimpleProvidersRegistry { + private static final Set> PROVIDERS = new HashSet<>(); - static - { - PROVIDERS.add(OslcCompactJsonProvider.class); - PROVIDERS.add(OslcSimpleRdfJsonArrayProvider.class); - PROVIDERS.add(OslcSimpleRdfJsonCollectionProvider.class); - PROVIDERS.add(OslcRdfJsonProvider.class); - } + static { + PROVIDERS.add(OslcCompactJsonProvider.class); + PROVIDERS.add(OslcSimpleRdfJsonArrayProvider.class); + PROVIDERS.add(OslcSimpleRdfJsonCollectionProvider.class); + PROVIDERS.add(OslcRdfJsonProvider.class); + } - private Json4JSimpleProvidersRegistry() - { - super(); - } + private Json4JSimpleProvidersRegistry() { + super(); + } - /** - * Returns a mutable set of provider classes. Each request returns a new Set. - */ - public static final Set> getProviders() - { - return new HashSet<>(PROVIDERS); - } + /** + * Returns a mutable set of provider classes. Each request returns a new Set. + */ + public static final Set> getProviders() { + return new HashSet<>(PROVIDERS); + } } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JUpdatedProvidersRegistry.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JUpdatedProvidersRegistry.java index ad397b687..305d5712f 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JUpdatedProvidersRegistry.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/Json4JUpdatedProvidersRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,32 +16,24 @@ import java.util.HashSet; import java.util.Set; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated -public final class Json4JUpdatedProvidersRegistry -{ - private static final Set> PROVIDERS = new HashSet<>(); +public final class Json4JUpdatedProvidersRegistry { + private static final Set> PROVIDERS = new HashSet<>(); - static - { - PROVIDERS.add(OslcCompactJsonProvider.class); - PROVIDERS.add(OslcSimpleRdfJsonArrayProvider.class); - PROVIDERS.add(OslcSimpleRdfJsonCollectionProvider.class); - PROVIDERS.add(OslcRdfJsonProvider.class); - } + static { + PROVIDERS.add(OslcCompactJsonProvider.class); + PROVIDERS.add(OslcSimpleRdfJsonArrayProvider.class); + PROVIDERS.add(OslcSimpleRdfJsonCollectionProvider.class); + PROVIDERS.add(OslcRdfJsonProvider.class); + } - private Json4JUpdatedProvidersRegistry() - { - super(); - } + private Json4JUpdatedProvidersRegistry() { + super(); + } - /** - * Returns a mutable set of provider classes. Each request returns a new Set. - */ - public static final Set> getProviders() - { - return new HashSet<>(PROVIDERS); - } + /** + * Returns a mutable set of provider classes. Each request returns a new Set. + */ + public static final Set> getProviders() { + return new HashSet<>(PROVIDERS); + } } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java new file mode 100644 index 000000000..481aac74c --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/JsonHelper.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j; + +import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; +import java.util.Map; + +import javax.xml.datatype.DatatypeConfigurationException; + +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; + +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONModelBuilder; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.ResourceBuilder; + +import jakarta.json.JsonObject; + +public final class JsonHelper { + + /** + * System property {@value} : When "true", write "INF", "-INF", and "NaN" + * strings for Infinity, -Infinity, and NaN float and double values, + * respectively. Enabled by default. + * + * @see #OSLC4J_READ_SPECIAL_NUMS + */ + public static final String OSLC4J_WRITE_SPECIAL_NUMS = "org.eclipse.lyo.oslc4j.writeSpecialNumberValues"; + + /** + * System property {@value} : When "true", read "INF", "-INF", and "NaN" + * strings for Infinity, -Infinity, and NaN float and double values, + * respectively. Enabled by default. + * + * @see #OSLC4J_WRITE_SPECIAL_NUMS + */ + public static final String OSLC4J_READ_SPECIAL_NUMS = "org.eclipse.lyo.oslc4j.readSpecialNumberValues"; + + private JsonHelper() { + super(); + } + + public static JsonObject createJSON(final String descriptionAbout, final String responseInfoAbout, final ResponseInfo responseInfo, + final Object[] objects, final Map properties) throws DatatypeConfigurationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, OslcCoreApplicationException { + + return JSONModelBuilder.build(descriptionAbout, responseInfoAbout, responseInfo, objects, properties); + } + + public static Object[] fromJSON(final JsonObject jakartaObject, final Class beanClass) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, URISyntaxException { + + return ResourceBuilder.build(jakartaObject, beanClass); + } + +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java index 7401de774..e9620acdf 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,114 +19,62 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + import org.eclipse.lyo.oslc4j.core.model.Compact; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.MessageBodyReader; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Provider; - -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated @Provider @Produces(OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON) @Consumes(OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON) -public final class OslcCompactJsonProvider - extends AbstractOslcRdfJsonProvider - implements MessageBodyReader, - MessageBodyWriter -{ - public OslcCompactJsonProvider() - { - super(); - } +public final class OslcCompactJsonProvider extends AbstractOslcRdfJsonProvider implements MessageBodyReader, MessageBodyWriter { + public OslcCompactJsonProvider() { + super(); + } - @Override - public long getSize(final Compact compact, - final Class type, - final Type genericType, - final Annotation[] annotation, - final MediaType mediaType) - { - return -1; - } + @Override + public long getSize(final Compact compact, final Class type, final Type genericType, final Annotation[] annotation, + final MediaType mediaType) { + return -1; + } - @Override - public boolean isWriteable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - return (Compact.class.isAssignableFrom(type)) && - (isWriteable(type, - annotations, - OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON_TYPE, - mediaType)); - } + @Override + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + return (Compact.class.isAssignableFrom(type)) + && (isWriteable(type, annotations, OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON_TYPE, mediaType)); + } - @Override - public void writeTo(final Compact compact, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - writeTo(false, - new Compact[] {compact}, - OslcMediaType.APPLICATION_JSON_TYPE, - map, - outputStream); - } + @Override + public void writeTo(final Compact compact, final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap map, final OutputStream outputStream) throws IOException, WebApplicationException { + writeTo(false, new Compact[] { compact }, OslcMediaType.APPLICATION_JSON_TYPE, map, outputStream); + } - @Override - public boolean isReadable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - return (Compact.class.isAssignableFrom(type)) && - (isReadable(type, - OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON_TYPE, - mediaType)); - } + @Override + public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + return (Compact.class.isAssignableFrom(type)) && (isReadable(type, OslcMediaType.APPLICATION_X_OSLC_COMPACT_JSON_TYPE, mediaType)); + } - @Override - public Compact readFrom(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws IOException, - WebApplicationException - { - final Object[] objects = readFrom(type, - OslcMediaType.APPLICATION_JSON_TYPE, - map, - inputStream); + @Override + public Compact readFrom(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap map, final InputStream inputStream) throws IOException, WebApplicationException { + final Object[] objects = readFrom(type, OslcMediaType.APPLICATION_JSON_TYPE, map, inputStream); - if ((objects != null) && - (objects.length > 0)) - { - final Object object = objects[0]; + if ((objects != null) && (objects.length > 0)) { + final Object object = objects[0]; - if (object instanceof Compact) - { - return (Compact) object; - } - } + if (object instanceof Compact) { + return (Compact) object; + } + } - return null; - } -} + return null; + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java index bee0743d5..8e6865793 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,100 +19,51 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.MessageBodyReader; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Provider; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated @Provider @Produces(OslcMediaType.APPLICATION_JSON) @Consumes(OslcMediaType.APPLICATION_JSON) -public class OslcRdfJsonArrayProvider - extends AbstractOslcRdfJsonProvider - implements MessageBodyReader, - MessageBodyWriter -{ - public OslcRdfJsonArrayProvider() - { - super(); - } +public class OslcRdfJsonArrayProvider extends AbstractOslcRdfJsonProvider implements MessageBodyReader, MessageBodyWriter { + public OslcRdfJsonArrayProvider() { + super(); + } - @Override - public long getSize(final Object[] objects, - final Class type, - final Type genericType, - final Annotation[] annotation, - final MediaType mediaType) - { - return -1; - } + @Override + public long getSize(final Object[] objects, final Class type, final Type genericType, final Annotation[] annotation, + final MediaType mediaType) { + return -1; + } - @Override - public boolean isWriteable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - return (type.isArray()) && - (isWriteable(type.getComponentType(), - annotations, - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType)); - } + @Override + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + return (type.isArray()) && (isWriteable(type.getComponentType(), annotations, OslcMediaType.APPLICATION_JSON_TYPE, mediaType)); + } - @Override - public void writeTo(final Object[] objects, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - writeTo(true, - objects, - mediaType, - map, - outputStream); - } + @Override + public void writeTo(final Object[] objects, final Class type, final Type genericType, final Annotation[] annotations, + final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) + throws IOException, WebApplicationException { + writeTo(true, objects, mediaType, map, outputStream); + } - @Override - public boolean isReadable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - return (type.isArray()) && - (isReadable(type.getComponentType(), - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType)); - } + @Override + public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + return (type.isArray()) && (isReadable(type.getComponentType(), OslcMediaType.APPLICATION_JSON_TYPE, mediaType)); + } - @Override - public Object[] readFrom(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws IOException, - WebApplicationException - { - return readFrom(type.getComponentType(), - mediaType, - map, - inputStream); - } -} + @Override + public Object[] readFrom(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap map, final InputStream inputStream) throws IOException, WebApplicationException { + return readFrom(type.getComponentType(), mediaType, map, inputStream); + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java index 19ec1d72a..39fc90cb7 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,219 +19,115 @@ import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.net.URI; -import java.util.AbstractCollection; -import java.util.AbstractList; -import java.util.AbstractSequentialList; -import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.NavigableSet; -import java.util.Queue; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.eclipse.lyo.oslc4j.core.CoreHelper; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.MessageBodyReader; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Provider; - -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated + +import com.sodius.oslc.core.provider.internal.RdfCollections; + @Provider @Produces(OslcMediaType.APPLICATION_JSON) @Consumes(OslcMediaType.APPLICATION_JSON) -public class OslcRdfJsonCollectionProvider - extends AbstractOslcRdfJsonProvider - implements MessageBodyReader>, - MessageBodyWriter> -{ - private final static Logger log = LoggerFactory.getLogger(OslcRdfJsonCollectionProvider.class); - - public OslcRdfJsonCollectionProvider() - { - super(); - } - - @Override - public long getSize(final Collection collection, - final Class type, - final Type genericType, - final Annotation[] annotation, - final MediaType mediaType) - { - return -1; - } - - @Override - public boolean isWriteable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - if (Collection.class.isAssignableFrom(type) && (genericType instanceof ParameterizedType)) { - final ParameterizedType parameterizedType = (ParameterizedType) genericType; - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - if (actualTypeArguments.length == 1) { - final Type actualTypeArgument = actualTypeArguments[0]; - - if (actualTypeArgument instanceof Class || actualTypeArgument instanceof TypeVariable) { - return isWriteable(CoreHelper.getActualTypeArgument(actualTypeArgument), - annotations, - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType); - } - return false; +public class OslcRdfJsonCollectionProvider extends AbstractOslcRdfJsonProvider + implements MessageBodyReader>, MessageBodyWriter> { + public OslcRdfJsonCollectionProvider() { + super(); + } + + @Override + public long getSize(final Collection collection, final Class type, final Type genericType, final Annotation[] annotation, + final MediaType mediaType) { + return -1; + } + + @Override + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + if ((Collection.class.isAssignableFrom(type)) && (genericType instanceof ParameterizedType)) { + final ParameterizedType parameterizedType = (ParameterizedType) genericType; + + final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length == 1) { + final Type actualTypeArgument = actualTypeArguments[0]; + + if (actualTypeArgument instanceof Class) { + return isWriteable((Class) actualTypeArgument, annotations, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } + } + } + + return false; + } + + @Override + public void writeTo(final Collection collection, final Class type, final Type genericType, final Annotation[] annotations, + final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) + throws IOException, WebApplicationException { + writeTo(true, collection.toArray(new Object[collection.size()]), mediaType, map, outputStream); + } + + @Override + public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + if ((Collection.class.isAssignableFrom(type)) && (genericType instanceof ParameterizedType)) { + final ParameterizedType parameterizedType = (ParameterizedType) genericType; + + final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length == 1) { + final Type actualTypeArgument = actualTypeArguments[0]; + + if (URI.class.equals(actualTypeArgument) && (OslcMediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType))) { + return true; + } else { + return isReadable((Class) actualTypeArgument, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } } - } - - return false; - } - - @Override - public void writeTo(final Collection collection, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - writeTo(true, - collection.toArray(new Object[collection.size()]), - mediaType, - map, - outputStream); - } - - @Override - public boolean isReadable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) { - if ((Collection.class.isAssignableFrom(type)) && - (genericType instanceof ParameterizedType)) { - final ParameterizedType parameterizedType = (ParameterizedType) genericType; - - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - if (actualTypeArguments.length == 1) { - final Type actualTypeArgument = actualTypeArguments[0]; - - if (URI.class.equals(actualTypeArgument) - && (OslcMediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType))) { - return true; - } else { - return isReadable((Class) actualTypeArgument, - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType); - } - } - } - - return false; - } - - @Override - public Collection readFrom(final Class> type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws IOException, - WebApplicationException - { - if (genericType instanceof ParameterizedType) - { - final ParameterizedType parameterizedType = (ParameterizedType) genericType; - - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - if (actualTypeArguments.length == 1) - { - final Type actualTypeArgument = actualTypeArguments[0]; - - if (actualTypeArgument instanceof Class) - { - final Object[] objects = readFrom((Class) actualTypeArgument, - mediaType, - map, - inputStream); - - final Collection collection; - - // Handle the Collection, List, Deque, Queue interfaces. - // Handle the AbstractCollection, AbstractList, AbstractSequentialList classes - if ((Collection.class.equals(type)) || - (List.class.equals(type))|| - (Deque.class.equals(type)) || - (Queue.class.equals(type)) || - (AbstractCollection.class.equals(type)) || - (AbstractList.class.equals(type)) || - (AbstractSequentialList.class.equals(type))) - { - collection = new LinkedList<>(); - } - // Handle the Set interface - // Handle the AbstractSet class - else if ((Set.class.equals(type)) || - (AbstractSet.class.equals(type))) - { - collection = new HashSet<>(); - } - // Handle the SortedSet and NavigableSet interfaces - else if ((SortedSet.class.equals(type)) || - (NavigableSet.class.equals(type))) - { - collection = new TreeSet<>(); - } - // Not handled above. Let's try newInstance with possible failure. - else - { - try - { - @SuppressWarnings("cast") - final Collection tempCollection = ((Collection) type.newInstance()); - - collection = tempCollection; - } - catch (final Exception exception) - { - throw new WebApplicationException(exception, - buildBadRequestResponse(exception, - mediaType, - map)); - } - } - - collection.addAll(Arrays.asList(objects)); - - return collection; - } - } - } - - return null; - } -} + } + + return false; + } + + @Override + @SuppressWarnings("java:S3776") // Complex legacy method + public Collection readFrom(final Class> type, final Type genericType, final Annotation[] annotations, + final MediaType mediaType, final MultivaluedMap map, final InputStream inputStream) + throws IOException, WebApplicationException { + if (genericType instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) genericType; + + final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length == 1) { + final Type actualTypeArgument = actualTypeArguments[0]; + + if (actualTypeArgument instanceof Class) { + final Object[] objects = readFrom((Class) actualTypeArgument, mediaType, map, inputStream); + + final Collection collection; + try { + collection = RdfCollections.createCollection(type); + } catch (final Exception exception) { + throw new WebApplicationException(exception, buildBadRequestResponse(exception, mediaType, map)); + } + + collection.addAll(Arrays.asList(objects)); + + return collection; + } + } + } + + return null; // NOSONAR it is intended to return null + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java index 7c6195dff..3c43c821e 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -20,266 +20,166 @@ import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.net.URI; import java.util.Collection; import java.util.Map; -import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + import org.eclipse.lyo.oslc4j.core.model.FilteredResource; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; import org.eclipse.lyo.oslc4j.core.model.ResponseInfoArray; import org.eclipse.lyo.oslc4j.core.model.ResponseInfoCollection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.MessageBodyReader; -import jakarta.ws.rs.ext.MessageBodyWriter; -import jakarta.ws.rs.ext.Provider; +import com.sodius.oslc.core.provider.internal.LyoProviderUtils; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated @Provider @Produces(OslcMediaType.APPLICATION_JSON) @Consumes(OslcMediaType.APPLICATION_JSON) -public final class OslcRdfJsonProvider - extends AbstractOslcRdfJsonProvider - implements MessageBodyReader, - MessageBodyWriter -{ - private final static Logger log = LoggerFactory.getLogger(OslcRdfJsonProvider.class); - - public OslcRdfJsonProvider() - { - super(); - } - - @Override - public long getSize(final Object object, - final Class type, - final Type genericType, - final Annotation[] annotation, - final MediaType mediaType) - { - return -1; - } - - @Override - public boolean isWriteable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - Class actualType; +public class OslcRdfJsonProvider extends AbstractOslcRdfJsonProvider implements MessageBodyReader, MessageBodyWriter { + public OslcRdfJsonProvider() { + super(); + } + + @Override + public long getSize(final Object object, final Class type, final Type genericType, final Annotation[] annotation, final MediaType mediaType) { + return -1; + } + + @Override + @SuppressWarnings("java:S3776") // Complex legacy method + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + Class actualType; + + if (FilteredResource.class.isAssignableFrom(type) && (genericType instanceof ParameterizedType)) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length != 1) { + return false; + } - if (FilteredResource.class.isAssignableFrom(type) && - (genericType instanceof ParameterizedType)) - { - ParameterizedType parameterizedType = (ParameterizedType)genericType; - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments[0] instanceof Class) { + actualType = (Class) actualTypeArguments[0]; + } else if (actualTypeArguments[0] instanceof ParameterizedType) { + parameterizedType = (ParameterizedType) actualTypeArguments[0]; + actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments.length != 1) - { - return false; - } + if ((actualTypeArguments.length != 1) || !(actualTypeArguments[0] instanceof Class)) { + return false; + } - if (actualTypeArguments[0] instanceof Class) - { - actualType = (Class)actualTypeArguments[0]; - } - else if (actualTypeArguments[0] instanceof ParameterizedType) - { - parameterizedType = (ParameterizedType)actualTypeArguments[0]; - actualTypeArguments = parameterizedType.getActualTypeArguments(); + actualType = (Class) actualTypeArguments[0]; + } else if (actualTypeArguments[0] instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) actualTypeArguments[0]; + Type componentType = genericArrayType.getGenericComponentType(); - if (actualTypeArguments.length == 1) { - if (actualTypeArguments[0] instanceof Class) { - actualType = (Class) actualTypeArguments[0]; - } else if(actualTypeArguments[0] instanceof TypeVariable) { - log.error("GenericEntity-based capture of entity generic type is not supported"); - return false; - } else { - return false; - } - } else { + if (!(componentType instanceof Class)) { return false; } + actualType = (Class) componentType; + } else { + return false; } - else if (actualTypeArguments[0] instanceof GenericArrayType) - { - GenericArrayType genericArrayType = - (GenericArrayType)actualTypeArguments[0]; - Type componentType = genericArrayType.getGenericComponentType(); - - if (! (componentType instanceof Class)) - { - return false; - } - - actualType = (Class)componentType; - } - else - { - return false; - } - - Type rawType = parameterizedType.getRawType(); - if (URI.class.equals(actualType) - && (ResponseInfoCollection.class.equals(rawType) || ResponseInfoArray.class.equals(rawType))) - { - return true; - } - } - else - { - actualType = type; - } - - return isWriteable(actualType, - annotations, - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType); - } - - @Override - public void writeTo(final Object object, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - Object[] objects; - Map properties = null; - String descriptionURI = null; - String responseInfoURI = null; - ResponseInfo responseInfo = null; - - if (object instanceof FilteredResource) - { - final FilteredResource filteredResource = - (FilteredResource)object; - - properties = filteredResource.properties(); - - if (filteredResource instanceof ResponseInfo) - { - responseInfo = (ResponseInfo)filteredResource; - String requestURI = OSLC4JUtils.resolveURI(httpServletRequest, true); - responseInfoURI = requestURI; - - FilteredResource container = responseInfo.getContainer(); - if (container != null) - { - URI containerAboutURI = container.getAbout(); - if (containerAboutURI != null) - { - descriptionURI = containerAboutURI.toString(); - } - } - if (null == descriptionURI) - { - descriptionURI = requestURI; - } - - final String queryString = httpServletRequest.getQueryString(); - - if ((queryString != null) && - (isOslcQuery(queryString))) - { - responseInfoURI += "?" + queryString; - } - - if (filteredResource instanceof ResponseInfoArray) - { - objects = ((ResponseInfoArray)filteredResource).array(); - } - else - { - Collection collection = - ((ResponseInfoCollection)filteredResource).collection(); - - objects = collection.toArray(new Object[collection.size()]); - } - } - else - { - Object nestedObject = filteredResource.resource(); - - if (nestedObject instanceof Object[]) - { - objects = (Object[])nestedObject; - } - else if (nestedObject instanceof Collection) - { - objects = ((Collection)nestedObject).toArray(); - } - else - { - objects = new Object[] { object }; - } - } - } - else - { - objects = new Object[] { object }; - } - - writeTo(objects, - mediaType, - map, - outputStream, - properties, - descriptionURI, - responseInfoURI, - responseInfo); - } + Type rawType = parameterizedType.getRawType(); + if (URI.class.equals(actualType) && (ResponseInfoCollection.class.equals(rawType) || ResponseInfoArray.class.equals(rawType))) { + return true; + } + } else { + actualType = type; + } + + return isWriteable(actualType, annotations, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } + + @Override + @SuppressWarnings("java:S3776") // Complex legacy method + public void writeTo(final Object object, final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap map, final OutputStream outputStream) throws IOException, WebApplicationException { + Object[] objects; + Map properties = null; + String descriptionURI = null; + String responseInfoURI = null; + ResponseInfo responseInfo = null; + + if (object instanceof FilteredResource) { + final FilteredResource filteredResource = (FilteredResource) object; + + properties = filteredResource.properties(); + + if (filteredResource instanceof ResponseInfo) { + responseInfo = (ResponseInfo) filteredResource; + + String requestURI = LyoProviderUtils.resolveURI(httpServletRequest); + responseInfoURI = requestURI; + + FilteredResource container = responseInfo.getContainer(); + if (container != null) { + URI containerAboutURI = container.getAbout(); + if (containerAboutURI != null) { + descriptionURI = containerAboutURI.toString(); + } + } + if (null == descriptionURI) { + descriptionURI = requestURI; + } - @Override - public boolean isReadable(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType) - { - return isReadable(type, - OslcMediaType.APPLICATION_JSON_TYPE, - mediaType); - } + final String queryString = httpServletRequest.getQueryString(); - @Override - public Object readFrom(final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws IOException, - WebApplicationException - { - final Object[] objects = readFrom(type, - mediaType, - map, - inputStream); + if ((queryString != null) && (isOslcQuery(queryString))) { + responseInfoURI += "?" + queryString; + } - if ((objects != null) && - (objects.length > 0)) - { - return objects[0]; - } + if (filteredResource instanceof ResponseInfoArray) { + objects = ((ResponseInfoArray) filteredResource).array(); + } else if (filteredResource instanceof ResponseInfoCollection) { + Collection collection = ((ResponseInfoCollection) filteredResource).collection(); + objects = collection.toArray(new Object[collection.size()]); + } else { + throw new IllegalStateException("Unexpected type of ResponseInfo: " + filteredResource.getClass().getName()); + } + } else { + Object nestedObject = filteredResource.resource(); - return null; - } -} + if (nestedObject instanceof Object[]) { + objects = (Object[]) nestedObject; + } else if (nestedObject instanceof Collection) { + objects = ((Collection) nestedObject).toArray(); + } else { + objects = new Object[] { object }; + } + } + } else { + objects = new Object[] { object }; + } + + writeTo(objects, mediaType, map, outputStream, properties, descriptionURI, responseInfoURI, responseInfo); + } + + @Override + public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + return isReadable(type, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } + + @Override + public Object readFrom(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap map, final InputStream inputStream) throws IOException, WebApplicationException { + final Object[] objects = readFrom(type, mediaType, map, inputStream); + + if ((objects != null) && (objects.length > 0)) { + return objects[0]; + } + + return null; + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java index 9716957d8..c5fb2a60b 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,48 +18,27 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.Provider; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.Provider; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated @Provider @Produces(OslcMediaType.APPLICATION_JSON) @Consumes(OslcMediaType.APPLICATION_JSON) -public final class OslcSimpleRdfJsonArrayProvider - extends OslcRdfJsonArrayProvider -{ - public OslcSimpleRdfJsonArrayProvider() - { - super(); - } +public final class OslcSimpleRdfJsonArrayProvider extends OslcRdfJsonArrayProvider { + public OslcSimpleRdfJsonArrayProvider() { + super(); + } - @Override - public void writeTo(final Object[] objects, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - writeTo(objects, - mediaType, - map, - outputStream, - null, - null, - null, - null); - } -} + @Override + public void writeTo(final Object[] objects, final Class type, final Type genericType, final Annotation[] annotations, + final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) + throws IOException, WebApplicationException { + writeTo(objects, mediaType, map, outputStream, null, null, null, null); + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java index ca4c3e30c..be0a89ecb 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Contributors to the Eclipse Foundation + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,48 +19,27 @@ import java.lang.reflect.Type; import java.util.Collection; -import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.Provider; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.ext.Provider; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -/** - * Use JSON-LD support in Jena provider. - */ -@Deprecated @Provider @Produces(OslcMediaType.APPLICATION_JSON) @Consumes(OslcMediaType.APPLICATION_JSON) -public final class OslcSimpleRdfJsonCollectionProvider - extends OslcRdfJsonCollectionProvider -{ - public OslcSimpleRdfJsonCollectionProvider() - { - super(); - } +public final class OslcSimpleRdfJsonCollectionProvider extends OslcRdfJsonCollectionProvider { + public OslcSimpleRdfJsonCollectionProvider() { + super(); + } - @Override - public void writeTo(final Collection collection, - final Class type, - final Type genericType, - final Annotation[] annotations, - final MediaType mediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws IOException, - WebApplicationException - { - writeTo(collection.toArray(new Object[collection.size()]), - mediaType, - map, - outputStream, - null, - null, - null, - null); - } -} + @Override + public void writeTo(final Collection collection, final Class type, final Type genericType, final Annotation[] annotations, + final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) + throws IOException, WebApplicationException { + writeTo(collection.toArray(new Object[collection.size()]), mediaType, map, outputStream, null, null, null, null); + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java new file mode 100644 index 000000000..b73a57323 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import javax.xml.namespace.QName; + +import com.sodius.oslc.core.provider.internal.NamespaceMappings; + +/* + * Common constants and methods for reading and writing Json content + */ +abstract class AbstractBuilder { + static final String JSON_PROPERTY_DELIMITER = ":"; + + static final String PREFIXES = "prefixes"; + static final String PROPERTY_ABOUT = "about"; + static final String PROPERTY_MEMBER = "member"; + static final String PROPERTY_RESOURCE = "resource"; + static final String PROPERTY_TITLE = "title"; + static final String PROPERTY_RESULTS = "results"; + static final String PROPERTY_RESPONSE_INFO = "responseInfo"; + static final String PROPERTY_TOTAL_COUNT = "totalCount"; + static final String PROPERTY_NEXT_PAGE = "nextPage"; + static final String PROPERTY_TYPE = "type"; + static final String PROPERTY_FIRST = "first"; + static final String PROPERTY_REST = "rest"; + static final String PROPERTY_NIL = "nil"; + + static final String POSITIVE_INF = "INF"; + static final String NEGATIVE_INF = "-INF"; + static final String NOT_A_NUMBER = "NaN"; + + private final NamespaceMappings namespaceMappings; + + AbstractBuilder(NamespaceMappings namespaceMappings) { + this.namespaceMappings = namespaceMappings; + } + + final NamespaceMappings getNamespaceMappings() { + return namespaceMappings; + } + + final String generateKey(QName qname) { + return generateKey(qname.getNamespaceURI(), qname.getLocalPart(), qname.getPrefix()); + } + + final String generateKey(String namespace, String localPart, String prefix) { + QName qname = namespaceMappings.generateQName(namespace, localPart, prefix); + return qname.getPrefix() + JSON_PROPERTY_DELIMITER + qname.getLocalPart(); + } + +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java new file mode 100644 index 000000000..4f9b2fd67 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Iterator; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonException; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; + +/** + * This class aims to wrap Jakarta JsonArray to be usable in JsonHelper as before. + * Unlike Wink's JSONArray, Jakarta ones are immutable, which makes us work with JsonArrayBuilder before building the object. + */ +public class JSONArray implements Iterable { + + private JsonArrayBuilder builder; + + public JSONArray() { + this.builder = Json.createArrayBuilder(); + } + + public JSONArray(JsonArray array) { + this.builder = Json.createArrayBuilder(array); + } + + /* + * Builds the jakarta.json array. + * As this method is also internally called for implementing read-only operations, + * the underlying builder is reset with the built array instead of being set to empty, + * so that additional values can still be added later on. + */ + public JsonArray build() { + JsonArray jakartaObject = builder.build(); + this.builder = Json.createArrayBuilder(jakartaObject); + return jakartaObject; + } + + @Override + public String toString() { + return build().toString(); + } + + public boolean isEmpty() { + return build().isEmpty(); + } + + public int size() { + return build().size(); + } + + public JsonValue get(int index) { + return build().get(index); + } + + public JSONObject getJSONObject(int index) { + JsonArray array = build(); + JsonValue value = array.get(index); + if (value != null) { + return new JSONObject((JsonObject) value); + } else { + throw new JsonException("The value for index: [" + index + "] was null. Object required."); + } + } + + public void add(Object object) { + if (object instanceof String) { + this.builder.add((String) object); + } else if (object instanceof JSONObject) { + this.builder.add(((JSONObject) object).build()); + } else if (object instanceof JSONArray) { + this.builder.add(((JSONArray) object).build()); + } else if (object instanceof JsonObjectBuilder) { + this.builder.add((JsonObjectBuilder) object); + } else if (object instanceof JsonArrayBuilder) { + this.builder.add((JsonArrayBuilder) object); + } else if (object instanceof JsonValue) { + this.builder.add((JsonValue) object); + } else if (object instanceof Boolean) { + this.builder.add((Boolean) object); + } else if (object instanceof BigDecimal) { + this.builder.add((BigDecimal) object); + } else if (object instanceof BigInteger) { + this.builder.add((BigInteger) object); + } else if (object instanceof Long) { + this.builder.add((Long) object); + } else if (object instanceof Double) { + this.builder.add((Double) object); + } else if (object instanceof Integer) { + this.builder.add((Integer) object); + } else { + throw new IllegalArgumentException("Element cannot be added to JsonArray."); + } + } + + @Override + public Iterator iterator() { + return build().iterator(); + } + +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java new file mode 100644 index 000000000..17bde17bc --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.namespace.QName; + +import org.eclipse.lyo.oslc4j.core.NestedWildcardProperties; +import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; +import org.eclipse.lyo.oslc4j.core.SingletonWildcardProperties; +import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; +import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; +import org.eclipse.lyo.oslc4j.core.exception.MessageExtractor; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreInvalidPropertyTypeException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreRelativeURIException; +import org.eclipse.lyo.oslc4j.core.model.IExtendedResource; +import org.eclipse.lyo.oslc4j.core.model.IReifiedResource; +import org.eclipse.lyo.oslc4j.core.model.IResource; +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; +import org.eclipse.lyo.oslc4j.core.model.TypeFactory; +import org.eclipse.lyo.oslc4j.core.model.XMLLiteral; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sodius.oslc.core.provider.internal.JavaResourceShape; +import com.sodius.oslc.core.provider.internal.LyoProviderUtils; +import com.sodius.oslc.core.provider.internal.NamespaceMappings; +import com.sodius.oslc.core.provider.internal.PropertyAccessor; +import com.sodius.oslc.core.provider.internal.RdfCollections; +import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; + +@SuppressWarnings("java:S3776") // Complex legacy class +public class JSONModelBuilder extends AbstractBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(JSONModelBuilder.class); + + public static JsonObject build(String descriptionAbout, String responseInfoAbout, ResponseInfo responseInfo, Object[] objects, + Map properties) throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, OslcCoreApplicationException { + + return new JSONModelBuilder().buildResource(descriptionAbout, responseInfoAbout, responseInfo, objects, properties); + } + + private JSONModelBuilder() { + super(NamespaceMappings.global()); + } + + private JsonObject buildResource(String descriptionAbout, String responseInfoAbout, ResponseInfo responseInfo, Object[] objects, + Map properties) throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, OslcCoreApplicationException { + + JSONObject resultJSONObject = new JSONObject(); + + if (descriptionAbout != null) { + resultJSONObject.put(getRdfKey(PROPERTY_ABOUT), descriptionAbout); + + JSONArray jsonArray = new JSONArray(); + for (Object object : objects) { + HashMap visitedObjects = new HashMap<>(); + JSONObject jsonObject = buildSingleResource(object, new JSONObject(), properties, visitedObjects); + jsonArray.add(jsonObject); + } + + /* Support for Container rdf:type */ + if (LyoProviderUtils.isQueryResultListAsContainer()) { + + JSONObject containerTypeJSONObject = new JSONObject(); + containerTypeJSONObject.put(getRdfKey(PROPERTY_RESOURCE), OslcConstants.TYPE_CONTAINER); + + JSONArray containerTypesJSONArray = new JSONArray(); + containerTypesJSONArray.add(containerTypeJSONObject); + resultJSONObject.put(getRdfKey(PROPERTY_TYPE), containerTypesJSONArray); + + Map visitedObjects = new HashMap<>(); + buildExtendedProperties(resultJSONObject, responseInfo.getContainer(), properties, visitedObjects); + } + + resultJSONObject.put(getRdfsKey(PROPERTY_MEMBER), jsonArray); + + if (responseInfoAbout != null) { + + JSONObject responseInfoJSONObject = new JSONObject(); + responseInfoJSONObject.put(getRdfKey(PROPERTY_ABOUT), responseInfoAbout); + + if (responseInfo != null) { + responseInfoJSONObject.put(getOslcKey(PROPERTY_TOTAL_COUNT), + responseInfo.totalCount() == null ? Integer.valueOf(objects.length) : responseInfo.totalCount()); + + if (responseInfo.nextPage() != null) { + JSONObject nextPageJSONObject = new JSONObject(); + nextPageJSONObject.put(getRdfKey(PROPERTY_RESOURCE), responseInfo.nextPage()); + responseInfoJSONObject.put(getOslcKey(PROPERTY_NEXT_PAGE), nextPageJSONObject); + } + + JSONObject responseInfoTypeJSONObject = new JSONObject(); + responseInfoTypeJSONObject.put(getRdfKey(PROPERTY_RESOURCE), OslcConstants.TYPE_RESPONSE_INFO); + + JSONArray responseInfoTypesJSONArray = new JSONArray(); + responseInfoTypesJSONArray.add(responseInfoTypeJSONObject); + + responseInfoJSONObject.put(getRdfKey(PROPERTY_TYPE), responseInfoTypesJSONArray); + resultJSONObject.put(getOslcKey(PROPERTY_RESPONSE_INFO), responseInfoJSONObject); + + Map visitedObjects = new HashMap<>(); + buildExtendedProperties(responseInfoJSONObject, responseInfo, properties, visitedObjects); + } + } + } + + // unique object? + else if (objects.length == 1) { + HashMap visitedObjects = new HashMap<>(); + buildSingleResource(objects[0], resultJSONObject, properties, visitedObjects); + } + + // Set the namespace prefixes + Map namespaces = getNamespaceMappings().getMappings(); + if (!namespaces.isEmpty()) { + JSONObject prefixes = new JSONObject(); + namespaces.forEach(prefixes::put); + resultJSONObject.put(PREFIXES, prefixes); + } + + return resultJSONObject.build(); + } + + private void buildAccessor(Class resourceClass, PropertyAccessor accessor, JSONObject jsonObject, Object value, + Map nestedProperties, boolean onlyNested) throws DatatypeConfigurationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, OslcCoreApplicationException { + + boolean isRdfContainer; + OslcRdfCollectionType collectionType = accessor.getCollectionType().orElse(null); + if ((collectionType != null) && RdfCollections.isRdfCollection(collectionType)) { + isRdfContainer = true; + } else { + isRdfContainer = false; + } + + Object localResourceValue; + + Method method = accessor.getGetter(); + Class returnType = method.getReturnType(); + + if (returnType.isArray() || Collection.class.isAssignableFrom(returnType)) { + JSONArray jsonArray = new JSONArray(); + + if (returnType.isArray()) { + // We cannot cast to Object[] in case this is an array of primitives. We will use Array reflection instead. + // Strange case about primitive arrays: they cannot be cast to Object[], but retrieving their individual elements + // does not return primitives, but the primitive object wrapping counterparts like Integer, Byte, Double, etc. + int length = Array.getLength(value); + for (int index = 0; index < length; index++) { + Object object = Array.get(value, index); + Object localResource = buildValue(resourceClass, method, object, nestedProperties, onlyNested); + if (localResource != null) { + jsonArray.add(localResource); + } + } + } else { + @SuppressWarnings("unchecked") + Collection collection = (Collection) value; + for (Object object : collection) { + Object localResource = buildValue(resourceClass, method, object, nestedProperties, onlyNested); + if (localResource != null) { + jsonArray.add(localResource); + } + } + } + + if (jsonArray.isEmpty()) { + localResourceValue = null; + } else { + if (isRdfContainer) { + localResourceValue = buildContainer(collectionType, jsonArray); + } else { + localResourceValue = jsonArray; + } + } + } else { + localResourceValue = buildValue(resourceClass, method, value, nestedProperties, onlyNested); + } + + if (localResourceValue != null) { + jsonObject.put(generateKey(accessor.getQName()), localResourceValue); + } + } + + private Object buildContainer(OslcRdfCollectionType collectionType, JSONArray jsonArray) { + if (RdfCollections.RDF_LIST.equals(collectionType.collectionType())) { + JSONObject listObject = new JSONObject(); + + listObject.put(getRdfKey(PROPERTY_RESOURCE), OslcConstants.RDF_NAMESPACE + PROPERTY_NIL); + + for (int i = jsonArray.size() - 1; i >= 0; i--) { + Object o = jsonArray.get(i); + + JSONObject newListObject = new JSONObject(); + newListObject.put(getRdfKey(PROPERTY_FIRST), o); + newListObject.put(getRdfKey(PROPERTY_REST), listObject); + + listObject = newListObject; + } + + return listObject; + } + + JSONObject container = new JSONObject(); + container.put(getRdfKey(collectionType.collectionType()), jsonArray); + return container; + } + + private void buildResource(Object object, Class objectClass, JSONObject jsonObject, Map properties, + Map visitedObjects) throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, OslcCoreApplicationException { + + visitedObjects.put(object, jsonObject); + buildResourceProperties(object, objectClass, jsonObject, properties, visitedObjects); + + JSONArray rdfTypesJSONArray = new JSONArray(); + + if (objectClass.getAnnotation(OslcResourceShape.class) != null) { + String qualifiedName = TypeFactory.getQualifiedName(objectClass); + if (qualifiedName != null) { + addType(rdfTypesJSONArray, qualifiedName); + } + } + + if (object instanceof IExtendedResource) { + IExtendedResource extendedResource = (IExtendedResource) object; + buildExtendedTypes(extendedResource, properties, rdfTypesJSONArray); + } + + jsonObject.put(getRdfKey(PROPERTY_TYPE), rdfTypesJSONArray); + } + + private void addType(JSONArray rdfTypesJSONArray, String typeURI) { + JSONObject rdfTypeJSONObject = new JSONObject(); + rdfTypeJSONObject.put(getRdfKey(PROPERTY_RESOURCE), typeURI); + rdfTypesJSONArray.add(rdfTypeJSONObject); + } + + private void buildResourceProperties(Object object, Class objectClass, JSONObject jsonObject, Map properties, + Map visitedObjects) + throws IllegalAccessException, InvocationTargetException, DatatypeConfigurationException, OslcCoreApplicationException { + + if (properties == OSLC4JConstants.OSL4J_PROPERTY_SINGLETON) { + return; + } + + // loop on getters defined on the class + for (PropertyAccessor accessor : JavaResourceShape.valueOf(objectClass).getAccessors()) { + + // obtain the value from the getter + Object value = accessor.getGetter().invoke(object); + if (value != null) { + + Map nestedProperties = null; + boolean onlyNested = false; + + if (properties != null) { + @SuppressWarnings("unchecked") + Map map = (Map) properties.get(accessor.getPropertyDefinition()); + + if (map != null) { + nestedProperties = map; + } else if ((properties instanceof SingletonWildcardProperties) && !(properties instanceof NestedWildcardProperties)) { + nestedProperties = OSLC4JConstants.OSL4J_PROPERTY_SINGLETON; + } else if (properties instanceof NestedWildcardProperties) { + nestedProperties = ((NestedWildcardProperties) properties).commonNestedProperties(); + onlyNested = !(properties instanceof SingletonWildcardProperties); + } else { + continue; + } + } + + buildAccessor(objectClass, accessor, jsonObject, value, nestedProperties, onlyNested); + } + } + + if (object instanceof IExtendedResource) { + IExtendedResource extendedResource = (IExtendedResource) object; + buildExtendedProperties(jsonObject, extendedResource, properties, visitedObjects); + } + } + + private void buildExtendedProperties(JSONObject jsonObject, IExtendedResource extendedResource, Map properties, + Map visitedObjects) + throws DatatypeConfigurationException, IllegalAccessException, InvocationTargetException, OslcCoreApplicationException { + + String rdfTypeKey = getRdfKey(PROPERTY_TYPE); + JSONArray typesJSONArray; + if (jsonObject.containsKey(rdfTypeKey)) { + typesJSONArray = new JSONArray((JsonArray) jsonObject.get(rdfTypeKey)); + } else { + typesJSONArray = new JSONArray(); + } + + buildExtendedTypes(extendedResource, properties, typesJSONArray); + + if (typesJSONArray.size() > 0) { + jsonObject.put(getRdfKey(PROPERTY_TYPE), typesJSONArray); + } + + if (extendedResource.getExtendedProperties() != null) { + for (Map.Entry extendedProperty : extendedResource.getExtendedProperties().entrySet()) { + String namespace = extendedProperty.getKey().getNamespaceURI(); + String localName = extendedProperty.getKey().getLocalPart(); + Map nestedProperties = null; + boolean onlyNested = false; + + if (properties != null) { + @SuppressWarnings("unchecked") + Map map = (Map) properties.get(namespace + localName); + + if (map != null) { + nestedProperties = map; + } else if ((properties instanceof SingletonWildcardProperties) && !(properties instanceof NestedWildcardProperties)) { + nestedProperties = OSLC4JConstants.OSL4J_PROPERTY_SINGLETON; + } else if (properties instanceof NestedWildcardProperties) { + nestedProperties = ((NestedWildcardProperties) properties).commonNestedProperties(); + onlyNested = !(properties instanceof SingletonWildcardProperties); + } else { + continue; + } + } + + Object value = buildExtendedValue(extendedProperty.getValue(), nestedProperties, onlyNested, visitedObjects); + + if ((value == null) && !onlyNested) { + LOGGER.warn("Could not add extended property {} for resource {}", extendedProperty.getKey(), extendedResource.getAbout()); + } else { + // avoid serializing empty arrays + if (!((value instanceof JSONArray) && (((JSONArray) value).isEmpty()))) { + jsonObject.put(generateKey(extendedProperty.getKey()), value); + } + } + } + } + } + + private boolean isPropertyAccepted(Map properties, String propertyName) { + return (properties == null) // + || (properties.get(propertyName) != null) // + || (properties instanceof NestedWildcardProperties) // + || (properties instanceof SingletonWildcardProperties); + } + + /* + * Add properties for RDF types defined on the IExtendedResource + */ + private void buildExtendedTypes(IExtendedResource extendedResource, Map properties, JSONArray typesJSONArray) { + if (!isPropertyAccepted(properties, OslcConstants.RDF_NAMESPACE + PROPERTY_TYPE)) { + return; + } + + if ((extendedResource.getTypes() != null) && !extendedResource.getTypes().isEmpty()) { + + // make sure we don't add a type already part of the array + Set knownTypes = new HashSet<>(); + for (JsonValue jsonValue : typesJSONArray) { + String typeName = ((JsonObject) jsonValue).getString(getRdfKey(PROPERTY_RESOURCE)); + knownTypes.add(typeName); + } + + for (URI type : extendedResource.getTypes()) { + String typeName = type.toString(); + if (knownTypes.add(typeName)) { + addType(typesJSONArray, typeName); + } + } + } + } + + private Object buildExtendedValue(Object object, Map nestedProperties, boolean onlyNested, Map visitedObjects) + throws DatatypeConfigurationException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, + OslcCoreApplicationException { + + if (object instanceof Collection) { + JSONArray jsonArray = new JSONArray(); + @SuppressWarnings("unchecked") + Collection c = (Collection) object; + for (Object next : c) { + Object nextJson = buildExtendedValue(next, nestedProperties, onlyNested, visitedObjects); + if (nextJson != null) { + jsonArray.add(nextJson); + } + } + if (RdfCollections.isSequence(c) && !c.isEmpty()) { + JSONObject sequence = new JSONObject(); + sequence.put(getRdfKey(RdfCollections.RDF_SEQ), jsonArray); + return sequence; + } else { + return jsonArray; + } + } + + else if ((object instanceof String) || (object instanceof Boolean) || (object instanceof Number)) { + if (onlyNested) { + return null; + } + return object; + } + + else if (object instanceof XMLLiteral) { + if (onlyNested) { + return null; + } + + // XMLLiterals are treated as strings in the OSLC 2.0 JSON format. + return ((XMLLiteral) object).getValue(); + } + + else if (object instanceof Date) { + if (onlyNested) { + return null; + } + + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime((Date) object); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar).toString(); + } + + else if (object instanceof URI) { + if (onlyNested) { + return null; + } + return buildResourceReference(object.getClass(), null, (URI) object); + } + + else if ((object instanceof IResource) && !visitedObjects.containsKey(object)) { + return buildSingleResource(object, new JSONObject(), nestedProperties, visitedObjects); + } + + else if (object instanceof IReifiedResource) { + return buildReifiedResource(object.getClass(), null, (IReifiedResource) object, nestedProperties); + } + + else if (visitedObjects.containsKey(object)) { + JSONObject returnObject = visitedObjects.get(object); + if (!returnObject.isEmpty()) { + return returnObject; + } + } + + return null; + } + + private Object buildValue(Class resourceClass, Method method, Object object, Map nestedProperties, boolean onlyNested) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + OslcCoreApplicationException { + + // Handle special float values. + if ((object instanceof Float) && writeSpecialNumberValues()) { + if (onlyNested) { + return null; + } + + Float f = (Float) object; + if (f.compareTo(Float.POSITIVE_INFINITY) == 0) { + return POSITIVE_INF; + } + + if (f.compareTo(Float.NEGATIVE_INFINITY) == 0) { + return NEGATIVE_INF; + } + + if (f.isNaN()) { + return NOT_A_NUMBER; + } + } + + // Handle special double values. + if ((object instanceof Double) && writeSpecialNumberValues()) { + if (onlyNested) { + return null; + } + + Double d = (Double) object; + if (d.compareTo(Double.POSITIVE_INFINITY) == 0) { + return POSITIVE_INF; + } + + if (d.compareTo(Double.NEGATIVE_INFINITY) == 0) { + return NEGATIVE_INF; + } + + if (d.isNaN()) { + return NOT_A_NUMBER; + } + } + + if ((object instanceof String) || (object instanceof Boolean) || (object instanceof Number)) { + if (onlyNested) { + return null; + } + return object; + } + + else if (object instanceof URI) { + if (onlyNested) { + return null; + } + return buildResourceReference(resourceClass, method, (URI) object); + } + + else if (object instanceof Date) { + if (onlyNested) { + return null; + } + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime((Date) object); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar).toString(); + } + + else if (object instanceof IReifiedResource) { + return buildReifiedResource(object.getClass(), method, (IReifiedResource) object, nestedProperties); + } + + Map visitedObjects = new HashMap<>(); + return buildSingleResource(object, new JSONObject(), nestedProperties, visitedObjects); + } + + private Object buildReifiedResource(Class resourceClass, Method method, IReifiedResource reifiedResource, Map properties) + throws IllegalAccessException, InvocationTargetException, DatatypeConfigurationException, OslcCoreApplicationException { + + Object value = reifiedResource.getValue(); + if (value == null) { + return null; + } + + if (!(value instanceof URI)) { + // The OSLC JSON serialization doesn't support reification on anything except + // resources by reference (typically links with labels). Throw an exception + // if the value isn't a URI. + // See http://open-services.net/bin/view/Main/OslcCoreSpecAppendixLinks + if (method == null) { + // OslcCoreInvalidPropertyTypeException requires a method, which we don't have here. + // Let's log the exception message + Object[] messageArgs = new Object[] { resourceClass.getName(), "", "" }; // NOSONAR + LOGGER.warn(MessageExtractor.getMessage("InvalidPropertyTypeException", messageArgs)); + return null; + } else { + throw new OslcCoreInvalidPropertyTypeException(resourceClass, method, method.getReturnType()); + } + } + + // Add the resource reference value. + JSONObject jsonObject = buildResourceReference(resourceClass, method, (URI) value); + + // Add any reified statements. + Map visitedObjects = new HashMap<>(); + buildResourceProperties(reifiedResource, resourceClass, jsonObject, properties, visitedObjects); + + return jsonObject; + } + + private JSONObject buildResourceReference(Class resourceClass, Method method, URI uri) throws OslcCoreRelativeURIException { + if (LyoProviderUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) { + throw new OslcCoreRelativeURIException(resourceClass, (method == null) ? "" : method.getName(), uri); + } + + JSONObject jsonObject = new JSONObject(); + jsonObject.put(getRdfKey(PROPERTY_RESOURCE), uri.toString()); + return jsonObject; + } + + private JSONObject buildSingleResource(Object object, JSONObject jsonObject, Map properties, + Map visitedObjects) throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, OslcCoreApplicationException { + + if (object instanceof URI) { + jsonObject.put(getRdfKey(PROPERTY_RESOURCE), ((URI) object).toASCIIString()); + visitedObjects.put(object, jsonObject); + } else { + // Collect the namespace prefix -> namespace mappings + Class objectClass = object.getClass(); + getNamespaceMappings().addMappings(objectClass); + + if (object instanceof IResource) { + URI aboutURI = ((IResource) object).getAbout(); + addAboutURI(jsonObject, objectClass, aboutURI); + } + + buildResource(object, objectClass, jsonObject, properties, visitedObjects); + } + + return jsonObject; + } + + private void addAboutURI(JSONObject jsonObject, Class objectClass, URI aboutURI) throws OslcCoreRelativeURIException { + if (aboutURI != null) { + if (LyoProviderUtils.relativeURIsAreDisabled() && !aboutURI.isAbsolute()) { + throw new OslcCoreRelativeURIException(objectClass, "getAbout", aboutURI); + } + + jsonObject.put(getRdfKey(PROPERTY_ABOUT), aboutURI.toString()); + } + } + + private static boolean writeSpecialNumberValues() { + return "true".equals(System.getProperty(JsonHelper.OSLC4J_WRITE_SPECIAL_NUMS, "true")); + } + + private String getRdfKey(String localPart) { + return generateKey(OslcConstants.RDF_NAMESPACE, localPart, OslcConstants.RDF_NAMESPACE_PREFIX); + } + + private String getRdfsKey(String localPart) { + return generateKey(OslcConstants.RDFS_NAMESPACE, localPart, OslcConstants.RDFS_NAMESPACE_PREFIX); + } + + private String getOslcKey(String localPart) { + return generateKey(OslcConstants.OSLC_CORE_NAMESPACE, localPart, OslcConstants.OSLC_CORE_NAMESPACE_PREFIX); + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java new file mode 100644 index 000000000..e19b70851 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map.Entry; +import java.util.Set; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonException; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; + +/** + * This class aims to wrap Jakarta JsonObject to be usable in JsonHelper as before. + * Unlike Wink's JSONObject, Jakarta ones are immutable, which makes us work with JsonObjectBuilder before building the object. + */ +public class JSONObject { + + private JsonObjectBuilder builder; + + public JSONObject() { + this.builder = Json.createObjectBuilder(); + } + + public JSONObject(JsonObject object) { + this.builder = Json.createObjectBuilder(object); + } + + /* + * Builds the jakarta.json object. + * As this method is also internally called for implementing read-only operations, + * the underlying builder is reset with the built object instead of being set to empty, + * so that additional properties can still be added later on. + */ + public JsonObject build() { + JsonObject jakartaObject = builder.build(); + this.builder = Json.createObjectBuilder(jakartaObject); + return jakartaObject; + } + + @Override + public String toString() { + return build().toString(); + } + + public int size() { + return build().size(); + } + + public Object opt(String key) { + return build().get(key); + } + + public Object get(String key) { + JsonObject object = build(); + if (object.containsKey(key)) { + return object.get(key); + } else { + throw new JsonException("The key [" + key + "] was not in the map."); + } + } + + public String optString(String key) { + JsonObject object = build(); + return object.getString(key, null); + } + + public String getString(String key) { + JsonObject object = build(); + if (object.containsKey(key)) { + return object.getString(key); + } else { + throw new JsonException("The value for key: [" + key + "] was null. Object required."); + } + } + + public JSONArray optJSONArray(String key) { + JsonObject object = build(); + return object.containsKey(key) ? new JSONArray(object.getJsonArray(key)) : null; + } + + public JSONObject optJSONObject(String key) { + JsonObject object = build(); + return object.containsKey(key) ? new JSONObject(object.getJsonObject(key)) : null; + } + + public JSONArray getJSONArray(String key) { + JsonObject object = build(); + if (object.containsKey(key)) { + return new JSONArray(object.getJsonArray(key)); + } else { + throw new JsonException("The value for key: [" + key + "] was null. Object required."); + } + } + + @SuppressWarnings("java:S3776") // Implementation is not that complex + public void put(String key, Object object) { + if (object == null) { + this.builder.addNull(key); + } else if (object instanceof String) { + this.builder.add(key, (String) object); + } else if (object instanceof JSONObject) { + this.builder.add(key, ((JSONObject) object).build()); + } else if (object instanceof JSONArray) { + this.builder.add(key, ((JSONArray) object).build()); + } else if (object instanceof JsonObjectBuilder) { + this.builder.add(key, (JsonObjectBuilder) object); + } else if (object instanceof JsonArrayBuilder) { + this.builder.add(key, (JsonArrayBuilder) object); + } else if (object instanceof JsonValue) { + this.builder.add(key, (JsonValue) object); + } else if (object instanceof Boolean) { + this.builder.add(key, (Boolean) object); + } else if (object instanceof BigDecimal) { + this.builder.add(key, (BigDecimal) object); + } else if (object instanceof BigInteger) { + this.builder.add(key, (BigInteger) object); + } else if (object instanceof Long) { + this.builder.add(key, (Long) object); + } else if (object instanceof Short) { + this.builder.add(key, (Short) object); + } else if (object instanceof Double) { + this.builder.add(key, (Double) object); + } else if (object instanceof Float) { + this.builder.add(key, (Float) object); + } else if (object instanceof Integer) { + this.builder.add(key, (Integer) object); + } else { + throw new IllegalArgumentException("Unexpected value type for key '" + key + "': " + object.getClass().getName()); + } + } + + public Set> entrySet() { + return build().entrySet(); + } + + public boolean containsKey(String key) { + return build().containsKey(key); + } + + public boolean has(String key) { + return build().containsKey(key); + } + + public boolean isEmpty() { + return build().isEmpty(); + } + +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java new file mode 100644 index 000000000..31102acec --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java @@ -0,0 +1,826 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.namespace.QName; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingNamespaceDeclarationException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingNamespacePrefixException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMisusedOccursException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreRelativeURIException; +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; +import org.eclipse.lyo.oslc4j.core.model.AnyResource; +import org.eclipse.lyo.oslc4j.core.model.IExtendedResource; +import org.eclipse.lyo.oslc4j.core.model.IReifiedResource; +import org.eclipse.lyo.oslc4j.core.model.IResource; +import org.eclipse.lyo.oslc4j.core.model.Link; +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; +import org.eclipse.lyo.oslc4j.core.model.XMLLiteral; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sodius.oslc.core.provider.internal.CollectionSetterInvoker; +import com.sodius.oslc.core.provider.internal.JavaResourceShape; +import com.sodius.oslc.core.provider.internal.LyoProviderUtils; +import com.sodius.oslc.core.provider.internal.NamespaceMappings; +import com.sodius.oslc.core.provider.internal.PropertyAccessor; +import com.sodius.oslc.core.provider.internal.RdfCollections; +import com.sodius.oslc.core.provider.internal.ResourceShapes; +import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; + +import jakarta.json.JsonArray; +import jakarta.json.JsonNumber; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; + +@SuppressWarnings("java:S3776") // Complex legacy class +public final class ResourceBuilder extends AbstractBuilder { + + private static final String RDF_ABOUT_URI = OslcConstants.RDF_NAMESPACE + PROPERTY_ABOUT; + private static final String RDF_TYPE_URI = OslcConstants.RDF_NAMESPACE + PROPERTY_TYPE; + private static final String RDF_NIL_URI = OslcConstants.RDF_NAMESPACE + PROPERTY_NIL; + private static final String RDF_RESOURCE_URI = OslcConstants.RDF_NAMESPACE + PROPERTY_RESOURCE; + + private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+"); + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBuilder.class); + + public static Object[] build(JsonObject jakartaObject, Class beanClass) throws DatatypeConfigurationException, IllegalAccessException, + IllegalArgumentException, InstantiationException, InvocationTargetException, OslcCoreApplicationException, URISyntaxException { + + try { + return new ResourceBuilder().buildResources(jakartaObject, beanClass); + } catch (NoSuchMethodException e) { + throw new InvocationTargetException(e); + } + } + + private ResourceBuilder() { + super(NamespaceMappings.empty()); + } + + private Object buildResource(JSONObject resourceJSONObject, Class beanClass) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, URISyntaxException, NoSuchMethodException { + + Object bean = beanClass.getDeclaredConstructor().newInstance(); + Set rdfTypes = new HashSet<>(); + buildResource(resourceJSONObject, beanClass, bean, rdfTypes); + return bean; + } + + private Object[] buildResources(JsonObject jakartaObject, Class beanClass) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, URISyntaxException, NoSuchMethodException { + + List beans = new ArrayList<>(); + + JSONObject jsonObject = new JSONObject(jakartaObject); + + // First read the prefixes and set up maps so we can create full property definition values later + Object prefixes = jsonObject.opt(PREFIXES); + if (prefixes instanceof JsonObject) { + JSONObject prefixesJSONObject = new JSONObject((JsonObject) prefixes); + addMappings(prefixesJSONObject); + } + + JSONArray jsonArray = null; + + // Look for rdfs:member + if (getNamespaceMappings().containsPrefix(OslcConstants.RDFS_NAMESPACE_PREFIX)) { + Object members = jsonObject.opt(getRdfsKey(PROPERTY_MEMBER)); + if (members instanceof JsonArray) { + + // If the Java class defines an accessor for rdfs:member property, + // the caller expects to get an instance of the shape with some members assigned (PRDOSLC-1376). + // Otherwise the caller expects a collection of those members + Optional membersAccessor = JavaResourceShape.valueOf(beanClass) + .getAccessor(OslcConstants.RDFS_NAMESPACE + PROPERTY_MEMBER); + if (membersAccessor.isEmpty()) { + jsonArray = new JSONArray((JsonArray) members); + } + } + } + + // Look for oslc:results. Seen in ChangeManagement. + if ((jsonArray == null) && getNamespaceMappings().containsPrefix(OslcConstants.OSLC_CORE_NAMESPACE_PREFIX)) { + Object results = jsonObject.opt(getOslcKey(PROPERTY_RESULTS)); + if (results instanceof JsonArray) { + jsonArray = new JSONArray((JsonArray) results); + } + } + + // array? + if (jsonArray != null) { + for (JsonValue arrayValue : jsonArray) { + if (arrayValue instanceof JsonObject) { + JSONObject arrayObject = new JSONObject((JsonObject) arrayValue); + if (URI.class.equals(beanClass)) { + String uri = arrayObject.optString(getRdfKey(PROPERTY_RESOURCE)); + beans.add(URI.create(uri)); + } else { + Object resource = buildResource(arrayObject, beanClass); + beans.add(resource); + } + } + } + } + + // resource + else { + Object resource = buildResource(jsonObject, beanClass); + beans.add(resource); + } + + return beans.toArray((Object[]) Array.newInstance(beanClass, beans.size())); + } + + /** + * Returns a list of rdf:types for a given json object. If the list was + * populated before, returns the given list. This list will only be + * populated if the property inferTypeFromShape is set to true. + * + * @param jsonObject + * @param rdfPrefix + * @param types + * @return List of rdf:types + * @throws OslcCoreMissingNamespaceDeclarationException + */ + private Set getRdfTypesFromJsonObject(JSONObject jsonObject, Set types) throws OslcCoreMissingNamespaceDeclarationException { + // The list of rdf:types will be populated only if the property + // inferTypeFromShape is set and if the list was not populated before. + // This is necessary because for an inline object, the retuned + // rdf:type is not from the parent object, it is from the actual + // resource. + if (ResourceShapes.inferTypeFromShape() && types.isEmpty()) { + String typeProperty = getRdfKey(PROPERTY_TYPE); + if (jsonObject.has(typeProperty)) { + JSONArray array = jsonObject.getJSONArray(typeProperty); + for (int i = 0; i < array.size(); ++i) { + JSONObject typeObj = array.getJSONObject(i); + String resTypePropertyValue = typeObj.getString(getRdfKey(PROPERTY_RESOURCE)); + types.add(resTypePropertyValue); + } + } + } + return types; + } + + private void buildResource(JSONObject jsonObject, Class beanClass, Object bean, Set rdfTypes) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, URISyntaxException, NoSuchMethodException { + + boolean isIReifiedResource = false; + + // rdf:about + if (bean instanceof IResource) { + Object aboutURIObject = jsonObject.opt(getRdfKey(PROPERTY_ABOUT)); + + if (aboutURIObject instanceof JsonString) { + URI aboutURI = new URI(((JsonString) aboutURIObject).getString()); + if (LyoProviderUtils.relativeURIsAreDisabled() && !aboutURI.isAbsolute()) { + throw new OslcCoreRelativeURIException(beanClass, "setAbout", aboutURI); + } + ((IResource) bean).setAbout(aboutURI); + } + } + + // reified? + else if (bean instanceof IReifiedResource) { + isIReifiedResource = true; + + @SuppressWarnings("unchecked") + IReifiedResource reifiedResource = (IReifiedResource) bean; + String resourceReference = jsonObject.getString(getRdfKey(PROPERTY_RESOURCE)); + + try { + reifiedResource.setValue(new URI(resourceReference)); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + } + + IExtendedResource extendedResource; + Map extendedProperties; + if (bean instanceof IExtendedResource) { + extendedResource = (IExtendedResource) bean; + extendedProperties = new HashMap<>(); + extendedResource.setExtendedProperties(extendedProperties); + } else { + extendedResource = null; + extendedProperties = null; + } + + // get the list of rdf types + rdfTypes = getRdfTypesFromJsonObject(jsonObject, rdfTypes); + + for (Entry entry : jsonObject.entrySet()) { + String prefixedName = entry.getKey(); + JsonValue jsonValue = entry.getValue(); + + // detect the property definition used + String[] split = prefixedName.split(JSON_PROPERTY_DELIMITER); + if (split.length != 2) { + if (!PREFIXES.equals(prefixedName)) { + LOGGER.warn("Ignored JSON property '{}'.", prefixedName); + } + } else { + QName qName = getExistingQName(split[0], split[1]); + String propertyDefinition = qName.getNamespaceURI() + qName.getLocalPart(); + + // no setter -> use an extended property + Optional accessor = JavaResourceShape.valueOf(beanClass).getAccessor(propertyDefinition); + if (!accessor.isPresent()) { + if (RDF_ABOUT_URI.equals(propertyDefinition) || (isIReifiedResource && RDF_RESOURCE_URI.equals(propertyDefinition))) { + // Ignore missing property definitions for rdf:about, rdf:types and + // rdf:resource for IReifiedResources. + } else if (RDF_TYPE_URI.equals(propertyDefinition)) { + if (extendedResource != null) { + fillInRdfType(jsonObject, extendedResource); + } + // Otherwise ignore missing propertyDefinition for rdf:type. + } else { + if (extendedProperties == null) { + LOGGER.debug("Set method not found for object type: {}, propertyDefinition: {}", beanClass.getName(), propertyDefinition); + } else { + Object value = buildExtendedValue(jsonValue, beanClass, qName, rdfTypes); + if (value != null) { + extendedProperties.put(qName, value); + } + } + } + } + + // use the setter + else { + buildAccessor(beanClass, bean, rdfTypes, jsonValue, accessor.get()); + } + } + } + } + + private void buildAccessor(Class beanClass, Object bean, Set rdfTypes, JsonValue jsonValue, PropertyAccessor accessor) + throws DatatypeConfigurationException, IllegalAccessException, InstantiationException, InvocationTargetException, + OslcCoreApplicationException, URISyntaxException, NoSuchMethodException { + + Method setMethod = accessor.getSetter(); + Class setMethodParameterClass = setMethod.getParameterTypes()[0]; + Class setMethodComponentParameterClass = setMethodParameterClass; + + boolean multiple = false; + if (setMethodComponentParameterClass.isArray()) { + multiple = true; + setMethodComponentParameterClass = setMethodComponentParameterClass.getComponentType(); + } else if (Collection.class.isAssignableFrom(setMethodComponentParameterClass)) { + multiple = true; + Type genericParameterType = setMethod.getGenericParameterTypes()[0]; + + if (genericParameterType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericParameterType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length == 1) { + Type actualTypeArgument = actualTypeArguments[0]; + if (actualTypeArgument instanceof Class) { + setMethodComponentParameterClass = (Class) actualTypeArgument; + } + } + } + } + + // build the value + Object parameter = buildAccessorValue(beanClass, accessor, setMethodParameterClass, setMethodComponentParameterClass, jsonValue, rdfTypes); + if (parameter != null) { + + // If the method expects an array or collection and a single value was found, + // use CollectionSetterInvoker to wrap the value and call the method. + if (multiple && !((parameter instanceof Collection) || parameter.getClass().isArray())) { + CollectionSetterInvoker invoker = new CollectionSetterInvoker(bean); + invoker.add(accessor, parameter); + invoker.invokeAll(); + } + + // call the method with the value + else { + setMethod.invoke(bean, parameter); + } + } + } + + /* + * Infer the appropriate bean value from the JSON value. We can't rely on + * the setter parameter type since this is an extended value that has no + * setter in the bean. + */ + private Object buildExtendedValue(JsonValue jsonValue, Class beanClass, QName propertyQName, Set rdfTypes) + throws DatatypeConfigurationException, URISyntaxException, IllegalArgumentException, IllegalAccessException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, NoSuchMethodException { + + // Json array? + if (jsonValue instanceof JsonArray) { + JsonArray array = (JsonArray) jsonValue; + return buildExtendedArray(array, beanClass, propertyQName, rdfTypes); + } + + // Json object? + else if (jsonValue instanceof JsonObject) { + JSONObject o = new JSONObject((JsonObject) jsonValue); + return buildExtendedResource(o, beanClass, propertyQName, rdfTypes); + } + + // string? + else if (jsonValue instanceof JsonString) { + String jsonStringValue = ((JsonString) jsonValue).getString(); + return buildExtendedString(jsonStringValue, propertyQName, rdfTypes); + } + + // number? + else if (jsonValue instanceof JsonNumber) { + JsonNumber jsonNumber = (JsonNumber) jsonValue; + return buildExtendedNumber(jsonNumber, propertyQName, rdfTypes); + } + + // boolean? + else if (JsonValue.TRUE.equals(jsonValue)) { + return true; + } else if (JsonValue.FALSE.equals(jsonValue)) { + return false; + } + + // null? + else if (JsonValue.NULL.equals(jsonValue)) { + return null; + } + + // what's that?! + else { + throw new IllegalStateException("Unexpected type of JsonValue: " + jsonValue.getClass().getName()); + } + } + + @SuppressWarnings("java:S1130") // InvocationTargetException can be raised by early Lyo versions + private Object buildExtendedNumber(JsonNumber value, QName propertyQName, Set rdfTypes) + throws DatatypeConfigurationException, InstantiationException, InvocationTargetException { + + if (value.isIntegral()) { + // fix for Bug 412789 + // There is no need to infer data type from resource shapes as integer values do not have ambiguity cases + return value.intValue(); + } else { + // fix for Bug 412789 + // try to infer data type from resource shapes for Double + if (ResourceShapes.inferTypeFromShape()) { + Object newObject = ResourceShapes.getValueBasedOnResourceShapeType(rdfTypes, propertyQName, value.doubleValue()); + + // return the value only if the type was really inferred from + // the resource shape, otherwise keep the same behavior + if (null != newObject) { + + // return the new value only for ambiguous case + if ((newObject instanceof Double) || (newObject instanceof Float) || (newObject instanceof BigDecimal)) { // NOSONAR + return newObject; + } + } + } + + return value.doubleValue(); + } + } + + @SuppressWarnings("java:S1130") // InvocationTargetException can be raised by early Lyo versions + private Object buildExtendedString(String value, QName propertyQName, Set rdfTypes) + throws DatatypeConfigurationException, InstantiationException, InvocationTargetException { + + // fix for Bug 412789 + // try to infer the data type from resource shapes for Strings + if (ResourceShapes.inferTypeFromShape()) { + Object newObject = ResourceShapes.getValueBasedOnResourceShapeType(rdfTypes, propertyQName, value); + + // return the value only if the type was really inferred from + // the resource shape, otherwise keep the same behavior + if (null != newObject) { + + // return the new value only for ambiguous case + if ((newObject instanceof String) || (newObject instanceof XMLLiteral) || (newObject instanceof Date)) { // NOSONAR + return newObject; + } + } + } + + // If it's a number, don't try converting to a date (PRDOSLC-723) + if (NUMBER_PATTERN.matcher(value).matches()) { + return value; + } + + // Check if it's in the OSLC date format. + try { + return DatatypeFactory.newInstance().newXMLGregorianCalendar(value).toGregorianCalendar().getTime(); + } catch (IllegalArgumentException e) { + // It's not a date. Treat it as a string. + return value; + } + } + + private Object buildExtendedResource(JSONObject o, Class beanClass, QName propertyQName, Set rdfTypes) + throws URISyntaxException, DatatypeConfigurationException, IllegalAccessException, InstantiationException, InvocationTargetException, + OslcCoreApplicationException, NoSuchMethodException { + + // resource reference? + String resourceURIValue = o.optString(getRdfKey(PROPERTY_RESOURCE)); + if (resourceURIValue != null) { + URI uri = new URI(resourceURIValue); + if (LyoProviderUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) { + throw new OslcCoreRelativeURIException(beanClass, "", uri); + } + + // is there also a title? + String title = o.optString(getDctermsKey(PROPERTY_TITLE)); + if (title != null) { + // create a Link + return new Link(uri, title); + } + + // create a URI + else { + return uri; + } + } + + // sequence? + else if (RdfCollections.isSequenceSupported() && o.containsKey(getRdfKey(RdfCollections.RDF_SEQ))) { + JSONArray sequenceMembers = o.getJSONArray(getRdfKey(RdfCollections.RDF_SEQ)); + return buildExtendedSequence(sequenceMembers, beanClass, propertyQName, rdfTypes); + } + + // Handle an inline resource. + else { + AbstractResource any = new AnyResource(); + buildResource(o, AnyResource.class, any, rdfTypes); + return any; + } + } + + private Object buildExtendedSequence(JSONArray sequenceMembers, Class beanClass, QName propertyQName, Set rdfTypes) + throws URISyntaxException, DatatypeConfigurationException, IllegalAccessException, InstantiationException, InvocationTargetException, + OslcCoreApplicationException, NoSuchMethodException { + + List sequence = RdfCollections.createSequence(); + for (JsonValue jsonValue : sequenceMembers) { + Object value = buildExtendedValue(jsonValue, beanClass, propertyQName, rdfTypes); + sequence.add(value); + } + return sequence; + } + + private Object buildExtendedArray(JsonArray array, Class beanClass, QName propertyQName, Set rdfTypes) + throws DatatypeConfigurationException, URISyntaxException, IllegalAccessException, InstantiationException, InvocationTargetException, + OslcCoreApplicationException, NoSuchMethodException { + + List collection = new ArrayList<>(); + for (JsonValue element : new JSONArray(array)) { + collection.add(buildExtendedValue(element, beanClass, propertyQName, rdfTypes)); + } + return collection; + } + + private void fillInRdfType(JSONObject jsonObject, IExtendedResource resource) + throws URISyntaxException, OslcCoreMissingNamespaceDeclarationException { + + String typeProperty = getRdfKey(PROPERTY_TYPE); + if (jsonObject.has(typeProperty)) { + JSONArray array = jsonObject.getJSONArray(typeProperty); + for (int i = 0; i < array.size(); ++i) { + JSONObject typeObj = array.getJSONObject(i); + resource.addType(new URI(typeObj.getString(getRdfKey(PROPERTY_RESOURCE)))); + } + } + } + + private boolean isRdfListNode(PropertyAccessor accessor, Object jsonValue) throws OslcCoreMissingNamespaceDeclarationException { + if (!(jsonValue instanceof JsonObject)) { + return false; + } + + JSONObject jsonObject = new JSONObject((JsonObject) jsonValue); + + boolean isListNode = jsonObject.has(getRdfKey(PROPERTY_FIRST)) && jsonObject.has(getRdfKey(PROPERTY_REST)); + if (isListNode) { + return true; + } + + boolean isNilResource = RDF_NIL_URI.equals(jsonObject.optString(getRdfKey(PROPERTY_RESOURCE))); + if (!isNilResource) { + return false; + } + + OslcRdfCollectionType collectionType = accessor.getCollectionType().orElse(null); + if ((collectionType != null) && OslcConstants.RDF_NAMESPACE.equals(collectionType.namespaceURI()) // NOSONAR + && RdfCollections.RDF_LIST.equals(collectionType.collectionType())) { + return true; + } + + return false; + } + + private Object buildAccessorValue(Class beanClass, PropertyAccessor accessor, Class setMethodParameterClass, + Class setMethodComponentParameterClass, JsonValue jsonValue, Set rdfTypes) + throws DatatypeConfigurationException, IllegalAccessException, IllegalArgumentException, InstantiationException, + InvocationTargetException, OslcCoreApplicationException, URISyntaxException, NoSuchMethodException { + + // determine whether the object is a RDF collection container + boolean isRdfContainerNode = isRdfListNode(accessor, jsonValue); + JSONArray container = null; + if (!isRdfContainerNode && (jsonValue instanceof JsonObject)) { + JSONObject parent = new JSONObject((JsonObject) jsonValue); + + container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_ALT)); + if (container == null) { + container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_BAG)); + } + if (container == null) { + container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_SEQ)); + } + isRdfContainerNode = container != null; + } + + // json object? + if (!isRdfContainerNode && (jsonValue instanceof JsonObject)) { + JSONObject nestedJSONObject = new JSONObject((JsonObject) jsonValue); + + if (!IReifiedResource.class.isAssignableFrom(setMethodComponentParameterClass)) { + // If this is the special case for an rdf:resource? + Object uriObject = nestedJSONObject.opt(getRdfKey(PROPERTY_RESOURCE)); + + if (uriObject instanceof JsonString) { + URI uri = new URI(((JsonString) uriObject).getString()); + + if (LyoProviderUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) { + throw new OslcCoreRelativeURIException(beanClass, accessor.getSetter().getName(), uri); + } + + return uri; + } + } + + Object nestedBean = setMethodComponentParameterClass.getDeclaredConstructor().newInstance(); + buildResource(nestedJSONObject, setMethodComponentParameterClass, nestedBean, rdfTypes); + return nestedBean; + } + + // json array? + else if ((jsonValue instanceof JsonArray) || isRdfContainerNode) { + JSONArray jsonArray; + + if (isRdfContainerNode && (container == null)) { + jsonArray = new JSONArray(); + + JSONObject listNode = new JSONObject((JsonObject) jsonValue); + while ((listNode != null) && !RDF_NIL_URI.equals(listNode.opt(getRdfKey(PROPERTY_RESOURCE)))) { + Object o = listNode.opt(getRdfKey(PROPERTY_FIRST)); + jsonArray.add(o); + listNode = listNode.optJSONObject(getRdfKey(PROPERTY_REST)); + } + } else if (isRdfContainerNode) { + JSONArray array = container; + jsonArray = array; + } else { + JSONArray array = new JSONArray((JsonArray) jsonValue); + jsonArray = array; + } + + // build the array/collection members + List tempList = new ArrayList<>(); + for (JsonValue jsonArrayEntryObject : jsonArray) { + Object parameterArrayObject = buildAccessorValue(beanClass, accessor, setMethodComponentParameterClass, + setMethodComponentParameterClass, jsonArrayEntryObject, rdfTypes); + + tempList.add(parameterArrayObject); + } + + // array? + if (setMethodParameterClass.isArray()) { + // To support primitive arrays, we have to use Array reflection to set individual elements. We cannot use Collection.toArray. + // Array.set will unwrap objects to their corresponding primitives. + Object array = Array.newInstance(setMethodComponentParameterClass, jsonArray.size()); + + int index = 0; + for (Object parameterArrayObject : tempList) { + Array.set(array, index, parameterArrayObject); + index++; + } + + return array; + } + + // collection? + else if (Collection.class.isAssignableFrom(setMethodParameterClass)) { + Collection collection = RdfCollections.createCollection(setMethodParameterClass); + collection.addAll(tempList); + return collection; + } + + else if (!tempList.isEmpty()) { + // Resource is expecting a single value but a collection is defined on the Json object. + // Log a warning and return only the first value + OslcCoreMisusedOccursException e = new OslcCoreMisusedOccursException(beanClass, accessor.getSetter()); + LOGGER.warn(e.getMessage()); + return tempList.get(0); + } else { + return null; + } + } + + // null? + else if ((jsonValue == null) || JsonValue.NULL.equals(jsonValue)) { + return buildAccessorNullValue(setMethodComponentParameterClass); + } + + // convert from the string representation + else if (jsonValue instanceof JsonString) { + String stringValue = ((JsonString) jsonValue).getString(); + return buildAccessorString(stringValue, setMethodComponentParameterClass); + } + + // convert from the string representation + else { + String stringValue = jsonValue.toString(); + return buildAccessorString(stringValue, setMethodComponentParameterClass); + } + } + + private Object buildAccessorString(String value, Class type) throws DatatypeConfigurationException { + if (String.class == type) { + return value; + } else if ((Boolean.class == type) || (Boolean.TYPE == type)) { + // Cannot use Boolean.parseBoolean since it supports case-insensitive TRUE. + if (Boolean.TRUE.toString().equals(value)) { + return Boolean.TRUE; + } else if (Boolean.FALSE.toString().equals(value)) { + return Boolean.FALSE; + } else { + throw new IllegalArgumentException("'" + value + "' has wrong format for Boolean."); + } + } else if ((Byte.class == type) || (Byte.TYPE == type)) { + return Byte.valueOf(value); + } else if ((Short.class == type) || (Short.TYPE == type)) { + return Short.valueOf(value); + } else if ((Integer.class == type) || (Integer.TYPE == type)) { + return Integer.valueOf(value); + } else if ((Long.class == type) || (Long.TYPE == type)) { + return Long.valueOf(value); + } else if (BigInteger.class == type) { + return new BigInteger(value); + } else if ((Float.class == type) || (Float.TYPE == type)) { + if (readSpecialNumberValues()) { + if (POSITIVE_INF.equals(value) || "Infinity".equals(value)) { + return Float.POSITIVE_INFINITY; + } + if (NEGATIVE_INF.equals(value) || "-Infinity".equals(value)) { + return Float.NEGATIVE_INFINITY; + } + if (NOT_A_NUMBER.equals(value)) { + return Float.NaN; + } + } + + return Float.valueOf(value); + } else if ((Double.class == type) || (Double.TYPE == type)) { + if (readSpecialNumberValues()) { + if (POSITIVE_INF.equals(value) || "Infinity".equals(value)) { + return Double.POSITIVE_INFINITY; + } + if (NEGATIVE_INF.equals(value) || "-Infinity".equals(value)) { + return Double.NEGATIVE_INFINITY; + } + if (NOT_A_NUMBER.equals(value)) { + return Double.NaN; + } + } + + return Double.valueOf(value); + } else if (Date.class == type) { + return DatatypeFactory.newInstance().newXMLGregorianCalendar(value).toGregorianCalendar().getTime(); + } else { + return null; + } + } + + private Object buildAccessorNullValue(Class type) { + if ((Boolean.class == type) || (Boolean.TYPE == type)) { + throw new IllegalArgumentException("Boolean cannot be null."); + } + + // expected double? + if (Double.TYPE == type) { + if (readSpecialNumberValues()) { + LOGGER.warn("Null double value treated as NaN."); + return Double.NaN; + } else { + throw new IllegalArgumentException("Null double value not allowed. You can change this behavior by setting system property of " + + JsonHelper.OSLC4J_READ_SPECIAL_NUMS + " to true."); + } + } + + // expected float? + if (Float.TYPE == type) { + if (readSpecialNumberValues()) { + LOGGER.warn("Null float value treated as NaN."); + return Float.NaN; + } else { + throw new IllegalArgumentException("Null float value not allowed. You can change this behavior by setting system property of " + + JsonHelper.OSLC4J_READ_SPECIAL_NUMS + " to true."); + } + } + + // expected integral number? + if ((Short.TYPE == type) || (Integer.TYPE == type) || (Long.TYPE == type)) { + throw new IllegalArgumentException("Null values not allowed for type " + type); + } + + return null; + } + + private static boolean readSpecialNumberValues() { + return "true".equals(System.getProperty(JsonHelper.OSLC4J_READ_SPECIAL_NUMS, "true")); + } + + private void addMappings(JSONObject prefixes) { + for (Entry prefixEntry : prefixes.entrySet()) { + String prefix = prefixEntry.getKey(); + Object namespace = prefixEntry.getValue(); + + if (namespace instanceof JsonString) { + String namespaceString = ((JsonString) namespace).getString(); + getNamespaceMappings().addKnownMapping(prefix, namespaceString); + } + } + } + + private String getRdfKey(String localPart) throws OslcCoreMissingNamespaceDeclarationException { + return getExistingKey(OslcConstants.RDF_NAMESPACE, localPart, OslcConstants.RDF_NAMESPACE_PREFIX); + } + + private String getRdfsKey(String localPart) throws OslcCoreMissingNamespaceDeclarationException { + return getExistingKey(OslcConstants.RDFS_NAMESPACE, localPart, OslcConstants.RDFS_NAMESPACE_PREFIX); + } + + private String getDctermsKey(String localPart) throws OslcCoreMissingNamespaceDeclarationException { + return getExistingKey(OslcConstants.DCTERMS_NAMESPACE, localPart, OslcConstants.DCTERMS_NAMESPACE_PREFIX); + } + + private String getOslcKey(String localPart) throws OslcCoreMissingNamespaceDeclarationException { + return getExistingKey(OslcConstants.OSLC_CORE_NAMESPACE, localPart, OslcConstants.OSLC_CORE_NAMESPACE_PREFIX); + } + + private String getExistingKey(String namespace, String localPart, String prefix) throws OslcCoreMissingNamespaceDeclarationException { + if (!getNamespaceMappings().containsPrefix(prefix)) { + throw new OslcCoreMissingNamespaceDeclarationException(namespace); + } + return generateKey(namespace, localPart, prefix); + } + + private QName getExistingQName(String prefix, String localPart) throws OslcCoreMissingNamespacePrefixException { + String namespace = getNamespaceMappings().getMappings().get(prefix); + if (namespace == null) { + throw new OslcCoreMissingNamespacePrefixException(prefix); + } + + return new QName(namespace, localPart, prefix); + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java index 6687233cf..a98054dee 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java @@ -11,8 +11,8 @@ import javax.xml.datatype.DatatypeConfigurationException; -import org.apache.wink.json4j.JSONException; -import org.apache.wink.json4j.JSONObject; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; +import jakarta.json.JsonException; import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; import org.eclipse.lyo.oslc4j.provider.json4j.test.resources.TestResource; import org.glassfish.jersey.client.ClientConfig; @@ -96,7 +96,7 @@ public void testSetEndpoint() { @ParameterizedTest @ArgumentsSource(ProviderCollectionTestArguments.class) public void testQueryList(String path, boolean isExpectedQuery) - throws JSONException, DatatypeConfigurationException, URISyntaxException, OslcCoreApplicationException, + throws JsonException, DatatypeConfigurationException, URISyntaxException, OslcCoreApplicationException, InvocationTargetException, IllegalAccessException, InstantiationException { Response response = target(path).request(MediaType.APPLICATION_JSON).get(); response.bufferEntity(); @@ -106,7 +106,7 @@ public void testQueryList(String path, boolean isExpectedQuery) List rdfResponse = response.readEntity(new GenericType<>() { }); String rdfResponseString = response.readEntity(String.class); - JSONObject responseJSON = new JSONObject(rdfResponseString); + JSONObject responseJSON = new JSONObject(jakarta.json.Json.createReader(new java.io.StringReader(rdfResponseString)).readObject()); Object[] objects = JsonHelper.fromJSON(responseJSON, TestResource.class); diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java new file mode 100644 index 000000000..74ad70fc4 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java @@ -0,0 +1,39 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.json.JsonValue; +import org.junit.jupiter.api.Test; + +public class JSONArrayTest { + + @Test + public void testAddAndGet() { + JSONArray array = new JSONArray(); + array.add("one"); + array.add(2); + + assertEquals(2, array.size()); + assertFalse(array.isEmpty()); + + // JsonString.toString() includes quotes, so "one" becomes ""one"" + // JsonNumber.toString() is "2" + assertTrue(array.get(0).toString().contains("one")); + assertEquals("2", array.get(1).toString()); + } + + @Test + public void testIterator() { + JSONArray array = new JSONArray(); + array.add("A"); + array.add("B"); + + int count = 0; + for (JsonValue v : array) { + count++; + } + assertEquals(2, count); + } +} diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java new file mode 100644 index 000000000..2a2d10462 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java @@ -0,0 +1,67 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.json.JsonException; +import org.junit.jupiter.api.Test; + +public class JSONObjectTest { + + @Test + public void testPutAndGet() { + JSONObject json = new JSONObject(); + json.put("key", "value"); + assertEquals("value", json.get("key")); + assertEquals("value", json.getString("key")); + assertEquals("value", json.optString("key")); + } + + @Test + public void testOptStringDefaults() { + JSONObject json = new JSONObject(); + assertEquals(null, json.optString("missing")); + } + + @Test + public void testGetMissing() { + JSONObject json = new JSONObject(); + assertThrows(JsonException.class, () -> json.get("missing")); + } + + @Test + public void testNestedObject() { + JSONObject parent = new JSONObject(); + JSONObject child = new JSONObject(); + child.put("childKey", "childValue"); + parent.put("parentKey", child); + + JSONObject retrievedChild = parent.getJSONObject("parentKey"); + assertNotNull(retrievedChild); + assertEquals("childValue", retrievedChild.getString("childKey")); + } + + @Test + public void testJsonArray() { + JSONObject json = new JSONObject(); + JSONArray array = new JSONArray(); + array.add("item1"); + array.add("item2"); + json.put("arrayKey", array); + + JSONArray retrievedArray = json.getJSONArray("arrayKey"); + assertNotNull(retrievedArray); + assertEquals(2, retrievedArray.size()); + assertEquals("item1", retrievedArray.get(0).toString().replace(""", "")); // JsonString.toString() includes quotes + } + + @Test + public void testBuildAndToString() { + JSONObject json = new JSONObject(); + json.put("k", "v"); + String s = json.toString(); + assertTrue(s.contains(""k":"v"") || s.contains(""k": "v"")); + } +} diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java new file mode 100644 index 000000000..6418f1943 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java @@ -0,0 +1,46 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import jakarta.json.Json; +import jakarta.json.JsonReader; +import org.eclipse.lyo.oslc4j.core.model.Service; +import org.eclipse.lyo.oslc4j.core.model.ServiceProvider; +import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; +import org.junit.jupiter.api.Test; + +public class ServiceProviderTest { + + @Test + public void testUsage() throws Exception { + InputStream is = ServiceProviderTest.class.getResourceAsStream("/provider.json"); + assertNotNull(is, "Could not read file: provider.json"); + + JsonReader reader = Json.createReader(is); + JSONObject jsonObject = new JSONObject(reader.readObject()); + + Object[] objects = JsonHelper.fromJSON(jsonObject, ServiceProvider.class); + // Cast each element or create new array + ServiceProvider[] providers = new ServiceProvider[objects.length]; + System.arraycopy(objects, 0, providers, 0, objects.length); + + assertEquals(1, providers.length, "Incorrect number of service providers"); + + Service[] services = providers[0].getServices(); + assertEquals(1, services.length, "Incorrect number of services"); + + URI[] usages = services[0].getUsages(); + assertEquals(2, usages.length, "Incorrect number of usages"); + + List usageList = Arrays.asList(usages); + assertTrue(usageList.contains(new URI("http://example.com/ns#usage1")), "Missing ex:usage1"); + assertTrue(usageList.contains(new URI("http://example.com/ns#usage2")), "Missing ex:usage2"); + } +} diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java index 8f8b4c93f..ed54846f5 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java @@ -27,10 +27,9 @@ import javax.xml.namespace.QName; -import org.apache.wink.json4j.JSONArray; -import org.apache.wink.json4j.JSONException; -import org.apache.wink.json4j.JSONObject; -import org.apache.wink.json4j.OrderedJSONObject; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONArray; +import jakarta.json.JsonException; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; import org.eclipse.lyo.oslc4j.core.model.AbstractResource; import org.eclipse.lyo.oslc4j.provider.json4j.OslcRdfJsonProvider; import org.eclipse.lyo.oslc4j.provider.json4j.test.resources.EmptyNameResource; @@ -71,12 +70,12 @@ public void testJenaOslcNameEmptyString() throws Exception { } private JSONObject getJSONObject(Object resource, final OslcRdfJsonProvider oslcRdfJsonProvider) - throws IOException, WebApplicationException, JSONException { + throws IOException, WebApplicationException, JsonException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); oslcRdfJsonProvider.writeTo(resource, resource.getClass(), resource.getClass(), new Annotation[0], MediaType.APPLICATION_JSON_TYPE, null, outputStream); ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); - JSONObject jsonObject = new JSONObject(inputStream); + JSONObject jsonObject = new JSONObject(jakarta.json.Json.createReader(inputStream).readObject()); return jsonObject; } @@ -131,11 +130,11 @@ public void testJenaDefaultOslcName() throws Exception { verifyRDFTypes(new String[] { typeToAdd, TestResource.TEST_NAMESPACE + "UnnamedResource" }, rdfTypes); } - private void verifyRDFTypes(String[] expectedRDFTypes, JSONArray rdfTypes) throws JSONException { + private void verifyRDFTypes(String[] expectedRDFTypes, JSONArray rdfTypes) throws JsonException { List actualRdfTypesList = new ArrayList<>(); for (Object node : rdfTypes) { - OrderedJSONObject obj = (OrderedJSONObject) node; - String type = obj.values().iterator().next().toString(); + JSONObject obj = (JSONObject) node; + String type = obj.build().values().iterator().next().toString(); actualRdfTypesList.add(type); } for (String expectedRdfType : expectedRDFTypes) { diff --git a/core/oslc4j-json4j-provider/src/test/resources/provider.json b/core/oslc4j-json4j-provider/src/test/resources/provider.json new file mode 100644 index 000000000..8780e9c41 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/test/resources/provider.json @@ -0,0 +1,66 @@ +{ + "prefixes": { + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "dcterms": "http://purl.org/dc/terms/", + "oslc": "http://open-services.net/ns/core#" + }, + "rdf:about": "http://example.com/bugs/service-descriptor.xml", + "rdf:type": [ + { + "rdf:resource": "http://open-services.net/ns/core#ServiceProvider" + } + ], + "dcterms:title": "Blogging Service", + "dcterms:description": "Example OSLC Blog Service", + "dcterms:contributor": [ + { + "rdf:type": [ + { + "rdf:resource": "http://open-services.net/ns/core#Contributor" + } + ], + "dcterms:title": "OSLC Core Workgroup documentation department", + "dcterms:identifier": "com.example.oslc.blogservice", + "oslc:icon": { + "rdf:resource": "http://example.com/icons/blogservice.ico" + } + } + ], + "oslc:service": [ + { + "rdf:type": [ + { "rdf:resource": "http://open-services.net/ns/core#Service" } + ], + "oslc:domain": "http://example.com/xmlns/example-cm#", + "oslc:usage": [ + { "rdf:resource": "http://example.com/ns#usage1" }, + { "rdf:resource": "http://example.com/ns#usage2" } + ], + "oslc:creationFactory": [ + { + "rdf:type": [{ "rdf:resource": "http://open-services.net/ns/core#CreationFactory" }], + "dcterms:title": "Location for creation of Blog Comments", + "oslc:label": "Blog Comments", + "oslc:creation": { "rdf:resource": "http://example.com/creation/comments" }, + "oslc:shape": { "rdf:resource": "http://example.com/shapes/blogcomment" } + }, + { + "rdf:type": [{ "rdf:resource": "http://open-services.net/ns/core#CreationFactory" }], + "dcterms:title": "Location for creation of Blog Entries", + "oslc:label": "Blog Entries", + "oslc:creation": { "rdf:resource": "http://example.com/creation/entries" }, + "oslc:shape": { "rdf:resource": "http://example.com/shapes/blogentry" } + } + ], + "oslc:queryCapability": [ + { + "rdf:type": [{ "rdf:resource": "http://open-services.net/ns/core#QueryCapability" }], + "dcterms:title": "Blog Entry and Comment Query", + "oslc:label": "blogquery", + "oslc:queryBase": { "rdf:resource": "http://example.com/query" }, + "oslc:shape": { "rdf:resource": "http://example.com/shapes/blogquery" } + } + ] + } + ] +} \ No newline at end of file diff --git a/core/oslc4j-utils/pom.xml b/core/oslc4j-utils/pom.xml index ef2ba97dd..d2bf4953e 100644 --- a/core/oslc4j-utils/pom.xml +++ b/core/oslc4j-utils/pom.xml @@ -24,11 +24,6 @@ org.eclipse.lyo.oslc4j.core oslc4j-jena-provider - - org.eclipse.lyo.oslc4j.core - oslc4j-core-wink - ${v.lyo} - jakarta.ws.rs diff --git a/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java b/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java index 24cbe574d..921d29322 100644 --- a/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java +++ b/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java @@ -24,7 +24,7 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.RDFWriterI; import org.apache.jena.util.FileUtils; -import org.apache.wink.json4j.JSONObject; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; import org.eclipse.lyo.oslc4j.provider.jena.JenaModelHelper; import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; @@ -73,7 +73,7 @@ public void marshal(Object[] resources, OutputStream os) throws WebApplicationEx } else if(mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)){ JSONObject jo = JsonHelper.createJSON(null, null, null, resources, null); - jo.write(os); + jakarta.json.Json.createWriter(os).write(jo.build()); } else{ throw new RuntimeException("Unknown Media Type: " + mediaType); diff --git a/pom.xml b/pom.xml index 359d3f7de..b7709dacc 100644 --- a/pom.xml +++ b/pom.xml @@ -419,13 +419,7 @@ ${v.jersey} - - com.fasterxml.jackson - jackson-bom - ${v.jackson} - pom - import - + org.apache.commons diff --git a/server/oauth-webapp/pom.xml b/server/oauth-webapp/pom.xml index ec24d976d..a41def27f 100644 --- a/server/oauth-webapp/pom.xml +++ b/server/oauth-webapp/pom.xml @@ -51,8 +51,9 @@ provided - org.apache.wink - wink-json4j + org.eclipse.lyo.oslc4j.core + oslc4j-json4j-provider + ${v.lyo} provided diff --git a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java index 8e2095a3c..65675443e 100644 --- a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java +++ b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java @@ -19,9 +19,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.wink.json4j.JSONArray; -import org.apache.wink.json4j.JSONException; -import org.apache.wink.json4j.JSONObject; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONArray; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; import org.eclipse.lyo.server.oauth.core.OAuthConfiguration; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStore; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStoreException; diff --git a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java index e2027aeeb..d84e148ed 100644 --- a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java +++ b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java @@ -24,9 +24,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.wink.json4j.JSON; -import org.apache.wink.json4j.JSONException; -import org.apache.wink.json4j.JSONObject; +import jakarta.json.Json; +import jakarta.json.JsonException; + +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; import org.eclipse.lyo.server.oauth.core.Application; import org.eclipse.lyo.server.oauth.core.AuthenticationException; import org.eclipse.lyo.server.oauth.core.OAuthConfiguration; @@ -278,7 +279,7 @@ public Response doPostAccessToken() throws IOException, ServletException { public Response provisionalKey() throws NullPointerException, IOException { try { // Create the consumer from the request. - JSONObject request = (JSONObject) JSON.parse(httpRequest.getInputStream()); + JSONObject request = new JSONObject(Json.createReader(httpRequest.getInputStream()).readObject()); String name = null; if (request.has("name") && request.get("name") != null) { @@ -309,9 +310,9 @@ public Response provisionalKey() throws NullPointerException, IOException { JSONObject response = new JSONObject(); response.put("key", key); - return Response.ok(response.write()) + return Response.ok(response.toString()) .header(OAuthServerConstants.HDR_CACHE_CONTROL, OAuthServerConstants.NO_CACHE).build(); - } catch (JSONException e) { + } catch (JsonException e) { log.info("Encountered an exception while processing JSON: {}", e.getMessage()); return Response.status(Status.BAD_REQUEST).build(); } catch (ConsumerStoreException e) { diff --git a/server/oslc-ui-model/pom.xml b/server/oslc-ui-model/pom.xml index 9b681322b..51403da75 100644 --- a/server/oslc-ui-model/pom.xml +++ b/server/oslc-ui-model/pom.xml @@ -21,17 +21,20 @@ org.eclipse.lyo.oslc4j.core oslc4j-core - - com.fasterxml.jackson.core - jackson-core - - com.fasterxml.jackson.core - jackson-annotations + jakarta.json.bind + jakarta.json.bind-api + 3.0.0 + + + org.eclipse.parsson + parsson + 1.1.7 - com.fasterxml.jackson.core - jackson-databind + org.eclipse + yasson + 3.0.3 diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Link.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Link.java index f8f05b5a4..59fb5a36c 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Link.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Link.java @@ -13,14 +13,12 @@ */ package org.eclipse.lyo.server.ui.model; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.json.bind.annotation.JsonbPropertyOrder; import java.util.Objects; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ +@JsonbPropertyOrder({ "link", "title" }) @@ -31,14 +29,14 @@ public class Link { * (Required) * */ - @JsonProperty("link") + @JsonbProperty("link") private String link; /** * * (Required) * */ - @JsonProperty("title") + @JsonbProperty("title") private String title; /** @@ -46,7 +44,7 @@ public class Link { * (Required) * */ - @JsonProperty("link") + @JsonbProperty("link") public String getLink() { return link; } @@ -56,7 +54,7 @@ public String getLink() { * (Required) * */ - @JsonProperty("link") + @JsonbProperty("link") public void setLink(String link) { this.link = link; } @@ -66,7 +64,7 @@ public void setLink(String link) { * (Required) * */ - @JsonProperty("title") + @JsonbProperty("title") public String getTitle() { return title; } @@ -76,7 +74,7 @@ public String getTitle() { * (Required) * */ - @JsonProperty("title") + @JsonbProperty("title") public void setTitle(String title) { this.title = title; } diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Preview.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Preview.java index c81f009ad..060c5df47 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Preview.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Preview.java @@ -17,12 +17,10 @@ import java.util.List; import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.json.bind.annotation.JsonbPropertyOrder; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ +@JsonbPropertyOrder({ "properties" }) public class Preview { @@ -32,7 +30,7 @@ public class Preview { * (Required) * */ - @JsonProperty("properties") + @JsonbProperty("properties") private List properties = new ArrayList<>(); /** @@ -40,7 +38,7 @@ public class Preview { * (Required) * */ - @JsonProperty("properties") + @JsonbProperty("properties") public List getProperties() { return properties; } @@ -50,7 +48,7 @@ public List getProperties() { * (Required) * */ - @JsonProperty("properties") + @JsonbProperty("properties") public void setProperties(List properties) { this.properties = properties; } diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PreviewFactory.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PreviewFactory.java index 477e200b7..4438ba242 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PreviewFactory.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PreviewFactory.java @@ -13,8 +13,8 @@ */ package org.eclipse.lyo.server.ui.model; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; import org.eclipse.lyo.core.util.StringUtils; import org.eclipse.lyo.oslc4j.core.annotation.OslcName; import org.eclipse.lyo.oslc4j.core.annotation.OslcOccurs; @@ -98,12 +98,13 @@ public static Preview getPreview(final AbstractResource aResource, List public static String getPreviewAsJsonString(final AbstractResource aResource, List getterMethodNames, boolean showPropertyHeadingsAsLinks) throws IllegalAccessException, - IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, - JsonProcessingException { + IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Preview preview = PreviewFactory.getPreview(aResource, getterMethodNames, showPropertyHeadingsAsLinks); - ObjectMapper mapper = new ObjectMapper(); - String previewAsString = mapper.writeValueAsString(preview); - return previewAsString; + try (Jsonb jsonb = JsonbBuilder.create()) { + return jsonb.toJson(preview); + } catch (Exception e) { + throw new RuntimeException(e); + } } diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Property.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Property.java index 3d14f2b02..28344de2f 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Property.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/Property.java @@ -13,14 +13,12 @@ */ package org.eclipse.lyo.server.ui.model; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.json.bind.annotation.JsonbPropertyOrder; import java.util.Objects; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ +@JsonbPropertyOrder({ "propertyDefintion", "propertyValue" }) @@ -31,14 +29,14 @@ public class Property { * (Required) * */ - @JsonProperty("propertyDefintion") + @JsonbProperty("propertyDefintion") private PropertyDefintion propertyDefintion; /** * * (Required) * */ - @JsonProperty("propertyValue") + @JsonbProperty("propertyValue") private PropertyValue propertyValue; /** @@ -46,7 +44,7 @@ public class Property { * (Required) * */ - @JsonProperty("propertyDefintion") + @JsonbProperty("propertyDefintion") public PropertyDefintion getPropertyDefintion() { return propertyDefintion; } @@ -56,7 +54,7 @@ public PropertyDefintion getPropertyDefintion() { * (Required) * */ - @JsonProperty("propertyDefintion") + @JsonbProperty("propertyDefintion") public void setPropertyDefintion(PropertyDefintion propertyDefintion) { this.propertyDefintion = propertyDefintion; } @@ -66,7 +64,7 @@ public void setPropertyDefintion(PropertyDefintion propertyDefintion) { * (Required) * */ - @JsonProperty("propertyValue") + @JsonbProperty("propertyValue") public PropertyValue getPropertyValue() { return propertyValue; } @@ -76,7 +74,7 @@ public PropertyValue getPropertyValue() { * (Required) * */ - @JsonProperty("propertyValue") + @JsonbProperty("propertyValue") public void setPropertyValue(PropertyValue propertyValue) { this.propertyValue = propertyValue; } diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyDefintion.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyDefintion.java index 998b02df7..5d3289d9e 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyDefintion.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyDefintion.java @@ -17,14 +17,12 @@ import java.util.Map; import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonValue; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ +import jakarta.json.bind.adapter.JsonbAdapter; +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.json.bind.annotation.JsonbPropertyOrder; +import jakarta.json.bind.annotation.JsonbTypeAdapter; + +@JsonbPropertyOrder({ "data", "representationType" }) @@ -35,14 +33,14 @@ public class PropertyDefintion { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") private Object data; /** * * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") private PropertyDefintion.RepresentationType representationType; /** @@ -50,7 +48,7 @@ public class PropertyDefintion { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") public Object getData() { return data; } @@ -60,7 +58,7 @@ public Object getData() { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") public void setData(Object data) { this.data = data; } @@ -70,7 +68,7 @@ public void setData(Object data) { * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") public PropertyDefintion.RepresentationType getRepresentationType() { return representationType; } @@ -80,7 +78,7 @@ public PropertyDefintion.RepresentationType getRepresentationType() { * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") public void setRepresentationType(PropertyDefintion.RepresentationType representationType) { this.representationType = representationType; } @@ -125,6 +123,7 @@ public boolean equals(Object other) { return ((Objects.equals(this.data, rhs.data))&&(Objects.equals(this.representationType, rhs.representationType))); } + @JsonbTypeAdapter(RepresentationTypeAdapter.class) public enum RepresentationType { TEXT("Text"), @@ -147,12 +146,10 @@ public String toString() { return this.value; } - @JsonValue public String value() { return this.value; } - @JsonCreator public static PropertyDefintion.RepresentationType fromValue(String value) { PropertyDefintion.RepresentationType constant = CONSTANTS.get(value); if (constant == null) { @@ -164,4 +161,16 @@ public static PropertyDefintion.RepresentationType fromValue(String value) { } + public static class RepresentationTypeAdapter implements JsonbAdapter { + @Override + public String adaptToJson(RepresentationType obj) throws Exception { + return obj.value(); + } + + @Override + public RepresentationType adaptFromJson(String obj) throws Exception { + return RepresentationType.fromValue(obj); + } + } + } diff --git a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyValue.java b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyValue.java index 22a487371..a5673f889 100644 --- a/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyValue.java +++ b/server/oslc-ui-model/src/main/java/org/eclipse/lyo/server/ui/model/PropertyValue.java @@ -13,14 +13,12 @@ */ package org.eclipse.lyo.server.ui.model; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import jakarta.json.bind.annotation.JsonbProperty; +import jakarta.json.bind.annotation.JsonbPropertyOrder; import java.util.Objects; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ +@JsonbPropertyOrder({ "data", "representAsList", "representationType" @@ -32,16 +30,16 @@ public class PropertyValue { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") private Object data; - @JsonProperty("representAsList") + @JsonbProperty("representAsList") private Boolean representAsList; /** * * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") private PropertyDefintion.RepresentationType representationType; /** @@ -49,7 +47,7 @@ public class PropertyValue { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") public Object getData() { return data; } @@ -59,17 +57,17 @@ public Object getData() { * (Required) * */ - @JsonProperty("data") + @JsonbProperty("data") public void setData(Object data) { this.data = data; } - @JsonProperty("representAsList") + @JsonbProperty("representAsList") public Boolean getRepresentAsList() { return representAsList; } - @JsonProperty("representAsList") + @JsonbProperty("representAsList") public void setRepresentAsList(Boolean representAsList) { this.representAsList = representAsList; } @@ -79,7 +77,7 @@ public void setRepresentAsList(Boolean representAsList) { * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") public PropertyDefintion.RepresentationType getRepresentationType() { return representationType; } @@ -89,7 +87,7 @@ public PropertyDefintion.RepresentationType getRepresentationType() { * (Required) * */ - @JsonProperty("representationType") + @JsonbProperty("representationType") public void setRepresentationType(PropertyDefintion.RepresentationType representationType) { this.representationType = representationType; } diff --git a/server/pom.xml b/server/pom.xml index d97408451..3cb70f682 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -47,16 +47,7 @@ oauth-httpclient4 20090913 - - org.apache.wink - wink-server - 1.4 - - - org.apache.wink - wink-json4j - 1.4 - + stax From 7dbb4f9252125ef40932d06b1d5ff3dcadafd5b7 Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 14 Feb 2026 15:42:18 +0100 Subject: [PATCH 2/2] fix: strong typing and fixes --- .../json4j/AbstractOslcRdfJsonProvider.java | 22 +- .../json4j/OslcCompactJsonProvider.java | 16 +- .../json4j/OslcRdfJsonArrayProvider.java | 16 +- .../json4j/OslcRdfJsonCollectionProvider.java | 18 +- .../provider/json4j/OslcRdfJsonProvider.java | 18 +- .../OslcSimpleRdfJsonArrayProvider.java | 12 +- .../OslcSimpleRdfJsonCollectionProvider.java | 12 +- .../json4j/internal/AbstractBuilder.java | 2 +- .../internal/CollectionSetterInvoker.java | 79 ++++++ .../provider/json4j/internal/JSONArray.java | 30 +- .../json4j/internal/JSONException.java | 30 ++ .../json4j/internal/JSONModelBuilder.java | 16 +- .../provider/json4j/internal/JSONObject.java | 43 ++- .../json4j/internal/JavaResourceShape.java | 146 ++++++++++ .../json4j/internal/LyoProviderUtils.java | 79 ++++++ .../internal/MethodAnnotationCache.java | 57 ++++ .../json4j/internal/NamespaceMappings.java | 258 ++++++++++++++++++ .../json4j/internal/PropertyAccessor.java | 96 +++++++ .../json4j/internal/RdfCollections.java | 129 +++++++++ .../json4j/internal/ResourceBuilder.java | 102 +++---- .../oslc4j/provider/json4j/internal/Uris.java | 49 ++++ .../OslcJsonProviderCollectionTest.java | 2 +- .../json4j/internal/JSONArrayTest.java | 2 +- .../json4j/internal/JSONObjectTest.java | 4 +- .../json4j/internal/ServiceProviderTest.java | 2 +- .../json4j/test/JsonOslcNameTest.java | 2 +- .../utils/marshallers/OSLC4JMarshaller.java | 6 +- .../webapp/services/ConsumersService.java | 1 + 28 files changed, 1116 insertions(+), 133 deletions(-) create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/CollectionSetterInvoker.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONException.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JavaResourceShape.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/LyoProviderUtils.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/MethodAnnotationCache.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/NamespaceMappings.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/PropertyAccessor.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/RdfCollections.java create mode 100644 core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/Uris.java diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java index a9de79d77..189eb9d4f 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/AbstractOslcRdfJsonProvider.java @@ -22,16 +22,16 @@ import java.util.Locale; import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.ext.Providers; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.ResponseBuilder; +import jakarta.ws.rs.ext.Providers; import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; @@ -42,7 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sodius.oslc.core.provider.internal.LyoProviderUtils; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.LyoProviderUtils; import jakarta.json.Json; import jakarta.json.JsonObject; diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java index e9620acdf..9811f7ef5 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcCompactJsonProvider.java @@ -19,14 +19,14 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.Compact; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java index 8e6865793..dbca094cc 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonArrayProvider.java @@ -19,14 +19,14 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java index 39fc90cb7..d3ec064ca 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonCollectionProvider.java @@ -23,18 +23,18 @@ import java.util.Arrays; import java.util.Collection; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; -import com.sodius.oslc.core.provider.internal.RdfCollections; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.RdfCollections; @Provider @Produces(OslcMediaType.APPLICATION_JSON) diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java index 3c43c821e..3d5de15ca 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcRdfJsonProvider.java @@ -24,14 +24,14 @@ import java.util.Collection; import java.util.Map; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.FilteredResource; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; @@ -39,7 +39,7 @@ import org.eclipse.lyo.oslc4j.core.model.ResponseInfoArray; import org.eclipse.lyo.oslc4j.core.model.ResponseInfoCollection; -import com.sodius.oslc.core.provider.internal.LyoProviderUtils; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.LyoProviderUtils; @Provider @Produces(OslcMediaType.APPLICATION_JSON) diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java index c5fb2a60b..3164b6af7 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonArrayProvider.java @@ -18,12 +18,12 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java index be0a89ecb..26425927e 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcSimpleRdfJsonCollectionProvider.java @@ -19,12 +19,12 @@ import java.lang.reflect.Type; import java.util.Collection; -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.Provider; import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java index b73a57323..b36196220 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/AbstractBuilder.java @@ -15,7 +15,7 @@ import javax.xml.namespace.QName; -import com.sodius.oslc.core.provider.internal.NamespaceMappings; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.NamespaceMappings; /* * Common constants and methods for reading and writing Json content diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/CollectionSetterInvoker.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/CollectionSetterInvoker.java new file mode 100644 index 000000000..d9894bacd --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/CollectionSetterInvoker.java @@ -0,0 +1,79 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/* + * Collects values that are to assign using a set method that takes either an array or a collection as parameter. + * + * This is needed for Jena, as values for arrays are not required to be contiguous. + * + * This is needed for Json, as a single value may be present in the Json object + * whereas an array or collection is expected by the Java class used for data binding. + */ +public class CollectionSetterInvoker { + + private final Object bean; + private final Map> accessorValues; + + /* + * Creates an instance for the bean we are trying to populate. + */ + public CollectionSetterInvoker(Object bean) { + this.bean = bean; + this.accessorValues = new HashMap<>(); + } + + /* + * Collects a value to use with the set method corresponding to the given property definition. + */ + public void add(PropertyAccessor accessor, Object value) { + List values = accessorValues.computeIfAbsent(accessor, v -> new ArrayList<>()); + values.add(value); + } + + /* + * Invokes all set methods for which values have been collected + */ + public void invokeAll() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { + + for (Map.Entry> accessorValuesEntry : accessorValues.entrySet()) { + PropertyAccessor accessor = accessorValuesEntry.getKey(); + List values = accessorValuesEntry.getValue(); + Method setMethod = accessor.getSetter(); + Class parameterClass = setMethod.getParameterTypes()[0]; + + // array? + if (parameterClass.isArray()) { + Class setMethodComponentParameterClass = parameterClass.getComponentType(); + + // To support primitive arrays, we have to use Array reflection to + // set individual elements. We cannot use Collection.toArray. + // Array.set will unwrap objects to their corresponding primitives. + Object array = Array.newInstance(setMethodComponentParameterClass, values.size()); + + int index = 0; + for (Object value : values) { + Array.set(array, index++, value); + } + + setMethod.invoke(bean, new Object[] { array }); // NOSONAR java:S3878 + } + + // Else - we are dealing with a collection or a subclass of collection + else { + Collection collection = RdfCollections.createCollection(parameterClass); + collection.addAll(values); + setMethod.invoke(bean, collection); + } + } + + this.accessorValues.clear(); + } +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java index 4f9b2fd67..61c339b65 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArray.java @@ -29,7 +29,7 @@ * This class aims to wrap Jakarta JsonArray to be usable in JsonHelper as before. * Unlike Wink's JSONArray, Jakarta ones are immutable, which makes us work with JsonArrayBuilder before building the object. */ -public class JSONArray implements Iterable { +public class JSONArray implements Iterable { private JsonArrayBuilder builder; @@ -66,8 +66,8 @@ public int size() { return build().size(); } - public JsonValue get(int index) { - return build().get(index); + public Object get(int index) { + return JSONObject.toValue(build().get(index)); } public JSONObject getJSONObject(int index) { @@ -111,8 +111,28 @@ public void add(Object object) { } @Override - public Iterator iterator() { - return build().iterator(); + public Iterator iterator() { + return new Iterator() { + private final Iterator it = build().iterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public Object next() { + return JSONObject.toValue(it.next()); + } + }; + } + + public String write() { + return toString(); + } + + public void write(java.io.Writer writer) { + Json.createWriter(writer).write(build()); } } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONException.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONException.java new file mode 100644 index 000000000..82a9f3d31 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +public class JSONException extends Exception { + private static final long serialVersionUID = 1L; + + public JSONException(String message) { + super(message); + } + + public JSONException(Throwable cause) { + super(cause); + } + + public JSONException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java index 17bde17bc..3336ecc10 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONModelBuilder.java @@ -48,11 +48,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sodius.oslc.core.provider.internal.JavaResourceShape; -import com.sodius.oslc.core.provider.internal.LyoProviderUtils; -import com.sodius.oslc.core.provider.internal.NamespaceMappings; -import com.sodius.oslc.core.provider.internal.PropertyAccessor; -import com.sodius.oslc.core.provider.internal.RdfCollections; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JavaResourceShape; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.LyoProviderUtils; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.NamespaceMappings; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.PropertyAccessor; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.RdfCollections; import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; import jakarta.json.JsonArray; @@ -320,7 +320,7 @@ private void buildExtendedProperties(JSONObject jsonObject, IExtendedResource ex String rdfTypeKey = getRdfKey(PROPERTY_TYPE); JSONArray typesJSONArray; if (jsonObject.containsKey(rdfTypeKey)) { - typesJSONArray = new JSONArray((JsonArray) jsonObject.get(rdfTypeKey)); + typesJSONArray = (JSONArray) jsonObject.get(rdfTypeKey); } else { typesJSONArray = new JSONArray(); } @@ -387,8 +387,8 @@ private void buildExtendedTypes(IExtendedResource extendedResource, Map knownTypes = new HashSet<>(); - for (JsonValue jsonValue : typesJSONArray) { - String typeName = ((JsonObject) jsonValue).getString(getRdfKey(PROPERTY_RESOURCE)); + for (Object jsonValue : typesJSONArray) { + String typeName = ((JSONObject) jsonValue).getString(getRdfKey(PROPERTY_RESOURCE)); knownTypes.add(typeName); } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java index e19b70851..2e035c36f 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObject.java @@ -63,18 +63,40 @@ public int size() { } public Object opt(String key) { - return build().get(key); + return build().containsKey(key) ? toValue(build().get(key)) : null; } public Object get(String key) { JsonObject object = build(); if (object.containsKey(key)) { - return object.get(key); + return toValue(object.get(key)); } else { throw new JsonException("The key [" + key + "] was not in the map."); } } + public static Object toValue(JsonValue value) { + if (value == null) return null; + switch (value.getValueType()) { + case STRING: + return ((jakarta.json.JsonString) value).getString(); + case NUMBER: + return ((jakarta.json.JsonNumber) value).numberValue(); + case TRUE: + return Boolean.TRUE; + case FALSE: + return Boolean.FALSE; + case NULL: + return null; + case OBJECT: + return new JSONObject((JsonObject) value); + case ARRAY: + return new JSONArray((jakarta.json.JsonArray) value); + default: + return value; + } + } + public String optString(String key) { JsonObject object = build(); return object.getString(key, null); @@ -99,6 +121,15 @@ public JSONObject optJSONObject(String key) { return object.containsKey(key) ? new JSONObject(object.getJsonObject(key)) : null; } + public JSONObject getJSONObject(String key) { + JsonObject object = build(); + if (object.containsKey(key)) { + return new JSONObject(object.getJsonObject(key)); + } else { + throw new JsonException("The value for key: [" + key + "] was null. Object required."); + } + } + public JSONArray getJSONArray(String key) { JsonObject object = build(); if (object.containsKey(key)) { @@ -161,4 +192,12 @@ public boolean isEmpty() { return build().isEmpty(); } + public String write() { + return toString(); + } + + public void write(java.io.Writer writer) { + Json.createWriter(writer).write(build()); + } + } diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JavaResourceShape.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JavaResourceShape.java new file mode 100644 index 000000000..b4edb3bb2 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JavaResourceShape.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcName; +import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreInvalidPropertyDefinitionException; +import org.eclipse.lyo.oslc4j.core.exception.OslcCoreMissingSetMethodException; + +/* + * Provides facilities related to a Java class representing an OSLC Resource Shape. + * Gives access to property accessors, a property accessor being the pairing of getter/setter methods for one OSLC property. + * Enables getting and setting values, either through a property accessor or extended properties. + */ +public class JavaResourceShape { + private static final String METHOD_NAME_START_GET = "get"; //$NON-NLS-1$ + private static final String METHOD_NAME_START_IS = "is"; //$NON-NLS-1$ + private static final String METHOD_NAME_START_SET = "set"; //$NON-NLS-1$ + + private static final Map, JavaResourceShape> SHAPES = new HashMap<>(); + + public static synchronized JavaResourceShape valueOf(Class resourceClass) throws OslcCoreApplicationException { + JavaResourceShape shape = SHAPES.get(resourceClass); + if (shape == null) { + shape = JavaResourceShapeFactory.create(resourceClass); + SHAPES.put(resourceClass, shape); + } + return shape; + } + + private final Map accessors; + + private JavaResourceShape() { + this.accessors = new HashMap<>(); + } + + public Collection getAccessors() { + return Collections.unmodifiableCollection(this.accessors.values()); + } + + public Optional getAccessor(String propertyDefinition) { + return Optional.ofNullable(this.accessors.get(propertyDefinition)); + } + + private static class JavaResourceShapeFactory { + + private static JavaResourceShape create(Class resourceClass) throws OslcCoreApplicationException { + JavaResourceShape shape = new JavaResourceShape(); + + // look for methods without any parameter + for (final Method method : resourceClass.getMethods()) { + if (method.getParameterTypes().length == 0) { + final String getMethodName = method.getName(); + + // getSomething()? + if (getMethodName.startsWith(METHOD_NAME_START_GET) && (getMethodName.length() > METHOD_NAME_START_GET.length())) { + parse(resourceClass, shape, method, METHOD_NAME_START_GET); + } + + // isSomething()? + else if (getMethodName.startsWith(METHOD_NAME_START_IS) && (getMethodName.length() > METHOD_NAME_START_IS.length())) { + parse(resourceClass, shape, method, METHOD_NAME_START_IS); + } + } + } + + return shape; + } + + private static void parse(Class resourceClass, JavaResourceShape shape, Method getter, String prefix) throws OslcCoreApplicationException { + + // retain the getter only if there's an OSLC annotation + OslcPropertyDefinition oslcPropertyDefinitionAnnotation = MethodAnnotationCache.OSLC_PROPERTY_DEFINITION.getAnnotation(getter); + if (oslcPropertyDefinitionAnnotation != null) { + + // We need to find the set companion setMethod + String setMethodName = METHOD_NAME_START_SET + getter.getName().substring(prefix.length()); + Class getMethodReturnType = getter.getReturnType(); + try { + final Method setter = resourceClass.getMethod(setMethodName, getMethodReturnType); + addAccessor(resourceClass, shape, oslcPropertyDefinitionAnnotation, getter, prefix, setter); + } catch (final NoSuchMethodException exception) { + throw new OslcCoreMissingSetMethodException(resourceClass, getter, exception); + } + } + } + + private static void addAccessor(Class resourceClass, JavaResourceShape shape, OslcPropertyDefinition propertyDefinitionAnnotation, + Method getter, String prefix, Method setter) throws OslcCoreApplicationException { + + String propertyDefinition = propertyDefinitionAnnotation.value(); + + String propertyName = getPropertyName(getter, prefix); + if (!propertyDefinition.endsWith(propertyName)) { + throw new OslcCoreInvalidPropertyDefinitionException(resourceClass, getter, propertyDefinitionAnnotation); + } + + PropertyAccessor accessor = new PropertyAccessor(propertyDefinition, propertyName, getter, setter); + shape.accessors.put(propertyDefinition, accessor); + } + + private static String getPropertyName(Method getter, String prefix) { + final OslcName nameAnnotation = MethodAnnotationCache.OSLC_NAME.getAnnotation(getter); + if (nameAnnotation != null) { + return nameAnnotation.value(); + } else { + return getDefaultPropertyName(getter, prefix); + } + } + + private static String getDefaultPropertyName(Method method, String prefix) { + String methodName = method.getName(); + int startingIndex = prefix.length(); + int endingIndex = startingIndex + 1; + + // We want the name to start with a lower-case letter + final String lowercasedFirstCharacter = methodName.substring(startingIndex, endingIndex).toLowerCase(Locale.ENGLISH); + + if (methodName.length() == endingIndex) { + return lowercasedFirstCharacter; + } + + return lowercasedFirstCharacter + methodName.substring(endingIndex); + } + } +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/LyoProviderUtils.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/LyoProviderUtils.java new file mode 100644 index 000000000..a090f0ab9 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/LyoProviderUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.UriBuilder; + +import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; + +/* + * Extracted from org.eclipse.lyo.oslc4j.core.OSLC4JUtils, + * to remove dependency on org.apache.jena.ext.com.google.common.base.Strings, + * which is no longer provided by Jena 4.9 + */ +public class LyoProviderUtils { + + /** + * Returns the boolean value of the system property {@code org.eclipse.lyo.oslc4j.disableRelativeURIs}. + * Default is {@code true} if not set (relative URIs will not be allowed) + * + * @return true if relative URIs are disabled, or if the property is not set. + */ + public static boolean relativeURIsAreDisabled() { + return Boolean.parseBoolean(System.getProperty(OSLC4JConstants.OSLC4J_DISABLE_RELATIVE_URIS, Boolean.TRUE.toString())); + } + + /** + * Resolve a URI, usually a resource subject or info URI. + * Query parameters from the request are not copied to the resolved URI. + * + * @param request + * request to base resolved URI on + * @return String containing the resolved URI + */ + public static String resolveURI(HttpServletRequest request) { + return UriBuilder.fromPath(request.getContextPath() + request.getServletPath() + request.getPathInfo()) // + .scheme(request.getScheme()) // + .host(request.getServerName()) // + .port(request.getServerPort()) // + .build().normalize().toString(); + } + + /** + * Returns the boolean value of the system property {@code org.eclipse.lyo.oslc4j.useBeanClassForParsing}. + * Default is {@code false} if not set. + * Setting to {@code true} should be used for matching the resource {@code rdf:type} to + * the {@code describes} parameter of the {@code OslcResourceShape} annotation. + * + * @return true if Java bean class must be used as is for binding, false otherwise. + */ + /* + * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=412755 + */ + public static boolean useBeanClassForParsing() { + return Boolean.getBoolean(OSLC4JConstants.OSLC4J_USE_BEAN_CLASS_FOR_PARSING); + } + + /** + * Return if the query result list type will be http://www.w3.org/2000/01/rdf-schema#Container + * or there will be no type. Default is no type. + */ + public static boolean isQueryResultListAsContainer() { + return Boolean.getBoolean(OSLC4JConstants.OSLC4J_QUERY_RESULT_LIST_AS_CONTAINER); + } + + private LyoProviderUtils() { + } +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/MethodAnnotationCache.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/MethodAnnotationCache.java new file mode 100644 index 000000000..4a1ffb6a1 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/MethodAnnotationCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcName; +import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; +import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; +import org.eclipse.lyo.oslc4j.core.annotation.OslcValueType; +import org.eclipse.lyo.oslc4j.core.model.InheritedMethodAnnotationHelper; + +/* + * Use a cache to avoid searching annotations every time a method is accessed when serializing a resource. + * https://sodius.atlassian.net/browse/SECOLLAB-596 + */ +class MethodAnnotationCache { + + static final MethodAnnotationCache OSLC_NAME = new MethodAnnotationCache<>(OslcName.class); + static final MethodAnnotationCache OSLC_VALUE_TYPE = new MethodAnnotationCache<>(OslcValueType.class); + static final MethodAnnotationCache OSLC_RDF_COLLECTION_TYPE = new MethodAnnotationCache<>(OslcRdfCollectionType.class); + static final MethodAnnotationCache OSLC_PROPERTY_DEFINITION = new MethodAnnotationCache<>(OslcPropertyDefinition.class); + + private final Class type; + private final Map annotations; + + private MethodAnnotationCache(Class type) { + this.type = type; + this.annotations = new HashMap<>(); + } + + synchronized A getAnnotation(Method method) { + A annotation = annotations.get(method); // NOSONAR + if (annotation == null) { + // let's not try again getting the annotation if it wasn't found in the first place + if (!annotations.containsKey(method)) { // NOSONAR + annotation = InheritedMethodAnnotationHelper.getAnnotation(method, type); + } + annotations.put(method, annotation); + } + return annotation; + } +} \ No newline at end of file diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/NamespaceMappings.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/NamespaceMappings.java new file mode 100644 index 000000000..8a65c3c1d --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/NamespaceMappings.java @@ -0,0 +1,258 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.BiConsumer; + +import javax.xml.namespace.QName; + +import org.eclipse.lyo.oslc4j.core.OslcGlobalNamespaceProvider; +import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespaceDefinition; +import org.eclipse.lyo.oslc4j.core.annotation.OslcSchema; +import org.eclipse.lyo.oslc4j.core.model.IOslcCustomNamespaceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Enables collecting and generating namespace prefix/uri mappings. + */ +public class NamespaceMappings { + + private static final Logger LOGGER = LoggerFactory.getLogger(NamespaceMappings.class); + + private static final String GENERATED_PREFIX_START = "j."; + + /* + * Creates an instance with no pre-configured mapping + */ + public static NamespaceMappings empty() { + return new NamespaceMappings(); + } + + /* + * Creates an instance with some pre-configured mappings + * that notifies the given consumer when a prefix/namespace mapping is added + */ + public static NamespaceMappings of(Map prefixToNamespace, BiConsumer updateListener) { + NamespaceMappings mappings = new NamespaceMappings(); + prefixToNamespace.forEach(mappings::addMapping); + mappings.updateListener = updateListener; + return mappings; + } + + /* + * Creates an instance with mappings contributed by OslcGlobalNamespaceProvider + */ + public static NamespaceMappings global() { + NamespaceMappings mappings = new NamespaceMappings(); + OslcGlobalNamespaceProvider.getInstance().getPrefixDefinitionMap().forEach(mappings::addMapping); + return mappings; + } + + private final Map prefixToNamespace; + private final Map namespaceToPrefix; + private BiConsumer updateListener; + + private NamespaceMappings() { + this.prefixToNamespace = new TreeMap<>(); + this.namespaceToPrefix = new HashMap<>(); + } + + public boolean containsPrefix(String prefix) { + return this.prefixToNamespace.containsKey(prefix); + } + + public Map getMappings() { + return Collections.unmodifiableMap(this.prefixToNamespace); + } + + private void addMapping(String prefix, String namespace) { + namespaceToPrefix.put(namespace, prefix); + addUnidirectionalMapping(prefix, namespace); + } + + private void addUnidirectionalMapping(String prefix, String namespace) { + prefixToNamespace.put(prefix, namespace); + if (updateListener != null) { + updateListener.accept(prefix, namespace); + } + } + + /* + * Adds a mapping for the given prefix/namespace. + * Generates a new prefix based on the given one if it conflicts with an existing mapping. + */ + public void addKnownMapping(String prefix, String namespace) { + generateQName(namespace, "unused-local-part", prefix); + } + + /* + * Add a mapping for the given prefix/namespace. + * Generates a new prefix based on the given one if it conflicts with an existing mapping. + */ + public void addKnownMapping(QName qName) { + generateQName(qName.getNamespaceURI(), qName.getLocalPart(), qName.getPrefix()); + } + + /* + * Adds mappings based on OslcSchema annotations of the given type and all its super types + */ + public void addMappings(Class type) { + + // lookup the OSLC schema of the package defining the class + OslcSchema oslcSchemaAnnotation = type.getPackage().getAnnotation(OslcSchema.class); + if (oslcSchemaAnnotation != null) { + + // register each mapping of the schema + for (OslcNamespaceDefinition oslcNamespaceDefinitionAnnotation : oslcSchemaAnnotation.value()) { + String prefix = oslcNamespaceDefinitionAnnotation.prefix(); + String namespace = oslcNamespaceDefinitionAnnotation.namespaceURI(); + addMapping(prefix, namespace); + } + + // Adding custom prefixes obtained from an implementation, if there is an implementation. + Class customNamespaceProvider = oslcSchemaAnnotation.customNamespaceProvider(); + if (!customNamespaceProvider.isInterface()) { + addCustomMappings(customNamespaceProvider); + } + } + + // recurse on super class + Class superClass = type.getSuperclass(); + if (superClass != null) { + addMappings(superClass); + } + + // recurse on implemented interfaces + Class[] interfaces = type.getInterfaces(); + if (interfaces != null) { + Arrays.stream(interfaces).forEach(this::addMappings); + } + } + + private void addCustomMappings(Class customNamespaceProvider) { + try { + IOslcCustomNamespaceProvider customNamespaceProviderImpl = customNamespaceProvider.getDeclaredConstructor().newInstance(); + Map customNamespacePrefixes = customNamespaceProviderImpl.getCustomNamespacePrefixes(); + if (null != customNamespacePrefixes) { + for (Map.Entry namespaceEntry : customNamespacePrefixes.entrySet()) { + addMapping(namespaceEntry.getKey(), namespaceEntry.getValue()); + } + } + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) { + throw new RuntimeException("Failed to instantiate the custom namespace provider implementation: " // NOSONAR + + customNamespaceProvider.getClass().getName(), e); + } + } + + /* + * Returns a QName that includes the prefix associated to the given namespace or a newly generated one if none + */ + public QName generateQName(String namespace, String localPart) { + Objects.requireNonNull(namespace); + Objects.requireNonNull(localPart); + return generateQNameForNamespace(namespace, localPart); + } + + /* + * Returns a QName for the given parameters which includes a prefix, which is either the one yet associated to the namespace, + * the given suggested prefix if not yet associated to another namespace or a newly generated one if none + */ + public QName generateQName(String namespace, String localPart, String prefix) { + Objects.requireNonNull(namespace); + Objects.requireNonNull(localPart); + // prefix might be null + + // use the suggested prefix, if any + if ((prefix != null) && !prefix.isEmpty()) { + return generateQNameForPrefix(namespace, localPart, prefix); + } + + // only use the namespace information + else { + return generateQNameForNamespace(namespace, localPart); + } + } + + /* + * Prefix is known to be non-empty here + */ + private QName generateQNameForPrefix(String namespace, String localPart, String prefix) { + + // lookup associated namespace, if any + String existingNamespace = prefixToNamespace.get(prefix); + + // prefix already bound to a namespace? + if (existingNamespace != null) { + + // the expected namespace? + if (namespace.equals(existingNamespace)) { + return new QName(namespace, localPart, prefix); + } else { + // let the developer know he uses an inappropriate prefix + LOGGER.warn("Ignoring prefix {} which is bound to {} instead of {}", prefix, existingNamespace, namespace); + + // and keep going with namespace information only + return generateQNameForNamespace(namespace, localPart); + } + } + + else { + // lookup existing prefix associated to this namespace + String existingPrefix = namespaceToPrefix.get(namespace); + + // no prefix yet associated? + if (existingPrefix == null) { + + // register this suggested prefix + addMapping(prefix, namespace); + return generateQNameForNamespace(namespace, localPart); + } + + // associated to a different prefix + else { + // It's uncommon but not invalid to have two prefixes for the same namespace. + // Register this prefix->namespace association but don't change the opposite association + addUnidirectionalMapping(prefix, namespace); + + return new QName(namespace, localPart, prefix); + } + } + } + + private QName generateQNameForNamespace(String namespace, String localPart) { + + // empty namespace? + if ((namespace == null) || namespace.isEmpty()) { + LOGGER.warn("Ignoring empty namespace associated to local part: {}", localPart); + return new QName(namespace, localPart); + } + + // use the existing prefix associated to the namespace, if any + String existingPrefix = namespaceToPrefix.get(namespace); + if (existingPrefix != null) { + return new QName(namespace, localPart, existingPrefix); + } + + else { + // generate a new prefix for the namespace + String prefix = generatePrefix(); + addMapping(prefix, namespace); + return new QName(namespace, localPart, prefix); + } + } + + private String generatePrefix() { + int i = 0; + String candidatePrefix; + do { + candidatePrefix = GENERATED_PREFIX_START + i; + ++i; + } while (containsPrefix(candidatePrefix)); + return candidatePrefix; + } +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/PropertyAccessor.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/PropertyAccessor.java new file mode 100644 index 000000000..055ebd9a9 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/PropertyAccessor.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.xml.namespace.QName; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; +import org.eclipse.lyo.oslc4j.core.annotation.OslcValueType; +import org.eclipse.lyo.oslc4j.core.model.IResource; + +/* + * A pairing of getter/setter methods for one OSLC property on a Java class representing an OSLC Resource Shape. + */ +public class PropertyAccessor { + + private final String propertyDefinition; + private final String propertyName; + private final Method getter; + private final Method setter; + private final Optional valueType; + private final Optional collectionType; + + PropertyAccessor(String propertyDefinition, String propertyName, Method getter, Method setter) { + this.propertyDefinition = propertyDefinition; + this.propertyName = propertyName; + this.getter = getter; + this.setter = setter; + this.valueType = Optional.ofNullable(MethodAnnotationCache.OSLC_VALUE_TYPE.getAnnotation(getter)); + this.collectionType = Optional.ofNullable(MethodAnnotationCache.OSLC_RDF_COLLECTION_TYPE.getAnnotation(getter)); + } + + public String getPropertyDefinition() { + return propertyDefinition; + } + + public QName getQName() { + String namespace = propertyDefinition.substring(0, propertyDefinition.length() - propertyName.length()); + return new QName(namespace, propertyName); + } + + public Optional getValueType() { + return valueType; + } + + public Optional getCollectionType() { + return collectionType; + } + + public Method getGetter() { + return getter; + } + + Object getValue(IResource resource) { + try { + return getter.invoke(resource); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // NOSONAR + } + } + + void setValue(IResource resource, Object value) { + try { + setter.invoke(resource, value); + } catch (IllegalArgumentException e) { + if ((value != null) && (setter.getParameterTypes().length == 1)) { + String message = "Method '" + setter.getName() + "' called with " + value.getClass().getName() + " while expected " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + setter.getParameterTypes()[0].getName(); + throw new IllegalArgumentException(message, e); + } else { + throw e; + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); // NOSONAR + } + } + + public Method getSetter() { + return setter; + } + +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/RdfCollections.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/RdfCollections.java new file mode 100644 index 000000000..fc376daba --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/RdfCollections.java @@ -0,0 +1,129 @@ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.AbstractSequentialList; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.NavigableSet; +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcRdfCollectionType; +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; + +/* + * Various utilities for providers to handle RDF collections. + */ +public class RdfCollections { + + // Following constants are not defined in all versions of OslcRdfCollectionType + public static final String RDF_LIST = "List"; + public static final String RDF_ALT = "Alt"; + public static final String RDF_BAG = "Bag"; + public static final String RDF_SEQ = "Seq"; + + private static final Set RDF_COLLECTION_TYPES = new HashSet<>(Arrays.asList(RDF_LIST, RDF_ALT, RDF_BAG, RDF_SEQ)); + + /* + * The type of List that represents a RDF sequence. + * When extended properties are used, there is no OSLC annotation that helps at determining whether a given List is ordered (i.e. a sequence) + * or unordered. We cannot assume a collection is ordered simply because it implements List. + * Using a dedicated implementation class helps at being deterministic both for reading and writing content. + * + * Not setting a dedicated type for representing sequences implies: + * - the writer will write all List instances as unordered collections + * - the reader will read RDF sequences as ArrayList instances + */ + private static Class> sequenceType; + + public static boolean isRdfCollection(OslcRdfCollectionType collectionType) { + return OslcConstants.RDF_NAMESPACE.equals(collectionType.namespaceURI()) && RDF_COLLECTION_TYPES.contains(collectionType.collectionType()); + } + + /* + * Sets the type of List used to represent RDF sequences in extended properties. + * This type must define a public 0-arg constructor. + */ + public static synchronized void setSequenceType(Class> sequenceType) { + RdfCollections.sequenceType = sequenceType; + } + + /* + * Determines whether a specific implementation type was provided to support sequences. + */ + public static boolean isSequenceSupported() { + return sequenceType != null; + } + + /* + * Determines whether a specific implementation type was provided to support sequences + * and the given collection matches that type. + */ + public static synchronized boolean isSequence(Collection collection) { + return isSequenceSupported() && sequenceType.equals(collection.getClass()); + } + + /* + * Creates a sequence List. + * If a dedicated type was registered to represent sequences, an instance of such type is created. + * Otherwise an UnsupportedOperationException is thrown. Developer is to check isSequenceSupported() first. + */ + @SuppressWarnings("unchecked") + public static List createSequence() { + if (isSequenceSupported()) { + try { + return (List) sequenceType.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) { + throw new RuntimeException("Failed to create a sequence List", e); // NOSONAR + } + } else { + // this is a developer error, the isSequenceSupported() method should have been called first + throw new UnsupportedOperationException("No support for RDF:Seq was provided"); + } + } + + /* + * Creates a new Collection instance with an implementation type that matches the contract of the given interface type. + */ + public static Collection createCollection(Class type) + throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + + // Handle the Collection, List, Deque, Queue interfaces. + // Handle the AbstractCollection, AbstractList, AbstractSequentialList classes + if ((Collection.class == type) || (List.class == type) || (Deque.class == type) || (Queue.class == type) || (AbstractCollection.class == type) + || (AbstractList.class == type) || (AbstractSequentialList.class == type)) { + return new LinkedList<>(); + } + + // Handle the Set interface + // Handle the AbstractSet class + else if ((Set.class == type) || (AbstractSet.class == type)) { + return new HashSet<>(); + } + + // Handle the SortedSet and NavigableSet interfaces + else if ((SortedSet.class == type) || (NavigableSet.class == type)) { + return new TreeSet<>(); + } + + // Not handled above. Let's try newInstance with possible failure. + else { + @SuppressWarnings("unchecked") + Collection tempCollection = (Collection) type.getDeclaredConstructor().newInstance(); + return tempCollection; + } + } + + private RdfCollections() { + } + +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java index 31102acec..d1d196ba3 100644 --- a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ResourceBuilder.java @@ -55,13 +55,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sodius.oslc.core.provider.internal.CollectionSetterInvoker; -import com.sodius.oslc.core.provider.internal.JavaResourceShape; -import com.sodius.oslc.core.provider.internal.LyoProviderUtils; -import com.sodius.oslc.core.provider.internal.NamespaceMappings; -import com.sodius.oslc.core.provider.internal.PropertyAccessor; -import com.sodius.oslc.core.provider.internal.RdfCollections; -import com.sodius.oslc.core.provider.internal.ResourceShapes; +import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.CollectionSetterInvoker; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JavaResourceShape; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.LyoProviderUtils; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.NamespaceMappings; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.PropertyAccessor; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.RdfCollections; import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; import jakarta.json.JsonArray; @@ -115,17 +115,17 @@ private Object[] buildResources(JsonObject jakartaObject, Class beanClass) JSONObject jsonObject = new JSONObject(jakartaObject); // First read the prefixes and set up maps so we can create full property definition values later - Object prefixes = jsonObject.opt(PREFIXES); + JsonValue prefixes = jakartaObject.get(PREFIXES); if (prefixes instanceof JsonObject) { JSONObject prefixesJSONObject = new JSONObject((JsonObject) prefixes); addMappings(prefixesJSONObject); } - JSONArray jsonArray = null; + JsonArray jsonArray = null; // Look for rdfs:member if (getNamespaceMappings().containsPrefix(OslcConstants.RDFS_NAMESPACE_PREFIX)) { - Object members = jsonObject.opt(getRdfsKey(PROPERTY_MEMBER)); + JsonValue members = jakartaObject.get(getRdfsKey(PROPERTY_MEMBER)); if (members instanceof JsonArray) { // If the Java class defines an accessor for rdfs:member property, @@ -134,16 +134,16 @@ private Object[] buildResources(JsonObject jakartaObject, Class beanClass) Optional membersAccessor = JavaResourceShape.valueOf(beanClass) .getAccessor(OslcConstants.RDFS_NAMESPACE + PROPERTY_MEMBER); if (membersAccessor.isEmpty()) { - jsonArray = new JSONArray((JsonArray) members); + jsonArray = (JsonArray) members; } } } // Look for oslc:results. Seen in ChangeManagement. if ((jsonArray == null) && getNamespaceMappings().containsPrefix(OslcConstants.OSLC_CORE_NAMESPACE_PREFIX)) { - Object results = jsonObject.opt(getOslcKey(PROPERTY_RESULTS)); + JsonValue results = jakartaObject.get(getOslcKey(PROPERTY_RESULTS)); if (results instanceof JsonArray) { - jsonArray = new JSONArray((JsonArray) results); + jsonArray = (JsonArray) results; } } @@ -178,7 +178,6 @@ private Object[] buildResources(JsonObject jakartaObject, Class beanClass) * populated if the property inferTypeFromShape is set to true. * * @param jsonObject - * @param rdfPrefix * @param types * @return List of rdf:types * @throws OslcCoreMissingNamespaceDeclarationException @@ -189,7 +188,7 @@ private Set getRdfTypesFromJsonObject(JSONObject jsonObject, Set // This is necessary because for an inline object, the retuned // rdf:type is not from the parent object, it is from the actual // resource. - if (ResourceShapes.inferTypeFromShape() && types.isEmpty()) { + if (OSLC4JUtils.inferTypeFromShape() && types.isEmpty()) { String typeProperty = getRdfKey(PROPERTY_TYPE); if (jsonObject.has(typeProperty)) { JSONArray array = jsonObject.getJSONArray(typeProperty); @@ -213,8 +212,8 @@ private void buildResource(JSONObject jsonObject, Class beanClass, Object bea if (bean instanceof IResource) { Object aboutURIObject = jsonObject.opt(getRdfKey(PROPERTY_ABOUT)); - if (aboutURIObject instanceof JsonString) { - URI aboutURI = new URI(((JsonString) aboutURIObject).getString()); + if (aboutURIObject instanceof String) { + URI aboutURI = new URI((String) aboutURIObject); if (LyoProviderUtils.relativeURIsAreDisabled() && !aboutURI.isAbsolute()) { throw new OslcCoreRelativeURIException(beanClass, "setAbout", aboutURI); } @@ -372,8 +371,7 @@ else if (jsonValue instanceof JsonString) { // number? else if (jsonValue instanceof JsonNumber) { - JsonNumber jsonNumber = (JsonNumber) jsonValue; - return buildExtendedNumber(jsonNumber, propertyQName, rdfTypes); + return buildExtendedNumber(((JsonNumber) jsonValue).numberValue(), propertyQName, rdfTypes); } // boolean? @@ -384,7 +382,7 @@ else if (JsonValue.TRUE.equals(jsonValue)) { } // null? - else if (JsonValue.NULL.equals(jsonValue)) { + else if ((jsonValue == null) || (JsonValue.NULL.equals(jsonValue))) { return null; } @@ -395,18 +393,18 @@ else if (JsonValue.NULL.equals(jsonValue)) { } @SuppressWarnings("java:S1130") // InvocationTargetException can be raised by early Lyo versions - private Object buildExtendedNumber(JsonNumber value, QName propertyQName, Set rdfTypes) + private Object buildExtendedNumber(Number value, QName propertyQName, Set rdfTypes) throws DatatypeConfigurationException, InstantiationException, InvocationTargetException { - if (value.isIntegral()) { + if (value instanceof Integer || value instanceof Long || value instanceof Short || value instanceof Byte || value instanceof BigInteger) { // fix for Bug 412789 // There is no need to infer data type from resource shapes as integer values do not have ambiguity cases - return value.intValue(); + return value; } else { // fix for Bug 412789 // try to infer data type from resource shapes for Double - if (ResourceShapes.inferTypeFromShape()) { - Object newObject = ResourceShapes.getValueBasedOnResourceShapeType(rdfTypes, propertyQName, value.doubleValue()); + if (OSLC4JUtils.inferTypeFromShape()) { + Object newObject = OSLC4JUtils.getValueBasedOnResourceShapeType(new HashSet<>(rdfTypes), propertyQName, value.doubleValue()); // return the value only if the type was really inferred from // the resource shape, otherwise keep the same behavior @@ -429,8 +427,8 @@ private Object buildExtendedString(String value, QName propertyQName, Set(rdfTypes), propertyQName, value); // return the value only if the type was really inferred from // the resource shape, otherwise keep the same behavior @@ -499,11 +497,11 @@ else if (RdfCollections.isSequenceSupported() && o.containsKey(getRdfKey(RdfColl private Object buildExtendedSequence(JSONArray sequenceMembers, Class beanClass, QName propertyQName, Set rdfTypes) throws URISyntaxException, DatatypeConfigurationException, IllegalAccessException, InstantiationException, InvocationTargetException, OslcCoreApplicationException, NoSuchMethodException { - List sequence = RdfCollections.createSequence(); - for (JsonValue jsonValue : sequenceMembers) { - Object value = buildExtendedValue(jsonValue, beanClass, propertyQName, rdfTypes); - sequence.add(value); + JsonArray jakartaArray = sequenceMembers.build(); + for (JsonValue v : jakartaArray) { + Object value = buildExtendedValue(v, beanClass, propertyQName, rdfTypes); + sequence.add(value); } return sequence; } @@ -513,7 +511,7 @@ private Object buildExtendedArray(JsonArray array, Class beanClass, QName pro OslcCoreApplicationException, NoSuchMethodException { List collection = new ArrayList<>(); - for (JsonValue element : new JSONArray(array)) { + for (JsonValue element : array) { collection.add(buildExtendedValue(element, beanClass, propertyQName, rdfTypes)); } return collection; @@ -532,7 +530,7 @@ private void fillInRdfType(JSONObject jsonObject, IExtendedResource resource) } } - private boolean isRdfListNode(PropertyAccessor accessor, Object jsonValue) throws OslcCoreMissingNamespaceDeclarationException { + private boolean isRdfListNode(PropertyAccessor accessor, JsonValue jsonValue) throws OslcCoreMissingNamespaceDeclarationException { if (!(jsonValue instanceof JsonObject)) { return false; } @@ -565,16 +563,19 @@ private Object buildAccessorValue(Class beanClass, PropertyAccessor accessor, // determine whether the object is a RDF collection container boolean isRdfContainerNode = isRdfListNode(accessor, jsonValue); - JSONArray container = null; + JsonArray container = null; if (!isRdfContainerNode && (jsonValue instanceof JsonObject)) { JSONObject parent = new JSONObject((JsonObject) jsonValue); - container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_ALT)); - if (container == null) { - container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_BAG)); + JSONArray c = parent.optJSONArray(getRdfKey(RdfCollections.RDF_ALT)); + if (c == null) { + c = parent.optJSONArray(getRdfKey(RdfCollections.RDF_BAG)); + } + if (c == null) { + c = parent.optJSONArray(getRdfKey(RdfCollections.RDF_SEQ)); } - if (container == null) { - container = parent.optJSONArray(getRdfKey(RdfCollections.RDF_SEQ)); + if (c != null) { + container = c.build(); } isRdfContainerNode = container != null; } @@ -587,8 +588,8 @@ private Object buildAccessorValue(Class beanClass, PropertyAccessor accessor, // If this is the special case for an rdf:resource? Object uriObject = nestedJSONObject.opt(getRdfKey(PROPERTY_RESOURCE)); - if (uriObject instanceof JsonString) { - URI uri = new URI(((JsonString) uriObject).getString()); + if (uriObject instanceof String) { + URI uri = new URI((String) uriObject); if (LyoProviderUtils.relativeURIsAreDisabled() && !uri.isAbsolute()) { throw new OslcCoreRelativeURIException(beanClass, accessor.getSetter().getName(), uri); @@ -605,23 +606,22 @@ private Object buildAccessorValue(Class beanClass, PropertyAccessor accessor, // json array? else if ((jsonValue instanceof JsonArray) || isRdfContainerNode) { - JSONArray jsonArray; + JsonArray jsonArray; if (isRdfContainerNode && (container == null)) { - jsonArray = new JSONArray(); + JSONArray wrapperArray = new JSONArray(); JSONObject listNode = new JSONObject((JsonObject) jsonValue); while ((listNode != null) && !RDF_NIL_URI.equals(listNode.opt(getRdfKey(PROPERTY_RESOURCE)))) { Object o = listNode.opt(getRdfKey(PROPERTY_FIRST)); - jsonArray.add(o); + wrapperArray.add(o); listNode = listNode.optJSONObject(getRdfKey(PROPERTY_REST)); } + jsonArray = wrapperArray.build(); } else if (isRdfContainerNode) { - JSONArray array = container; - jsonArray = array; + jsonArray = container; } else { - JSONArray array = new JSONArray((JsonArray) jsonValue); - jsonArray = array; + jsonArray = (JsonArray) jsonValue; } // build the array/collection members @@ -667,7 +667,7 @@ else if (!tempList.isEmpty()) { } // null? - else if ((jsonValue == null) || JsonValue.NULL.equals(jsonValue)) { + else if ((jsonValue == null) || (JsonValue.NULL.equals(jsonValue))) { return buildAccessorNullValue(setMethodComponentParameterClass); } @@ -783,7 +783,7 @@ private static boolean readSpecialNumberValues() { private void addMappings(JSONObject prefixes) { for (Entry prefixEntry : prefixes.entrySet()) { String prefix = prefixEntry.getKey(); - Object namespace = prefixEntry.getValue(); + JsonValue namespace = prefixEntry.getValue(); if (namespace instanceof JsonString) { String namespaceString = ((JsonString) namespace).getString(); @@ -823,4 +823,4 @@ private QName getExistingQName(String prefix, String localPart) throws OslcCoreM return new QName(namespace, localPart, prefix); } -} \ No newline at end of file +} diff --git a/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/Uris.java b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/Uris.java new file mode 100644 index 000000000..5e6dfb5d8 --- /dev/null +++ b/core/oslc4j-json4j-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/Uris.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.oslc4j.provider.json4j.internal; + +import java.net.URI; +import java.net.URISyntaxException; + +public final class Uris { + + /** + * Creates a URI from a String value form. + * Tries to 'normalize' the URI if the string contains spaces. + * + * @param value + * the String Url to normalize + * @return the normalized URI + * @throws URISyntaxException + * if an URI cannot be created from the String Url + */ + // typically used for RMM 7.0 or Polarion Compacts having spaces in query parameters + public static URI create(String value) throws URISyntaxException { + + try { + return new URI(value); + } catch (URISyntaxException e) { + // spaces are invalid in URIs + if (value.contains(" ")) { //$NON-NLS-1$ + return new URI(value.replace(' ', '+')); + } else { + throw e; + } + } + } + + private Uris() { + } + +} diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java index a98054dee..56f1664ef 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/OslcJsonProviderCollectionTest.java @@ -107,7 +107,7 @@ public void testQueryList(String path, boolean isExpectedQuery) }); String rdfResponseString = response.readEntity(String.class); JSONObject responseJSON = new JSONObject(jakarta.json.Json.createReader(new java.io.StringReader(rdfResponseString)).readObject()); - Object[] objects = JsonHelper.fromJSON(responseJSON, TestResource.class); + Object[] objects = JsonHelper.fromJSON(responseJSON.build(), TestResource.class); boolean isQueryResponse = responseJSON.get("oslc:responseInfo") != null; diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java index 74ad70fc4..922112d1e 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONArrayTest.java @@ -31,7 +31,7 @@ public void testIterator() { array.add("B"); int count = 0; - for (JsonValue v : array) { + for (Object v : array) { count++; } assertEquals(2, count); diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java index 2a2d10462..da50f98f7 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/JSONObjectTest.java @@ -54,7 +54,7 @@ public void testJsonArray() { JSONArray retrievedArray = json.getJSONArray("arrayKey"); assertNotNull(retrievedArray); assertEquals(2, retrievedArray.size()); - assertEquals("item1", retrievedArray.get(0).toString().replace(""", "")); // JsonString.toString() includes quotes + assertEquals("item1", retrievedArray.get(0)); } @Test @@ -62,6 +62,6 @@ public void testBuildAndToString() { JSONObject json = new JSONObject(); json.put("k", "v"); String s = json.toString(); - assertTrue(s.contains(""k":"v"") || s.contains(""k": "v"")); + assertTrue(s.contains("\"k\":\"v\"") || s.contains("\"k\": \"v\"")); } } diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java index 6418f1943..ce6b4db9d 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/internal/ServiceProviderTest.java @@ -26,7 +26,7 @@ public void testUsage() throws Exception { JsonReader reader = Json.createReader(is); JSONObject jsonObject = new JSONObject(reader.readObject()); - Object[] objects = JsonHelper.fromJSON(jsonObject, ServiceProvider.class); + Object[] objects = JsonHelper.fromJSON(jsonObject.build(), ServiceProvider.class); // Cast each element or create new array ServiceProvider[] providers = new ServiceProvider[objects.length]; System.arraycopy(objects, 0, providers, 0, objects.length); diff --git a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java index ed54846f5..beb55a9c6 100644 --- a/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java +++ b/core/oslc4j-json4j-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/json4j/test/JsonOslcNameTest.java @@ -134,7 +134,7 @@ private void verifyRDFTypes(String[] expectedRDFTypes, JSONArray rdfTypes) throw List actualRdfTypesList = new ArrayList<>(); for (Object node : rdfTypes) { JSONObject obj = (JSONObject) node; - String type = obj.build().values().iterator().next().toString(); + String type = obj.getString("rdf:resource"); actualRdfTypesList.add(type); } for (String expectedRdfType : expectedRDFTypes) { diff --git a/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java b/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java index 921d29322..9faf8c5dd 100644 --- a/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java +++ b/core/oslc4j-utils/src/main/java/org/eclipse/lyo/core/utils/marshallers/OSLC4JMarshaller.java @@ -24,10 +24,10 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.RDFWriterI; import org.apache.jena.util.FileUtils; -import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; import org.eclipse.lyo.oslc4j.provider.jena.JenaModelHelper; import org.eclipse.lyo.oslc4j.provider.json4j.JsonHelper; +import jakarta.json.JsonObject; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; @@ -72,8 +72,8 @@ public void marshal(Object[] resources, OutputStream os) throws WebApplicationEx } } else if(mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)){ - JSONObject jo = JsonHelper.createJSON(null, null, null, resources, null); - jakarta.json.Json.createWriter(os).write(jo.build()); + JsonObject jo = JsonHelper.createJSON(null, null, null, resources, null); + jakarta.json.Json.createWriter(os).write(jo); } else{ throw new RuntimeException("Unknown Media Type: " + mediaType); diff --git a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java index 65675443e..a856321f7 100644 --- a/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java +++ b/server/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/ConsumersService.java @@ -21,6 +21,7 @@ import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONArray; import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONObject; +import org.eclipse.lyo.oslc4j.provider.json4j.internal.JSONException; import org.eclipse.lyo.server.oauth.core.OAuthConfiguration; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStore; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStoreException;