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