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.Arguments;
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      Arguments.checkNotNull("configurationProperties", configurationProperties);
77  
78      this.configurationProperties = configurationProperties;
79    }
80  
81    // ****************************** Inner Classes *****************************
82  
83    // ********************************* Methods ********************************
84  
85    // --- init -----------------------------------------------------------------
86  
87    // --- get&set --------------------------------------------------------------
88  
89    // --- business -------------------------------------------------------------
90  
91    /**
92     * {@inheritDoc}
93     *
94     * @throws InvalidArgumentsException when arguments are passed. Only no arg
95     *           methods are supported.
96     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
97     *      java.lang.reflect.Method, java.lang.Object[])
98     */
99    public Object invoke(final Object proxy, final Method method,
100       final Object[] args) throws InvalidArgumentsException
101   {
102     if (proxy == null)
103     {
104       return null;
105     }
106 
107     final String methodName = method.getName();
108     if ("equals".equals(methodName))
109     {
110       return handleMethodEquals(proxy, args);
111     }
112 
113     checkNoArgs(args);
114 
115     if ("toString".equals(methodName))
116     {
117       return handleMethodToString(proxy);
118     }
119     else if ("hashCode".equals(methodName))
120     {
121       return handleMethodHashCode(proxy);
122     }
123 
124     return handlePropertyMethod(method);
125   }
126 
127   private Object handlePropertyMethod(final Method method)
128     throws UnknownPropertyException, PropertyValidationException,
129     InvalidArgumentsException
130   {
131     final Class<?> declaringClass = method.getDeclaringClass();
132     final PropertiesContext context =
133         configurationProperties.getContext(declaringClass);
134     final PropertyMetaDataParser parser =
135         PropertyMetaDataParser.create(context);
136 
137     if (isPropertyKeyMethod(method))
138     {
139       final Method propertyMethod =
140           PropertyMethodNameUtilities.fetchPropertyMethod(method);
141       return parser.readKey(propertyMethod);
142     }
143     else if (isPropertyDescriptorMethod(method))
144     {
145       final Method propertyMethod =
146           PropertyMethodNameUtilities.fetchPropertyMethod(method);
147       return parser.readDescriptor(propertyMethod);
148     }
149     else if (isPropertyMethod(method))
150     {
151       final PropertyKey key = parser.readKey(method);
152       return configurationProperties.getPropertyValue(key);
153     }
154     else
155     {
156       throw new InvalidArgumentsException(
157           "The method '" + method.getName() + "' of interface '"
158               + method.getDeclaringClass().getCanonicalName()
159               + "' is not a property method, not a property key method and "
160               + "not a property descriptor method.");
161     }
162   }
163 
164   Object handleMethodEquals(final Object proxy, final Object[] args)
165   {
166     checkOneArg(args);
167     return proxy == args[0];
168   }
169 
170   private Object handleMethodHashCode(final Object proxy)
171     throws SecurityException
172   {
173     final Method[] methods = proxy.getClass().getMethods();
174     int hash = proxy.getClass().getCanonicalName().hashCode();
175     for (int i = 0; i < methods.length; i++)
176     {
177       hash = hash * 37 + methods[i].getName().hashCode();
178     }
179     return hash;
180   }
181 
182   private Object handleMethodToString(final Object proxy)
183     throws SecurityException
184   {
185     final StringBuilder toStringBuilder =
186         new StringBuilder("Proxy for: ").append(proxy.getClass().getName());
187     final Method[] methods = proxy.getClass().getMethods();
188     for (int i = 0; i < methods.length; i++)
189     {
190       toStringBuilder.append("::").append(methods[i].getName());
191     }
192     return toStringBuilder.toString();
193   }
194 
195   private boolean isPropertyKeyMethod(final Method m)
196   {
197     final Class<?> returnType = m.getReturnType();
198     return PropertyKey.class.equals(returnType);
199   }
200 
201   private boolean isPropertyDescriptorMethod(final Method m)
202   {
203     final Class<?> returnType = m.getReturnType();
204     return PropertyDescriptor.class.equals(returnType);
205   }
206 
207   private boolean isPropertyMethod(final Method m)
208   {
209     return !PropertyMethodNameUtilities.isMethodAnnotatedWithAnnotation(
210         PropertyMetaDataMethod.class, m);
211   }
212 
213   /**
214    * Check that the method called has exactly one arg.
215    *
216    * @param args the method args.
217    */
218   private void checkOneArg(final Object[] args)
219   {
220     if (args == null || args.length != 1)
221     {
222       throw new InvalidArgumentsException("Equals need one parameter.");
223     }
224   }
225 
226   /**
227    * Check that the method called has no args, as only noarg methods are
228    * supported for property methods right now.
229    *
230    * @param args the method args.
231    */
232   private void checkNoArgs(final Object[] args)
233   {
234     if (args != null && args.length > 0)
235     {
236       throw new InvalidArgumentsException(
237           "Only methods without arguments are supported for property methods.");
238     }
239   }
240 
241   // --- object basics --------------------------------------------------------
242 
243 }