/******************************************************************************** | |
* Copyright (c) 2015-2018 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 v. 2.0 which is available at | |
* http://www.eclipse.org/legal/epl-2.0. | |
* | |
* SPDX-License-Identifier: EPL-2.0 | |
* | |
********************************************************************************/ | |
package org.eclipse.mdm.businessobjects.utils; | |
import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.MEDIATYPE_APPLICATION_PROTOBUF; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Type; | |
import javax.ws.rs.Consumes; | |
import javax.ws.rs.Produces; | |
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.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import com.google.common.io.ByteStreams; | |
import com.google.protobuf.AbstractMessage; | |
import com.google.protobuf.CodedInputStream; | |
import com.google.protobuf.InvalidProtocolBufferException; | |
import com.google.protobuf.Message; | |
/** | |
* MessageBodyProvider for handling protobuf payloads. | |
* | |
*/ | |
@Provider | |
@Consumes({ MEDIATYPE_APPLICATION_PROTOBUF }) | |
@Produces({ MEDIATYPE_APPLICATION_PROTOBUF }) | |
public class ProtobufMessageBodyProvider implements MessageBodyReader<Message>, MessageBodyWriter<Message> { | |
private static final Logger LOG = LoggerFactory.getLogger(ProtobufMessageBodyProvider.class); | |
private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB | |
private static final int MAX_SIZE = DEFAULT_SIZE_LIMIT; | |
/* | |
* (non-Javadoc) | |
* | |
* @see javax.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, | |
* java.lang.reflect.Type, java.lang.annotation.Annotation[], | |
* javax.ws.rs.core.MediaType) | |
*/ | |
@Override | |
public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations, | |
final MediaType mediaType) { | |
return Message.class.isAssignableFrom(type); | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, | |
* java.lang.reflect.Type, java.lang.annotation.Annotation[], | |
* javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, | |
* java.io.InputStream) | |
*/ | |
@Override | |
public Message readFrom(final Class<Message> type, final Type genericType, final Annotation[] annotations, | |
final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) | |
throws IOException { | |
try { | |
final Method newBuilder = type.getMethod("newBuilder"); | |
final AbstractMessage.Builder<?> builder = (AbstractMessage.Builder<?>) newBuilder.invoke(type); | |
CodedInputStream in = CodedInputStream.newInstance(entityStream); | |
in.setSizeLimit(MAX_SIZE); | |
byte[] b = ByteStreams.toByteArray(entityStream); | |
LOG.debug("Reading type {} with size {}.", genericType, b.length); | |
return builder.mergeFrom(b).build(); | |
} catch (InvalidProtocolBufferException e) { | |
throw new IOException("Could not read Protobuf entity!", e); | |
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { | |
throw new IOException("Could not retrive builder for type " + genericType, e); | |
} | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object, | |
* java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], | |
* javax.ws.rs.core.MediaType) | |
*/ | |
@Override | |
public long getSize(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations, | |
final MediaType mediaType) { | |
return -1; // as method is not actually used by JAX-RS | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class, | |
* java.lang.reflect.Type, java.lang.annotation.Annotation[], | |
* javax.ws.rs.core.MediaType) | |
*/ | |
@Override | |
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations, | |
final MediaType mediaType) { | |
return Message.class.isAssignableFrom(type); | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, | |
* java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], | |
* javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, | |
* java.io.OutputStream) | |
*/ | |
@Override | |
public void writeTo(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations, | |
final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders, | |
final OutputStream entityStream) throws IOException { | |
if (LOG.isDebugEnabled()) { | |
LOG.debug("Writing type {} with size {}.", genericType, m.getSerializedSize()); | |
} | |
m.writeTo(entityStream); | |
} | |
} |