http://git-wip-us.apache.org/repos/asf/ignite/blob/f177d431/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java ---------------------------------------------------------------------- diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java new file mode 100644 index 0000000..f09b583 --- /dev/null +++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java @@ -0,0 +1,259 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.rest.protocols.http.jetty; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; +import com.fasterxml.jackson.databind.ser.SerializerFactory; +import java.io.IOException; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.apache.ignite.internal.LessNamingBean; +import org.apache.ignite.internal.visor.cache.VisorCache; +import org.apache.ignite.internal.visor.util.VisorExceptionWrapper; +import org.apache.ignite.lang.IgniteBiTuple; +import org.apache.ignite.lang.IgniteUuid; + +/** + * Custom object mapper for HTTP REST API. + */ +public class GridJettyObjectMapper extends ObjectMapper { + /** + * Default constructor. + */ + public GridJettyObjectMapper() { + super(null, new CustomSerializerProvider(), null); + + setDateFormat(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US)); + + SimpleModule module = new SimpleModule(); + + module.addSerializer(Throwable.class, THROWABLE_SERIALIZER); + module.addSerializer(IgniteBiTuple.class, IGNITE_TUPLE_SERIALIZER); + module.addSerializer(IgniteUuid.class, IGNITE_UUID_SERIALIZER); + module.addSerializer(LessNamingBean.class, LESS_NAMING_SERIALIZER); + + registerModule(module); + } + + /** Custom {@code null} value serializer. */ + private static final JsonSerializer<Object> NULL_SERIALIZER = new JsonSerializer<Object>() { + /** {@inheritDoc} */ + @Override public void serialize(Object val, JsonGenerator gen, SerializerProvider ser) throws IOException { + gen.writeNull(); + } + }; + + /** Custom {@code null} string serializer. */ + private static final JsonSerializer<Object> NULL_STRING_SERIALIZER = new JsonSerializer<Object>() { + /** {@inheritDoc} */ + @Override public void serialize(Object val, JsonGenerator gen, SerializerProvider ser) throws IOException { + gen.writeString(""); + } + }; + + /** + * Custom serializers provider that provide special serializers for {@code null} values. + */ + private static class CustomSerializerProvider extends DefaultSerializerProvider { + /** + * Default constructor. + */ + CustomSerializerProvider() { + super(); + } + + /** + * Full constructor. + * + * @param src Blueprint object used as the baseline for this instance. + * @param cfg Provider configuration. + * @param f Serializers factory. + */ + CustomSerializerProvider(SerializerProvider src, SerializationConfig cfg, SerializerFactory f) { + super(src, cfg, f); + } + + /** {@inheritDoc} */ + public DefaultSerializerProvider createInstance(SerializationConfig cfg, SerializerFactory jsf) { + return new CustomSerializerProvider(this, cfg, jsf); + } + + /** {@inheritDoc} */ + @Override public JsonSerializer<Object> findNullValueSerializer(BeanProperty prop) throws JsonMappingException { + if (prop.getType().getRawClass() == String.class) + return NULL_STRING_SERIALIZER; + + return NULL_SERIALIZER; + } + } + + /** Custom serializer for {@link Throwable} */ + private static final JsonSerializer<Throwable> THROWABLE_SERIALIZER = new JsonSerializer<Throwable>() { + /** {@inheritDoc} */ + @Override public void serialize(Throwable e, JsonGenerator gen, SerializerProvider ser) throws IOException { + gen.writeStartObject(); + + if (e instanceof VisorExceptionWrapper) { + VisorExceptionWrapper wrapper = (VisorExceptionWrapper)e; + + gen.writeStringField("className", wrapper.getClassName()); + } + else + gen.writeStringField("className", e.getClass().getName()); + + if (e.getMessage() != null) + gen.writeStringField("message", e.getMessage()); + + if (e.getCause() != null) + gen.writeObjectField("cause", e.getCause()); + + if (e instanceof SQLException) { + SQLException sqlE = (SQLException)e; + + gen.writeNumberField("errorCode", sqlE.getErrorCode()); + gen.writeStringField("SQLState", sqlE.getSQLState()); + } + + gen.writeEndObject(); + } + }; + + /** Custom serializer for {@link IgniteUuid} */ + private static final JsonSerializer<IgniteUuid> IGNITE_UUID_SERIALIZER = new JsonSerializer<IgniteUuid>() { + /** {@inheritDoc} */ + @Override public void serialize(IgniteUuid uid, JsonGenerator gen, SerializerProvider ser) throws IOException { + gen.writeString(uid.toString()); + } + }; + + /** Custom serializer for {@link IgniteBiTuple} */ + private static final JsonSerializer<IgniteBiTuple> IGNITE_TUPLE_SERIALIZER = new JsonSerializer<IgniteBiTuple>() { + /** {@inheritDoc} */ + @Override public void serialize(IgniteBiTuple t, JsonGenerator gen, SerializerProvider ser) throws IOException { + gen.writeStartObject(); + + gen.writeObjectField("key", t.getKey()); + gen.writeObjectField("value", t.getValue()); + + gen.writeEndObject(); + } + }; + + /** + * Custom serializer for Visor classes with non JavaBeans getters. + */ + private static final JsonSerializer<Object> LESS_NAMING_SERIALIZER = new JsonSerializer<Object>() { + /** Methods to exclude. */ + private final Collection<String> exclMtds = Arrays.asList("toString", "hashCode", "clone", "getClass"); + + /** */ + private final Map<Class<?>, Collection<Method>> clsCache = new HashMap<>(); + + /** */ + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + + /** {@inheritDoc} */ + @Override public void serialize(Object bean, JsonGenerator gen, SerializerProvider ser) throws IOException { + if (bean != null) { + gen.writeStartObject(); + + Class<?> cls = bean.getClass(); + + Collection<Method> methods; + + // Get descriptor from cache. + rwLock.readLock().lock(); + + try { + methods = clsCache.get(cls); + } + finally { + rwLock.readLock().unlock(); + } + + // If missing in cache - build descriptor + if (methods == null) { + Method[] publicMtds = cls.getMethods(); + + methods = new ArrayList<>(publicMtds.length); + + for (Method mtd : publicMtds) { + Class retType = mtd.getReturnType(); + + String mtdName = mtd.getName(); + + if (mtd.getParameterTypes().length != 0 || + retType == void.class || retType == cls || + exclMtds.contains(mtdName) || + (VisorCache.class.isAssignableFrom(retType) && "history".equals(mtdName))) + continue; + + mtd.setAccessible(true); + + methods.add(mtd); + } + + // Allow multiple puts for the same class - they will simply override. + rwLock.writeLock().lock(); + + try { + clsCache.put(cls, methods); + } + finally { + rwLock.writeLock().unlock(); + } + } + + // Extract fields values using descriptor and build JSONObject. + for (Method mtd : methods) { + try { + Object prop = mtd.invoke(bean); + + if (prop != null) + gen.writeObjectField(mtd.getName(), prop); + } + catch (IOException ioe) { + throw ioe; + } + catch (Exception e) { + throw new IOException(e); + } + } + + gen.writeEndObject(); + } + } + }; +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/f177d431/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java ---------------------------------------------------------------------- diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java index b6a386b..c2679df 100644 --- a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java +++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.processors.rest.protocols.http.jetty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -24,6 +26,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; @@ -34,17 +37,11 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import net.sf.json.JSON; -import net.sf.json.JSONException; -import net.sf.json.JSONSerializer; -import net.sf.json.JsonConfig; -import net.sf.json.processors.JsonValueProcessor; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.processors.rest.GridRestCommand; import org.apache.ignite.internal.processors.rest.GridRestProtocolHandler; import org.apache.ignite.internal.processors.rest.GridRestResponse; -import org.apache.ignite.internal.processors.rest.client.message.GridClientTaskResultBean; import org.apache.ignite.internal.processors.rest.request.DataStructuresRequest; import org.apache.ignite.internal.processors.rest.request.GridRestCacheRequest; import org.apache.ignite.internal.processors.rest.request.GridRestLogRequest; @@ -68,32 +65,30 @@ import static org.apache.ignite.internal.processors.rest.GridRestCommand.EXECUTE import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED; /** - * Jetty REST handler. The following URL format is supported: - * {@code /ignite?cmd=cmdName¶m1=abc¶m2=123} + * Jetty REST handler. The following URL format is supported: {@code /ignite?cmd=cmdName¶m1=abc¶m2=123} */ public class GridJettyRestHandler extends AbstractHandler { - /** JSON value processor that does not transform input object. */ - private static final JsonValueProcessor SKIP_STR_VAL_PROC = new JsonValueProcessor() { - @Override public Object processArrayValue(Object o, JsonConfig jsonConfig) { - return o; - } - - @Override public Object processObjectValue(String s, Object o, JsonConfig jsonConfig) { - return o; - } - }; + /** Used to sent request charset. */ + private static final String CHARSET = StandardCharsets.UTF_8.name(); /** Logger. */ private final IgniteLogger log; + /** Authentication checker. */ private final IgniteClosure<String, Boolean> authChecker; + /** Request handlers. */ private GridRestProtocolHandler hnd; + /** Default page. */ private volatile String dfltPage; + /** Favicon. */ private volatile byte[] favicon; + /** Mapper from Java object to JSON. */ + private final ObjectMapper jsonMapper; + /** * Creates new HTTP requests handler. * @@ -108,6 +103,7 @@ public class GridJettyRestHandler extends AbstractHandler { this.hnd = hnd; this.log = log; this.authChecker = authChecker; + this.jsonMapper = new GridJettyObjectMapper(); // Init default page and favicon. try { @@ -137,14 +133,14 @@ public class GridJettyRestHandler extends AbstractHandler { * @param key Key. * @param params Parameters map. * @param dfltVal Default value. - * @return Long value from parameters map or {@code dfltVal} if null - * or not exists. + * @return Long value from parameters map or {@code dfltVal} if null or not exists. * @throws IgniteCheckedException If parsing failed. */ - @Nullable private static Long longValue(String key, Map<String, Object> params, Long dfltVal) throws IgniteCheckedException { + @Nullable private static Long longValue(String key, Map<String, Object> params, + Long dfltVal) throws IgniteCheckedException { assert key != null; - String val = (String) params.get(key); + String val = (String)params.get(key); try { return val == null ? dfltVal : Long.valueOf(val); @@ -160,17 +156,16 @@ public class GridJettyRestHandler extends AbstractHandler { * @param key Key. * @param params Parameters map. * @param dfltVal Default value. - * @return Integer value from parameters map or {@code dfltVal} if null - * or not exists. + * @return Integer value from parameters map or {@code dfltVal} if null or not exists. * @throws IgniteCheckedException If parsing failed. */ - @Nullable private static Integer intValue(String key, Map<String, Object> params, Integer dfltVal) throws IgniteCheckedException { + private static int intValue(String key, Map<String, Object> params, int dfltVal) throws IgniteCheckedException { assert key != null; - String val = (String) params.get(key); + String val = (String)params.get(key); try { - return val == null ? dfltVal : Integer.valueOf(val); + return val == null ? dfltVal : Integer.parseInt(val); } catch (NumberFormatException ignore) { throw new IgniteCheckedException("Failed to parse parameter of Integer type [" + key + "=" + val + "]"); @@ -182,14 +177,13 @@ public class GridJettyRestHandler extends AbstractHandler { * * @param key Key. * @param params Parameters map. - * @return UUID value from parameters map or {@code null} if null - * or not exists. + * @return UUID value from parameters map or {@code null} if null or not exists. * @throws IgniteCheckedException If parsing failed. */ @Nullable private static UUID uuidValue(String key, Map<String, Object> params) throws IgniteCheckedException { assert key != null; - String val = (String) params.get(key); + String val = (String)params.get(key); try { return val == null ? null : UUID.fromString(val); @@ -208,7 +202,7 @@ public class GridJettyRestHandler extends AbstractHandler { InputStream in = getClass().getResourceAsStream("rest.html"); if (in != null) { - LineNumberReader rdr = new LineNumberReader(new InputStreamReader(in)); + LineNumberReader rdr = new LineNumberReader(new InputStreamReader(in, CHARSET)); try { StringBuilder buf = new StringBuilder(2048); @@ -217,7 +211,7 @@ public class GridJettyRestHandler extends AbstractHandler { buf.append(line); if (!line.endsWith(" ")) - buf.append(" "); + buf.append(' '); } dfltPage = buf.toString(); @@ -368,39 +362,35 @@ public class GridJettyRestHandler extends AbstractHandler { throw (Error)e; } - JsonConfig cfg = new GridJettyJsonConfig(log); - - // Workaround for not needed transformation of string into JSON object. - if (cmdRes.getResponse() instanceof String) - cfg.registerJsonValueProcessor(cmdRes.getClass(), "response", SKIP_STR_VAL_PROC); - - // Workaround for not needed transformation of result field string into JSON object at GridClientTaskResultBean. - if (cmdRes.getResponse() instanceof GridClientTaskResultBean - && ((GridClientTaskResultBean)cmdRes.getResponse()).getResult() instanceof String) - cfg.registerJsonValueProcessor(cmdRes.getResponse().getClass(), "result", SKIP_STR_VAL_PROC); - - JSON json; + String json; try { - json = JSONSerializer.toJSON(cmdRes, cfg); + json = jsonMapper.writeValueAsString(cmdRes); } - catch (JSONException e) { - U.error(log, "Failed to convert response to JSON: " + cmdRes, e); + catch (JsonProcessingException e1) { + U.error(log, "Failed to convert response to JSON: " + cmdRes, e1); + + GridRestResponse resFailed = new GridRestResponse(STATUS_FAILED, e1.getMessage()); - json = JSONSerializer.toJSON(new GridRestResponse(STATUS_FAILED, e.getMessage()), cfg); + try { + json = jsonMapper.writeValueAsString(resFailed); + } + catch (JsonProcessingException e2) { + json = "{\"successStatus\": \"1\", \"error:\" \"" + e2.getMessage() + "\"}}"; + } } try { if (log.isDebugEnabled()) - log.debug("Parsed command response into JSON object: " + json.toString(2)); + log.debug("Parsed command response into JSON object: " + json); - res.getWriter().write(json.toString()); + res.getWriter().write(json); if (log.isDebugEnabled()) log.debug("Processed HTTP request [action=" + act + ", jsonRes=" + cmdRes + ", req=" + req + ']'); } catch (IOException e) { - U.error(log, "Failed to send HTTP response: " + json.toString(2), e); + U.error(log, "Failed to send HTTP response: " + json, e); } } @@ -510,10 +500,10 @@ public class GridJettyRestHandler extends AbstractHandler { case NODE: { GridRestTopologyRequest restReq0 = new GridRestTopologyRequest(); - restReq0.includeMetrics(Boolean.parseBoolean((String) params.get("mtr"))); - restReq0.includeAttributes(Boolean.parseBoolean((String) params.get("attr"))); + restReq0.includeMetrics(Boolean.parseBoolean((String)params.get("mtr"))); + restReq0.includeAttributes(Boolean.parseBoolean((String)params.get("attr"))); - restReq0.nodeIp((String) params.get("ip")); + restReq0.nodeIp((String)params.get("ip")); restReq0.nodeId(uuidValue("id", params)); @@ -527,12 +517,12 @@ public class GridJettyRestHandler extends AbstractHandler { case NOOP: { GridRestTaskRequest restReq0 = new GridRestTaskRequest(); - restReq0.taskId((String) params.get("id")); - restReq0.taskName((String) params.get("name")); + restReq0.taskId((String)params.get("id")); + restReq0.taskName((String)params.get("name")); restReq0.params(values("p", params)); - restReq0.async(Boolean.parseBoolean((String) params.get("async"))); + restReq0.async(Boolean.parseBoolean((String)params.get("async"))); restReq0.timeout(longValue("timeout", params, 0L)); @@ -544,7 +534,7 @@ public class GridJettyRestHandler extends AbstractHandler { case LOG: { GridRestLogRequest restReq0 = new GridRestLogRequest(); - restReq0.path((String) params.get("path")); + restReq0.path((String)params.get("path")); restReq0.from(intValue("from", params, -1)); restReq0.to(intValue("to", params, -1)); @@ -569,9 +559,9 @@ public class GridJettyRestHandler extends AbstractHandler { restReq0.arguments(values("arg", params).toArray()); - restReq0.typeName((String) params.get("type")); + restReq0.typeName((String)params.get("type")); - String pageSize = (String) params.get("pageSize"); + String pageSize = (String)params.get("pageSize"); if (pageSize != null) restReq0.pageSize(Integer.parseInt(pageSize)); @@ -612,7 +602,7 @@ public class GridJettyRestHandler extends AbstractHandler { case FETCH_SQL_QUERY: { RestQueryRequest restReq0 = new RestQueryRequest(); - String qryId = (String) params.get("qryId"); + String qryId = (String)params.get("qryId"); if (qryId != null) restReq0.queryId(Long.parseLong(qryId)); @@ -632,7 +622,7 @@ public class GridJettyRestHandler extends AbstractHandler { case CLOSE_SQL_QUERY: { RestQueryRequest restReq0 = new RestQueryRequest(); - String qryId = (String) params.get("qryId"); + String qryId = (String)params.get("qryId"); if (qryId != null) restReq0.queryId(Long.parseLong(qryId)); @@ -659,7 +649,7 @@ public class GridJettyRestHandler extends AbstractHandler { restReq.credentials(cred); } - String clientId = (String) params.get("clientId"); + String clientId = (String)params.get("clientId"); try { if (clientId != null) @@ -669,7 +659,7 @@ public class GridJettyRestHandler extends AbstractHandler { // Ignore invalid client id. Rest handler will process this logic. } - String destId = (String) params.get("destId"); + String destId = (String)params.get("destId"); try { if (destId != null) @@ -679,7 +669,7 @@ public class GridJettyRestHandler extends AbstractHandler { // Don't fail - try to execute locally. } - String sesTokStr = (String) params.get("sessionToken"); + String sesTokStr = (String)params.get("sessionToken"); try { if (sesTokStr != null) @@ -699,7 +689,7 @@ public class GridJettyRestHandler extends AbstractHandler { * @param params Parameters map. * @return Values. */ - @Nullable protected List<Object> values(String keyPrefix, Map<String, Object> params) { + protected List<Object> values(String keyPrefix, Map<String, Object> params) { assert keyPrefix != null; List<Object> vals = new LinkedList<>(); @@ -720,15 +710,14 @@ public class GridJettyRestHandler extends AbstractHandler { * @param req Request. * @return Command. */ - @Nullable GridRestCommand command(ServletRequest req) { + @Nullable private GridRestCommand command(ServletRequest req) { String cmd = req.getParameter("cmd"); return cmd == null ? null : GridRestCommand.fromKey(cmd.toLowerCase()); } /** - * Parses HTTP parameters in an appropriate format and return back map of - * values to predefined list of names. + * Parses HTTP parameters in an appropriate format and return back map of values to predefined list of names. * * @param req Request. * @return Map of parsed parameters. http://git-wip-us.apache.org/repos/asf/ignite/blob/f177d431/parent/pom.xml ---------------------------------------------------------------------- diff --git a/parent/pom.xml b/parent/pom.xml index c493248..d78deac 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -74,6 +74,7 @@ <httpclient.version>4.5.1</httpclient.version> <httpcore.version>4.4.3</httpcore.version> <jackson.version>1.9.13</jackson.version> + <jackson2.version>2.7.5</jackson2.version> <javax.cache.bundle.version>1.0.0_1</javax.cache.bundle.version> <javax.cache.version>1.0.0</javax.cache.version> <jetty.version>9.2.11.v20150529</jetty.version>
