001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 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*/
020package org.granite.osgi.classloader;
021
022import java.util.Enumeration;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.Set;
026
027import org.granite.logging.Logger;
028import org.granite.messaging.service.annotations.RemoteDestination;
029import org.osgi.framework.Bundle;
030
031/**
032 * Granite DataService classloader
033 * scan packages then load qualified GDS classes.
034 * @author <a href="mailto:gembin@gmail.com">gembin@gmail.com</a>
035 * @since 1.1.0
036 */
037public class ServiceClassLoader {
038        private static final Logger log=Logger.getLogger(ServiceClassLoader.class);
039        private static final String CLASS_SUFFIX = ".class";
040        private Set<String> classesSet = new HashSet<String>();
041        /**
042         * a Bundle which is used to load classes
043         */
044        private Bundle bundle;
045        public void setBundle(Bundle bundle) {
046                this.bundle = bundle;
047        }
048        /**
049         * @param className
050         * @return path of a class
051         */
052        private static String packageForPath(String className) {
053                return className.replace('.', '/');
054        }
055        /**
056         * @param path
057         * @return package of a class
058         */
059        private static String pathForPackage(String path) {
060                return path.replace('/', '.').replace('\\', '.');
061        }
062        /**
063         * resolve package to find all the classes in a specified package of a bundle
064         * @param packageNamePath
065         * @param recursive
066         */
067        private void resolvePackage(String packageNamePath,boolean recursive){
068                //if(log.isInfoEnabled())
069                //  log.info("Resolving..."+packageNamePath);
070                @SuppressWarnings("unchecked")
071                Enumeration<String> en = bundle.getEntryPaths(packageNamePath);
072                if (en != null) {
073                        while (en.hasMoreElements()) {
074                                String entryPath = en.nextElement();
075                                //recursive subpackages if wildcard is presented
076                                if(recursive && entryPath.endsWith("/")){ 
077                                        resolvePackage(entryPath,recursive);
078                                }else if(entryPath.endsWith(CLASS_SUFFIX)){
079                                        String className = entryPath.substring(0,entryPath.length()- CLASS_SUFFIX.length());
080                                        classesSet.add(pathForPackage(className));
081                                }
082                        }
083                } 
084        }
085        /**
086         * @param className
087         * @return valid Service class annotated with @RemoteDestination
088         */
089        public Class<?> loadClass(String className){
090                try {
091                        Class<?> clazz = bundle.loadClass(className);
092                        if (clazz.isAnnotationPresent(RemoteDestination.class)) {
093                                if(log.isInfoEnabled())
094                                  log.info(clazz.toString() + " is a valid GDS Service");
095                                return clazz;
096                        } 
097                } catch (ClassNotFoundException e) {
098                        e.printStackTrace();
099                }
100                return null;
101        }
102        /**
103         * Scan the packages and load all the qualified GraniteDS classes
104         * @param packages
105         * @return a set of valid Service classes annotated with @RemoteDestination
106         */
107        public Set<Class<?>> loadClasses(String[] packages) {
108                Set<Class<?>> classes = new HashSet<Class<?>>();
109                if (packages != null){
110                        for (int i = 0; i < packages.length; i++) {
111                                String packageName = packages[i];
112                                if (bundle != null) {
113                                        boolean recursive=packageName.endsWith("*");
114                                        if(recursive)
115                                                packageName=packageName.substring(0, packageName.length()-2);//remove wildcard '*'
116                                        resolvePackage(packageForPath(packageName),recursive);
117                                        Iterator<String> it=classesSet.iterator();
118                                        while(it.hasNext()){
119                                                Class<?> clazz=null;
120                                                try {
121                                                        clazz = bundle.loadClass(it.next());
122                                                        if (clazz!=null && clazz.isAnnotationPresent(RemoteDestination.class)) {
123                                                                if(log.isInfoEnabled())
124                                                                        log.info(clazz.toString() + " is a valid GDS Service");
125                                                                classes.add(clazz);
126                                                        } 
127                                                } catch (ClassNotFoundException e) {
128                                                        log.error("Service class not found", e);
129                                                }
130                                        }
131                                } else {
132                                        if(log.isInfoEnabled()) 
133                                                log.info("Bundle is not specified, cannot load classes!!");
134                                }
135                        }
136                }
137                return classes;
138        }
139}