blob: e452e3c3fc76f455658d5b44c74430ae17680156 [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dm.impl;
20
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
24import java.lang.reflect.Proxy;
25import java.util.Arrays;
26import java.util.LinkedHashMap;
27import java.util.Map;
Pierre De Rop3cf52462016-02-20 21:15:24 +000028import java.util.concurrent.Callable;
29import java.util.concurrent.Executor;
30import java.util.concurrent.FutureTask;
31import java.util.concurrent.TimeUnit;
32
33import org.osgi.service.cm.ConfigurationException;
Pierre De Rop3a00a212015-03-01 09:27:46 +000034
35/**
36 * Utility methods for invoking callbacks. Lookups of callbacks are accellerated by using a LRU cache.
37 *
38 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
39 */
40public class InvocationUtil {
41 private static final Map<Key, Method> m_methodCache;
42 static {
43 int size = 2048;
44 // TODO enable this again
45// try {
46// String value = System.getProperty(DependencyManager.METHOD_CACHE_SIZE);
47// if (value != null) {
48// size = Integer.parseInt(value);
49// }
50// }
51// catch (Exception e) {}
52 m_methodCache = new LRUMap(Math.max(size, 64));
53 }
54
55 /**
Pierre De Rop3cf52462016-02-20 21:15:24 +000056 * Interface internally used to handle a ConfigurationAdmin update synchronously, in a component executor queue.
57 */
58 @FunctionalInterface
59 public interface ConfigurationHandler {
60 public void handle() throws Exception;
61 }
62
63 /**
64 * Max time to wait until a configuration update callback has returned.
65 */
66 private final static int UPDATED_MAXWAIT = 30000; // max time to wait until a CM update has completed
67
68 /**
Pierre De Rop3a00a212015-03-01 09:27:46 +000069 * Invokes a callback method on an instance. The code will search for a callback method with
70 * the supplied name and any of the supplied signatures in order, invoking the first one it finds.
71 *
72 * @param instance the instance to invoke the method on
73 * @param methodName the name of the method
74 * @param signatures the ordered list of signatures to look for
75 * @param parameters the parameter values to use for each potential signature
76 * @return whatever the method returns
77 * @throws NoSuchMethodException when no method could be found
78 * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
79 * @throws IllegalAccessException when the method cannot be accessed
80 * @throws InvocationTargetException when the method that was invoked throws an exception
81 */
82 public static Object invokeCallbackMethod(Object instance, String methodName, Class<?>[][] signatures, Object[][] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
83 Class<?> currentClazz = instance.getClass();
84 while (currentClazz != null && currentClazz != Object.class) {
85 try {
86 return invokeMethod(instance, currentClazz, methodName, signatures, parameters, false);
87 }
88 catch (NoSuchMethodException nsme) {
89 // ignore
90 }
91 currentClazz = currentClazz.getSuperclass();
92 }
93 throw new NoSuchMethodException(methodName);
94 }
95
96 /**
97 * Invoke a method on an instance.
98 *
99 * @param object the instance to invoke the method on
100 * @param clazz the class of the instance
101 * @param name the name of the method
102 * @param signatures the signatures to look for in order
103 * @param parameters the parameter values for the signatures
104 * @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
105 * @return whatever the method returns
106 * @throws NoSuchMethodException when no method could be found
107 * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
108 * @throws IllegalAccessException when the method cannot be accessed
109 * @throws InvocationTargetException when the method that was invoked throws an exception
110 */
111 public static Object invokeMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException {
112 if (object == null) {
113 throw new IllegalArgumentException("Instance cannot be null");
114 }
115 if (clazz == null) {
116 throw new IllegalArgumentException("Class cannot be null");
117 }
118
119 // if we're talking to a proxy here, dig one level deeper to expose the
120 // underlying invocation handler (we do the same for injecting instances)
121 if (Proxy.isProxyClass(clazz)) {
122 object = Proxy.getInvocationHandler(object);
123 clazz = object.getClass();
124 }
125
126 Method m = null;
127 for (int i = 0; i < signatures.length; i++) {
128 Class<?>[] signature = signatures[i];
129 m = getDeclaredMethod(clazz, name, signature, isSuper);
130 if (m != null) {
131 return m.invoke(object, parameters[i]);
132 }
133 }
134 throw new NoSuchMethodException(name);
135 }
136
137 private static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] signature, boolean isSuper) {
138 // first check our cache
139 Key key = new Key(clazz, name, signature);
140 Method m = null;
141 synchronized (m_methodCache) {
142 m = (Method) m_methodCache.get(key);
143 if (m != null) {
144 return m;
145 }
146 else if (m_methodCache.containsKey(key)) {
147 // the key is in our cache, it just happens to have a null value
148 return null;
149 }
150 }
151 // then do a lookup
152 try {
153 m = clazz.getDeclaredMethod(name, signature);
154 if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
155 m.setAccessible(true);
156 }
157 }
158 catch (NoSuchMethodException e) {
159 }
160 synchronized (m_methodCache) {
161 m_methodCache.put(key, m);
162 }
163 return m;
164 }
165
166 public static class Key {
167 private final Class<?> m_clazz;
168 private final String m_name;
169 private final Class<?>[] m_signature;
170
171 public Key(Class<?> clazz, String name, Class<?>[] signature) {
172 m_clazz = clazz;
173 m_name = name;
174 m_signature = signature;
175 }
176
177 public int hashCode() {
178 final int prime = 31;
179 int result = 1;
180 result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode());
181 result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
182 result = prime * result + Arrays.hashCode(m_signature);
183 return result;
184 }
185
186 public boolean equals(Object obj) {
187 if (this == obj)
188 return true;
189 if (obj == null)
190 return false;
191 if (getClass() != obj.getClass())
192 return false;
193 Key other = (Key) obj;
194 if (m_clazz == null) {
195 if (other.m_clazz != null)
196 return false;
197 }
198 else if (!m_clazz.equals(other.m_clazz))
199 return false;
200 if (m_name == null) {
201 if (other.m_name != null)
202 return false;
203 }
204 else if (!m_name.equals(other.m_name))
205 return false;
206 if (!Arrays.equals(m_signature, other.m_signature))
207 return false;
208 return true;
209 }
210 }
211
212 @SuppressWarnings("serial")
213 public static class LRUMap extends LinkedHashMap<Key, Method> {
214 private final int m_size;
215
216 public LRUMap(int size) {
217 m_size = size;
218 }
219
220 protected boolean removeEldestEntry(java.util.Map.Entry<Key, Method> eldest) {
221 return size() > m_size;
222 }
223 }
Pierre De Rop3cf52462016-02-20 21:15:24 +0000224
225 /**
226 * Invokes a configuration update callback synchronously, but through the component executor queue.
227 */
228 public static void invokeUpdated(Executor queue, ConfigurationHandler handler) throws ConfigurationException {
229 Callable<Exception> result = () -> {
230 try {
231 handler.handle();
232 } catch (Exception e) {
233 return e;
234 }
235 return null;
236 };
237
238 FutureTask<Exception> ft = new FutureTask<>(result);
239 queue.execute(ft);
240
241 try {
242 Exception err = ft.get(UPDATED_MAXWAIT, TimeUnit.MILLISECONDS);
243 if (err != null) {
244 throw err;
245 }
246 }
247
248 catch (ConfigurationException e) {
249 throw e;
250 }
251
252 catch (Throwable error) {
253 Throwable rootCause = error.getCause();
254 if (rootCause != null) {
255 if (rootCause instanceof ConfigurationException) {
256 throw (ConfigurationException) rootCause;
257 }
258 throw new ConfigurationException("", "Configuration update error, unexpected exception.", rootCause);
259 } else {
260 throw new ConfigurationException("", "Configuration update error, unexpected exception.", error);
261 }
262 }
263 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000264}