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    }