diff --git a/pippo-content-type-parent/pippo-csv/src/main/java/ro/pippo/csv/CsvEngine.java b/pippo-content-type-parent/pippo-csv/src/main/java/ro/pippo/csv/CsvEngine.java index 5f474da37..a8d2dfc06 100644 --- a/pippo-content-type-parent/pippo-csv/src/main/java/ro/pippo/csv/CsvEngine.java +++ b/pippo-content-type-parent/pippo-csv/src/main/java/ro/pippo/csv/CsvEngine.java @@ -219,6 +219,11 @@ public T fromString(String content, Class classOfT) { } } + @Override + public byte[] toByteArray(Object object) { + return toString(object).getBytes(); + } + public String objectToString(Object object) { if (object == null) { return null; diff --git a/pippo-content-type-parent/pippo-fastjson/src/main/java/ro/pippo/fastjson/FastjsonEngine.java b/pippo-content-type-parent/pippo-fastjson/src/main/java/ro/pippo/fastjson/FastjsonEngine.java index 3c0434190..0d98c7d29 100644 --- a/pippo-content-type-parent/pippo-fastjson/src/main/java/ro/pippo/fastjson/FastjsonEngine.java +++ b/pippo-content-type-parent/pippo-fastjson/src/main/java/ro/pippo/fastjson/FastjsonEngine.java @@ -42,7 +42,8 @@ public String getContentType() { @Override public String toString(Object object) { - return JSON.toJSONString(object, SerializerFeature.UseISO8601DateFormat); + return JSON.toJSONString(object, + SerializerFeature.UseISO8601DateFormat); } @Override @@ -50,4 +51,10 @@ public T fromString(String content, Class classOfT) { return JSON.parseObject(content, classOfT); } + @Override + public byte[] toByteArray(Object object) { + return JSON.toJSONBytes(object, + SerializerFeature.UseISO8601DateFormat); + } + } diff --git a/pippo-content-type-parent/pippo-gson/src/main/java/ro/pippo/gson/GsonEngine.java b/pippo-content-type-parent/pippo-gson/src/main/java/ro/pippo/gson/GsonEngine.java index e38b3ad18..ad3c84b8d 100644 --- a/pippo-content-type-parent/pippo-gson/src/main/java/ro/pippo/gson/GsonEngine.java +++ b/pippo-content-type-parent/pippo-gson/src/main/java/ro/pippo/gson/GsonEngine.java @@ -64,6 +64,11 @@ public T fromString(String content, Class classOfT) { return gson().fromJson(content, classOfT); } + @Override + public byte[] toByteArray(Object object) { + return gson().toJson(object).getBytes(); + } + private Gson gson() { return new GsonBuilder() .registerTypeAdapter(Date.class, new ISO8601DateTimeTypeAdapter()) diff --git a/pippo-content-type-parent/pippo-jackson/src/main/java/ro/pippo/jackson/JacksonBaseEngine.java b/pippo-content-type-parent/pippo-jackson/src/main/java/ro/pippo/jackson/JacksonBaseEngine.java index 0011a0ef8..f036e0499 100644 --- a/pippo-content-type-parent/pippo-jackson/src/main/java/ro/pippo/jackson/JacksonBaseEngine.java +++ b/pippo-content-type-parent/pippo-jackson/src/main/java/ro/pippo/jackson/JacksonBaseEngine.java @@ -68,4 +68,13 @@ public T fromString(String content, Class classOfT) { } } + @Override + public byte[] toByteArray(Object object) { + try { + return objectMapper.writeValueAsBytes(object); + } catch (JsonProcessingException e) { + throw new PippoRuntimeException(e, "Error serializing object to {}", getContentType()); + } + } + } diff --git a/pippo-content-type-parent/pippo-jaxb/src/main/java/ro/pippo/jaxb/JaxbEngine.java b/pippo-content-type-parent/pippo-jaxb/src/main/java/ro/pippo/jaxb/JaxbEngine.java index d39051e02..d1f15739d 100644 --- a/pippo-content-type-parent/pippo-jaxb/src/main/java/ro/pippo/jaxb/JaxbEngine.java +++ b/pippo-content-type-parent/pippo-jaxb/src/main/java/ro/pippo/jaxb/JaxbEngine.java @@ -28,6 +28,7 @@ import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayOutputStream; import java.io.StringReader; import java.io.StringWriter; @@ -86,4 +87,21 @@ public T fromString(String content, Class classOfT) { } } + @Override + public byte[] toByteArray(Object object) { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, prettyPrint); + + ByteArrayOutputStream writer = new ByteArrayOutputStream(); + jaxbMarshaller.marshal(object, writer); + + return writer.toByteArray(); + } catch (JAXBException e) { + throw new PippoRuntimeException(e, + "Failed to serialize '{}' to XML'", object.getClass().getName()); + } + } + } diff --git a/pippo-content-type-parent/pippo-protobuf/pom.xml b/pippo-content-type-parent/pippo-protobuf/pom.xml new file mode 100644 index 000000000..96531fe0f --- /dev/null +++ b/pippo-content-type-parent/pippo-protobuf/pom.xml @@ -0,0 +1,42 @@ + + + + + ro.pippo + pippo-content-type-parent + 1.13.0-SNAPSHOT + + + 4.0.0 + jar + pippo-protobuf + 1.13.0-SNAPSHOT + Pippo Protobuf + Protobuf integration + + + 3.8.0 + + + + + ro.pippo + pippo-core + ${project.version} + + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + + org.kohsuke.metainf-services + metainf-services + provided + + + + diff --git a/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufEngine.java b/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufEngine.java new file mode 100644 index 000000000..83d66075a --- /dev/null +++ b/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufEngine.java @@ -0,0 +1,82 @@ +package ro.pippo.protobuf; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; +import org.kohsuke.MetaInfServices; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ro.pippo.core.Application; +import ro.pippo.core.ContentTypeEngine; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +/** + * A Protobuf Engine + * + * @author Denys Vitali + */ + +@MetaInfServices +public class ProtobufEngine implements ContentTypeEngine { + private static final Logger log = + LoggerFactory.getLogger(ProtobufInitializer.class); + + @Override + public void init(Application application) { + + } + + @Override + public String getContentType() { + return "application/protobuf"; + } + + @Override + public String toString(Object msg) { + if(msg instanceof AbstractMessage){ + try { + return (String) + msg.getClass() + .getMethod("getMessage") + .invoke(msg); + } catch (IllegalAccessException | + InvocationTargetException | + NoSuchMethodException e) { + e.printStackTrace(); + } + } + return null; + } + + @Override + public T fromString(String content, Class classOfT) { + ByteString bs = ByteString.copyFrom(content, StandardCharsets.UTF_8); + + try { + return (T) classOfT.getMethod("fromString", ByteString.class) + .invoke(bs); + } catch (NoSuchMethodException | + IllegalAccessException | + InvocationTargetException e) { + log.error("Unable to deserialize protobuf object", e); + } + return null; + } + + @Override + public byte[] toByteArray(Object o) { + if(o instanceof AbstractMessage){ + try { + return(byte[]) + o.getClass() + .getMethod("toByteArray") + .invoke(o); + } catch (IllegalAccessException | + InvocationTargetException | + NoSuchMethodException e) { + e.printStackTrace(); + } + } + return new byte[]{}; + } +} diff --git a/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufInitializer.java b/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufInitializer.java new file mode 100644 index 000000000..2a280dcd3 --- /dev/null +++ b/pippo-content-type-parent/pippo-protobuf/src/main/java/ro/pippo/protobuf/ProtobufInitializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ro.pippo.protobuf; + +import org.kohsuke.MetaInfServices; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.pippo.core.Application; +import ro.pippo.core.Initializer; + +/** + * @author Denys Vitali + */ + +@MetaInfServices +public class ProtobufInitializer implements Initializer { + + private static final Logger log = LoggerFactory.getLogger(ProtobufInitializer.class); + + @Override + public void init(Application application) { + application.registerContentTypeEngine(ProtobufEngine.class); + } + + @Override + public void destroy(Application application) { + } + +} diff --git a/pippo-content-type-parent/pippo-snakeyaml/src/main/java/ro/pippo/snakeyaml/SnakeYamlEngine.java b/pippo-content-type-parent/pippo-snakeyaml/src/main/java/ro/pippo/snakeyaml/SnakeYamlEngine.java index 191c95bd2..f50d9f75c 100644 --- a/pippo-content-type-parent/pippo-snakeyaml/src/main/java/ro/pippo/snakeyaml/SnakeYamlEngine.java +++ b/pippo-content-type-parent/pippo-snakeyaml/src/main/java/ro/pippo/snakeyaml/SnakeYamlEngine.java @@ -48,4 +48,9 @@ public T fromString(String content, Class classOfT) { return (T) new Yaml().load(content); } + @Override + public byte[] toByteArray(Object object) { + return new Yaml().dump(object).getBytes(); + } + } diff --git a/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java b/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java index cecfeb6ad..99cf06c89 100644 --- a/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java +++ b/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java @@ -66,4 +66,9 @@ public T fromString(String content, Class classOfT) { return (T) xstream().fromXML(content); } + @Override + public byte[] toByteArray(Object object) { + return xstream().toXML(object).getBytes(); + } + } diff --git a/pippo-content-type-parent/pom.xml b/pippo-content-type-parent/pom.xml index 4d108b87d..6501c96e1 100644 --- a/pippo-content-type-parent/pom.xml +++ b/pippo-content-type-parent/pom.xml @@ -20,6 +20,7 @@ pippo-gson pippo-jackson pippo-jaxb + pippo-protobuf pippo-snakeyaml pippo-xstream diff --git a/pippo-core/src/main/java/ro/pippo/core/ContentTypeEngine.java b/pippo-core/src/main/java/ro/pippo/core/ContentTypeEngine.java index 962ea946d..7f6e9c02f 100644 --- a/pippo-core/src/main/java/ro/pippo/core/ContentTypeEngine.java +++ b/pippo-core/src/main/java/ro/pippo/core/ContentTypeEngine.java @@ -29,4 +29,5 @@ public interface ContentTypeEngine { T fromString(String content, Class classOfT); + byte[] toByteArray(Object object); } diff --git a/pippo-core/src/main/java/ro/pippo/core/HttpConstants.java b/pippo-core/src/main/java/ro/pippo/core/HttpConstants.java index 4a43650d6..d8635676b 100644 --- a/pippo-core/src/main/java/ro/pippo/core/HttpConstants.java +++ b/pippo-core/src/main/java/ro/pippo/core/HttpConstants.java @@ -120,6 +120,8 @@ public static final class ContentType { public static final String APPLICATION_JSON = "application/json"; public static final String APPLICATION_XML = "application/xml"; public static final String APPLICATION_X_YAML = "application/x-yaml"; + public static final String APPLICATION_PROTOBUF = "application/protobuf"; + public static final String TEXT_HTML = "text/html"; public static final String TEXT_XHTML = "text/xhtml"; public static final String TEXT_PLAIN = "text/plain"; diff --git a/pippo-core/src/main/java/ro/pippo/core/Response.java b/pippo-core/src/main/java/ro/pippo/core/Response.java index a1610c2d1..ea067c200 100644 --- a/pippo-core/src/main/java/ro/pippo/core/Response.java +++ b/pippo-core/src/main/java/ro/pippo/core/Response.java @@ -28,14 +28,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Date; @@ -825,6 +818,13 @@ public Response yaml() { return contentType(HttpConstants.ContentType.APPLICATION_X_YAML); } + /** + * Sets the Response content-type to application/protobuf. + */ + public Response protobuf() { + return contentType(HttpConstants.ContentType.APPLICATION_PROTOBUF); + } + /** * Writes the string content directly to the response. *

This method commits the response.

@@ -921,7 +921,16 @@ private void send(Object object, String contentType) { } header(HttpConstants.Header.CONTENT_TYPE, contentTypeEngine.getContentType()); - send(contentTypeEngine.toString(object)); + + try { + byte[] byteArr = contentTypeEngine.toByteArray(object); + contentLength(byteArr.length); + send(new BufferedInputStream(new ByteArrayInputStream(byteArr))); + } catch (IOException e) { + log.error(e.getMessage()); + log.warn("Sending ContentType toString instead..."); + send(contentTypeEngine.toString(object)); + } } /** diff --git a/pippo-core/src/main/java/ro/pippo/core/TextPlainEngine.java b/pippo-core/src/main/java/ro/pippo/core/TextPlainEngine.java index 8cd1b40db..dda550eb0 100644 --- a/pippo-core/src/main/java/ro/pippo/core/TextPlainEngine.java +++ b/pippo-core/src/main/java/ro/pippo/core/TextPlainEngine.java @@ -55,4 +55,9 @@ public T fromString(String content, Class classOfT) { classOfT.getName()); } + @Override + public byte[] toByteArray(Object object) { + return object.toString().getBytes(); + } + } diff --git a/pippo-core/src/main/java/ro/pippo/core/route/DefaultRouteContext.java b/pippo-core/src/main/java/ro/pippo/core/route/DefaultRouteContext.java index f948532b4..fbe55d561 100644 --- a/pippo-core/src/main/java/ro/pippo/core/route/DefaultRouteContext.java +++ b/pippo-core/src/main/java/ro/pippo/core/route/DefaultRouteContext.java @@ -345,6 +345,13 @@ public RouteContext html() { return this; } + @Override + public RouteContext protobuf(){ + response.protobuf(); + + return this; + } + @Override public RouteContext negotiateContentType() { response.contentType(request); @@ -352,6 +359,13 @@ public RouteContext negotiateContentType() { return this; } + @Override + public RouteContext contentType(String contentType) { + response.contentType(contentType); + + return this; + } + @Override public RouteContext status(int code) { response.status(code); diff --git a/pippo-core/src/main/java/ro/pippo/core/route/RouteContext.java b/pippo-core/src/main/java/ro/pippo/core/route/RouteContext.java index f958bca10..4764c0e45 100644 --- a/pippo-core/src/main/java/ro/pippo/core/route/RouteContext.java +++ b/pippo-core/src/main/java/ro/pippo/core/route/RouteContext.java @@ -146,8 +146,12 @@ public interface RouteContext { RouteContext html(); + RouteContext protobuf(); + RouteContext negotiateContentType(); + RouteContext contentType(String contentType); + RouteContext status(int code); String uriFor(String nameOrUriPattern, Map parameters);