001/* 002 * Copyright (C) 2015 Christian Schulte <cs@schulte.it> 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without 006 * modification, are permitted provided that the following conditions 007 * are met: 008 * 009 * o Redistributions of source code must retain the above copyright 010 * notice, this list of conditions and the following disclaimer. 011 * 012 * o Redistributions in binary form must reproduce the above copyright 013 * notice, this list of conditions and the following disclaimer in 014 * the documentation and/or other materials provided with the 015 * distribution. 016 * 017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 027 * 028 * $JOMC: DefaultServiceFactory.java 5293 2016-08-29 17:41:51Z schulte $ 029 * 030 */ 031package org.jomc.modlet; 032 033import java.lang.reflect.InvocationTargetException; 034import java.lang.reflect.Method; 035import java.lang.reflect.Modifier; 036import java.lang.reflect.UndeclaredThrowableException; 037import java.text.MessageFormat; 038import java.util.ArrayList; 039import java.util.List; 040import java.util.ResourceBundle; 041import java.util.concurrent.Callable; 042import java.util.concurrent.CancellationException; 043import java.util.concurrent.ExecutionException; 044import java.util.concurrent.Future; 045import java.util.logging.Level; 046 047/** 048 * Default {@code ServiceFactory} implementation. 049 * 050 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 051 * @version $JOMC: DefaultServiceFactory.java 5293 2016-08-29 17:41:51Z schulte $ 052 * @see ModelContext#createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) 053 * @since 1.9 054 */ 055public class DefaultServiceFactory implements ServiceFactory 056{ 057 058 /** 059 * Constant for the name of the system property controlling property {@code defaultOrdinal}. 060 * 061 * @see #getDefaultOrdinal() 062 */ 063 private static final String DEFAULT_ORDINAL_PROPERTY_NAME = 064 "org.jomc.modlet.DefaultServiceFactory.defaultOrdinal"; 065 066 /** 067 * Default value of the ordinal number of the factory. 068 * 069 * @see #getDefaultOrdinal() 070 */ 071 private static final Integer DEFAULT_ORDINAL = 0; 072 073 /** 074 * Default ordinal number of the factory. 075 */ 076 private static volatile Integer defaultOrdinal; 077 078 /** 079 * Ordinal number of the factory. 080 */ 081 private volatile Integer ordinal; 082 083 /** 084 * Creates a new {@code DefaultServiceFactory} instance. 085 */ 086 public DefaultServiceFactory() 087 { 088 super(); 089 } 090 091 /** 092 * Gets the default ordinal number of the factory. 093 * <p> 094 * The default ordinal number is controlled by system property 095 * {@code org.jomc.modlet.DefaultServiceFactory.defaultOrdinal} holding the default ordinal number of the 096 * factory. If that property is not set, the {@code 0} default is returned. 097 * </p> 098 * 099 * @return The default ordinal number of the factory. 100 * 101 * @see #setDefaultOrdinal(java.lang.Integer) 102 */ 103 public static int getDefaultOrdinal() 104 { 105 if ( defaultOrdinal == null ) 106 { 107 defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL ); 108 } 109 110 return defaultOrdinal; 111 } 112 113 /** 114 * Sets the default ordinal number of the factory. 115 * 116 * @param value The new default ordinal number of the factory or {@code null}. 117 * 118 * @see #getDefaultOrdinal() 119 */ 120 public static void setDefaultOrdinal( final Integer value ) 121 { 122 defaultOrdinal = value; 123 } 124 125 /** 126 * Gets the ordinal number of the factory. 127 * 128 * @return The ordinal number of the factory. 129 * 130 * @see #getDefaultOrdinal() 131 * @see #setOrdinal(java.lang.Integer) 132 */ 133 public final int getOrdinal() 134 { 135 if ( this.ordinal == null ) 136 { 137 this.ordinal = getDefaultOrdinal(); 138 } 139 140 return this.ordinal; 141 } 142 143 /** 144 * Sets the ordinal number of the factory. 145 * 146 * @param value The new ordinal number of the factory or {@code null}. 147 * 148 * @see #getOrdinal() 149 */ 150 public final void setOrdinal( final Integer value ) 151 { 152 this.ordinal = value; 153 } 154 155 public <T> T createServiceObject( final ModelContext context, final Service service, final Class<T> type ) 156 throws ModelException 157 { 158 if ( context == null ) 159 { 160 throw new NullPointerException( "context" ); 161 } 162 if ( service == null ) 163 { 164 throw new NullPointerException( "service" ); 165 } 166 if ( type == null ) 167 { 168 throw new NullPointerException( "type" ); 169 } 170 171 try 172 { 173 final Class<?> clazz = context.findClass( service.getClazz() ); 174 175 if ( clazz == null ) 176 { 177 throw new ModelException( getMessage( "serviceNotFound", service.getOrdinal(), service. 178 getIdentifier(), 179 service.getClazz() ) ); 180 181 } 182 183 if ( !type.isAssignableFrom( clazz ) ) 184 { 185 throw new ModelException( getMessage( "illegalService", service.getOrdinal(), service. 186 getIdentifier(), 187 service.getClazz(), type.getName() ) ); 188 189 } 190 191 final T serviceObject = clazz.asSubclass( type ).newInstance(); 192 193 if ( !service.getProperty().isEmpty() ) 194 { 195 if ( context.getExecutorService() != null && service.getProperty().size() > 1 ) 196 { 197 final List<Callable<Void>> tasks = 198 new ArrayList<Callable<Void>>( service.getProperty().size() ); 199 200 for ( int i = 0, s0 = service.getProperty().size(); i < s0; i++ ) 201 { 202 final Property p = service.getProperty().get( i ); 203 204 tasks.add( new Callable<Void>() 205 { 206 207 public Void call() throws ModelException 208 { 209 initProperty( context, serviceObject, p.getName(), p.getValue() ); 210 return null; 211 } 212 213 } ); 214 } 215 216 for ( final Future<Void> task : context.getExecutorService().invokeAll( tasks ) ) 217 { 218 task.get(); 219 } 220 } 221 else 222 { 223 for ( int i = 0, s0 = service.getProperty().size(); i < s0; i++ ) 224 { 225 final Property p = service.getProperty().get( i ); 226 this.initProperty( context, serviceObject, p.getName(), p.getValue() ); 227 } 228 } 229 } 230 231 return serviceObject; 232 } 233 catch ( final CancellationException e ) 234 { 235 throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e ); 236 } 237 catch ( final InterruptedException e ) 238 { 239 throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e ); 240 } 241 catch ( final InstantiationException e ) 242 { 243 throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e ); 244 } 245 catch ( final IllegalAccessException e ) 246 { 247 throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e ); 248 } 249 catch ( final ExecutionException e ) 250 { 251 if ( e.getCause() instanceof ModelException ) 252 { 253 throw (ModelException) e.getCause(); 254 } 255 else if ( e.getCause() instanceof RuntimeException ) 256 { 257 // The fork-join framework breaks the exception handling contract of Callable by re-throwing any 258 // exception caught using a runtime exception. 259 if ( e.getCause().getCause() instanceof ModelException ) 260 { 261 throw (ModelException) e.getCause().getCause(); 262 } 263 else if ( e.getCause().getCause() instanceof RuntimeException ) 264 { 265 throw (RuntimeException) e.getCause().getCause(); 266 } 267 else if ( e.getCause().getCause() instanceof Error ) 268 { 269 throw (Error) e.getCause().getCause(); 270 } 271 else if ( e.getCause().getCause() instanceof Exception ) 272 { 273 // Checked exception not declared to be thrown by the Callable's 'call' method. 274 throw new UndeclaredThrowableException( e.getCause().getCause() ); 275 } 276 else 277 { 278 throw (RuntimeException) e.getCause(); 279 } 280 } 281 else if ( e.getCause() instanceof Error ) 282 { 283 throw (Error) e.getCause(); 284 } 285 else 286 { 287 // Checked exception not declared to be thrown by the Callable's 'call' method. 288 throw new UndeclaredThrowableException( e.getCause() ); 289 } 290 } 291 } 292 293 private <T> void initProperty( final ModelContext context, final T object, final String propertyName, 294 final String propertyValue ) 295 throws ModelException 296 { 297 if ( object == null ) 298 { 299 throw new NullPointerException( "object" ); 300 } 301 if ( propertyName == null ) 302 { 303 throw new NullPointerException( "propertyName" ); 304 } 305 306 try 307 { 308 final char[] chars = propertyName.toCharArray(); 309 310 if ( Character.isLowerCase( chars[0] ) ) 311 { 312 chars[0] = Character.toUpperCase( chars[0] ); 313 } 314 315 final String methodNameSuffix = String.valueOf( chars ); 316 Method getterMethod = null; 317 318 try 319 { 320 getterMethod = object.getClass().getMethod( "get" + methodNameSuffix ); 321 } 322 catch ( final NoSuchMethodException e ) 323 { 324 if ( context.isLoggable( Level.FINEST ) ) 325 { 326 context.log( Level.FINEST, null, e ); 327 } 328 329 getterMethod = null; 330 } 331 332 if ( getterMethod == null ) 333 { 334 try 335 { 336 getterMethod = object.getClass().getMethod( "is" + methodNameSuffix ); 337 } 338 catch ( final NoSuchMethodException e ) 339 { 340 if ( context.isLoggable( Level.FINEST ) ) 341 { 342 context.log( Level.FINEST, null, e ); 343 } 344 345 getterMethod = null; 346 } 347 } 348 349 if ( getterMethod == null ) 350 { 351 throw new ModelException( getMessage( "getterMethodNotFound", object.getClass().getName(), 352 propertyName ) ); 353 354 } 355 356 final Class<?> propertyType = getterMethod.getReturnType(); 357 Class<?> boxedPropertyType = propertyType; 358 Class<?> unboxedPropertyType = propertyType; 359 360 if ( Boolean.TYPE.equals( propertyType ) ) 361 { 362 boxedPropertyType = Boolean.class; 363 } 364 else if ( Character.TYPE.equals( propertyType ) ) 365 { 366 boxedPropertyType = Character.class; 367 } 368 else if ( Byte.TYPE.equals( propertyType ) ) 369 { 370 boxedPropertyType = Byte.class; 371 } 372 else if ( Short.TYPE.equals( propertyType ) ) 373 { 374 boxedPropertyType = Short.class; 375 } 376 else if ( Integer.TYPE.equals( propertyType ) ) 377 { 378 boxedPropertyType = Integer.class; 379 } 380 else if ( Long.TYPE.equals( propertyType ) ) 381 { 382 boxedPropertyType = Long.class; 383 } 384 else if ( Float.TYPE.equals( propertyType ) ) 385 { 386 boxedPropertyType = Float.class; 387 } 388 else if ( Double.TYPE.equals( propertyType ) ) 389 { 390 boxedPropertyType = Double.class; 391 } 392 393 if ( Boolean.class.equals( propertyType ) ) 394 { 395 unboxedPropertyType = Boolean.TYPE; 396 } 397 else if ( Character.class.equals( propertyType ) ) 398 { 399 unboxedPropertyType = Character.TYPE; 400 } 401 else if ( Byte.class.equals( propertyType ) ) 402 { 403 unboxedPropertyType = Byte.TYPE; 404 } 405 else if ( Short.class.equals( propertyType ) ) 406 { 407 unboxedPropertyType = Short.TYPE; 408 } 409 else if ( Integer.class.equals( propertyType ) ) 410 { 411 unboxedPropertyType = Integer.TYPE; 412 } 413 else if ( Long.class.equals( propertyType ) ) 414 { 415 unboxedPropertyType = Long.TYPE; 416 } 417 else if ( Float.class.equals( propertyType ) ) 418 { 419 unboxedPropertyType = Float.TYPE; 420 } 421 else if ( Double.class.equals( propertyType ) ) 422 { 423 unboxedPropertyType = Double.TYPE; 424 } 425 426 Method setterMethod = null; 427 428 try 429 { 430 setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, boxedPropertyType ); 431 } 432 catch ( final NoSuchMethodException e ) 433 { 434 if ( context.isLoggable( Level.FINEST ) ) 435 { 436 context.log( Level.FINEST, null, e ); 437 } 438 439 setterMethod = null; 440 } 441 442 if ( setterMethod == null && !boxedPropertyType.equals( unboxedPropertyType ) ) 443 { 444 try 445 { 446 setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, unboxedPropertyType ); 447 } 448 catch ( final NoSuchMethodException e ) 449 { 450 if ( context.isLoggable( Level.FINEST ) ) 451 { 452 context.log( Level.FINEST, null, e ); 453 } 454 455 setterMethod = null; 456 } 457 } 458 459 if ( setterMethod == null ) 460 { 461 throw new ModelException( getMessage( "setterMethodNotFound", object.getClass().getName(), 462 propertyName ) ); 463 464 } 465 466 if ( boxedPropertyType.equals( Character.class ) ) 467 { 468 if ( propertyValue == null || propertyValue.length() != 1 ) 469 { 470 throw new ModelException( getMessage( "unsupportedCharacterValue", object.getClass().getName(), 471 propertyName ) ); 472 473 } 474 475 setterMethod.invoke( object, propertyValue.charAt( 0 ) ); 476 } 477 else if ( propertyValue != null ) 478 { 479 invocation: 480 { 481 if ( boxedPropertyType.equals( String.class ) ) 482 { 483 setterMethod.invoke( object, propertyValue ); 484 break invocation; 485 } 486 487 try 488 { 489 setterMethod.invoke( object, boxedPropertyType.getConstructor( String.class ). 490 newInstance( propertyValue ) ); 491 492 break invocation; 493 } 494 catch ( final NoSuchMethodException e1 ) 495 { 496 if ( context.isLoggable( Level.FINEST ) ) 497 { 498 context.log( Level.FINEST, null, e1 ); 499 } 500 } 501 502 try 503 { 504 final Method valueOf = boxedPropertyType.getMethod( "valueOf", String.class ); 505 506 if ( Modifier.isStatic( valueOf.getModifiers() ) 507 && ( valueOf.getReturnType().equals( boxedPropertyType ) 508 || valueOf.getReturnType().equals( unboxedPropertyType ) ) ) 509 { 510 setterMethod.invoke( object, valueOf.invoke( null, propertyValue ) ); 511 break invocation; 512 } 513 } 514 catch ( final NoSuchMethodException e2 ) 515 { 516 if ( context.isLoggable( Level.FINEST ) ) 517 { 518 context.log( Level.FINEST, null, e2 ); 519 } 520 } 521 522 throw new ModelException( getMessage( "unsupportedPropertyType", object.getClass().getName(), 523 propertyName, propertyType.getName() ) ); 524 525 } 526 } 527 else 528 { 529 setterMethod.invoke( object, (Object) null ); 530 } 531 } 532 catch ( final IllegalAccessException e ) 533 { 534 throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(), 535 object.getClass().getName() ), e ); 536 537 } 538 catch ( final InvocationTargetException e ) 539 { 540 throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(), 541 object.getClass().getName() ), e ); 542 543 } 544 catch ( final InstantiationException e ) 545 { 546 throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(), 547 object.getClass().getName() ), e ); 548 549 } 550 } 551 552 private static String getMessage( final String key, final Object... arguments ) 553 { 554 return MessageFormat.format( ResourceBundle.getBundle( 555 DefaultServiceFactory.class.getName().replace( '.', '/' ) ).getString( key ), arguments ); 556 557 } 558 559}