| /* |
| * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 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 |
| */ |
| |
| // Contributors: |
| // dclarke/tware - Initial implementation |
| // 09-01-2014-2.6.0 Dmitry Kornilov |
| // - Fields filtering (projection), application/schema+json media type handling |
| package org.eclipse.persistence.jpa.rs.util; |
| |
| import java.beans.PropertyChangeListener; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectOutputStream; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.util.List; |
| |
| import jakarta.ws.rs.WebApplicationException; |
| import jakarta.ws.rs.core.HttpHeaders; |
| import jakarta.ws.rs.core.MediaType; |
| import jakarta.ws.rs.core.StreamingOutput; |
| import jakarta.xml.bind.JAXBContext; |
| import jakarta.xml.bind.JAXBException; |
| import jakarta.xml.bind.Marshaller; |
| |
| import org.eclipse.persistence.dynamic.DynamicEntity; |
| import org.eclipse.persistence.internal.dynamic.DynamicEntityImpl; |
| import org.eclipse.persistence.jaxb.MarshallerProperties; |
| import org.eclipse.persistence.jpa.rs.PersistenceContext; |
| import org.eclipse.persistence.jpa.rs.exceptions.JPARSException; |
| import org.eclipse.persistence.jpa.rs.features.fieldsfiltering.FieldsFilter; |
| import org.eclipse.persistence.jpa.rs.resources.common.AbstractResource; |
| import org.eclipse.persistence.jpa.rs.util.list.ReportQueryResultList; |
| import org.eclipse.persistence.jpa.rs.util.xmladapters.LinkAdapter; |
| |
| /** |
| * Simple {@link StreamingOutput} implementation that uses the provided |
| * {@link JAXBContext} to marshal the result when requested to either XML or |
| * JSON based on the accept media provided. |
| * |
| * @author dclarke |
| * @since EclipseLink 2.4.0 |
| */ |
| public class StreamingOutputMarshaller implements StreamingOutput { |
| private final PersistenceContext context; |
| private final Object result; |
| private final MediaType mediaType; |
| private FieldsFilter filter; |
| |
| public StreamingOutputMarshaller(PersistenceContext context, Object result, MediaType acceptedType) { |
| this.context = context; |
| this.result = result; |
| this.mediaType = acceptedType; |
| } |
| |
| /** |
| * This constructor is used for fields filtering. Only attributes included in fields parameter are marshalled. |
| * |
| * @param context persistence context. |
| * @param result entity to process. |
| * @param acceptedTypes accepted media types. |
| * @param filter containing a list of fields to filter out from the response. |
| */ |
| public StreamingOutputMarshaller(PersistenceContext context, Object result, List<MediaType> acceptedTypes, FieldsFilter filter) { |
| this(context, result, acceptedTypes); |
| this.filter = filter; |
| } |
| |
| /** |
| * Creates a new StreamingOutputMarshaller. |
| * |
| * @param context persistence context. |
| * @param result entity to process. |
| * @param acceptedTypes accepted media types. |
| */ |
| public StreamingOutputMarshaller(PersistenceContext context, Object result, List<MediaType> acceptedTypes) { |
| this(context, result, mediaType(acceptedTypes)); |
| } |
| |
| @Override |
| public void write(OutputStream output) throws IOException, WebApplicationException { |
| if (result instanceof byte[] && this.mediaType.equals(MediaType.APPLICATION_OCTET_STREAM_TYPE)) { |
| output.write((byte[]) result); |
| output.flush(); |
| output.close(); |
| } else if (result instanceof String) { |
| OutputStreamWriter writer = new OutputStreamWriter(output, StandardCharsets.UTF_8); |
| writer.write((String) result); |
| writer.flush(); |
| writer.close(); |
| } else { |
| if ((this.context != null && this.context.getJAXBContext() != null && this.result != null) && |
| (this.mediaType.equals(MediaType.APPLICATION_JSON_TYPE) || this.mediaType.equals(MediaType.APPLICATION_XML_TYPE))) { |
| try { |
| if (result instanceof ReportQueryResultList) { |
| if (mediaType == MediaType.APPLICATION_JSON_TYPE) { |
| // avoid outer QueryResultList class (outer grouping name) in JSON responses |
| context.marshallEntity(((ReportQueryResultList) result).getItems(), mediaType, output); |
| } else { |
| context.marshallEntity(result, mediaType, output); |
| } |
| } else { |
| if (filter != null) { |
| context.marshallEntity(result, filter, mediaType, output); |
| } else { |
| context.marshallEntity(result, mediaType, output); |
| } |
| } |
| return; |
| } catch (Exception ex) { |
| JPARSLogger.exception(context.getSessionLog(), "jpars_caught_exception", new Object[] {}, ex); |
| throw JPARSException.exceptionOccurred(ex); |
| } |
| } |
| |
| if (this.mediaType.equals(MediaType.APPLICATION_OCTET_STREAM_TYPE)) { |
| // could not marshall, try serializing |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(baos); |
| oos.writeObject(result); |
| oos.flush(); |
| oos.close(); |
| output.write(baos.toByteArray()); |
| } else { |
| if (context != null) { |
| JPARSLogger.error(context.getSessionLog(), "jpars_could_not_marshal_requested_result_to_requested_type", new Object[] { result }); |
| } else { |
| JPARSLogger.error("jpars_could_not_marshal_requested_result_to_requested_type", new Object[] { result }); |
| } |
| throw new WebApplicationException(); |
| } |
| } |
| } |
| |
| /** |
| * Identify the preferred {@link MediaType} from the list provided. This |
| * will check for JSON string or {@link MediaType} first then XML. |
| * |
| * @param types |
| * List of {@link MediaType} values; |
| * @return selected {@link MediaType} |
| */ |
| public static MediaType mediaType(List<MediaType> types) { |
| MediaType aMediaType; |
| if (types != null) { |
| for (MediaType type : types) { |
| aMediaType = type; |
| if (aMediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) { |
| return MediaType.APPLICATION_JSON_TYPE; |
| } |
| if (aMediaType.isCompatible(MediaType.APPLICATION_XML_TYPE)) { |
| return MediaType.APPLICATION_XML_TYPE; |
| } |
| if (aMediaType.isCompatible(MediaType.APPLICATION_OCTET_STREAM_TYPE)) { |
| return MediaType.APPLICATION_OCTET_STREAM_TYPE; |
| } |
| if (aMediaType.isCompatible(AbstractResource.APPLICATION_SCHEMA_JSON_TYPE)) { |
| return AbstractResource.APPLICATION_SCHEMA_JSON_TYPE; |
| } |
| } |
| } |
| // An unsupported media type never comes to resource, no need to throw exception here. |
| return MediaType.APPLICATION_JSON_TYPE; |
| } |
| |
| public static Marshaller createMarshaller(PersistenceContext context, MediaType mediaType) throws JAXBException { |
| Marshaller marshaller = context.getJAXBContext().createMarshaller(); |
| marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, mediaType.toString()); |
| marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false); |
| marshaller.setAdapter(new LinkAdapter(context.getBaseURI().toString(), context)); |
| marshaller.setListener(new Marshaller.Listener() { |
| @Override |
| public void beforeMarshal(Object source) { |
| if (source instanceof DynamicEntity) { |
| DynamicEntityImpl sourceImpl = (DynamicEntityImpl) source; |
| PropertyChangeListener listener = sourceImpl._persistence_getPropertyChangeListener(); |
| sourceImpl._persistence_setPropertyChangeListener(null); |
| ((DynamicEntity) source).set("self", source); |
| sourceImpl._persistence_setPropertyChangeListener(listener); |
| } |
| } |
| }); |
| return marshaller; |
| } |
| |
| public static MediaType getResponseMediaType(HttpHeaders headers) { |
| MediaType mediaType = MediaType.TEXT_PLAIN_TYPE; |
| if (headers != null) { |
| List<MediaType> accepts = headers.getAcceptableMediaTypes(); |
| if (accepts != null && accepts.size() > 0) { |
| try { |
| mediaType = StreamingOutputMarshaller.mediaType(accepts); |
| } catch (Exception ex) { |
| JPARSLogger.exception("Exception in getResponseMediaType", new Object[]{headers}, ex); |
| } |
| } |
| } |
| return mediaType; |
| } |
| } |