View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte <cs@schulte.it>, 2015-018
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 5051 2015-05-30 17:29:32Z 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.text.MessageFormat;
37  import java.util.ResourceBundle;
38  import java.util.logging.Level;
39  
40  /**
41   * Default {@code ServiceFactory} implementation.
42   *
43   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
44   * @version $JOMC: DefaultServiceFactory.java 5051 2015-05-30 17:29:32Z schulte $
45   * @see ModelContext#createServiceObjects(java.lang.String, java.lang.String, java.lang.Class)
46   * @since 1.9
47   */
48  public class DefaultServiceFactory implements ServiceFactory
49  {
50  
51      /**
52       * Constant for the name of the system property controlling property {@code defaultOrdinal}.
53       *
54       * @see #getDefaultOrdinal()
55       */
56      private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
57          "org.jomc.modlet.DefaultServiceFactory.defaultOrdinal";
58  
59      /**
60       * Default value of the ordinal number of the factory.
61       *
62       * @see #getDefaultOrdinal()
63       */
64      private static final Integer DEFAULT_ORDINAL = 0;
65  
66      /**
67       * Default ordinal number of the factory.
68       */
69      private static volatile Integer defaultOrdinal;
70  
71      /**
72       * Ordinal number of the factory.
73       */
74      private Integer ordinal;
75  
76      /**
77       * Creates a new {@code DefaultServiceFactory} instance.
78       */
79      public DefaultServiceFactory()
80      {
81          super();
82      }
83  
84      /**
85       * Gets the default ordinal number of the factory.
86       * <p>
87       * The default ordinal number is controlled by system property
88       * {@code org.jomc.modlet.DefaultServiceFactory.defaultOrdinal} holding the default ordinal number of the
89       * factory. If that property is not set, the {@code 0} default is returned.
90       * </p>
91       *
92       * @return The default ordinal number of the factory.
93       *
94       * @see #setDefaultOrdinal(java.lang.Integer)
95       */
96      public static int getDefaultOrdinal()
97      {
98          if ( defaultOrdinal == null )
99          {
100             defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL );
101         }
102 
103         return defaultOrdinal;
104     }
105 
106     /**
107      * Sets the default ordinal number of the factory.
108      *
109      * @param value The new default ordinal number of the factory or {@code null}.
110      *
111      * @see #getDefaultOrdinal()
112      */
113     public static void setDefaultOrdinal( final Integer value )
114     {
115         defaultOrdinal = value;
116     }
117 
118     /**
119      * Gets the ordinal number of the factory.
120      *
121      * @return The ordinal number of the factory.
122      *
123      * @see #getDefaultOrdinal()
124      * @see #setOrdinal(java.lang.Integer)
125      */
126     public final int getOrdinal()
127     {
128         if ( this.ordinal == null )
129         {
130             this.ordinal = getDefaultOrdinal();
131         }
132 
133         return this.ordinal;
134     }
135 
136     /**
137      * Sets the ordinal number of the factory.
138      *
139      * @param value The new ordinal number of the factory or {@code null}.
140      *
141      * @see #getOrdinal()
142      */
143     public final void setOrdinal( final Integer value )
144     {
145         this.ordinal = value;
146     }
147 
148     public <T> T createServiceObject( final ModelContext context, final Service service, final Class<T> type )
149         throws ModelException
150     {
151         if ( context == null )
152         {
153             throw new NullPointerException( "context" );
154         }
155         if ( service == null )
156         {
157             throw new NullPointerException( "service" );
158         }
159         if ( type == null )
160         {
161             throw new NullPointerException( "type" );
162         }
163 
164         try
165         {
166             final Class<?> clazz = context.findClass( service.getClazz() );
167 
168             if ( clazz == null )
169             {
170                 throw new ModelException( getMessage( "serviceNotFound", service.getOrdinal(), service.
171                                                       getIdentifier(),
172                                                       service.getClazz() ) );
173 
174             }
175 
176             if ( !type.isAssignableFrom( clazz ) )
177             {
178                 throw new ModelException( getMessage( "illegalService", service.getOrdinal(), service.
179                                                       getIdentifier(),
180                                                       service.getClazz(), type.getName() ) );
181 
182             }
183 
184             final T serviceObject = clazz.asSubclass( type ).newInstance();
185 
186             for ( int i = 0, s0 = service.getProperty().size(); i < s0; i++ )
187             {
188                 final Property p = service.getProperty().get( i );
189                 this.initProperty( context, serviceObject, p.getName(), p.getValue() );
190             }
191 
192             return serviceObject;
193         }
194         catch ( final InstantiationException e )
195         {
196             throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
197         }
198         catch ( final IllegalAccessException e )
199         {
200             throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
201         }
202     }
203 
204     private <T> void initProperty( final ModelContext context, final T object, final String propertyName,
205                                    final String propertyValue )
206         throws ModelException
207     {
208         if ( object == null )
209         {
210             throw new NullPointerException( "object" );
211         }
212         if ( propertyName == null )
213         {
214             throw new NullPointerException( "propertyName" );
215         }
216 
217         try
218         {
219             final char[] chars = propertyName.toCharArray();
220 
221             if ( Character.isLowerCase( chars[0] ) )
222             {
223                 chars[0] = Character.toUpperCase( chars[0] );
224             }
225 
226             final String methodNameSuffix = String.valueOf( chars );
227             Method getterMethod = null;
228 
229             try
230             {
231                 getterMethod = object.getClass().getMethod( "get" + methodNameSuffix );
232             }
233             catch ( final NoSuchMethodException e )
234             {
235                 if ( context.isLoggable( Level.FINEST ) )
236                 {
237                     context.log( Level.FINEST, null, e );
238                 }
239 
240                 getterMethod = null;
241             }
242 
243             if ( getterMethod == null )
244             {
245                 try
246                 {
247                     getterMethod = object.getClass().getMethod( "is" + methodNameSuffix );
248                 }
249                 catch ( final NoSuchMethodException e )
250                 {
251                     if ( context.isLoggable( Level.FINEST ) )
252                     {
253                         context.log( Level.FINEST, null, e );
254                     }
255 
256                     getterMethod = null;
257                 }
258             }
259 
260             if ( getterMethod == null )
261             {
262                 throw new ModelException( getMessage( "getterMethodNotFound", object.getClass().getName(),
263                                                       propertyName ) );
264 
265             }
266 
267             final Class<?> propertyType = getterMethod.getReturnType();
268             Class<?> boxedPropertyType = propertyType;
269             Class<?> unboxedPropertyType = propertyType;
270 
271             if ( Boolean.TYPE.equals( propertyType ) )
272             {
273                 boxedPropertyType = Boolean.class;
274             }
275             else if ( Character.TYPE.equals( propertyType ) )
276             {
277                 boxedPropertyType = Character.class;
278             }
279             else if ( Byte.TYPE.equals( propertyType ) )
280             {
281                 boxedPropertyType = Byte.class;
282             }
283             else if ( Short.TYPE.equals( propertyType ) )
284             {
285                 boxedPropertyType = Short.class;
286             }
287             else if ( Integer.TYPE.equals( propertyType ) )
288             {
289                 boxedPropertyType = Integer.class;
290             }
291             else if ( Long.TYPE.equals( propertyType ) )
292             {
293                 boxedPropertyType = Long.class;
294             }
295             else if ( Float.TYPE.equals( propertyType ) )
296             {
297                 boxedPropertyType = Float.class;
298             }
299             else if ( Double.TYPE.equals( propertyType ) )
300             {
301                 boxedPropertyType = Double.class;
302             }
303 
304             if ( Boolean.class.equals( propertyType ) )
305             {
306                 unboxedPropertyType = Boolean.TYPE;
307             }
308             else if ( Character.class.equals( propertyType ) )
309             {
310                 unboxedPropertyType = Character.TYPE;
311             }
312             else if ( Byte.class.equals( propertyType ) )
313             {
314                 unboxedPropertyType = Byte.TYPE;
315             }
316             else if ( Short.class.equals( propertyType ) )
317             {
318                 unboxedPropertyType = Short.TYPE;
319             }
320             else if ( Integer.class.equals( propertyType ) )
321             {
322                 unboxedPropertyType = Integer.TYPE;
323             }
324             else if ( Long.class.equals( propertyType ) )
325             {
326                 unboxedPropertyType = Long.TYPE;
327             }
328             else if ( Float.class.equals( propertyType ) )
329             {
330                 unboxedPropertyType = Float.TYPE;
331             }
332             else if ( Double.class.equals( propertyType ) )
333             {
334                 unboxedPropertyType = Double.TYPE;
335             }
336 
337             Method setterMethod = null;
338 
339             try
340             {
341                 setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, boxedPropertyType );
342             }
343             catch ( final NoSuchMethodException e )
344             {
345                 if ( context.isLoggable( Level.FINEST ) )
346                 {
347                     context.log( Level.FINEST, null, e );
348                 }
349 
350                 setterMethod = null;
351             }
352 
353             if ( setterMethod == null && !boxedPropertyType.equals( unboxedPropertyType ) )
354             {
355                 try
356                 {
357                     setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, unboxedPropertyType );
358                 }
359                 catch ( final NoSuchMethodException e )
360                 {
361                     if ( context.isLoggable( Level.FINEST ) )
362                     {
363                         context.log( Level.FINEST, null, e );
364                     }
365 
366                     setterMethod = null;
367                 }
368             }
369 
370             if ( setterMethod == null )
371             {
372                 throw new ModelException( getMessage( "setterMethodNotFound", object.getClass().getName(),
373                                                       propertyName ) );
374 
375             }
376 
377             if ( boxedPropertyType.equals( Character.class ) )
378             {
379                 if ( propertyValue == null || propertyValue.length() != 1 )
380                 {
381                     throw new ModelException( getMessage( "unsupportedCharacterValue", object.getClass().getName(),
382                                                           propertyName ) );
383 
384                 }
385 
386                 setterMethod.invoke( object, propertyValue.charAt( 0 ) );
387             }
388             else if ( propertyValue != null )
389             {
390                 invocation:
391                 {
392                     if ( boxedPropertyType.equals( String.class ) )
393                     {
394                         setterMethod.invoke( object, propertyValue );
395                         break invocation;
396                     }
397 
398                     try
399                     {
400                         setterMethod.invoke( object, boxedPropertyType.getConstructor( String.class ).
401                                              newInstance( propertyValue ) );
402 
403                         break invocation;
404                     }
405                     catch ( final NoSuchMethodException e1 )
406                     {
407                         if ( context.isLoggable( Level.FINEST ) )
408                         {
409                             context.log( Level.FINEST, null, e1 );
410                         }
411                     }
412 
413                     try
414                     {
415                         final Method valueOf = boxedPropertyType.getMethod( "valueOf", String.class );
416 
417                         if ( Modifier.isStatic( valueOf.getModifiers() )
418                                  && ( valueOf.getReturnType().equals( boxedPropertyType )
419                                       || valueOf.getReturnType().equals( unboxedPropertyType ) ) )
420                         {
421                             setterMethod.invoke( object, valueOf.invoke( null, propertyValue ) );
422                             break invocation;
423                         }
424                     }
425                     catch ( final NoSuchMethodException e2 )
426                     {
427                         if ( context.isLoggable( Level.FINEST ) )
428                         {
429                             context.log( Level.FINEST, null, e2 );
430                         }
431                     }
432 
433                     throw new ModelException( getMessage( "unsupportedPropertyType", object.getClass().getName(),
434                                                           propertyName, propertyType.getName() ) );
435 
436                 }
437             }
438             else
439             {
440                 setterMethod.invoke( object, (Object) null );
441             }
442         }
443         catch ( final IllegalAccessException e )
444         {
445             throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
446                                                   object.getClass().getName() ), e );
447 
448         }
449         catch ( final InvocationTargetException e )
450         {
451             throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
452                                                   object.getClass().getName() ), e );
453 
454         }
455         catch ( final InstantiationException e )
456         {
457             throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
458                                                   object.getClass().getName() ), e );
459 
460         }
461     }
462 
463     private static String getMessage( final String key, final Object... arguments )
464     {
465         return MessageFormat.format( ResourceBundle.getBundle(
466             DefaultServiceFactory.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
467 
468     }
469 
470 }