diff --git a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java index d195c97c4..6fbc18354 100644 --- a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java +++ b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/AbstractOslcRdfXmlProvider.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; + import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; @@ -29,6 +30,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.ext.Providers; + import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFReader; @@ -37,6 +39,7 @@ 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.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,388 +51,329 @@ import org.slf4j.LoggerFactory; /** - * @author Russell Boykin, Alberto Giammaria, Chris Peters, Gianluca Bernardini, Andrew Berezovskyi + * @author Russell Boykin, Alberto Giammaria, Chris Peters, Gianluca Bernardini, + * Andrew Berezovskyi */ -public abstract class AbstractOslcRdfXmlProvider -{ - private static final Logger log = LoggerFactory.getLogger(AbstractOslcRdfXmlProvider.class.getName - ()); - - /** - * System property {@value} : When "true", always abbreviate RDF/XML, even - * when asked for application/rdf+xml. Otherwise, abbreviated RDF/XML is - * only returned when application/xml is requested. Does not affect - * text/turtle responses. - * - * @see RdfXmlAbbreviatedWriter - */ - @Deprecated - public static final String OSLC4J_ALWAYS_XML_ABBREV = "org.eclipse.lyo.oslc4j.alwaysXMLAbbrev"; - - /** - * System property {@value} : When "true" (default), fail on when reading a - * property value that is not a legal instance of a datatype. When "false", - * skip over invalid values in extended properties. - */ - @Deprecated - public static final String OSLC4J_STRICT_DATATYPES = "org.eclipse.lyo.oslc4j.strictDatatypes"; - - private static final Annotation[] ANNOTATIONS_EMPTY_ARRAY = new Annotation[0]; - private static final Class CLASS_OSLC_ERROR = Error.class; - private static final ErrorHandler ERROR_HANDLER = new ErrorHandler(); - - 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 AbstractOslcRdfXmlProvider() - { - super(); - } - - protected void writeTo(final Object[] objects, - final MediaType baseMediaType, - final MultivaluedMap map, - final OutputStream outputStream, - final Map properties, - final String descriptionURI, - final String responseInfoURI, - final ResponseInfo responseInfo) - throws WebApplicationException - { - String serializationLanguage = getSerializationLanguage(baseMediaType); - - // This is a special case to handle RDNG GET on a ServiceProvider resource. - // RDNG can only consume RDF/XML-ABBREV although its sometimes sends Accept=application/rdf+xml. - // The org.eclipse.lyo.oslc4j.alwaysXMLAbbrevOnlyProviders system property is used - // to turn off this special case - if ((objects != null && objects[0] != null) && - ( objects[0] instanceof ServiceProviderCatalog || objects[0] instanceof ServiceProvider ) && - serializationLanguage.equals(FileUtils.langXML) && - "true".equals(System.getProperty("org.eclipse.lyo.oslc4j.alwaysXMLAbbrevOnlyProviders"))) { - serializationLanguage = FileUtils.langXMLAbbrev; - log.info("Using RDF/XML-ABBREV for ServiceProvider resources"); - } - - writeObjectsTo(objects, - outputStream, - properties, - descriptionURI, - responseInfoURI, - responseInfo, - serializationLanguage - ); - } - - private void writeObjectsTo(final Object[] objects, final OutputStream outputStream, - final Map properties, final String descriptionURI, - final String responseInfoURI, final ResponseInfo responseInfo, - final String serializationLanguage) { - try - { - final Model model = JenaModelHelper.createJenaModel(descriptionURI, - responseInfoURI, - responseInfo, - objects, - properties); - RDFWriter writer = getRdfWriter(serializationLanguage, 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")); - throw new IllegalStateException(exception); - } - } - - private RDFWriter getRdfWriter(final String serializationLanguage, final Model model) { - RDFWriter writer; - if (serializationLanguage.equals(FileUtils.langXMLAbbrev)) - { - writer = new RdfXmlAbbreviatedWriter(); +public abstract class AbstractOslcRdfXmlProvider { + private static final Logger log = LoggerFactory.getLogger(AbstractOslcRdfXmlProvider.class.getName()); + + /** + * System property {@value} : When "true", always abbreviate RDF/XML, even when + * asked for application/rdf+xml. Otherwise, abbreviated RDF/XML is only + * returned when application/xml is requested. Does not affect text/turtle + * responses. + * + * @see RdfXmlAbbreviatedWriter + */ + @Deprecated + public static final String OSLC4J_ALWAYS_XML_ABBREV = "org.eclipse.lyo.oslc4j.alwaysXMLAbbrev"; + + /** + * System property {@value} : When "true" (default), fail on when reading a + * property value that is not a legal instance of a datatype. When "false", skip + * over invalid values in extended properties. + */ + @Deprecated + public static final String OSLC4J_STRICT_DATATYPES = "org.eclipse.lyo.oslc4j.strictDatatypes"; + + private static final Annotation[] ANNOTATIONS_EMPTY_ARRAY = new Annotation[0]; + private static final Class CLASS_OSLC_ERROR = Error.class; + private static final ErrorHandler ERROR_HANDLER = new ErrorHandler(); + + 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 AbstractOslcRdfXmlProvider() { + super(); + } + + protected void writeTo(final Object[] objects, final MediaType baseMediaType, + final MultivaluedMap map, final OutputStream outputStream, + final Map properties, final String descriptionURI, final String responseInfoURI, + final ResponseInfo responseInfo) throws WebApplicationException { + String serializationLanguage = getSerializationLanguage(baseMediaType); + + // This is a special case to handle RDNG GET on a ServiceProvider resource. + // RDNG can only consume RDF/XML-ABBREV although its sometimes sends + // Accept=application/rdf+xml. + // The org.eclipse.lyo.oslc4j.alwaysXMLAbbrevOnlyProviders system property is + // used + // to turn off this special case + if ((objects != null && objects[0] != null) + && (objects[0] instanceof ServiceProviderCatalog || objects[0] instanceof ServiceProvider) + && serializationLanguage.equals(FileUtils.langXML) + && "true".equals(System.getProperty("org.eclipse.lyo.oslc4j.alwaysXMLAbbrevOnlyProviders"))) { + serializationLanguage = FileUtils.langXMLAbbrev; + log.info("Using RDF/XML-ABBREV for ServiceProvider resources"); + } + + writeObjectsTo(objects, outputStream, properties, descriptionURI, responseInfoURI, responseInfo, + serializationLanguage); + } + + private void writeObjectsTo(final Object[] objects, final OutputStream outputStream, + final Map properties, final String descriptionURI, final String responseInfoURI, + final ResponseInfo responseInfo, final String serializationLanguage) { + try { + final Model model = JenaModelHelper.createJenaModel(descriptionURI, responseInfoURI, responseInfo, objects, + properties); + RDFWriter writer = getRdfWriter(serializationLanguage, 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")); + throw new IllegalStateException(exception); } - else - { + } + + private RDFWriter getRdfWriter(final String serializationLanguage, final Model model) { + RDFWriter writer; + if (serializationLanguage.equals(FileUtils.langXMLAbbrev)) { + writer = new RdfXmlAbbreviatedWriter(); + } else { writer = model.getWriter(serializationLanguage); } - writer.setProperty("showXmlDeclaration", - "false"); - writer.setErrorHandler(ERROR_HANDLER); - return writer; - } - - protected void writeTo(final boolean queryResult, - final Object[] objects, - final MediaType baseMediaType, - final MultivaluedMap map, - final OutputStream outputStream) - throws WebApplicationException - { - boolean isClientSide = false; - - try { - httpServletRequest.getMethod(); - } catch (RuntimeException e) { - isClientSide = true; - } - - String descriptionURI = null; - // TODO Andrew@2019-04-18: stop using responseInfoURI nullity to detect Query results - String responseInfoURI = null; - - if (queryResult && ! isClientSide) - { - log.trace("Marshalling response objects as OSLC Query result (server-side only)"); - - final String method = httpServletRequest.getMethod(); - if ("GET".equals(method)) - { - descriptionURI = OSLC4JUtils.resolveURI(httpServletRequest,true); - responseInfoURI = descriptionURI; - - final String queryString = httpServletRequest.getQueryString(); - if ((queryString != null) && - (ProviderHelper.isOslcQuery(queryString))) - { - responseInfoURI += "?" + queryString; - } - } - - } - - final String serializationLanguage = getSerializationLanguage(baseMediaType); - - @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); - - writeObjectsTo( - objects, - outputStream, - properties, - descriptionURI, - responseInfoURI, - responseInfo, - serializationLanguage - ); - } - - /** - * Determine whether to serialize in xml or abreviated xml based upon mediatype. - *

- * application/rdf+xml yields xml - *

- * applicaton/xml yields abbreviated xml - */ - private String getSerializationLanguage(final MediaType baseMediaType) { - - if(baseMediaType == null) { - throw new IllegalArgumentException("Base media type can't be null"); - } - - List> mediaPairs = new ArrayList<>(); - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.TEXT_TURTLE_TYPE, - RDFLanguages.strLangTurtle)); - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_JSON_LD_TYPE, - RDFLanguages.strLangJSONLD)); - if (OSLC4JUtils.alwaysAbbrevXML()) { - // application/rdf+xml will be forcefully abbreviated - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_RDF_XML_TYPE, - FileUtils.langXMLAbbrev)); - } else { - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_RDF_XML_TYPE, - RDFLanguages.strLangRDFXML)); - - } - // application/xml will be abbreviated for compat reasons - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_XML_TYPE, - FileUtils.langXMLAbbrev)); - - mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.TEXT_XML_TYPE, - RDFLanguages.strLangRDFXML)); - - for (Map.Entry mediaPair : mediaPairs) { - if (baseMediaType.isCompatible(mediaPair.getKey())) { - log.trace("Using '{}' writer for '{}' Accept media type", - mediaPair.getValue(), - mediaPair.getKey()); - return mediaPair.getValue(); - } - } - - throw new IllegalArgumentException("Base media type can't be matched to any writer"); - } - - protected Object[] readFrom(final Class type, - final MediaType mediaType, - final MultivaluedMap map, - final InputStream inputStream) - throws WebApplicationException - { - final Model model = ModelFactory.createDefaultModel(); - - RDFReader reader = getRdfReader(mediaType, model); - - try - { - // Pass the empty string as the base URI. This allows Jena to - // resolve relative URIs commonly used to in reified statements - // for OSLC link labels. See this section of the CM specification - // for an example: - // http://open-services.net/bin/view/Main/CmSpecificationV2?sortcol=table;up=#Labels_for_Relationships - - reader.read(model, inputStream, ""); - - return JenaModelHelper.unmarshal(model, type); - } - catch (final Exception exception) - { - throw new WebApplicationException(exception, - buildBadRequestResponse(exception, - mediaType, - map)); - } - } - - private RDFReader getRdfReader(final MediaType mediaType, final Model model) { - RDFReader reader; - final String language = getSerializationLanguage(mediaType); - if (language.equals(FileUtils.langXMLAbbrev)) - { - reader = model.getReader(); // Default reader handles both xml and abbreviated xml - } else { - reader=model.getReader(language); - } - reader.setErrorHandler(ERROR_HANDLER); - return reader; - } - - protected Response buildBadRequestResponse(final Exception exception, - final MediaType initialErrorMediaType, - final MultivaluedMap map) - { - final MediaType determinedErrorMediaType = determineErrorMediaType(initialErrorMediaType, - 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); - } + writer.setProperty("showXmlDeclaration", "false"); + writer.setErrorHandler(ERROR_HANDLER); + return writer; + } + + protected void writeTo(final boolean queryResult, final Object[] objects, final MediaType baseMediaType, + final MultivaluedMap map, final OutputStream outputStream) throws WebApplicationException { + boolean isClientSide = false; + + try { + httpServletRequest.getMethod(); + } catch (RuntimeException e) { + isClientSide = true; + } + + String descriptionURI = null; + // TODO Andrew@2019-04-18: stop using responseInfoURI nullity to detect Query + // results + String responseInfoURI = null; + + if (queryResult && !isClientSide) { + log.trace("Marshalling response objects as OSLC Query result (server-side only)"); + + final String method = httpServletRequest.getMethod(); + if ("GET".equals(method)) { + descriptionURI = OSLC4JUtils.resolveURI(httpServletRequest, true); + responseInfoURI = descriptionURI; + + final String queryString = httpServletRequest.getQueryString(); + if ((queryString != null) && (ProviderHelper.isOslcQuery(queryString))) { + responseInfoURI += "?" + queryString; + } + } + + } + + final String serializationLanguage = getSerializationLanguage(baseMediaType); + + @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); + + writeObjectsTo(objects, outputStream, properties, descriptionURI, responseInfoURI, responseInfo, + serializationLanguage); + } + + /** + * Determine whether to serialize in xml or abreviated xml based upon mediatype. + *

+ * application/rdf+xml yields xml + *

+ * applicaton/xml yields abbreviated xml + */ + private String getSerializationLanguage(final MediaType baseMediaType) { + + if (baseMediaType == null) { + throw new IllegalArgumentException("Base media type can't be null"); + } + + List> mediaPairs = new ArrayList<>(); + mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.TEXT_TURTLE_TYPE, RDFLanguages.strLangTurtle)); + mediaPairs + .add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_JSON_LD_TYPE, RDFLanguages.strLangJSONLD)); + if (OSLC4JUtils.alwaysAbbrevXML()) { + // application/rdf+xml will be forcefully abbreviated + mediaPairs.add( + new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_RDF_XML_TYPE, FileUtils.langXMLAbbrev)); + } else { + mediaPairs.add( + new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_RDF_XML_TYPE, RDFLanguages.strLangRDFXML)); + + } + // application/xml will be abbreviated for compat reasons + mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.APPLICATION_XML_TYPE, FileUtils.langXMLAbbrev)); + + mediaPairs.add(new AbstractMap.SimpleEntry<>(OslcMediaType.TEXT_XML_TYPE, RDFLanguages.strLangRDFXML)); + + for (Map.Entry mediaPair : mediaPairs) { + if (baseMediaType.isCompatible(mediaPair.getKey())) { + log.trace("Using '{}' writer for '{}' Accept media type", mediaPair.getValue(), mediaPair.getKey()); + return mediaPair.getValue(); + } + } + + 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.isAnnotationPresent(OslcResourceShape.class)) { + if (isCompatible(actualMediaType, requiredMediaTypes)) { + return true; + } + } + + 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, final InputStream inputStream) throws WebApplicationException { + final Model model = ModelFactory.createDefaultModel(); + + RDFReader reader = getRdfReader(mediaType, model); + + try { + // Pass the empty string as the base URI. This allows Jena to + // resolve relative URIs commonly used to in reified statements + // for OSLC link labels. See this section of the CM specification + // for an example: + // http://open-services.net/bin/view/Main/CmSpecificationV2?sortcol=table;up=#Labels_for_Relationships + + reader.read(model, inputStream, ""); + + return JenaModelHelper.unmarshal(model, type); + } catch (final Exception exception) { + throw new WebApplicationException(exception, buildBadRequestResponse(exception, mediaType, map)); + } + } + + private RDFReader getRdfReader(final MediaType mediaType, final Model model) { + RDFReader reader; + final String language = getSerializationLanguage(mediaType); + if (language.equals(FileUtils.langXMLAbbrev)) { + reader = model.getReader(); // Default reader handles both xml and abbreviated xml + } else { + reader = model.getReader(language); + } + reader.setErrorHandler(ERROR_HANDLER); + return reader; + } + + protected Response buildBadRequestResponse(final Exception exception, final MediaType initialErrorMediaType, + final MultivaluedMap map) { + final MediaType determinedErrorMediaType = determineErrorMediaType(initialErrorMediaType, 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); + } + } diff --git a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlArrayProvider.java b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlArrayProvider.java index ab36e9525..e0b34572b 100644 --- a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlArrayProvider.java +++ b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlArrayProvider.java @@ -55,7 +55,11 @@ public boolean isWriteable(final Class type, final Type genericType, @Override public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { - return true; + if (type.isArray()) { + return isReadable(type.getComponentType(), mediaType, OslcMediaType.APPLICATION_RDF_XML_TYPE, + OslcMediaType.APPLICATION_XML_TYPE, OslcMediaType.TEXT_XML_TYPE); + } + return false; } @Override diff --git a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlProvider.java b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlProvider.java index 00b54adc9..1226bc770 100644 --- a/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlProvider.java +++ b/core/oslc4j-jena-provider/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/OslcRdfXmlProvider.java @@ -63,18 +63,18 @@ public boolean isWriteable(final Class type, 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 (actualTypeArguments[0] instanceof Class) { actualType = (Class)actualTypeArguments[0]; @@ -83,13 +83,13 @@ else if (actualTypeArguments[0] instanceof ParameterizedType) { parameterizedType = (ParameterizedType)actualTypeArguments[0]; actualTypeArguments = parameterizedType.getActualTypeArguments(); - + if (actualTypeArguments.length != 1 || ! (actualTypeArguments[0] instanceof Class)) { return false; } - + actualType = (Class)actualTypeArguments[0]; } else if (actualTypeArguments[0] instanceof GenericArrayType) @@ -97,12 +97,12 @@ 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 @@ -115,13 +115,13 @@ else if (actualTypeArguments[0] instanceof GenericArrayType) && (ResponseInfoCollection.class.equals(rawType) || ResponseInfoArray.class.equals(rawType))) { return true; - } + } } else { actualType = type; } - + return ProviderHelper.isSingleResourceType(actualType); } @@ -141,43 +141,43 @@ public void writeTo(final Object object, 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) + if (containerAboutURI != null) { descriptionURI = containerAboutURI.toString(); } - } + } if (null == descriptionURI) { descriptionURI = requestURI; } - + final String queryString = httpServletRequest.getQueryString(); - + if ((queryString != null) && (ProviderHelper.isOslcQuery(queryString))) { responseInfoURI += "?" + queryString; } - + if (filteredResource instanceof ResponseInfoArray) { objects = ((ResponseInfoArray)filteredResource).array(); @@ -186,14 +186,14 @@ public void writeTo(final Object object, { Collection collection = ((ResponseInfoCollection)filteredResource).collection(); - + objects = collection.toArray(new Object[0]); } } else { Object nestedObject = filteredResource.resource(); - + if (nestedObject instanceof Object[]) { objects = (Object[])nestedObject; @@ -212,7 +212,7 @@ else if (nestedObject instanceof Collection) { objects = new Object[] { object }; } - + writeTo(objects, mediaType, map, @@ -229,7 +229,8 @@ public boolean isReadable(final Class type, final Annotation[] annotations, final MediaType mediaType) { - return true; + return isReadable(type, mediaType, OslcMediaType.APPLICATION_RDF_XML_TYPE, OslcMediaType.APPLICATION_XML_TYPE, + OslcMediaType.TEXT_XML_TYPE, OslcMediaType.TEXT_TURTLE_TYPE); } @Override @@ -253,8 +254,8 @@ public Object readFrom(final Class type, // Fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=412755 if (OSLC4JUtils.useBeanClassForParsing() && objects.length > 1) { throw new IOException("Object length should not be greater than 1."); - } - + } + return objects[0]; } diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/MessageBodyReaderTest.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/MessageBodyReaderTest.java new file mode 100644 index 000000000..3cb02e952 --- /dev/null +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/MessageBodyReaderTest.java @@ -0,0 +1,65 @@ +package org.eclipse.lyo.oslc4j.provider.jena; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.provider.jena.test.resources.ResourceWithoutShape; +import org.eclipse.lyo.oslc4j.provider.jena.test.resources.TestResource; +import org.junit.Test; + +public class MessageBodyReaderTest { + + @Test + public void testOslcXmlProvider() { + OslcXmlProvider provider = new OslcXmlProvider(); + + boolean isReadable = provider.isReadable(TestResource.class, null, TestResource.class.getAnnotations(), + MediaType.APPLICATION_XML_TYPE); + assertTrue(isReadable); + + isReadable = provider.isReadable(TestResource.class, null, TestResource.class.getAnnotations(), + OslcMediaType.APPLICATION_RDF_XML_TYPE); + assertTrue(isReadable); + + isReadable = provider.isReadable(ResourceWithoutShape.class, null, ResourceWithoutShape.class.getAnnotations(), + MediaType.APPLICATION_XML_TYPE); + assertFalse(isReadable); + + isReadable = provider.isReadable(TestResource.class, null, TestResource.class.getAnnotations(), + MediaType.APPLICATION_JSON_TYPE); + assertFalse(isReadable); + + isReadable = provider.isReadable(Collection.class, null, TestResource.class.getAnnotations(), + MediaType.APPLICATION_JSON_TYPE); + assertFalse(isReadable); + } + + @Test + public void testOslcArrayProvider() { + + OslcRdfXmlArrayProvider provider = new OslcRdfXmlArrayProvider(); + + boolean isReadable = provider.isReadable(TestResource[].class, null, TestResource.class.getAnnotations(), + MediaType.APPLICATION_XML_TYPE); + assertTrue(isReadable); + + isReadable = provider.isReadable(TestResource[].class, null, TestResource.class.getAnnotations(), + OslcMediaType.APPLICATION_RDF_XML_TYPE); + assertTrue(isReadable); + + isReadable = provider.isReadable(ResourceWithoutShape[].class, null, + ResourceWithoutShape.class.getAnnotations(), OslcMediaType.APPLICATION_RDF_XML_TYPE); + assertFalse(isReadable); + + isReadable = provider.isReadable(TestResource[].class, null, TestResource.class.getAnnotations(), + OslcMediaType.APPLICATION_JSON_TYPE); + assertFalse(isReadable); + + } + +} \ No newline at end of file diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/resources/ResourceWithoutShape.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/resources/ResourceWithoutShape.java new file mode 100644 index 000000000..ea5dcf9ef --- /dev/null +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/test/resources/ResourceWithoutShape.java @@ -0,0 +1,8 @@ +package org.eclipse.lyo.oslc4j.provider.jena.test.resources; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class ResourceWithoutShape { + +} \ No newline at end of file