/*
* Copyright 2008-2014 by Emeric Vernat
*
* This file is part of Java Melody.
*
* 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 net.bull.javamelody;
import java.awt.datatransfer.DataFlavor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.util.Locale;
import java.util.Map;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.collections.MapConverter;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.xml.CompactWriter;
/**
* Liste des formats de transport entre un serveur de collecte et une application monitorée
* (hors protocole à priori http).
* @author Emeric Vernat
*/
enum TransportFormat {
/**
* Sérialisation java.
*/
SERIALIZED(DataFlavor.javaSerializedObjectMimeType),
/**
* XML (avec XStream/XPP).
* <br/>Selon http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking?ts=1237772203&updated=Benchmarking,
* la sérialisation java est 75% plus performante en temps que xml (xstream/xpp)
* et à peine plus gourmande en taille de flux.
*/
XML("text/xml; charset=utf-8"),
/**
* JSON (écriture en JSON avec XStream).
* Note : il serait possible aussi de le faire avec Jackson (http://jackson.codehaus.org/)
*/
JSON("application/json");
private static final String NULL_VALUE = "null";
// classe interne pour qu'elle ne soit pas chargée avec la classe TransportFormat
// et qu'ainsi on ne dépende pas de XStream si on ne se sert pas du format xml
private static final class XmlIO {
private static final String XML_CHARSET_NAME = "utf-8";
private XmlIO() {
super();
}
static void writeToXml(Serializable serializable, BufferedOutputStream bufferedOutput)
throws IOException {
final XStream xstream = createXStream(false);
// on wrappe avec un CompactWriter pour gagner 25% en taille de flux (retours chariots)
// et donc un peu en performances
final CompactWriter writer = new CompactWriter(new OutputStreamWriter(bufferedOutput,
XML_CHARSET_NAME));
try {
xstream.marshal(serializable, writer);
} finally {
writer.close();
}
}
static Object readFromXml(InputStream bufferedInput) throws IOException {
final XStream xstream = createXStream(false);
final InputStreamReader reader = new InputStreamReader(bufferedInput, XML_CHARSET_NAME);
try {
return xstream.fromXML(reader);
} finally {
reader.close();
}
}
static void writeToJson(Serializable serializable, BufferedOutputStream bufferedOutput)
throws IOException {
final XStream xstream = createXStream(true);
try {
xstream.toXML(serializable, bufferedOutput);
} finally {
bufferedOutput.close();
}
}
private static XStream createXStream(boolean json) {
final XStream xstream;
if (json) {
// format json
xstream = new XStream(new JsonHierarchicalStreamDriver());
xstream.setMode(XStream.NO_REFERENCES);
} else {
// sinon format xml, utilise la dépendance XPP3 par défaut
xstream = new XStream();
}
for (final Map.Entry<String, Class<?>> entry : XStreamAlias.getMap().entrySet()) {
xstream.alias(entry.getKey(), entry.getValue());
}
final MapConverter mapConverter = new MapConverter(xstream.getMapper()) {
/** {@inheritDoc} */
@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(Class type) {
return true; // Counter.requests est bien une map
}
};
xstream.registerLocalConverter(Counter.class, "requests", mapConverter);
xstream.registerLocalConverter(Counter.class, "rootCurrentContextsByThreadId",
mapConverter);
return xstream;
}
}
private final String code;
private final String mimeType;
private TransportFormat(String mimeType) {
this.mimeType = mimeType;
this.code = this.toString().toLowerCase(Locale.ENGLISH);
}
static TransportFormat valueOfIgnoreCase(String transportFormat) {
return valueOf(transportFormat.toUpperCase(Locale.ENGLISH).trim());
}
static boolean isATransportFormat(String format) {
if (format == null) {
return false;
}
final String upperCase = format.toUpperCase(Locale.ENGLISH).trim();
for (final TransportFormat transportFormat : TransportFormat.values()) {
if (transportFormat.toString().equals(upperCase)) {
return true;
}
}
return false;
}
static void pump(InputStream input, OutputStream output) throws IOException {
final byte[] bytes = new byte[4 * 1024];
int length = input.read(bytes);
while (length != -1) {
output.write(bytes, 0, length);
length = input.read(bytes);
}
}
String getCode() {
return code;
}
String getMimeType() {
return mimeType;
}
void checkDependencies() {
if (this == XML || this == JSON) {
try {
Class.forName("com.thoughtworks.xstream.XStream");
} catch (final ClassNotFoundException e) {
throw new RuntimeException(
"Classes of the XStream library not found. Add the XStream dependency in your webapp for the XML or JSON formats.",
e);
}
}
if (this == XML) {
try {
Class.forName("org.xmlpull.v1.XmlPullParser");
} catch (final ClassNotFoundException e) {
throw new RuntimeException(
"Classes of the XPP3 library not found. Add the XPP3 dependency in your webapp for the XML format.",
e);
}
}
}
void writeSerializableTo(Serializable serializable, OutputStream output) throws IOException {
final Serializable nonNullSerializable;
if (serializable == null) {
nonNullSerializable = NULL_VALUE;
} else {
nonNullSerializable = serializable;
}
final BufferedOutputStream bufferedOutput = new BufferedOutputStream(output);
switch (this) {
case SERIALIZED:
final ObjectOutputStream out = new ObjectOutputStream(bufferedOutput);
try {
out.writeObject(nonNullSerializable);
} finally {
out.close();
}
break;
case XML:
// Rq : sans xstream et si jdk 1.6, on pourrait sinon utiliser
// XMLStreamWriter et XMLStreamReader ou JAXB avec des annotations
XmlIO.writeToXml(nonNullSerializable, bufferedOutput);
break;
case JSON:
XmlIO.writeToJson(nonNullSerializable, bufferedOutput);
break;
default:
throw new IllegalStateException(toString());
}
}
Serializable readSerializableFrom(InputStream input) throws IOException, ClassNotFoundException {
final InputStream bufferedInput = new BufferedInputStream(input);
Object result;
switch (this) {
case SERIALIZED:
final ObjectInputStream in = new ObjectInputStream(bufferedInput);
try {
result = in.readObject();
} finally {
in.close();
}
break;
case XML:
result = XmlIO.readFromXml(bufferedInput);
break;
case JSON:
// pas possible avec JsonHierarchicalStreamDriver
// (http://xstream.codehaus.org/json-tutorial.html)
throw new UnsupportedOperationException();
default:
throw new IllegalStateException(toString());
}
if (NULL_VALUE.equals(result)) {
result = null;
}
// c'est un Serializable que l'on a écrit
return (Serializable) result;
}
}