001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 * This file is part of Granite Data Services.
006 *
007 * Granite Data Services is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU Library General Public License as published by
009 * the Free Software Foundation; either version 2 of the License, or (at your
010 * option) any later version.
011 *
012 * Granite Data Services is distributed in the hope that it will be useful, but
013 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 * for more details.
016 *
017 * You should have received a copy of the GNU Library General Public License
018 * along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020 package org.granite.client.messaging;
021
022 import static net.sf.extcos.util.Assert.iae;
023 import static net.sf.extcos.util.StringUtils.append;
024
025 import java.io.BufferedInputStream;
026 import java.io.IOException;
027 import java.io.InputStream;
028 import java.lang.annotation.Annotation;
029 import java.lang.reflect.InvocationTargetException;
030 import java.lang.reflect.Method;
031 import java.lang.reflect.Modifier;
032 import java.net.URL;
033 import java.security.AccessController;
034 import java.security.PrivilegedActionException;
035 import java.security.PrivilegedExceptionAction;
036 import java.util.ArrayList;
037 import java.util.HashMap;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Set;
041
042 import net.sf.extcos.internal.ArraySet;
043 import net.sf.extcos.spi.AnnotationMetadata;
044 import net.sf.extcos.spi.ClassLoaderHolder;
045 import net.sf.extcos.spi.ResourceAccessor;
046 import net.sf.extcos.util.Assert;
047 import net.sf.extcos.util.ClassUtils;
048
049 import org.granite.logging.Logger;
050 import org.objectweb.asm.AnnotationVisitor;
051 import org.objectweb.asm.ClassReader;
052 import org.objectweb.asm.Type;
053 import org.objectweb.asm.commons.EmptyVisitor;
054
055 /**
056 * @author William DRAI
057 */
058 public class JavaResourceAccessor implements ResourceAccessor {
059
060 private class BooleanHolder {
061 boolean value;
062 }
063
064 private class NameHolder {
065 String name;
066 }
067
068 private abstract class AnnotatedClassVisitor extends EmptyVisitor {
069 @Override
070 public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
071 if (shouldVisitAnnotation(visible)) {
072 if (annotations == null)
073 annotations = new HashMap<String, AnnotationMetadata>();
074
075 return new AnnotationVisitorImpl(desc);
076 }
077
078 return null;
079 }
080
081 protected abstract boolean shouldVisitAnnotation(boolean visible);
082 }
083
084 private class GeneralVisitor extends AnnotatedClassVisitor {
085
086 private final NameHolder nameHolder;
087 private final BooleanHolder isClassHolder;
088
089 private GeneralVisitor(final NameHolder nameHolder, final BooleanHolder isClassHolder) {
090 this.nameHolder = nameHolder;
091 this.isClassHolder = isClassHolder;
092 }
093
094 @Override
095 public void visit(final int version, final int access, final String name,
096 final String signature, final String superName,
097 final String[] interfaces) {
098
099 if (!(Modifier.isAbstract(access) || Modifier.isInterface(access))) {
100 isClassHolder.value = true;
101 nameHolder.name = name;
102
103 readInterfaces(superName, interfaces);
104 readSuperClasses(superName);
105 }
106 }
107
108 @Override
109 public void visitInnerClass(final String name, final String outerName,
110 final String innerName, final int access) {
111
112 if (isClassHolder.value && nameHolder.name != null && nameHolder.name.equals(name))
113 isClassHolder.value = false;
114 }
115
116 @Override
117 public void visitOuterClass(final String owner, final String name, final String desc) {
118 isClassHolder.value = false;
119 }
120
121 @Override
122 protected boolean shouldVisitAnnotation(final boolean visible) {
123 return isClassHolder.value && visible;
124 }
125 }
126
127 private class AnnotationVisitorImpl extends EmptyVisitor {
128
129 private final AnnotationMetadataImpl metadata;
130 private final String className;
131
132 private AnnotationVisitorImpl(final String desc) {
133 metadata = new AnnotationMetadataImpl();
134 className = Type.getType(desc).getClassName();
135 }
136
137 @Override
138 public void visit(final String name, final Object value) {
139 metadata.putParameter(name, value);
140 }
141
142 @Override
143 public void visitEnum(final String name, final String desc, final String value) {
144 try {
145 String enumName = Type.getType(desc).getClassName();
146 Class<?> enumClass = ClassLoaderHolder.getClassLoader().loadClass(enumName);
147 Method valueOf = enumClass.getDeclaredMethod("valueOf", String.class);
148 Object object = valueOf.invoke(null, value);
149 metadata.putParameter(name, object);
150 }
151 catch (Exception e) {
152 // ignored
153 }
154 }
155
156 @Override
157 public void visitEnd() {
158 try {
159 Class<?> annotationClass = ClassLoaderHolder.getClassLoader().loadClass(className);
160
161 // Check declared default values of attributes in the annotation type.
162 Method[] annotationAttributes = annotationClass.getMethods();
163 for (Method annotationAttribute : annotationAttributes) {
164 String attributeName = annotationAttribute.getName();
165 Object defaultValue = annotationAttribute.getDefaultValue();
166 if (defaultValue != null && !metadata.hasKey(attributeName))
167 metadata.putParameter(attributeName, defaultValue);
168 }
169 annotations.put(className, metadata);
170 }
171 catch (ClassNotFoundException ex) {
172 // Class not found - can't determine meta-annotations.
173 }
174 }
175 }
176
177 private class AnnotationMetadataImpl implements AnnotationMetadata {
178
179 private final Map<String, Object> parameters = new HashMap<String, Object>();
180
181 @Override
182 public Object getValue(final String key) {
183 return parameters.get(key);
184 }
185
186 @Override
187 public boolean hasKey(final String key) {
188 return parameters.containsKey(key);
189 }
190
191 protected void putParameter(final String key, final Object value) {
192 parameters.put(key, value);
193 }
194 }
195
196 private static Logger logger = Logger.getLogger(JavaResourceAccessor.class);
197
198 private static Method defineClass;
199 private static Method resolveClass;
200
201 static {
202 try {
203 AccessController.doPrivileged(
204 new PrivilegedExceptionAction<Void>() {
205 @Override
206 public Void run() throws Exception{
207 Class<?> cl = Class.forName("java.lang.ClassLoader");
208
209 defineClass = cl.getDeclaredMethod("defineClass",
210 new Class[] { String.class, byte[].class,
211 int.class, int.class });
212
213 resolveClass = cl.getDeclaredMethod("resolveClass",
214 Class.class);
215
216 return null;
217 }
218 });
219 }
220 catch (PrivilegedActionException pae) {
221 throw new RuntimeException("cannot initialize Java Resource Accessor", pae.getException());
222 }
223 }
224
225 private static final int ASM_FLAGS = ClassReader.SKIP_DEBUG + ClassReader.SKIP_CODE + ClassReader.SKIP_FRAMES;
226
227 private byte[] resourceBytes;
228 private URL resourceUrl;
229 private String className;
230
231 private Map<String, AnnotationMetadata> annotations;
232 private Set<String> interfaces;
233 private Set<String> superClasses;
234 private boolean isClass;
235
236 @Override
237 public Class<?> generateClass() {
238 if (!isClass)
239 return null;
240
241 Class<?> clazz = null;
242 ClassLoader loader = ClassLoaderHolder.getClassLoader();
243
244 try {
245 defineClass.setAccessible(true);
246 resolveClass.setAccessible(true);
247
248 clazz = (Class<?>)defineClass.invoke(loader,
249 className, resourceBytes, 0, resourceBytes.length);
250 resolveClass.invoke(loader, clazz);
251 }
252 catch (InvocationTargetException e) {
253 if (e.getCause() instanceof LinkageError) {
254 try {
255 clazz = Class.forName(className, true, loader);
256 }
257 catch (ClassNotFoundException e1) {
258 logger.error(append("Error creating class from URL [", resourceUrl.toString(), "]"), e1);
259 }
260 }
261 else {
262 logger.error(append("Error creating class from URL [",
263 resourceUrl.toString(), "]"), e.getCause());
264 }
265 }
266 catch (Exception e) {
267 logger.error(append("Error creating class from URL [",
268 resourceUrl.toString(), "]"), e);
269 }
270 finally {
271 defineClass.setAccessible(false);
272 resolveClass.setAccessible(false);
273 }
274
275 return clazz;
276 }
277
278 @Override
279 public AnnotationMetadata getAnnotationMetadata(final Class<? extends Annotation> annotation) {
280
281 if (isClass && annotations != null && annotations.containsKey(annotation.getCanonicalName()))
282 return annotations.get(annotation.getCanonicalName());
283
284 return null;
285 }
286
287 @Override
288 public boolean hasInterface(final Class<?> interfaze) {
289 if (isClass && interfaces != null)
290 return interfaces.contains(interfaze.getCanonicalName());
291
292 return false;
293 }
294
295 @Override
296 public boolean isClass() {
297 return isClass;
298 }
299
300 @Override
301 public boolean isSubclassOf(final Class<?> clazz) {
302 if (clazz == Object.class)
303 return true;
304
305 if (isClass && superClasses != null)
306 return superClasses.contains(clazz.getCanonicalName());
307
308 return false;
309 }
310
311 @Override
312 public void setResourceUrl(final URL resourceUrl) {
313 Assert.notNull(resourceUrl, iae());
314
315 try {
316 this.resourceBytes = readBytes(resourceUrl);
317 this.resourceUrl = resourceUrl;
318 readClassData();
319 }
320 catch (IOException e) {
321 isClass = false;
322 logger.error("Error reading resource", e);
323 }
324 }
325
326 private byte[] readBytes(final URL resourceUrl) throws IOException {
327 InputStream classStream = new BufferedInputStream(resourceUrl.openStream());
328 List<Byte> buffer = new ArrayList<Byte>();
329 int readByte;
330
331 while((readByte = classStream.read()) != -1) {
332 buffer.add((byte)readByte);
333 }
334
335 byte[] bytes = new byte[buffer.size()];
336 for (int i = 0; i < buffer.size(); i++)
337 bytes[i] = buffer.get(i);
338
339 return bytes;
340 }
341
342 private void readClassData() {
343 BooleanHolder isClassHolder = new BooleanHolder();
344 NameHolder nameHolder = new NameHolder();
345
346 ClassReader reader = new ClassReader(resourceBytes);
347 reader.accept(new GeneralVisitor(nameHolder, isClassHolder), ASM_FLAGS);
348
349 isClass = isClassHolder.value;
350
351 if (isClass)
352 className = ClassUtils.convertResourcePathToClassName(nameHolder.name);
353 else {
354 // if the resource isn't a valid class, clean memory
355 annotations = null;
356 interfaces = null;
357 superClasses = null;
358 resourceBytes = null;
359 resourceUrl = null;
360 }
361 }
362
363 private void readSuperClasses(final String superName) {
364 if (!"java/lang/Object".equals(superName)) {
365 if (superClasses == null) {
366 superClasses = new ArraySet<String>();
367 }
368
369 String superClass = ClassUtils.convertResourcePathToClassName(
370 superName);
371 superClasses.add(superClass);
372
373 try {
374 ClassReader reader = new ClassReader(superClass);
375 reader.accept(new AnnotatedClassVisitor() {
376 @Override
377 public void visit(final int version, final int access, final String name,
378 final String signature, final String superName, final String[] interfaces) {
379 readSuperClasses(superName);
380 }
381
382 @Override
383 protected boolean shouldVisitAnnotation(final boolean visible) {
384 return visible;
385 }
386 }, ASM_FLAGS);
387 }
388 catch (Exception e) {
389 // ignored
390 }
391 }
392 }
393
394 private void readInterfaces(final String superName, final String[] interfaces) {
395 if (this.interfaces == null && interfaces.length > 0)
396 this.interfaces = new ArraySet<String>();
397
398 for (String interfaze : interfaces) {
399 this.interfaces.add(
400 ClassUtils.convertResourcePathToClassName(interfaze));
401 readSuperInterfaces(interfaze);
402 }
403
404 readInheritedInterfaces(superName);
405 }
406
407 private void readInheritedInterfaces(final String superName) {
408 if ("java/lang/Object".equals(superName))
409 return;
410
411 readSuperInterfaces(superName);
412 }
413
414 private void readSuperInterfaces(final String type) {
415 try {
416 ClassReader reader = new ClassReader(ClassUtils.convertResourcePathToClassName(type));
417
418 reader.accept(new EmptyVisitor() {
419 @Override
420 public void visit(final int version, final int access, final String name,
421 final String signature, final String superName, final String[] interfaces) {
422 readInterfaces(superName, interfaces);
423 }
424 }, ASM_FLAGS);
425 }
426 catch (Exception e) {
427 // ignored
428 }
429 }
430 }