View Javadoc
1   /*
2    *   Copyright (C) 2015 Christian Schulte <cs@schulte.it>
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: DefaultServiceFactory.java 5293 2016-08-29 17:41:51Z schulte $
29   *
30   */
31  package org.jomc.modlet;
32  
33  import java.lang.reflect.InvocationTargetException;
34  import java.lang.reflect.Method;
35  import java.lang.reflect.Modifier;
36  import java.lang.reflect.UndeclaredThrowableException;
37  import java.text.MessageFormat;
38  import java.util.ArrayList;
39  import java.util.List;
40  import java.util.ResourceBundle;
41  import java.util.concurrent.Callable;
42  import java.util.concurrent.CancellationException;
43  import java.util.concurrent.ExecutionException;
44  import java.util.concurrent.Future;
45  import java.util.logging.Level;
46  
47  /**
48   * Default {@code ServiceFactory} implementation.
49   *
50   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
51   * @version $JOMC: DefaultServiceFactory.java 5293 2016-08-29 17:41:51Z schulte $
52   * @see ModelContext#createServiceObjects(java.lang.String, java.lang.String, java.lang.Class)
53   * @since 1.9
54   */
55  public class DefaultServiceFactory implements ServiceFactory
56  {
57  
58      /**
59       * Constant for the name of the system property controlling property {@code defaultOrdinal}.
60       *
61       * @see #getDefaultOrdinal()
62       */
63      private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
64          "org.jomc.modlet.DefaultServiceFactory.defaultOrdinal";
65  
66      /**
67       * Default value of the ordinal number of the factory.
68       *
69       * @see #getDefaultOrdinal()
70       */
71      private static final Integer DEFAULT_ORDINAL = 0;
72  
73      /**
74       * Default ordinal number of the factory.
75       */
76      private static volatile Integer defaultOrdinal;
77  
78      /**
79       * Ordinal number of the factory.
80       */
81      private volatile Integer ordinal;
82  
83      /**
84       * Creates a new {@code DefaultServiceFactory} instance.
85       */
86      public DefaultServiceFactory()
87      {
88          super();
89      }
90  
91      /**
92       * Gets the default ordinal number of the factory.
93       * <p>
94       * The default ordinal number is controlled by system property
95       * {@code org.jomc.modlet.DefaultServiceFactory.defaultOrdinal} holding the default ordinal number of the
96       * factory. If that property is not set, the {@code 0} default is returned.
97       * </p>
98       *
99       * @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 }