diff --git a/.travis.yml b/.travis.yml index ce7ebe2..ee5b2e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: java jdk: - - oraclejdk8 - openjdk8 - openjdk11 diff --git a/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/core/model/OslcMediaType.java b/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/core/model/OslcMediaType.java index 2a1c8ab..825089a 100644 --- a/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/core/model/OslcMediaType.java +++ b/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/core/model/OslcMediaType.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012 IBM Corporation. + * Copyright (c) 2012, 2018 IBM Corporation and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -15,6 +15,7 @@ * Alberto Giammaria - initial API and implementation * Chris Peters - initial API and implementation * Gianluca Bernardini - initial API and implementation + * Andrew Berezovskyi - refactoring and new media types *******************************************************************************/ package org.eclipse.lyo.oslc4j.core.model; @@ -22,35 +23,53 @@ public interface OslcMediaType { - public final static String APPLICATION = "application"; - public final static String TEXT = "text"; + String APPLICATION_RDF_XML = "application/rdf+xml"; + MediaType APPLICATION_RDF_XML_TYPE = MediaType.valueOf(APPLICATION_RDF_XML); - public final static String RDF_XML = "rdf+xml"; - public final static String APPLICATION_RDF_XML = APPLICATION + "/" + RDF_XML; - public final static MediaType APPLICATION_RDF_XML_TYPE = new MediaType(APPLICATION, RDF_XML); + String APPLICATION_JSON_LD = "application/ld+json"; + MediaType APPLICATION_JSON_LD_TYPE = MediaType.valueOf(APPLICATION_JSON_LD); - public final static String JSON_LD = "ld+json"; - public final static String APPLICATION_JSON_LD = APPLICATION + "/" + JSON_LD; - public final static MediaType APPLICATION_JSON_LD_TYPE = new MediaType(APPLICATION, JSON_LD); + String TEXT_TURTLE = "text/turtle"; + MediaType TEXT_TURTLE_TYPE = MediaType.valueOf(TEXT_TURTLE); - public final static String APPLICATION_JSON = MediaType.APPLICATION_JSON; - public final static MediaType APPLICATION_JSON_TYPE = MediaType.APPLICATION_JSON_TYPE; + String RDF_JSON_MIME = "application/rdf+json"; + MediaType RDF_JSON_MT = MediaType.valueOf(RDF_JSON_MIME); - public final static String APPLICATION_XML = MediaType.APPLICATION_XML; - public final static MediaType APPLICATION_XML_TYPE = MediaType.APPLICATION_XML_TYPE; + String N_TRIPLES_MIME = "application/n-triples"; + MediaType N_TRIPLES_MT = MediaType.valueOf(N_TRIPLES_MIME); - public final static String TEXT_XML = MediaType.TEXT_XML; - public final static MediaType TEXT_XML_TYPE = MediaType.TEXT_XML_TYPE; + // OSLC specific serialisations - public final static String TURTLE="turtle"; - public final static String TEXT_TURTLE = TEXT + "/" + TURTLE; - public final static MediaType TEXT_TURTLE_TYPE = new MediaType(TEXT, TURTLE); + String APPLICATION_X_OSLC_COMPACT_XML = "application" + "/" + "x-oslc-compact+xml"; + MediaType APPLICATION_X_OSLC_COMPACT_XML_TYPE = new MediaType("application", + "x-oslc-compact+xml"); - public final static String X_OSLC_COMPACT_XML = "x-oslc-compact+xml"; - public final static String APPLICATION_X_OSLC_COMPACT_XML = APPLICATION + "/" + X_OSLC_COMPACT_XML; - public final static MediaType APPLICATION_X_OSLC_COMPACT_XML_TYPE = new MediaType(APPLICATION, X_OSLC_COMPACT_XML); + String APPLICATION_X_OSLC_COMPACT_JSON = "application/x-oslc-compact+json"; + MediaType APPLICATION_X_OSLC_COMPACT_JSON_TYPE = new MediaType("application", + "x-oslc-compact+json"); + + // Non-standard RDF serialisations + + String APPLICATION_JSON = MediaType.APPLICATION_JSON; + MediaType APPLICATION_JSON_TYPE = MediaType.APPLICATION_JSON_TYPE; + + String APPLICATION_XML = MediaType.APPLICATION_XML; + MediaType APPLICATION_XML_TYPE = MediaType.APPLICATION_XML_TYPE; + + String TEXT_XML = MediaType.TEXT_XML; + MediaType TEXT_XML_TYPE = MediaType.TEXT_XML_TYPE; + + String RDF_THRIFT_MIME = "application/rdf+thrift"; + MediaType RDF_THRIFT_MT = MediaType.valueOf(RDF_THRIFT_MIME); + + // Deprecated + + @Deprecated String APPLICATION = "application"; + @Deprecated String TEXT = "text"; + @Deprecated String RDF_XML = "rdf+xml"; + @Deprecated String JSON_LD = "ld+json"; + @Deprecated String TURTLE = "turtle"; + @Deprecated String X_OSLC_COMPACT_XML = "x-oslc-compact+xml"; + @Deprecated String X_OSLC_COMPACT_JSON = "x-oslc-compact+json"; - public final static String X_OSLC_COMPACT_JSON = "x-oslc-compact+json"; // TODO - Compact media type never defined in the OSLC spec for JSON - public final static String APPLICATION_X_OSLC_COMPACT_JSON = APPLICATION + "/" + X_OSLC_COMPACT_JSON; - public final static MediaType APPLICATION_X_OSLC_COMPACT_JSON_TYPE = new MediaType(APPLICATION, X_OSLC_COMPACT_JSON); } diff --git a/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java index 5d2134b..8aad071 100644 --- a/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java +++ b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java @@ -35,6 +35,8 @@ import org.apache.jena.util.FileUtils; import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; +import org.eclipse.lyo.oslc4j.core.annotation.OslcNotQueryResult; +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.OslcMediaType; @@ -48,7 +50,7 @@ /** * @author Russell Boykin, Alberto Giammaria, Chris Peters, Gianluca Bernardini, Andrew Berezovskyi */ -public abstract class AbstractOslcRdfXmlProvider +public abstract class AbstractOslcRdfXmlProvider extends AbstractRdfProvider { private static final Logger log = LoggerFactory.getLogger(AbstractOslcRdfXmlProvider.class.getName ()); @@ -85,6 +87,21 @@ protected AbstractOslcRdfXmlProvider() super(); } + protected static boolean isWriteable(final Class type, + final Annotation[] annotations, + final MediaType actualMediaType, + final MediaType ... requiredMediaTypes) + { + if (type.getAnnotation(OslcResourceShape.class) != null || + type.getAnnotation(OslcNotQueryResult.class) != null) + { + // We do not have annotations when running from the non-web client. + return isCompatible(actualMediaType, requiredMediaTypes); + } + + return false; + } + protected void writeTo(final Object[] objects, final MediaType baseMediaType, final MultivaluedMap map, @@ -280,6 +297,32 @@ private String getSerializationLanguage(final MediaType baseMediaType) { throw new IllegalArgumentException("Base media type can't be matched to any writer"); } + protected static boolean isReadable(final Class type, + final MediaType actualMediaType, + final MediaType ... requiredMediaTypes) + { + if (type.getAnnotation(OslcResourceShape.class) != null) + { + return isCompatible(actualMediaType, requiredMediaTypes); + } + + return false; + } + + protected static boolean isCompatible(final MediaType actualMediaType, + final MediaType... requiredMediaTypes) + { + for (final MediaType requiredMediaType : requiredMediaTypes) + { + if (requiredMediaType.isCompatible(actualMediaType)) + { + return true; + } + } + + return false; + } + protected Object[] readFrom(final Class type, final MediaType mediaType, final MultivaluedMap map, @@ -430,4 +473,17 @@ private boolean isAcceptableMediaType(final MediaType mediaType) 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; + } } diff --git a/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractRdfProvider.java b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractRdfProvider.java new file mode 100644 index 0000000..8f095de --- /dev/null +++ b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractRdfProvider.java @@ -0,0 +1,123 @@ +/*!***************************************************************************** + * Copyright (c) 2019 Andrew Berezovskyi. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Andrew Berezovskyi - initial implementation + *******************************************************************************/ +package org.eclipse.lyo.oslc4j.provider.jena; + +import java.io.InputStream; +import java.io.OutputStream; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.RDFWriter; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.util.FileUtils; +import org.eclipse.lyo.oslc4j.core.exception.MessageExtractor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TODO + * + * @since 4.0.0 + */ +public class AbstractRdfProvider { + + private final static Logger log = LoggerFactory.getLogger(AbstractRdfProvider.class); + + protected void writeTo(final boolean queryResult, final Object[] objects, + final MediaType baseMediaType, final MultivaluedMap map, + final OutputStream outputStream) throws WebApplicationException { + String descriptionURI = null; + String responseInfoURI = null; + + if (queryResult) { + throw new IllegalArgumentException( + "Query Result resources have to be constructed before marshalling"); + } + + writeNonQueryObjectsTo(objects, outputStream, baseMediaType); + } + + protected void writeNonQueryObjectsTo(final Object[] objects, final OutputStream outputStream, + final MediaType serializationLanguage) { + try { + final Model model = JenaModelHelper.createJenaModel(objects); + + RDFWriter writer = getRdfWriter(serializationLanguage.toString(), model); + + if (serializationLanguage.equals(FileUtils.langXML) || serializationLanguage.equals( + FileUtils.langXMLAbbrev)) { + // XML (and Jena) default to UTF-8, but many libs default to ASCII, so need + // to set this explicitly + writer.setProperty("showXmlDeclaration", "false"); + String xmlDeclaration = "\n"; + outputStream.write(xmlDeclaration.getBytes()); + } + writer.write(model, outputStream, null); + } catch (final Exception exception) { + log.warn(MessageExtractor.getMessage("ErrorSerializingResource"), exception); + // TODO Andrew@2018-03-03: use another exception + throw new WebApplicationException(exception); + } + } + + private RDFWriter getRdfWriter(final String serializationLanguage, final Model model) { + RDFWriter writer = null; + if (serializationLanguage.equals(FileUtils.langXMLAbbrev)) { + // TODO Andrew@2018-05-27: do this for the RDF/XML-ABBREV only iff the users want it + /* Personally, I don't like the idea of maintaining a separate ABBREV writer. I think + we need to start introducing some flags where users can disable legacy functionality + altogether and rely on Jena etc.*/ + writer = new RdfXmlAbbreviatedWriter(); + } else { + final Lang lang = RDFLanguages.nameToLang(serializationLanguage); +// RDFDataMgr.createGraphWriter(mapLang(lang)); +// final Graph graph = model.getGraph(); + writer = model.getWriter(lang.getName()); + // FIXME Andrew@2018-05-27: what's the purpose of name>lang>name conversion? + } + return writer; + } + + private RDFFormat mapLang(final Lang lang) { + // TODO Andrew@2018-05-27: allow configuration from users (compact vs pretty etc) + final RDFFormat rdfFormat = new RDFFormat(lang); + return rdfFormat; + } + + /*=== READER ================================*/ + + protected Object[] readFrom(final Class type, final MediaType mediaType, + final InputStream inputStream) throws WebApplicationException { + final Model model = ModelFactory.createDefaultModel(); + RDFDataMgr.read(model, inputStream, + RDFDataMgr.determineLang(null, mediaType.toString(), null)); + try { + return JenaModelHelper.unmarshal(model, type); + } catch (final Exception exception) { + // FIXME Andrew@2018-05-27: we shall just stop the JMH blanket of exceptions craziness + throw new RuntimeException(exception); +// throw new WebApplicationException(exception, buildBadRequestResponse(exception, +// mediaType, +// map)); + } + } + +} diff --git a/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/UniversalResourceSingleProvider.java b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/UniversalResourceSingleProvider.java new file mode 100644 index 0000000..6492c73 --- /dev/null +++ b/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/UniversalResourceSingleProvider.java @@ -0,0 +1,92 @@ +/*!***************************************************************************** + * Copyright (c) 2019 Andrew Berezovskyi. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Andrew Berezovskyi - initial implementation + *******************************************************************************/ +package org.eclipse.lyo.oslc4j.provider.jena; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.IResource; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; + +/** + * A new JAX-RS provider to (un)marshall IResource's from/to most common RDF formats. + *

+ * JMH accepts objects in general but there is a slight conflict in the JAX pipeline with the + * Collections and Arrays, which are technically also Objects. If someone needs raw Objects, they + * can use old Providers. + * + * @since 4.0.0 + */ +@Provider +@Produces({OslcMediaType.APPLICATION_JSON_LD, OslcMediaType.TEXT_TURTLE, + OslcMediaType.N_TRIPLES_MIME, OslcMediaType.RDF_JSON_MIME, OslcMediaType.RDF_THRIFT_MIME}) +@Consumes({OslcMediaType.APPLICATION_JSON_LD, OslcMediaType.TEXT_TURTLE, + OslcMediaType.N_TRIPLES_MIME, OslcMediaType.RDF_JSON_MIME, OslcMediaType.RDF_THRIFT_MIME}) +public class UniversalResourceSingleProvider extends AbstractRdfProvider + implements MessageBodyReader, MessageBodyWriter { + + private final int CANNOT_BE_DETERMINED_IN_ADVANCE = -1; + + @Override + public boolean isReadable(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return true; + } + + @Override + public IResource readFrom(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap httpHeaders, final InputStream entityStream) + throws IOException, WebApplicationException { + final Object[] objects = readFrom(type, mediaType, entityStream); + if (objects.length > 1) { + throw new IllegalArgumentException("Can't unmarshal a single resource from a model " + + "with multiple matching resources"); + } + final IResource resource = (IResource) objects[0]; + return resource; + } + + @Override + public boolean isWriteable(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return true; + } + + @Override + public long getSize(final IResource iResource, final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return CANNOT_BE_DETERMINED_IN_ADVANCE; + } + + @Override + public void writeTo(final IResource iResource, final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap httpHeaders, final OutputStream entityStream) + throws IOException, WebApplicationException { + writeNonQueryObjectsTo(new Object[]{iResource}, entityStream, mediaType); + } +} diff --git a/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/JsonLdTest.java b/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/JsonLdTest.java index 765e630..8dc08fd 100644 --- a/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/JsonLdTest.java +++ b/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/JsonLdTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Ricardo Herrera and others. + * Copyright (c) 2018, 2019 Ricardo Herrera and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -13,10 +13,10 @@ * * Ricardo Herrera - test data and initial check code * Andrew Berezovskyi - refactoring of the code into a unit test + * UniversalProvider tests *******************************************************************************/ package org.eclipse.lyo.oslc4j.provider.jena.test; -import com.github.jsonldjava.utils.Obj; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.ArrayList; @@ -28,6 +28,7 @@ import org.eclipse.lyo.oslc4j.provider.jena.OslcJsonLdArrayProvider; import org.eclipse.lyo.oslc4j.provider.jena.OslcJsonLdCollectionProvider; import org.eclipse.lyo.oslc4j.provider.jena.OslcJsonLdProvider; +import org.eclipse.lyo.oslc4j.provider.jena.UniversalResourceSingleProvider; import org.junit.Ignore; import org.junit.Test; @@ -42,22 +43,16 @@ */ public class JsonLdTest { @Test - @SuppressWarnings({ - "unchecked", - "rawtypes" - }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void testContentTypeTurtleUTF8() throws Exception { OslcJsonLdProvider provider = new OslcJsonLdProvider(); InputStream is = ServiceProviderTest.class.getResourceAsStream("/provider.jsonld"); assertNotNull("Could not read file: provider.jsonld", is); - ServiceProvider p = (ServiceProvider) provider.readFrom((Class) ServiceProvider.class, - null, - ServiceProvider.class.getAnnotations(), - OslcMediaType.APPLICATION_JSON_LD_TYPE, - null, - is); + ServiceProvider p = (ServiceProvider) provider.readFrom((Class) ServiceProvider.class, null, + ServiceProvider.class.getAnnotations(), OslcMediaType.APPLICATION_JSON_LD_TYPE, + null, is); assertNotNull("Provider was not read", p); } @@ -79,6 +74,7 @@ public void testWrite() throws Exception { } + @Ignore("GHBRB testing") @Test public void testWriteArray() throws Exception { final OslcJsonLdArrayProvider provider = new OslcJsonLdArrayProvider(); @@ -123,5 +119,33 @@ public void testWriteCollection() throws Exception { assertTrue("Provider was not read", jsonLD.contains("Hello world")); } + @Test + public void testUniversalProvider() throws Exception { + UniversalResourceSingleProvider provider = new UniversalResourceSingleProvider(); + + InputStream is = ServiceProviderTest.class.getResourceAsStream("/provider.jsonld"); + assertNotNull("Could not read file: provider.jsonld", is); + + ServiceProvider p = (ServiceProvider) provider.readFrom((Class) ServiceProvider.class, null, + ServiceProvider.class.getAnnotations(), OslcMediaType.APPLICATION_JSON_LD_TYPE, + null, is); + assertNotNull("Provider was not read", p); + } + + @Test + public void testWriteUniversalProvider() throws Exception { + ServiceProvider sp = new ServiceProvider(); + sp.setDescription("Hello world"); + UniversalResourceSingleProvider provider = new UniversalResourceSingleProvider(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + provider.writeTo(sp, ServiceProvider.class, ServiceProvider.class, + ServiceProvider.class.getAnnotations(), OslcMediaType.APPLICATION_JSON_LD_TYPE, + new MultivaluedHashMap<>(), outputStream); + final String jsonLD = outputStream.toString("UTF-8"); + + assertTrue("Provider was not read", jsonLD.contains("Hello world")); + } }