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 */ 019package org.apache.isis.viewer.restfulobjects.applib.util; 020 021import java.io.IOException; 022import java.lang.reflect.Constructor; 023import java.util.ArrayList; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027 028import javax.ws.rs.core.Response; 029 030import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 031import org.codehaus.jackson.JsonGenerationException; 032import org.codehaus.jackson.JsonGenerator; 033import org.codehaus.jackson.JsonNode; 034import org.codehaus.jackson.JsonParseException; 035import org.codehaus.jackson.JsonParser; 036import org.codehaus.jackson.JsonProcessingException; 037import org.codehaus.jackson.Version; 038import org.codehaus.jackson.map.BeanProperty; 039import org.codehaus.jackson.map.DeserializationConfig; 040import org.codehaus.jackson.map.DeserializationContext; 041import org.codehaus.jackson.map.DeserializerProvider; 042import org.codehaus.jackson.map.JsonDeserializer; 043import org.codehaus.jackson.map.JsonMappingException; 044import org.codehaus.jackson.map.JsonSerializer; 045import org.codehaus.jackson.map.ObjectMapper; 046import org.codehaus.jackson.map.SerializationConfig; 047import org.codehaus.jackson.map.SerializerProvider; 048import org.codehaus.jackson.map.deser.BeanDeserializerFactory; 049import org.codehaus.jackson.map.deser.JsonNodeDeserializer; 050import org.codehaus.jackson.map.deser.StdDeserializerProvider; 051import org.codehaus.jackson.map.module.SimpleModule; 052import org.codehaus.jackson.type.JavaType; 053import org.jboss.resteasy.client.ClientResponse; 054 055public final class JsonMapper { 056 057 /** 058 * Provides polymorphic deserialization to any subtype of 059 * {@link JsonRepresentation}. 060 */ 061 @SuppressWarnings("deprecation") 062 private static final class JsonRepresentationDeserializerFactory extends BeanDeserializerFactory { 063 @Override 064 public JsonDeserializer<Object> createBeanDeserializer(final DeserializationConfig config, final DeserializerProvider p, final JavaType type, final BeanProperty property) throws JsonMappingException { 065 final Class<?> rawClass = type.getRawClass(); 066 if (JsonRepresentation.class.isAssignableFrom(rawClass)) { 067 try { 068 // ensure has a constructor taking a JsonNode 069 final Constructor<?> rawClassConstructor = rawClass.getConstructor(JsonNode.class); 070 return new JsonRepresentationDeserializer(rawClassConstructor); 071 } catch (final SecurityException e) { 072 // fall through 073 } catch (final NoSuchMethodException e) { 074 // fall through 075 } 076 } 077 return super.createBeanDeserializer(config, p, type, property); 078 } 079 080 private static final class JsonRepresentationDeserializer extends JsonDeserializer<Object> { 081 private final JsonDeserializer<? extends JsonNode> jsonNodeDeser = JsonNodeDeserializer.getDeserializer(JsonNode.class); 082 083 private final Constructor<?> rawClassConstructor; 084 085 public JsonRepresentationDeserializer(final Constructor<?> rawClassConstructor) { 086 this.rawClassConstructor = rawClassConstructor; 087 } 088 089 @Override 090 public JsonRepresentation deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException { 091 final JsonNode jsonNode = jsonNodeDeser.deserialize(jp, ctxt); 092 try { 093 return (JsonRepresentation) rawClassConstructor.newInstance(jsonNode); 094 } catch (final Exception e) { 095 throw new IllegalStateException(e); 096 } 097 } 098 } 099 } 100 101 private static class JsonRepresentationSerializer extends JsonSerializer<Object> { 102 @Override 103 public void serialize(final Object value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { 104 final JsonRepresentation jsonRepresentation = (JsonRepresentation) value; 105 final JsonNode jsonNode = jsonRepresentation.asJsonNode(); 106 jgen.writeTree(jsonNode); 107 } 108 } 109 110 private static ObjectMapper createObjectMapper() { 111 // it's a shame that the serialization and deserialization mechanism 112 // used aren't symmetric... but it works. 113 final DeserializerProvider deserializerProvider = new StdDeserializerProvider(new JsonRepresentationDeserializerFactory()); 114 final ObjectMapper objectMapper = new ObjectMapper(null, null, deserializerProvider); 115 final SimpleModule jsonModule = new SimpleModule("json", new Version(1, 0, 0, null)); 116 jsonModule.addSerializer(JsonRepresentation.class, new JsonRepresentationSerializer()); 117 objectMapper.registerModule(jsonModule); 118 119 objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); 120 objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); 121 return objectMapper; 122 } 123 124 private static JsonMapper instance = new JsonMapper(); 125 126 // threadsafe 127 public final static JsonMapper instance() { 128 return instance; 129 } 130 131 private final ObjectMapper objectMapper; 132 133 private JsonMapper() { 134 objectMapper = createObjectMapper(); 135 } 136 137 @SuppressWarnings("unchecked") 138 public Map<String, Object> readAsMap(final String json) throws JsonParseException, JsonMappingException, IOException { 139 return read(json, LinkedHashMap.class); 140 } 141 142 public List<?> readAsList(final String json) throws JsonParseException, JsonMappingException, IOException { 143 return read(json, ArrayList.class); 144 } 145 146 public JsonRepresentation read(final String json) throws JsonParseException, JsonMappingException, IOException { 147 return read(json, JsonRepresentation.class); 148 } 149 150 public <T> T read(final String json, final Class<T> requiredType) throws JsonParseException, JsonMappingException, IOException { 151 return objectMapper.readValue(json, requiredType); 152 } 153 154 public <T> T read(final Response response, final Class<T> requiredType) throws JsonParseException, JsonMappingException, IOException { 155 final ClientResponse<?> clientResponse = (ClientResponse<?>) response; // a 156 // shame, 157 // but 158 // needed 159 // if 160 // calling 161 // resources 162 // directly 163 final Object entityObj = clientResponse.getEntity(String.class); 164 if (entityObj == null) { 165 return null; 166 } 167 if (!(entityObj instanceof String)) { 168 throw new IllegalArgumentException("response entity must be a String (was " + entityObj.getClass().getName() + ")"); 169 } 170 final String entity = (String) entityObj; 171 172 return read(entity, requiredType); 173 } 174 175 public String write(final Object object) throws JsonGenerationException, JsonMappingException, IOException { 176 return objectMapper.writeValueAsString(object); 177 } 178 179}