001 /* 002 * Copyright © 2008, 2012 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.Type; 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.JsonDeserializationManager; 064 import com.softwarementors.extjs.djn.gson.JsonException; 065 import com.softwarementors.extjs.djn.router.dispatcher.Dispatcher; 066 import com.softwarementors.extjs.djn.router.processor.RequestException; 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_OBJECT_TYPE_PARAMETER = false; 077 078 @NonNull private static final Logger logger = Logger.getLogger( JsonRequestProcessor.class); 079 // We need a globally unique thread-pool, not a pool per processor! 080 @CheckForNull private static volatile ExecutorService individualRequestsThreadPool; 081 @NonNull private JsonParser parser = new JsonParser(); 082 083 protected JsonParser getJsonParser() { 084 return this.parser; 085 } 086 087 @edu.umd.cs.findbugs.annotations.SuppressWarnings( value="NP_NONNULL_RETURN_VIOLATION", 088 justification="This method will never return null, because if the value it should return is null on entry, it assigns it first") 089 private ExecutorService getIndividualRequestsThreadPool() { 090 synchronized (JsonRequestProcessor.class) { 091 if( individualRequestsThreadPool == null ) { 092 individualRequestsThreadPool = createThreadPool(); 093 } 094 return individualRequestsThreadPool; 095 } 096 } 097 098 private ExecutorService createThreadPool() { 099 assert getGlobalConfiguration() != null; 100 101 ExecutorService result = new ThreadPoolExecutor( getGlobalConfiguration().getBatchRequestsMinThreadsPoolSize(), 102 getGlobalConfiguration().getBatchRequestsMaxThreadsPoolSize(), 103 getGlobalConfiguration().getBatchRequestsThreadKeepAliveSeconds(), 104 TimeUnit.SECONDS, 105 new LinkedBlockingQueue<Runnable>()); 106 return result; 107 } 108 109 public JsonRequestProcessor(Registry registry, Dispatcher dispatcher, GlobalConfiguration globalConfiguration) { 110 super(registry, dispatcher, globalConfiguration); 111 } 112 113 public String process(Reader reader, Writer writer) throws IOException { 114 String requestString = IOUtils.toString(reader); 115 if( logger.isDebugEnabled() ) { 116 logger.debug( "Request data (JSON)=>" + requestString ); 117 } 118 119 JsonRequestData[] requests = getIndividualJsonRequests( requestString ); 120 final boolean isBatched = requests.length > 1; 121 if( isBatched ) { 122 if( logger.isDebugEnabled() ) { 123 logger.debug( "Batched request: " + requests.length + " individual requests batched"); 124 } 125 } 126 127 Collection<String> responses = null; 128 boolean useMultipleThreadsIfBatched = isBatched && getGlobalConfiguration().getBatchRequestsMultithreadingEnabled(); 129 if( useMultipleThreadsIfBatched ) { 130 responses = processIndividualRequestsInMultipleThreads( requests); 131 } 132 else { 133 responses = processIndividualRequestsInThisThread(requests); 134 } 135 136 String result = convertInvididualResponsesToJsonString( responses); 137 writer.write( result ); 138 if( logger.isDebugEnabled() ) { 139 logger.debug( "ResponseData data (JSON)=>" + result ); 140 } 141 return result; 142 } 143 144 private Collection<String> processIndividualRequestsInThisThread(JsonRequestData[] requests) { 145 Collection<String> responses; 146 boolean isBatched = requests.length > 1; 147 responses = new ArrayList<String>(requests.length); 148 int requestNumber = 1; 149 for( JsonRequestData request : requests ) { 150 String response = processIndividualRequest( request, isBatched, requestNumber ); 151 responses.add( response ); 152 requestNumber++; 153 } 154 return responses; 155 } 156 157 private Collection<String> processIndividualRequestsInMultipleThreads( JsonRequestData[] requests) { 158 assert requests != null; 159 160 int individualRequestNumber = 1; 161 Collection<Callable<String>> tasks = new ArrayList<Callable<String>>(requests.length); 162 for (final JsonRequestData request : requests) { 163 JsonRequestProcessorThread thread = createJsonRequestProcessorThread(); 164 thread.initialize(this, request, individualRequestNumber); 165 tasks.add(thread); 166 individualRequestNumber++; 167 } 168 169 try { 170 ParallelTask<String> task = new ParallelTask<String>( 171 getIndividualRequestsThreadPool(), tasks, getGlobalConfiguration().getBatchRequestsMaxThreadsPerRequest()); 172 Collection<String> responses = task.get(); 173 return responses; 174 } 175 catch (InterruptedException e) { 176 List<String> responses = new ArrayList<String>(requests.length); 177 logger.error( "(Controlled) server error cancelled a batch of " + requests.length + " individual requests due to an InterruptedException exception. " + e.getMessage(), e); 178 for (final JsonRequestData request : requests) { 179 StandardErrorResponseData response = createJsonServerErrorResponse(request, e); 180 responses.add(getGson().toJson(response)); 181 } 182 return responses; 183 } 184 catch (ExecutionException e) { 185 UnexpectedException ex = UnexpectedException.forExecutionExceptionShouldNotHappenBecauseProcessorHandlesExceptionsAsServerErrorResponses(e); 186 logger.error( ex.getMessage(), ex ); 187 throw ex; 188 } 189 } 190 191 private JsonRequestProcessorThread createJsonRequestProcessorThread() { 192 Class<? extends JsonRequestProcessorThread> cls = getGlobalConfiguration().getJsonRequestProcessorThreadClass(); 193 try { 194 return cls.newInstance(); 195 } 196 catch (InstantiationException e) { 197 JsonRequestProcessorThreadConfigurationException ex = JsonRequestProcessorThreadConfigurationException.forUnableToInstantiateJsonRequestProcessorThread(cls, e); 198 logger.fatal( ex.getMessage(), ex); 199 throw ex; 200 } 201 catch (IllegalAccessException e) { 202 JsonRequestProcessorThreadConfigurationException ex = JsonRequestProcessorThreadConfigurationException.forUnableToInstantiateJsonRequestProcessorThread(cls, e); 203 logger.fatal( ex.getMessage(), ex); 204 throw ex; 205 } 206 } 207 208 private JsonRequestData[] getIndividualJsonRequests( String requestString ) { 209 assert !StringUtils.isEmpty(requestString); 210 211 JsonObject[] individualJsonRequests = parseIndividualJsonRequests(requestString, getJsonParser()); 212 JsonRequestData[] individualRequests = new JsonRequestData[individualJsonRequests.length]; 213 214 int i = 0; 215 for( JsonObject individualRequest : individualJsonRequests ) { 216 individualRequests[i] = createIndividualJsonRequest(individualRequest); 217 i++; 218 } 219 220 return individualRequests; 221 } 222 223 private static String convertInvididualResponsesToJsonString(Collection<String> responses) { 224 assert responses != null; 225 assert !responses.isEmpty(); 226 227 StringBuilder result = new StringBuilder(); 228 if( responses.size() > 1 ) { 229 result.append( "[\n" ); 230 } 231 int j = 0; 232 for( String response : responses ) { 233 result.append(response); 234 boolean isLast = j == responses.size() - 1; 235 if( !isLast) { 236 result.append( ","); 237 } 238 j++; 239 } 240 if( responses.size() > 1 ) { 241 result.append( "]"); 242 } 243 return result.toString(); 244 } 245 246 private Object[] getIndividualRequestParameters(JsonRequestData request) { 247 assert request != null; 248 249 RegisteredStandardMethod method = getStandardMethod( request.getAction(), request.getMethod()); 250 assert method != null; 251 252 Object[] parameters; 253 if( !method.getHandleParametersAsJsonArray()) { 254 checkJsonMethodParameterTypes( request.getJsonData(), method ); 255 parameters = jsonDataToMethodParameters(method, request.getJsonData(), method.getParameterTypes(), method.getGsonParameterTypes() ); 256 } 257 else { 258 parameters = new Object[] { request.getJsonData() }; 259 } 260 return parameters; 261 } 262 263 private Object[] jsonDataToMethodParameters(RegisteredStandardMethod method, JsonArray jsonParametersArray, Class<?>[] parameterTypes, Type[] gsonParameterTypes) { 264 assert method != null; 265 assert parameterTypes != null; 266 267 try { 268 JsonElement[] jsonParameters = getJsonElements(jsonParametersArray); 269 Object[] result = getMethodParameters(method, jsonParameters); 270 return result; 271 } 272 catch( JsonParseException ex ) { 273 throw JsonException.forFailedConversionFromJsonStringToMethodParameters( method, jsonParametersArray.toString(), parameterTypes, gsonParameterTypes, ex); 274 } 275 } 276 277 private static JsonElement[] getJsonElements(JsonArray jsonParameters) { 278 if( jsonParameters == null ) { 279 return new JsonElement[] {}; 280 } 281 282 JsonElement[] parameters; 283 284 JsonArray dataArray = jsonParameters; 285 parameters = new JsonElement[dataArray.size()]; 286 for( int i = 0; i < dataArray.size(); i++ ) { 287 parameters[i] = dataArray.get(i); 288 } 289 return parameters; 290 } 291 292 private static boolean isString( JsonElement element ) { 293 assert element != null; 294 295 return element.isJsonPrimitive() && ((JsonPrimitive)element).isString(); 296 } 297 298 private Object[] getMethodParameters(RegisteredStandardMethod method , JsonElement[] jsonParameters) { 299 assert method != null; 300 assert jsonParameters != null; 301 302 Class<?>[] parameterTypes = method.getParameterTypes(); 303 304 Object[] result = new Object[jsonParameters.length]; 305 for( int i = 0; i < jsonParameters.length; i++ ) { 306 JsonElement jsonValue = jsonParameters[i]; 307 Class<?> parameterType = parameterTypes[i]; 308 Object value = null; 309 310 Type gsonType = null; 311 if( method.getGsonParameterTypes() != null ) { 312 gsonType = method.getGsonParameterTypes()[i]; 313 } 314 315 value = jsonToJavaObject(jsonValue, parameterType, gsonType); 316 result[i] = value; 317 } 318 return result; 319 } 320 321 private @CheckForNull Object jsonToJavaObject(JsonElement jsonValue, Class<?> parameterType, Type gsonType) { 322 Object value; 323 if( jsonValue.isJsonNull() ) { 324 return null; 325 } 326 327 // Handle string in a special way, due to possibility of having a Java char type in the Java 328 // side 329 if( isString(jsonValue)) { 330 if( parameterType.equals(String.class)) { 331 value = jsonValue.getAsString(); 332 return value; 333 } 334 if(parameterType.equals(char.class) || parameterType.equals( Character.class) ) { 335 value = Character.valueOf(jsonValue.getAsString().charAt(0)); 336 return value; 337 } 338 } 339 340 // If Java parameter is Object, we perform 'magic': json string, number, boolean and 341 // null are converted to Java String, Double, Boolean and null. For json objects, 342 // we create a Map<String,Object>, and for json arrays an Object[], and then perform 343 // internal object conversion recursively using the same technique 344 if( parameterType.equals( Object.class ) && SUPPORTS_OBJECT_TYPE_PARAMETER) { 345 value = toSimpleJavaType(jsonValue); 346 return value; 347 } 348 349 // If the Java parameter is an array, but we are receiving a single item, we try to convert 350 // the item to a single item array so that the Java method can digest it 351 boolean useCustomGsonType = gsonType != null; 352 boolean fakeJsonArrayForManyValuedClasses = JsonDeserializationManager.isManyValuedClass(parameterType) && !jsonValue.isJsonArray(); 353 354 Type typeToInstantiate = parameterType; 355 if( useCustomGsonType ) { 356 typeToInstantiate = gsonType; 357 } 358 359 JsonElement json = jsonValue; 360 if( fakeJsonArrayForManyValuedClasses ) { 361 JsonArray fakeJson = new JsonArray(); 362 fakeJson.add(jsonValue); 363 json = fakeJson; 364 } 365 value = getGson().fromJson(json, typeToInstantiate); 366 return value; 367 } 368 369 private @CheckForNull Object toSimpleJavaType(JsonElement jsonValue) { 370 Object value = null; 371 if( !jsonValue.isJsonNull() ) { 372 if( jsonValue.isJsonPrimitive()) { 373 JsonPrimitive primitive = jsonValue.getAsJsonPrimitive(); 374 if( primitive.isBoolean()) { 375 value = Boolean.valueOf( primitive.getAsBoolean() ); 376 } 377 else if( primitive.isNumber()) { 378 value = Double.valueOf(primitive.getAsDouble()); 379 } 380 else if( primitive.isString()) { 381 value = primitive.getAsString(); 382 } 383 else { 384 throw UnexpectedException.forUnexpectedCodeBranchExecuted(); 385 } 386 } 387 else if( jsonValue.isJsonArray()) { 388 //This simply does not work (?) 389 JsonArray array = jsonValue.getAsJsonArray(); 390 Object[] result = new Object[array.size()]; 391 for( int i = 0; i < array.size(); i++ ) { 392 result[i] = toSimpleJavaType(array.get(i)); 393 } 394 value = result; 395 } 396 else if( jsonValue.isJsonObject() ) { 397 //This simply does not work (?) 398 //value = getGson().fromJson(jsonValue, Map.class ); 399 400 JsonObject obj = jsonValue.getAsJsonObject(); 401 Iterator<Entry<String,JsonElement>> properties = obj.entrySet().iterator(); 402 Map<String, Object> result = new HashMap<String,Object>(); 403 while( properties.hasNext() ) { 404 Entry<String,JsonElement> property = properties.next(); 405 JsonElement propertyValue = property.getValue(); 406 result.put( property.getKey(), toSimpleJavaType(propertyValue)); 407 } 408 value = result; 409 } 410 else { 411 throw UnexpectedException.forUnexpectedCodeBranchExecuted(); 412 } 413 } 414 415 return value; 416 } 417 418 private static JsonElement[] getIndividualRequestJsonParameters(JsonArray jsonParameters) { 419 if( jsonParameters == null ) { 420 return new JsonElement[] {}; 421 } 422 423 JsonElement[] parameters; 424 425 parameters = new JsonElement[jsonParameters.size()]; 426 for( int i = 0; i < jsonParameters.size(); i++ ) { 427 parameters[i] = jsonParameters.get(i); 428 } 429 return parameters; 430 } 431 432 private static void checkJsonMethodParameterTypes(JsonArray jsonData, RegisteredStandardMethod method) { 433 assert method != null; 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 static 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 true; 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 */ String 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 StandardSuccessResponseData response = new StandardSuccessResponseData( request.getTid(), action, method); 498 499 JsonDeserializationManager mgr = JsonDeserializationManager.getManager(); 500 try { 501 502 Object result = dispatchStandardMethod(action, method, parameters); 503 mgr.friendOnlyAccess_setRoot(result); 504 response.setResult(result); 505 String json = getGson().toJson(response); 506 if( isBatched ) { 507 if( logger.isDebugEnabled() ) { 508 timer.stop(); 509 timer.logDebugTimeInMilliseconds( " - Individual request #" + requestNumber + " response data=>" + json ); 510 resultReported = true; 511 } 512 } 513 return json; 514 } 515 finally { 516 mgr.friendOnlyAccess_dispose(); // Cleanup in case we are reusing thread 517 } 518 } 519 catch( Exception t ) { 520 StandardErrorResponseData response = createJsonServerErrorResponse(request, t); 521 String json = getGson().toJson(response); 522 logger.error( "(Controlled) server error: " + t.getMessage() + " for Method '" + request.getFullMethodName() + "'", t); 523 return json; 524 } 525 finally { 526 if( !resultReported ) { 527 timer.stop(); 528 // No point in logging individual requests when the request is not batched 529 if( isBatched ) { 530 if( logger.isDebugEnabled() ) { 531 timer.logDebugTimeInMilliseconds( " - Individual request #" + requestNumber + ": " + request.getFullMethodName() + ". Time"); 532 } 533 } 534 } 535 } 536 } 537 538 private static JsonObject[] parseIndividualJsonRequests(String requestString, JsonParser parser) { 539 assert !StringUtils.isEmpty(requestString); 540 assert parser != null; 541 542 JsonObject[] individualRequests; 543 JsonElement root = parser.parse( requestString ); 544 if( root.isJsonArray() ) { 545 JsonArray rootArray = (JsonArray)root; 546 if( rootArray.size() == 0 ) { 547 RequestException ex = RequestException.forRequestBatchMustHaveAtLeastOneRequest(); 548 logger.error( ex.getMessage(), ex ); 549 throw ex; 550 } 551 552 individualRequests = new JsonObject[rootArray.size()]; 553 int i = 0; 554 for( JsonElement item : rootArray ) { 555 if( !item.isJsonObject()) { 556 RequestException ex = RequestException.forRequestBatchItemMustBeAValidJsonObject(i); 557 logger.error( ex.getMessage(), ex ); 558 throw ex; 559 } 560 individualRequests[i] = (JsonObject)item; 561 i++; 562 } 563 } 564 else if( root.isJsonObject() ) { 565 individualRequests = new JsonObject[] {(JsonObject)root}; 566 } 567 else { 568 RequestException ex = RequestException.forRequestMustBeAValidJsonObjectOrArray(); 569 logger.error( ex.getMessage(), ex ); 570 throw ex; 571 } 572 573 return individualRequests; 574 } 575 576 private static JsonRequestData createIndividualJsonRequest( JsonObject element ) { 577 assert element != null; 578 579 String action = getNonEmptyJsonString( element, JsonRequestData.ACTION_ELEMENT ); 580 String method = getNonEmptyJsonString( element, JsonRequestData.METHOD_ELEMENT ); 581 Long tid = getNonEmptyJsonLong( element, JsonRequestData.TID_ELEMENT ); 582 String type = getNonEmptyJsonString( element, JsonRequestData.TYPE_ELEMENT ); 583 JsonArray jsonData = getMethodParametersJsonData(element); 584 JsonRequestData result = new JsonRequestData( type, action, method, tid, jsonData ); 585 return result; 586 } 587 588 @CheckForNull private static JsonArray getMethodParametersJsonData(JsonObject object) { 589 assert object != null; 590 591 JsonElement data = object.get(JsonRequestData.DATA_ELEMENT); 592 if( data == null ) { 593 RequestException ex = RequestException.forJsonElementMissing(JsonRequestData.DATA_ELEMENT); 594 logger.error( ex.getMessage(), ex ); 595 throw ex; 596 } 597 598 if( data.isJsonNull()) { 599 return null; 600 } 601 602 if( !data.isJsonNull() && !data.isJsonArray()) { 603 RequestException ex = RequestException.forJsonElementMustBeAJsonArray(JsonRequestData.DATA_ELEMENT, data.toString()); 604 logger.error( ex.getMessage(), ex ); 605 throw ex; 606 } 607 608 return (JsonArray)data; 609 } 610 611 private static <T> T getNonEmptyJsonPrimitiveValue( JsonObject object, String elementName, PrimitiveJsonValueGetter<T> getter ) { 612 assert object != null; 613 assert !StringUtils.isEmpty(elementName); 614 615 try { 616 JsonElement element = object.get( elementName ); 617 if( element == null ) { 618 RequestException ex = RequestException.forJsonElementMissing(elementName); 619 logger.error( ex.getMessage(), ex ); 620 throw ex; 621 } 622 623 // Take into account that the element must be a primitive, and then a string! 624 T result = null; 625 if( element.isJsonPrimitive() ) { 626 result = getter.checkedGet( (JsonPrimitive) element ); 627 } 628 629 if( result == null ) { 630 RequestException ex = RequestException.forJsonElementMustBeANonNullOrEmptyValue(elementName, getter.getValueType() ); 631 logger.error( ex.getMessage(), ex ); 632 throw ex; 633 } 634 635 return result; 636 } 637 catch( JsonParseException e ) { 638 String message = "Probably a DirectJNgine BUG: there should not be JSON parse exceptions: we should have checked ALL error conditions. " + e.getMessage(); 639 logger.error( message, e ); 640 assert false : message; 641 throw e; // Just to make the compiler happy -because of the assert 642 } 643 } 644 645 // A simple interface that helps us avoid duplicated code 646 // Its purpose is to retrieve a primitive value, or null 647 // if the primitive value is null or "empty" (makes sense for strings...) 648 interface PrimitiveJsonValueGetter<T> { 649 // Must return null if the specified primitive is not of type T or is "empty" 650 @CheckForNull T checkedGet( JsonPrimitive value ); 651 Class<T> getValueType(); 652 } 653 654 private static class PrimitiveJsonLongGetter implements PrimitiveJsonValueGetter<Long> { 655 public Long checkedGet(JsonPrimitive value) { 656 assert value != null; 657 658 if( value.isNumber() ) { 659 String v = value.toString(); 660 try { 661 return Long.valueOf( Long.parseLong(v) ); 662 } 663 catch( NumberFormatException ex ) { 664 return null; 665 } 666 } 667 return null; 668 } 669 670 public Class<Long> getValueType() { 671 return Long.class; 672 } 673 } 674 675 private static class PrimitiveJsonStringGetter implements PrimitiveJsonValueGetter<String> { 676 // @Override 677 public String checkedGet(JsonPrimitive value) { 678 assert value != null; 679 680 if( value.isString() ) { 681 String result = value.getAsString(); 682 if( result.equals("") ) 683 result = null; 684 return result; 685 } 686 return null; 687 } 688 689 // @Override 690 public Class<String> getValueType() { 691 return String.class; 692 } 693 694 } 695 696 private static Long getNonEmptyJsonLong( JsonObject object, String elementName ) { 697 assert object != null; 698 assert !StringUtils.isEmpty(elementName); 699 700 return getNonEmptyJsonPrimitiveValue( object, elementName, new PrimitiveJsonLongGetter() ); 701 } 702 703 private static String getNonEmptyJsonString( JsonObject object, String elementName ) { 704 assert object != null; 705 assert !StringUtils.isEmpty(elementName); 706 707 return getNonEmptyJsonPrimitiveValue( object, elementName, new PrimitiveJsonStringGetter() ); 708 } 709 710 }