View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.properties.spi.config.proxy;
17  
18  import java.io.Serializable;
19  import java.lang.reflect.Method;
20  
21  import de.smartics.properties.api.config.domain.SerializableConfigurationProperties;
22  import de.smartics.properties.api.config.domain.UnknownPropertyException;
23  import de.smartics.properties.api.core.annotations.PropertyMetaDataMethod;
24  import de.smartics.properties.api.core.domain.PropertiesContext;
25  import de.smartics.properties.api.core.domain.PropertyDescriptor;
26  import de.smartics.properties.api.core.domain.PropertyKey;
27  import de.smartics.properties.api.core.domain.PropertyValidationException;
28  import de.smartics.properties.spi.core.metadata.PropertyMetaDataParser;
29  import de.smartics.util.lang.Arg;
30  
31  /**
32   * The invocation handler receives every method invocation on the proxy that is
33   * wrapped over the properties interface. It analyzes the intent of this method
34   * call and provides (calculates) the return value. The main task is to detect
35   * whether a value, a property key or a property description has to be returned.
36   * After this decision making the task to calculate the return value is
37   * delegated to a specialized class.
38   */
39  public final class PropertiesProxyInvocationHandler implements
40      java.lang.reflect.InvocationHandler, Serializable
41  {
42    // ********************************* Fields *********************************
43  
44    // --- constants ------------------------------------------------------------
45  
46    /**
47     * The class version identifier.
48     */
49    private static final long serialVersionUID = 1L;
50  
51    // --- members --------------------------------------------------------------
52  
53    /**
54     * To access the properties.
55     *
56     * @serial
57     */
58    private final SerializableConfigurationProperties configurationProperties;
59  
60    // ****************************** Initializer *******************************
61  
62    // ****************************** Constructors ******************************
63  
64    /**
65     * Default constructor.
66     *
67     * @param configurationProperties the configuration properties on that the
68     *          method calls are delegated.
69     * @throws NullPointerException if {@code configurationProperties} is
70     *           <code>null</code>.
71     */
72    public PropertiesProxyInvocationHandler(
73        final SerializableConfigurationProperties configurationProperties)
74      throws NullPointerException
75    {
76      this.configurationProperties =
77          Arg.checkNotNull("configurationProperties", configurationProperties);
78    }
79  
80    // ****************************** Inner Classes *****************************
81  
82    // ********************************* Methods ********************************
83  
84    // --- init -----------------------------------------------------------------
85  
86    // --- get&set --------------------------------------------------------------
87  
88    // --- business -------------------------------------------------------------
89  
90    /**
91     * {@inheritDoc}
92     *
93     * @throws InvalidArgumentsException when arguments are passed. Only no arg
94     *           methods are supported.
95     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
96     *      java.lang.reflect.Method, java.lang.Object[])
97     */
98    public Object invoke(final Object proxy, final Method method,
99        final Object[] args) throws InvalidArgumentsException
100   {
101     if (proxy == null)
102     {
103       return null;
104     }
105 
106     final String methodName = method.getName();
107     if ("equals".equals(methodName))
108     {
109       return handleMethodEquals(proxy, args);
110     }
111 
112     checkNoArgs(args);
113 
114     if ("toString".equals(methodName))
115     {
116       return handleMethodToString(proxy);
117     }
118     else if ("hashCode".equals(methodName))
119     {
120       return handleMethodHashCode(proxy);
121     }
122 
123     return handlePropertyMethod(method);
124   }
125 
126   private Object handlePropertyMethod(final Method method)
127     throws UnknownPropertyException, PropertyValidationException,
128     InvalidArgumentsException
129   {
130     final Class<?> declaringClass = method.getDeclaringClass();
131     final PropertiesContext context =
132         configurationProperties.getContext(declaringClass);
133     final PropertyMetaDataParser parser =
134         PropertyMetaDataParser.create(context);
135 
136     if (isPropertyKeyMethod(method))
137     {
138       final Method propertyMethod =
139           PropertyMethodNameUtilities.fetchPropertyMethod(method);
140       return parser.readKey(propertyMethod);
141     }
142     else if (isPropertyDescriptorMethod(method))
143     {
144       final Method propertyMethod =
145           PropertyMethodNameUtilities.fetchPropertyMethod(method);
146       return parser.readDescriptor(propertyMethod);
147     }
148     else if (isPropertyMethod(method))
149     {
150       final PropertyKey key = parser.readKey(method);
151         final Object propertyValue =
152             configurationProperties.getPropertyValue(key);
153         return propertyValue;
154     }
155     else
156     {
157       throw new InvalidArgumentsException(
158           "The method '" + method.getName() + "' of interface '"
159               + method.getDeclaringClass().getCanonicalName()
160               + "' is not a property method, not a property key method and "
161               + "not a property descriptor method.");
162     }
163   }
164 
165   Object handleMethodEquals(final Object proxy, final Object[] args)
166   {
167     checkOneArg(args);
168     return proxy == args[0];
169   }
170 
171   private Object handleMethodHashCode(final Object proxy)
172     throws SecurityException
173   {
174     final Method[] methods = proxy.getClass().getMethods();
175     int hash = proxy.getClass().getCanonicalName().hashCode();
176     for (int i = 0; i < methods.length; i++)
177     {
178       hash = hash * 37 + methods[i].getName().hashCode();
179     }
180     return hash;
181   }
182 
183   private Object handleMethodToString(final Object proxy)
184     throws SecurityException
185   {
186     final StringBuilder toStringBuilder =
187         new StringBuilder("Proxy for: ").append(proxy.getClass().getName());
188     final Method[] methods = proxy.getClass().getMethods();
189     for (int i = 0; i < methods.length; i++)
190     {
191       toStringBuilder.append("::").append(methods[i].getName());
192     }
193     return toStringBuilder.toString();
194   }
195 
196   private boolean isPropertyKeyMethod(final Method m)
197   {
198     final Class<?> returnType = m.getReturnType();
199     return PropertyKey.class.equals(returnType);
200   }
201 
202   private boolean isPropertyDescriptorMethod(final Method m)
203   {
204     final Class<?> returnType = m.getReturnType();
205     return PropertyDescriptor.class.equals(returnType);
206   }
207 
208   private boolean isPropertyMethod(final Method m)
209   {
210     return !PropertyMethodNameUtilities.isMethodAnnotatedWithAnnotation(
211         PropertyMetaDataMethod.class, m);
212   }
213 
214   /**
215    * Check that the method called has exactly one arg.
216    *
217    * @param args the method args.
218    */
219   private void checkOneArg(final Object[] args)
220   {
221     if (args == null || args.length != 1)
222     {
223       throw new InvalidArgumentsException("Equals need one parameter.");
224     }
225   }
226 
227   /**
228    * Check that the method called has no args, as only noarg methods are
229    * supported for property methods right now.
230    *
231    * @param args the method args.
232    */
233   private void checkNoArgs(final Object[] args)
234   {
235     if (args != null && args.length > 0)
236     {
237       throw new InvalidArgumentsException(
238           "Only methods without arguments are supported for property methods.");
239     }
240   }
241 
242   // --- object basics --------------------------------------------------------
243 
244 }