001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.isis.viewer.restfulobjects.applib.util;
020
021 import java.io.IOException;
022 import java.lang.reflect.Constructor;
023 import java.util.ArrayList;
024 import java.util.LinkedHashMap;
025 import java.util.List;
026 import java.util.Map;
027
028 import javax.ws.rs.core.Response;
029
030 import org.codehaus.jackson.JsonGenerationException;
031 import org.codehaus.jackson.JsonGenerator;
032 import org.codehaus.jackson.JsonNode;
033 import org.codehaus.jackson.JsonParseException;
034 import org.codehaus.jackson.JsonParser;
035 import org.codehaus.jackson.JsonProcessingException;
036 import org.codehaus.jackson.Version;
037 import org.codehaus.jackson.map.BeanProperty;
038 import org.codehaus.jackson.map.DeserializationConfig;
039 import org.codehaus.jackson.map.DeserializationContext;
040 import org.codehaus.jackson.map.DeserializerProvider;
041 import org.codehaus.jackson.map.JsonDeserializer;
042 import org.codehaus.jackson.map.JsonMappingException;
043 import org.codehaus.jackson.map.JsonSerializer;
044 import org.codehaus.jackson.map.ObjectMapper;
045 import org.codehaus.jackson.map.SerializationConfig;
046 import org.codehaus.jackson.map.SerializerProvider;
047 import org.codehaus.jackson.map.deser.BeanDeserializerFactory;
048 import org.codehaus.jackson.map.deser.JsonNodeDeserializer;
049 import org.codehaus.jackson.map.deser.StdDeserializerProvider;
050 import org.codehaus.jackson.map.module.SimpleModule;
051 import org.codehaus.jackson.type.JavaType;
052 import org.jboss.resteasy.client.ClientResponse;
053
054 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
055
056 public final class JsonMapper {
057
058 /**
059 * Provides polymorphic deserialization to any subtype of
060 * {@link JsonRepresentation}.
061 */
062 @SuppressWarnings("deprecation")
063 private static final class JsonRepresentationDeserializerFactory extends BeanDeserializerFactory {
064 @Override
065 public JsonDeserializer<Object> createBeanDeserializer(final DeserializationConfig config, final DeserializerProvider p, final JavaType type, final BeanProperty property) throws JsonMappingException {
066 final Class<?> rawClass = type.getRawClass();
067 if (JsonRepresentation.class.isAssignableFrom(rawClass)) {
068 try {
069 // ensure has a constructor taking a JsonNode
070 final Constructor<?> rawClassConstructor = rawClass.getConstructor(JsonNode.class);
071 return new JsonRepresentationDeserializer(rawClassConstructor);
072 } catch (final SecurityException e) {
073 // fall through
074 } catch (final NoSuchMethodException e) {
075 // fall through
076 }
077 }
078 return super.createBeanDeserializer(config, p, type, property);
079 }
080
081 private static final class JsonRepresentationDeserializer extends JsonDeserializer<Object> {
082 private final JsonDeserializer<? extends JsonNode> jsonNodeDeser = JsonNodeDeserializer.getDeserializer(JsonNode.class);
083
084 private final Constructor<?> rawClassConstructor;
085
086 public JsonRepresentationDeserializer(final Constructor<?> rawClassConstructor) {
087 this.rawClassConstructor = rawClassConstructor;
088 }
089
090 @Override
091 public JsonRepresentation deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
092 final JsonNode jsonNode = jsonNodeDeser.deserialize(jp, ctxt);
093 try {
094 return (JsonRepresentation) rawClassConstructor.newInstance(jsonNode);
095 } catch (final Exception e) {
096 throw new IllegalStateException(e);
097 }
098 }
099 }
100 }
101
102 private static class JsonRepresentationSerializer extends JsonSerializer<Object> {
103 @Override
104 public void serialize(final Object value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException {
105 final JsonRepresentation jsonRepresentation = (JsonRepresentation) value;
106 final JsonNode jsonNode = jsonRepresentation.asJsonNode();
107 jgen.writeTree(jsonNode);
108 }
109 }
110
111 private static ObjectMapper createObjectMapper() {
112 // it's a shame that the serialization and deserialization mechanism
113 // used aren't symmetric... but it works.
114 final DeserializerProvider deserializerProvider = new StdDeserializerProvider(new JsonRepresentationDeserializerFactory());
115 final ObjectMapper objectMapper = new ObjectMapper(null, null, deserializerProvider);
116 final SimpleModule jsonModule = new SimpleModule("json", new Version(1, 0, 0, null));
117 jsonModule.addSerializer(JsonRepresentation.class, new JsonRepresentationSerializer());
118 objectMapper.registerModule(jsonModule);
119
120 objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
121 objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
122 return objectMapper;
123 }
124
125 private static JsonMapper instance = new JsonMapper();
126
127 // threadsafe
128 public final static JsonMapper instance() {
129 return instance;
130 }
131
132 private final ObjectMapper objectMapper;
133
134 private JsonMapper() {
135 objectMapper = createObjectMapper();
136 }
137
138 @SuppressWarnings("unchecked")
139 public Map<String, Object> readAsMap(final String json) throws JsonParseException, JsonMappingException, IOException {
140 return read(json, LinkedHashMap.class);
141 }
142
143 public List<?> readAsList(final String json) throws JsonParseException, JsonMappingException, IOException {
144 return read(json, ArrayList.class);
145 }
146
147 public JsonRepresentation read(final String json) throws JsonParseException, JsonMappingException, IOException {
148 return read(json, JsonRepresentation.class);
149 }
150
151 public <T> T read(final String json, final Class<T> requiredType) throws JsonParseException, JsonMappingException, IOException {
152 return objectMapper.readValue(json, requiredType);
153 }
154
155 public <T> T read(final Response response, final Class<T> requiredType) throws JsonParseException, JsonMappingException, IOException {
156 final ClientResponse<?> clientResponse = (ClientResponse<?>) response; // a
157 // shame,
158 // but
159 // needed
160 // if
161 // calling
162 // resources
163 // directly
164 final Object entityObj = clientResponse.getEntity(String.class);
165 if (entityObj == null) {
166 return null;
167 }
168 if (!(entityObj instanceof String)) {
169 throw new IllegalArgumentException("response entity must be a String (was " + entityObj.getClass().getName() + ")");
170 }
171 final String entity = (String) entityObj;
172
173 return read(entity, requiredType);
174 }
175
176 public String write(final Object object) throws JsonGenerationException, JsonMappingException, IOException {
177 return objectMapper.writeValueAsString(object);
178 }
179
180 }