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    }