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.rendering.domainobjects; 020 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.Arrays; 024import java.util.Date; 025import java.util.List; 026import java.util.Map; 027 028import com.google.common.base.Function; 029import com.google.common.collect.Iterables; 030import com.google.common.collect.Lists; 031import com.google.common.collect.Maps; 032 033import org.codehaus.jackson.node.NullNode; 034import org.joda.time.LocalDate; 035import org.joda.time.LocalDateTime; 036import org.joda.time.format.DateTimeFormat; 037import org.joda.time.format.DateTimeFormatter; 038 039import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 040import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; 041import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet; 042import org.apache.isis.core.metamodel.spec.ObjectSpecId; 043import org.apache.isis.core.metamodel.spec.ObjectSpecification; 044import org.apache.isis.core.runtime.system.context.IsisContext; 045import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 046 047/** 048 * Similar to Isis' value encoding, but with additional support for JSON 049 * primitives. 050 */ 051public final class JsonValueEncoder { 052 053 private JsonValueEncoder(){} 054 055 056 public static class ExpectedStringRepresentingValueException extends IllegalArgumentException { 057 private static final long serialVersionUID = 1L; 058 } 059 060 public static abstract class JsonValueConverter { 061 062 protected final String format; 063 protected final String xIsisFormat; 064 private final Class<?>[] classes; 065 066 public JsonValueConverter(String format, String xIsisFormat, Class<?>... classes) { 067 this.format = format; 068 this.xIsisFormat = xIsisFormat; 069 this.classes = classes; 070 } 071 072 public List<ObjectSpecId> getSpecIds() { 073 return Lists.newArrayList(Iterables.transform(Arrays.asList(classes), new Function<Class<?>, ObjectSpecId>() { 074 public ObjectSpecId apply(Class<?> cls) { 075 return new ObjectSpecId(cls.getName()); 076 } 077 })); 078 } 079 080 /** 081 * The value, otherwise <tt>null</tt>. 082 */ 083 public abstract ObjectAdapter asAdapter(JsonRepresentation repr); 084 085 public void appendValueAndFormat(ObjectAdapter objectAdapter, JsonRepresentation repr) { 086 append(repr, objectAdapter, format, xIsisFormat); 087 } 088 089 public Object asObject(ObjectAdapter objectAdapter) { 090 return objectAdapter.getObject(); 091 } 092 } 093 094 private static Map<ObjectSpecId, JsonValueConverter> converterBySpec = Maps.newLinkedHashMap(); 095 096 private static void putConverter(JsonValueConverter jvc) { 097 final List<ObjectSpecId> specIds = jvc.getSpecIds(); 098 for (ObjectSpecId specId : specIds) { 099 converterBySpec.put(specId, jvc); 100 } 101 } 102 103 static { 104 putConverter(new JsonValueConverter(null, "boolean", boolean.class, Boolean.class){ 105 @Override 106 public ObjectAdapter asAdapter(JsonRepresentation repr) { 107 if (repr.isBoolean()) { 108 return adapterFor(repr.asBoolean()); 109 } 110 return null; 111 } 112 }); 113 114 putConverter(new JsonValueConverter(null, "byte", byte.class, Byte.class){ 115 @Override 116 public ObjectAdapter asAdapter(JsonRepresentation repr) { 117 if (repr.isNumber()) { 118 return adapterFor(repr.asNumber().byteValue()); 119 } 120 if (repr.isInt()) { 121 return adapterFor((byte)(int)repr.asInt()); 122 } 123 if (repr.isLong()) { 124 return adapterFor((byte)(long)repr.asLong()); 125 } 126 if (repr.isBigInteger()) { 127 return adapterFor(repr.asBigInteger().byteValue()); 128 } 129 return null; 130 } 131 }); 132 133 putConverter(new JsonValueConverter(null, "short", short.class, Short.class){ 134 @Override 135 public ObjectAdapter asAdapter(JsonRepresentation repr) { 136 if (repr.isNumber()) { 137 return adapterFor(repr.asNumber().shortValue()); 138 } 139 if (repr.isInt()) { 140 return adapterFor((short)(int)repr.asInt()); 141 } 142 if (repr.isLong()) { 143 return adapterFor((short)(long)repr.asLong()); 144 } 145 if (repr.isBigInteger()) { 146 return adapterFor(repr.asBigInteger().shortValue()); 147 } 148 return null; 149 } 150 }); 151 152 putConverter(new JsonValueConverter("int", "int", int.class, Integer.class){ 153 @Override 154 public ObjectAdapter asAdapter(JsonRepresentation repr) { 155 if (repr.isInt()) { 156 return adapterFor(repr.asInt()); 157 } 158 if (repr.isLong()) { 159 return adapterFor((int)(long)repr.asLong()); 160 } 161 if (repr.isBigInteger()) { 162 return adapterFor(repr.asBigInteger().intValue()); 163 } 164 if (repr.isNumber()) { 165 return adapterFor(repr.asNumber().intValue()); 166 } 167 return null; 168 } 169 }); 170 171 putConverter(new JsonValueConverter("int", "long", long.class, Long.class){ 172 @Override 173 public ObjectAdapter asAdapter(JsonRepresentation repr) { 174 if (repr.isLong()) { 175 return adapterFor(repr.asLong()); 176 } 177 if (repr.isInt()) { 178 return adapterFor(repr.asInt()); 179 } 180 if (repr.isBigInteger()) { 181 return adapterFor(repr.asBigInteger().longValue()); 182 } 183 if (repr.isNumber()) { 184 return adapterFor(repr.asNumber().longValue()); 185 } 186 return null; 187 } 188 }); 189 190 putConverter(new JsonValueConverter("decimal", "float", float.class, Float.class){ 191 @Override 192 public ObjectAdapter asAdapter(JsonRepresentation repr) { 193 if (repr.isDouble()) { 194 return adapterFor((float)(double)repr.asDouble()); 195 } 196 if (repr.isNumber()) { 197 return adapterFor(repr.asNumber().floatValue()); 198 } 199 if (repr.isLong()) { 200 return adapterFor((float)repr.asLong()); 201 } 202 if (repr.isInt()) { 203 return adapterFor((float)repr.asInt()); 204 } 205 if (repr.isBigInteger()) { 206 return adapterFor(repr.asBigInteger().floatValue()); 207 } 208 return null; 209 } 210 }); 211 212 putConverter(new JsonValueConverter("decimal", "double", double.class, Double.class){ 213 @Override 214 public ObjectAdapter asAdapter(JsonRepresentation repr) { 215 if (repr.isDouble()) { 216 return adapterFor(repr.asDouble()); 217 } 218 if (repr.isLong()) { 219 return adapterFor((double)repr.asLong()); 220 } 221 if (repr.isInt()) { 222 return adapterFor((double)repr.asInt()); 223 } 224 if (repr.isBigInteger()) { 225 return adapterFor(repr.asBigInteger().doubleValue()); 226 } 227 if (repr.isBigDecimal()) { 228 return adapterFor(repr.asBigDecimal().doubleValue()); 229 } 230 if (repr.isNumber()) { 231 return adapterFor(repr.asNumber().doubleValue()); 232 } 233 return null; 234 } 235 }); 236 237 putConverter(new JsonValueConverter(null, "char", char.class, Character.class){ 238 @Override 239 public ObjectAdapter asAdapter(JsonRepresentation repr) { 240 if (repr.isString()) { 241 final String str = repr.asString(); 242 if(str != null && str.length()>0) { 243 return adapterFor(str.charAt(0)); 244 } 245 } 246 return null; 247 } 248 }); 249 250 putConverter(new JsonValueConverter("int", "biginteger", BigInteger.class){ 251 @Override 252 public ObjectAdapter asAdapter(JsonRepresentation repr) { 253 if (repr.isBigInteger()) { 254 return adapterFor(repr.asBigInteger()); 255 } 256 if (repr.isLong()) { 257 return adapterFor(BigInteger.valueOf(repr.asLong())); 258 } 259 if (repr.isInt()) { 260 return adapterFor(BigInteger.valueOf(repr.asInt())); 261 } 262 if (repr.isNumber()) { 263 return adapterFor(BigInteger.valueOf(repr.asNumber().longValue())); 264 } 265 return null; 266 } 267 }); 268 269 putConverter(new JsonValueConverter("decimal", "bigdecimal", BigDecimal.class){ 270 @Override 271 public ObjectAdapter asAdapter(JsonRepresentation repr) { 272 // TODO: if inferring a BigDecimal, need to get the scale from somewhere... 273 if (repr.isBigDecimal()) { 274 return adapterFor(repr.asBigDecimal()); 275 } 276 if (repr.isBigInteger()) { 277 return adapterFor(new BigDecimal(repr.asBigInteger())); 278 } 279 if (repr.isDouble()) { 280 return adapterFor(BigDecimal.valueOf(repr.asDouble())); 281 } 282 if (repr.isLong()) { 283 return adapterFor(BigDecimal.valueOf(repr.asLong())); 284 } 285 if (repr.isInt()) { 286 return adapterFor(BigDecimal.valueOf(repr.asInt())); 287 } 288 return null; 289 } 290 }); 291 292 putConverter(new JsonValueConverter("date", "jodalocaldate", LocalDate.class){ 293 294 final DateTimeFormatter yyyyMMdd = JsonRepresentation.yyyyMMdd; 295 296 @Override 297 public ObjectAdapter asAdapter(JsonRepresentation repr) { 298 if (repr.isString()) { 299 final String dateStr = repr.asString(); 300 try { 301 final Date parsedDate = yyyyMMdd.parseDateTime(dateStr).toDate(); 302 return adapterFor(parsedDate); 303 } catch (IllegalArgumentException ex) { 304 // fall through 305 } 306 } 307 return null; 308 } 309 310 @Override 311 public void appendValueAndFormat(ObjectAdapter objectAdapter, JsonRepresentation repr) { 312 final LocalDate date = (LocalDate) unwrap(objectAdapter); 313 final String dateStr = yyyyMMdd.print(date.toDateTimeAtStartOfDay()); 314 append(repr, dateStr, format, xIsisFormat); 315 } 316 }); 317 318 putConverter(new JsonValueConverter("date-time", "jodalocaldatetime", LocalDateTime.class){ 319 final DateTimeFormatter yyyyMMddHHmmss = JsonRepresentation.yyyyMMddTHHmmssZ; 320 @Override 321 public ObjectAdapter asAdapter(JsonRepresentation repr) { 322 if (repr.isString()) { 323 final String dateStr = repr.asString(); 324 try { 325 final Date parsedDate = yyyyMMddHHmmss.parseDateTime(dateStr).toDate(); 326 return adapterFor(parsedDate); 327 } catch (IllegalArgumentException ex) { 328 // fall through 329 } 330 } 331 return null; 332 } 333 334 @Override 335 public void appendValueAndFormat(ObjectAdapter objectAdapter, JsonRepresentation repr) { 336 final LocalDateTime date = (LocalDateTime) unwrap(objectAdapter); 337 final String dateStr = yyyyMMddHHmmss.print(date.toDateTime()); 338 append(repr, dateStr, format, xIsisFormat); 339 } 340 }); 341 342 } 343 344 345 346 public static ObjectAdapter asAdapter(final ObjectSpecification objectSpec, final JsonRepresentation argRepr) { 347 if (objectSpec == null) { 348 String reason = "ObjectSpec is null, cannot validate"; 349 argRepr.mapPut("invalidReason", reason); 350 throw new IllegalArgumentException(reason); 351 } 352 final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class); 353 if (encodableFacet == null) { 354 String reason = "ObjectSpec expected to have an EncodableFacet"; 355 argRepr.mapPut("invalidReason", reason); 356 throw new IllegalArgumentException(reason); 357 } 358 359 if(!argRepr.mapHas("value")) { 360 String reason = "No 'value' key"; 361 argRepr.mapPut("invalidReason", reason); 362 throw new IllegalArgumentException(reason); 363 } 364 final JsonRepresentation argValueRepr = argRepr.getRepresentation("value"); 365 if(argValueRepr == null) { 366 return null; 367 } 368 if (!argValueRepr.isValue()) { 369 String reason = "Representation must be of a value"; 370 argRepr.mapPut("invalidReason", reason); 371 throw new IllegalArgumentException(reason); 372 } 373 374 final JsonValueConverter jvc = converterBySpec.get(objectSpec.getSpecId()); 375 if(jvc == null) { 376 // best effort 377 if (argValueRepr.isString()) { 378 final String argStr = argValueRepr.asString(); 379 return encodableFacet.fromEncodedString(argStr); 380 } 381 382 final String reason = "Unable to parse value"; 383 argRepr.mapPut("invalidReason", reason); 384 throw new IllegalArgumentException(reason); 385 } 386 387 final ObjectAdapter asAdapter = jvc.asAdapter(argValueRepr); 388 if(asAdapter != null) { 389 return asAdapter; 390 } 391 392 // last attempt 393 if (argValueRepr.isString()) { 394 final String argStr = argValueRepr.asString(); 395 return encodableFacet.fromEncodedString(argStr); 396 } 397 398 final String reason = "Could not parse value '" + argValueRepr.asString() + "' as a " + objectSpec.getFullIdentifier(); 399 argRepr.mapPut("invalidReason", reason); 400 throw new IllegalArgumentException(reason); 401 } 402 403 public static void appendValueAndFormat(ObjectSpecification objectSpec, ObjectAdapter objectAdapter, JsonRepresentation repr) { 404 405 final JsonValueConverter jvc = converterBySpec.get(objectSpec.getSpecId()); 406 if(jvc != null) { 407 jvc.appendValueAndFormat(objectAdapter, repr); 408 } else { 409 final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class); 410 if (encodableFacet == null) { 411 throw new IllegalArgumentException("objectSpec expected to have EncodableFacet"); 412 } 413 Object value = objectAdapter != null? encodableFacet.toEncodedString(objectAdapter): NullNode.getInstance(); 414 append(repr, value, "decimal", "bigdecimal"); 415 } 416 } 417 418 public static Object asObject(final ObjectAdapter objectAdapter) { 419 if (objectAdapter == null) { 420 throw new IllegalArgumentException("objectAdapter cannot be null"); 421 } 422 final ObjectSpecification objectSpec = objectAdapter.getSpecification(); 423 424 final JsonValueConverter jvc = converterBySpec.get(objectSpec.getSpecId()); 425 if(jvc != null) { 426 return jvc.asObject(objectAdapter); 427 } 428 429 // else 430 final EncodableFacet encodableFacet = objectSpec.getFacet(EncodableFacet.class); 431 if (encodableFacet == null) { 432 throw new IllegalArgumentException("objectSpec expected to have EncodableFacet"); 433 } 434 return encodableFacet.toEncodedString(objectAdapter); 435 } 436 437 438 439 private static void append(JsonRepresentation repr, Object value, String format, String xIsisFormat) { 440 repr.mapPut("value", value); 441 if(format != null) { 442 repr.mapPut("format", format); 443 } 444 if(xIsisFormat != null) { 445 repr.mapPut("x-isis-format", xIsisFormat); 446 } 447 } 448 449 private static void append(JsonRepresentation repr, ObjectAdapter value, String format, String xIsisFormat) { 450 append(repr, unwrap(value), format, xIsisFormat); 451 } 452 453 private static Object unwrap(ObjectAdapter objectAdapter) { 454 return objectAdapter != null? objectAdapter.getObject(): NullNode.getInstance(); 455 } 456 457 458 459 private static ObjectAdapter adapterFor(Object value) { 460 return getAdapterManager().adapterFor(value); 461 } 462 463 public static AdapterManager getAdapterManager() { 464 return IsisContext.getPersistenceSession().getAdapterManager(); 465 } 466 467}