001 /* 002 * Copyright © 2008, 2009 Pedro Agulló Soliveres. 003 * 004 * This file is part of DirectJNgine. 005 * 006 * DirectJNgine is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License. 009 * 010 * Commercial use is permitted to the extent that the code/component(s) 011 * do NOT become part of another Open Source or Commercially developed 012 * licensed development library or toolkit without explicit permission. 013 * 014 * DirectJNgine is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public License 020 * along with DirectJNgine. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * This software uses the ExtJs library (http://extjs.com), which is 023 * distributed under the GPL v3 license (see http://extjs.com/license). 024 */ 025 026 package com.softwarementors.extjs.djn.router.processor.standard.json; 027 028 import java.io.IOException; 029 import java.io.Reader; 030 import java.io.Writer; 031 import java.lang.reflect.Array; 032 import java.util.ArrayList; 033 import java.util.Collection; 034 import java.util.HashMap; 035 import java.util.Iterator; 036 import java.util.List; 037 import java.util.Map; 038 import java.util.Map.Entry; 039 import java.util.concurrent.Callable; 040 import java.util.concurrent.ExecutionException; 041 import java.util.concurrent.ExecutorService; 042 import java.util.concurrent.LinkedBlockingQueue; 043 import java.util.concurrent.ThreadPoolExecutor; 044 import java.util.concurrent.TimeUnit; 045 046 import org.apache.commons.io.IOUtils; 047 import org.apache.log4j.Logger; 048 049 import com.google.gson.JsonArray; 050 import com.google.gson.JsonElement; 051 import com.google.gson.JsonObject; 052 import com.google.gson.JsonParseException; 053 import com.google.gson.JsonParser; 054 import com.google.gson.JsonPrimitive; 055 import com.softwarementors.extjs.djn.ClassUtils; 056 import com.softwarementors.extjs.djn.ParallelTask; 057 import com.softwarementors.extjs.djn.StringUtils; 058 import com.softwarementors.extjs.djn.Timer; 059 import com.softwarementors.extjs.djn.UnexpectedException; 060 import com.softwarementors.extjs.djn.api.RegisteredStandardMethod; 061 import com.softwarementors.extjs.djn.api.Registry; 062 import com.softwarementors.extjs.djn.config.GlobalConfiguration; 063 import com.softwarementors.extjs.djn.gson.JsonException; 064 import com.softwarementors.extjs.djn.router.dispatcher.Dispatcher; 065 import com.softwarementors.extjs.djn.router.processor.RequestException; 066 import com.softwarementors.extjs.djn.router.processor.ResponseData; 067 import com.softwarementors.extjs.djn.router.processor.standard.StandardErrorResponseData; 068 import com.softwarementors.extjs.djn.router.processor.standard.StandardRequestProcessorBase; 069 import com.softwarementors.extjs.djn.router.processor.standard.StandardSuccessResponseData; 070 071 import edu.umd.cs.findbugs.annotations.CheckForNull; 072 import edu.umd.cs.findbugs.annotations.NonNull; 073 074 public class JsonRequestProcessor extends StandardRequestProcessorBase { 075 /* Will not release this until extensive testings is performed */ 076 private final static boolean SUPPORTS_PASSING_SINGLE_ITEM_TO_ARRAY_PARAMETER = true; 077 private final static boolean SUPPORTS_OBJECT_TYPE_PARAMETER = true; 078 079 @NonNull private static final Logger logger = Logger.getLogger( JsonRequestProcessor.class); 080 // We need a globally unique thread-pool, not a pool per processor! 081 @CheckForNull private static volatile ExecutorService individualRequestsThreadPool; 082 @NonNull private JsonParser parser = new JsonParser(); 083 084 protected JsonParser getJsonParser() { 085 return this.parser; 086 } 087 088 @edu.umd.cs.findbugs.annotations.SuppressWarnings( value="NP_NONNULL_RETURN_VIOLATION", 089 justification="This method will never return null, because if the value it should return is null on entry, it assigns it first") 090 private ExecutorService getIndividualRequestsThreadPool() { 091 synchronized (JsonRequestProcessor.class) { 092 if( individualRequestsThreadPool == null ) { 093 individualRequestsThreadPool = createThreadPool(); 094 } 095 return individualRequestsThreadPool; 096 } 097 } 098 099 private ExecutorService createThreadPool() { 100 assert getGlobalConfiguration() != null; 101 102 ExecutorService result = new ThreadPoolExecutor( getGlobalConfiguration().getBatchRequestsMinThreadsPoolSize(), 103 getGlobalConfiguration().getBatchRequestsMaxThreadsPoolSize(), 104 getGlobalConfiguration().getBatchRequestsThreadKeepAliveSeconds(), 105 TimeUnit.SECONDS, 106 new LinkedBlockingQueue<Runnable>()); 107 return result; 108 } 109 110 public JsonRequestProcessor(Registry registry, Dispatcher dispatcher, GlobalConfiguration globalConfiguration) { 111 super(registry, dispatcher, globalConfiguration); 112 } 113 114 public String process(Reader reader, Writer writer) throws IOException { 115 String requestString = IOUtils.toString(reader); 116 if( logger.isDebugEnabled() ) { 117 logger.debug( "Request data (JSON)=>" + requestString ); 118 } 119 120 JsonRequestData[] requests = getIndividualJsonRequests( requestString ); 121 final boolean isBatched = requests.length > 1; 122 if( isBatched ) { 123 if( logger.isDebugEnabled() ) { 124 logger.debug( "Batched request: " + requests.length + " individual requests batched"); 125 } 126 } 127 128 Collection<ResponseData> responses = null; 129 boolean useMultipleThreadsIfBatched = isBatched && getGlobalConfiguration().getBatchRequestsMultithreadingEnabled(); 130 if( useMultipleThreadsIfBatched ) { 131 responses = processIndividualRequestsInMultipleThreads( requests); 132 } 133 else { 134 responses = processIndividualRequestsInThisThread(requests); 135 } 136 137 String result = convertInvididualResponsesToJsonString( responses); 138 writer.write( result ); 139 if( logger.isDebugEnabled() ) { 140 logger.debug( "ResponseData data (JSON)=>" + result ); 141 } 142 return result; 143 } 144 145 private Collection<ResponseData> processIndividualRequestsInThisThread(JsonRequestData[] requests) { 146 Collection<ResponseData> responses; 147 boolean isBatched = requests.length > 1; 148 responses = new ArrayList<ResponseData>(requests.length); 149 int requestNumber = 1; 150 for( JsonRequestData request : requests ) { 151 ResponseData response = processIndividualRequest( request, isBatched, requestNumber ); 152 responses.add( response ); 153 requestNumber++; 154 } 155 return responses; 156 } 157 158 private Collection<ResponseData> processIndividualRequestsInMultipleThreads( JsonRequestData[] requests) { 159 assert requests != null; 160 161 int individualRequestNumber = 1; 162 Collection<Callable<ResponseData>> tasks = new ArrayList<Callable<ResponseData>>(requests.length); 163 for (final JsonRequestData request : requests) { 164 JsonRequestProcessorThread thread = createJsonRequestProcessorThread(); 165 thread.initialize(this, request, individualRequestNumber); 166 tasks.add(thread); 167 individualRequestNumber++; 168 } 169 170 try { 171 ParallelTask<ResponseData> task = new ParallelTask<ResponseData>( 172 getIndividualRequestsThreadPool(), tasks, getGlobalConfiguration().getBatchRequestsMaxThreadsPerRequest()); 173 Collection<ResponseData> responses = task.get(); 174 return responses; 175 } 176 catch (InterruptedException e) { 177 List<ResponseData> responses = new ArrayList<ResponseData>(requests.length); 178 logger.error( "(Controlled) server error cancelled a batch of " + requests.length + " individual requests due to an InterruptedException exception. " + e.getMessage(), e); 179 for (final JsonRequestData request : requests) { 180 StandardErrorResponseData response = createJsonServerErrorResponse(request, e); 181 responses.add(response); 182 } 183 return responses; 184 } 185 catch (ExecutionException e) { 186 UnexpectedException ex = UnexpectedException.forExecutionExceptionShouldNotHappenBecauseProcessorHandlesExceptionsAsServerErrorResponses(e); 187 logger.error( ex.getMessage(), ex ); 188 throw ex; 189 } 190 } 191 192 private JsonRequestProcessorThread createJsonRequestProcessorThread() { 193 Class<? extends JsonRequestProcessorThread> cls = getGlobalConfiguration().getJsonRequestProcessorThreadClass(); 194 try { 195 return cls.newInstance(); 196 } 197 catch (InstantiationException e) { 198 JsonRequestProcessorThreadConfigurationException ex = JsonRequestProcessorThreadConfigurationException.forUnableToInstantiateJsonRequestProcessorThread(cls, e); 199 logger.fatal( ex.getMessage(), ex); 200 throw ex; 201 } 202 catch (IllegalAccessException e) { 203 JsonRequestProcessorThreadConfigurationException ex = JsonRequestProcessorThreadConfigurationException.forUnableToInstantiateJsonRequestProcessorThread(cls, e); 204 logger.fatal( ex.getMessage(), ex); 205 throw ex; 206 } 207 } 208 209 private JsonRequestData[] getIndividualJsonRequests( String requestString ) { 210 assert !StringUtils.isEmpty(requestString); 211 212 JsonObject[] individualJsonRequests = parseIndividualJsonRequests(requestString, getJsonParser()); 213 JsonRequestData[] individualRequests = new JsonRequestData[individualJsonRequests.length]; 214 215 int i = 0; 216 for( JsonObject individualRequest : individualJsonRequests ) { 217 individualRequests[i] = createIndividualJsonRequest(individualRequest); 218 i++; 219 } 220 221 return individualRequests; 222 } 223 224 private String convertInvididualResponsesToJsonString(Collection<ResponseData> responses) { 225 assert responses != null; 226 assert !responses.isEmpty(); 227 228 StringBuilder result = new StringBuilder(); 229 if( responses.size() > 1 ) { 230 result.append( "[\n" ); 231 } 232 int j = 0; 233 for( ResponseData response : responses ) { 234 appendIndividualResponseJsonString(response, result); 235 boolean isLast = j == responses.size() - 1; 236 if( !isLast) { 237 result.append( ","); 238 } 239 j++; 240 } 241 if( responses.size() > 1 ) { 242 result.append( "]"); 243 } 244 return result.toString(); 245 } 246 247 private Object[] getIndividualRequestParameters(JsonRequestData request) { 248 assert request != null; 249 250 RegisteredStandardMethod method = getStandardMethod( request.getAction(), request.getMethod()); 251 assert method != null; 252 253 Object[] parameters; 254 if( !method.getHandleParametersAsJsonArray()) { 255 checkJsonMethodParameterTypes( request.getJsonData(), method ); 256 parameters = jsonDataToMethodParameters(method, request.getJsonData(), method.getParameterTypes() /*, getDebug()*/ ); 257 } 258 else { 259 parameters = new Object[] { request.getJsonData() }; 260 } 261 return parameters; 262 } 263 264 private Object[] jsonDataToMethodParameters(RegisteredStandardMethod method, JsonArray jsonParametersArray, Class<?>[] parameterTypes) { 265 assert method != null; 266 assert parameterTypes != null; 267 268 try { 269 JsonElement[] jsonParameters = getJsonElements(jsonParametersArray); 270 Object[] result = getMethodParameters(parameterTypes, jsonParameters); 271 return result; 272 } 273 catch( JsonParseException ex ) { 274 throw JsonException.forFailedConversionFromJsonStringToMethodParameters( method, jsonParametersArray.toString(), parameterTypes, ex); 275 } 276 } 277 278 private JsonElement[] getJsonElements(JsonArray jsonParameters) { 279 if( jsonParameters == null ) { 280 return new JsonElement[] {}; 281 } 282 283 JsonElement[] parameters; 284 285 JsonArray dataArray = jsonParameters; 286 parameters = new JsonElement[dataArray.size()]; 287 for( int i = 0; i < dataArray.size(); i++ ) { 288 parameters[i] = dataArray.get(i); 289 } 290 return parameters; 291 } 292 293 private boolean isString( JsonElement element ) { 294 assert element != null; 295 296 return element.isJsonPrimitive() && ((JsonPrimitive)element).isString(); 297 } 298 299 private Object[] getMethodParameters(Class<?>[] parameterTypes, JsonElement[] jsonParameters) { 300 assert parameterTypes != null; 301 assert jsonParameters != null; 302 303 Object[] result = new Object[jsonParameters.length]; 304 for( int i = 0; i < jsonParameters.length; i++ ) { 305 JsonElement jsonValue = jsonParameters[i]; 306 Class<?> parameterType = parameterTypes[i]; 307 Object value = null; 308 value = jsonToJavaObject(jsonValue, parameterType); 309 result[i] = value; 310 } 311 return result; 312 } 313 314 private @CheckForNull Object jsonToJavaObject(JsonElement jsonValue, Class<?> parameterType) { 315 Object value; 316 if( jsonValue.isJsonNull() ) { 317 return null; 318 } 319 320 // Handle string in a special way, due to possibility of having a Java char type in the Java 321 // side 322 if( isString(jsonValue)) { 323 if( parameterType.equals(String.class)) { 324 value = jsonValue.getAsString(); 325 return value; 326 } 327 if(parameterType.equals(char.class) || parameterType.equals( Character.class) ) { 328 value = Character.valueOf(jsonValue.getAsString().charAt(0)); 329 return value; 330 } 331 } 332 333 // If Java parameter is Object, we perform 'magic': json string, number, boolean and 334 // null are converted to Java String, Double, Boolean and null. For json objects, 335 // we create a Map<String,Object>, and for json arrays an Object[], and then perform 336 // internal object conversion recursively using the same technique 337 if( parameterType.equals( Object.class ) && SUPPORTS_OBJECT_TYPE_PARAMETER) { 338 value = toSimpleJavaType(jsonValue); 339 return value; 340 } 341 /* Though I think this should not work without the 342 * code below, GSon seems to handle this case. 343 * Now, why not the Object param case, then? Really bizarre! 344 if( parameterType.equals( Object[].class) && SUPPORTS_OBJECT_ARRAY_TYPE_PARAMETER ) { 345 value = toSimpleJavaType(jsonValue); 346 return value; 347 } 348 if( parameterType.equals( Map.class) && SUPPORTS_MAP_TYPE_PARAMETER ) { 349 value = toSimpleJavaType(jsonValue); 350 return value; 351 } 352 */ 353 354 // If the Java parameter is an array, but we are receiving a single item, we try to convert 355 // the item to a single item array so that the Java method can digest it 356 boolean convertItemToSingleItemArray = parameterType.isArray() && !jsonValue.isJsonArray() && SUPPORTS_PASSING_SINGLE_ITEM_TO_ARRAY_PARAMETER; 357 Class<?> typeToInstantiate = parameterType; 358 if( convertItemToSingleItemArray ) { 359 typeToInstantiate = parameterType.getComponentType(); 360 } 361 String json = jsonValue.toString(); 362 value = getGson().fromJson(json, typeToInstantiate); 363 if( convertItemToSingleItemArray ) { 364 Object result = Array.newInstance(typeToInstantiate, 1); 365 Array.set( result, 0, value ); 366 return result; 367 } 368 return value; 369 } 370 371 private @CheckForNull Object toSimpleJavaType(JsonElement jsonValue) { 372 Object value = null; 373 if( jsonValue.isJsonNull() ) { 374 // value = null; 375 return value; 376 } 377 if( jsonValue.isJsonPrimitive()) { 378 JsonPrimitive primitive = jsonValue.getAsJsonPrimitive(); 379 if( primitive.isBoolean()) { 380 value = Boolean.valueOf( primitive.getAsBoolean() ); 381 } 382 else if( primitive.isNumber()) { 383 value = Double.valueOf(primitive.getAsDouble()); 384 } 385 else if( primitive.isString()) { 386 value = primitive.getAsString(); 387 } 388 assert value != null; 389 390 return value; 391 } 392 if( jsonValue.isJsonArray()) { 393 JsonArray array = jsonValue.getAsJsonArray(); 394 Object[] result = new Object[array.size()]; 395 for( int i = 0; i < array.size(); i++ ) { 396 result[i] = toSimpleJavaType(array.get(i)); 397 } 398 value = result; 399 return value; 400 } 401 if( jsonValue.isJsonObject() ) { 402 JsonObject obj = jsonValue.getAsJsonObject(); 403 Iterator<Entry<String,JsonElement>> properties = obj.entrySet().iterator(); 404 Map<String, Object> result = new HashMap<String,Object>(); 405 while( properties.hasNext() ) { 406 Entry<String,JsonElement> property = properties.next(); 407 JsonElement propertyValue = property.getValue(); 408 result.put( property.getKey(), toSimpleJavaType(propertyValue)); 409 } 410 value = result; 411 return value; 412 } 413 414 return value; 415 } 416 417 private JsonElement[] getIndividualRequestJsonParameters(JsonArray jsonParameters) { 418 if( jsonParameters == null ) { 419 return new JsonElement[] {}; 420 } 421 422 JsonElement[] parameters; 423 424 parameters = new JsonElement[jsonParameters.size()]; 425 for( int i = 0; i < jsonParameters.size(); i++ ) { 426 parameters[i] = jsonParameters.get(i); 427 } 428 return parameters; 429 } 430 431 private void checkJsonMethodParameterTypes(JsonArray jsonData, RegisteredStandardMethod method) { 432 assert method != null; 433 434 JsonElement[] jsonParameters = getIndividualRequestJsonParameters( jsonData ); 435 Class<?>[] parameterTypes = method.getParameterTypes(); 436 437 assert jsonParameters.length == parameterTypes.length; 438 439 for( int i = 0; i < parameterTypes.length; i++ ) { 440 Class<?> parameterType = parameterTypes[i]; 441 JsonElement jsonElement = jsonParameters[i]; 442 if( !isValidJsonTypeForJavaType(jsonElement, parameterType )) { 443 throw new IllegalArgumentException( "'" + jsonElement.toString() + "' is not a valid json text for the '" + parameterType.getName() + "' Java type"); 444 } 445 } 446 } 447 448 private boolean isValidJsonTypeForJavaType(JsonElement jsonElement, Class<?> parameterType) { 449 assert jsonElement != null; 450 assert parameterType != null; 451 452 // Check json nulls 453 if( jsonElement.isJsonNull() ) { 454 return !parameterType.isPrimitive(); 455 } 456 457 if( parameterType.isArray() ) { 458 // This is *always* ok because if the value is not a json array 459 // we will instantiate a single item array and attempt conversion 460 return jsonElement.isJsonArray() || SUPPORTS_PASSING_SINGLE_ITEM_TO_ARRAY_PARAMETER; 461 } 462 463 if( parameterType.equals( Boolean.class ) || parameterType.equals( boolean.class ) ) { 464 return jsonElement.isJsonPrimitive() && ((JsonPrimitive)jsonElement).isBoolean(); 465 } 466 else if( parameterType.equals( char.class ) || parameterType.equals( Character.class ) ) { 467 if( jsonElement.isJsonPrimitive() && ((JsonPrimitive)jsonElement).isString() ) { 468 return jsonElement.getAsString().length() == 1; 469 } 470 return false; 471 } 472 else if( parameterType.equals( String.class ) ) { 473 return jsonElement.isJsonPrimitive() && ((JsonPrimitive)jsonElement).isString(); 474 } 475 else if( ClassUtils.isNumericType(parameterType)) { 476 return jsonElement.isJsonPrimitive() && ((JsonPrimitive)jsonElement).isNumber(); 477 } 478 479 // If we arrived here, assume somebody will know how to handle the json element, maybe customizing Gson's serialization 480 return true; 481 } 482 483 /* package */ ResponseData processIndividualRequest( JsonRequestData request, boolean isBatched, int requestNumber ) { 484 assert request != null; 485 boolean resultReported = false; 486 487 Timer timer = new Timer(); 488 try { 489 if( isBatched ) { 490 if( logger.isDebugEnabled() ) { 491 logger.debug( " - Individual request #" + requestNumber + " request data=>" + getGson().toJson(request) ); 492 } 493 } 494 Object[] parameters = getIndividualRequestParameters( request); 495 String action = request.getAction(); 496 String method = request.getMethod(); 497 Object result = dispatchStandardMethod(action, method, parameters); 498 StandardSuccessResponseData response = new StandardSuccessResponseData( request.getTid(), action, method); 499 response.setResult(result); 500 if( isBatched ) { 501 if( logger.isDebugEnabled() ) { 502 timer.stop(); 503 timer.logDebugTimeInMilliseconds( " - Individual request #" + requestNumber + " response data=>" + getGson().toJson(response) ); 504 resultReported = true; 505 } 506 } 507 return response; 508 } 509 catch( Throwable t ) { 510 StandardErrorResponseData response = createJsonServerErrorResponse(request, t); 511 logger.error( "(Controlled) server error: " + t.getMessage() + " for Method '" + request.getFullMethodName() + "'", t); 512 return response; 513 } 514 finally { 515 if( !resultReported ) { 516 timer.stop(); 517 // No point in logging individual requests when the request is not batched 518 if( isBatched ) { 519 if( logger.isDebugEnabled() ) { 520 timer.logDebugTimeInMilliseconds( " - Individual request #" + requestNumber + ": " + request.getFullMethodName() + ". Time"); 521 } 522 } 523 } 524 } 525 } 526 527 private JsonObject[] parseIndividualJsonRequests(String requestString, JsonParser parser) { 528 assert !StringUtils.isEmpty(requestString); 529 assert parser != null; 530 531 JsonObject[] individualRequests; 532 JsonElement root = parser.parse( requestString ); 533 if( root.isJsonArray() ) { 534 JsonArray rootArray = (JsonArray)root; 535 if( rootArray.size() == 0 ) { 536 RequestException ex = RequestException.forRequestBatchMustHaveAtLeastOneRequest(); 537 logger.error( ex.getMessage(), ex ); 538 throw ex; 539 } 540 541 individualRequests = new JsonObject[rootArray.size()]; 542 int i = 0; 543 for( JsonElement item : rootArray ) { 544 if( !item.isJsonObject()) { 545 RequestException ex = RequestException.forRequestBatchItemMustBeAValidJsonObject(i); 546 logger.error( ex.getMessage(), ex ); 547 throw ex; 548 } 549 individualRequests[i] = (JsonObject)item; 550 i++; 551 } 552 } 553 else if( root.isJsonObject() ) { 554 individualRequests = new JsonObject[] {(JsonObject)root}; 555 } 556 else { 557 RequestException ex = RequestException.forRequestMustBeAValidJsonObjectOrArray(); 558 logger.error( ex.getMessage(), ex ); 559 throw ex; 560 } 561 562 return individualRequests; 563 } 564 565 private JsonRequestData createIndividualJsonRequest( JsonObject element ) { 566 assert element != null; 567 568 String action = getNonEmptyJsonString( element, JsonRequestData.ACTION_ELEMENT ); 569 String method = getNonEmptyJsonString( element, JsonRequestData.METHOD_ELEMENT ); 570 Long tid = getNonEmptyJsonLong( element, JsonRequestData.TID_ELEMENT ); 571 String type = getNonEmptyJsonString( element, JsonRequestData.TYPE_ELEMENT ); 572 JsonArray jsonData = getMethodParametersJsonData(element); 573 JsonRequestData result = new JsonRequestData( type, action, method, tid, jsonData ); 574 return result; 575 } 576 577 @CheckForNull private JsonArray getMethodParametersJsonData(JsonObject object) { 578 assert object != null; 579 580 JsonElement data = object.get(JsonRequestData.DATA_ELEMENT); 581 if( data == null ) { 582 RequestException ex = RequestException.forJsonElementMissing(JsonRequestData.DATA_ELEMENT); 583 logger.error( ex.getMessage(), ex ); 584 throw ex; 585 } 586 587 if( data.isJsonNull()) { 588 return null; 589 } 590 591 if( !data.isJsonNull() && !data.isJsonArray()) { 592 RequestException ex = RequestException.forJsonElementMustBeAJsonArray(JsonRequestData.DATA_ELEMENT, data.toString()); 593 logger.error( ex.getMessage(), ex ); 594 throw ex; 595 } 596 597 return (JsonArray)data; 598 } 599 600 private <T> T getNonEmptyJsonPrimitiveValue( JsonObject object, String elementName, PrimitiveJsonValueGetter<T> getter ) { 601 assert object != null; 602 assert !StringUtils.isEmpty(elementName); 603 604 try { 605 JsonElement element = object.get( elementName ); 606 if( element == null ) { 607 RequestException ex = RequestException.forJsonElementMissing(elementName); 608 logger.error( ex.getMessage(), ex ); 609 throw ex; 610 } 611 612 // Take into account that the element must be a primitive, and then a string! 613 T result = null; 614 if( element.isJsonPrimitive() ) { 615 result = getter.checkedGet( (JsonPrimitive) element ); 616 } 617 618 if( result == null ) { 619 RequestException ex = RequestException.forJsonElementMustBeANonNullOrEmptyValue(elementName, getter.getValueType() ); 620 logger.error( ex.getMessage(), ex ); 621 throw ex; 622 } 623 624 return result; 625 } 626 catch( JsonParseException e ) { 627 String message = "Probably a DirectJNgine BUG: there should not be JSON parse exceptions: we should have check ALL error conditions. " + e.getMessage(); 628 logger.error( message, e ); 629 assert false : message; 630 throw e; // Just to make the compiler happy -because of the assert 631 } 632 } 633 634 // A simple interface that helps us avoid duplicated code 635 // Its purpose is to retrieve a primitive value, or null 636 // if the primitive value is null or "empty" (makes sense for strings...) 637 interface PrimitiveJsonValueGetter<T> { 638 // Must return null if the specified primitive is not of type T or is "empty" 639 @CheckForNull T checkedGet( JsonPrimitive value ); 640 Class<T> getValueType(); 641 } 642 643 private static class PrimitiveJsonLongGetter implements PrimitiveJsonValueGetter<Long> { 644 public Long checkedGet(JsonPrimitive value) { 645 assert value != null; 646 647 if( value.isNumber() ) { 648 String v = value.toString(); 649 try { 650 return Long.valueOf( Long.parseLong(v) ); 651 } 652 catch( NumberFormatException ex ) { 653 return null; 654 } 655 } 656 return null; 657 } 658 659 public Class<Long> getValueType() { 660 return Long.class; 661 } 662 } 663 664 private static class PrimitiveJsonStringGetter implements PrimitiveJsonValueGetter<String> { 665 // @Override 666 public String checkedGet(JsonPrimitive value) { 667 assert value != null; 668 669 if( value.isString() ) { 670 String result = value.getAsString(); 671 if( result.equals("") ) 672 result = null; 673 return result; 674 } 675 return null; 676 } 677 678 // @Override 679 public Class<String> getValueType() { 680 return String.class; 681 } 682 683 } 684 685 private Long getNonEmptyJsonLong( JsonObject object, String elementName ) { 686 assert object != null; 687 assert !StringUtils.isEmpty(elementName); 688 689 return getNonEmptyJsonPrimitiveValue( object, elementName, new PrimitiveJsonLongGetter() ); 690 } 691 692 private String getNonEmptyJsonString( JsonObject object, String elementName ) { 693 assert object != null; 694 assert !StringUtils.isEmpty(elementName); 695 696 return getNonEmptyJsonPrimitiveValue( object, elementName, new PrimitiveJsonStringGetter() ); 697 } 698 699 }