All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mobicents.servlet.sip.annotations.ClassFileScanner Maven / Gradle / Ivy

There is a newer version: 4.0.128
Show newest version
/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see 
 */

package org.mobicents.servlet.sip.annotations;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.annotation.SipApplication;
import javax.servlet.sip.annotation.SipApplicationKey;
import javax.servlet.sip.annotation.SipListener;
import javax.servlet.sip.annotation.SipServlet;

import org.apache.catalina.loader.WebappClassLoader;
import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.annotation.ConcurrencyControl;
import org.mobicents.servlet.sip.catalina.CatalinaSipContext;
import org.mobicents.servlet.sip.catalina.SipServletImpl;

/**
 * This class implement the logic to enumerate all class files in
 * WEB-INF/classes and extract descriptor information. I am using this method because
 * if I look directly in a classloader it would have loaded all libs includeing third
 * party libs in WEB-INF/jar, and system libs. Parsing all these would be slow, so for
 * now we will only look in WEB-INF/classes since it works.
 * 
 * General TODO: Validation
 * 
 * @author Vladimir Ralev
 *
 */
public class ClassFileScanner {

	private static transient final Logger logger = Logger.getLogger(ClassFileScanner.class);
			
	private String docbase;
	
	private CatalinaSipContext sipContext;
	
	private String parsedAnnotatedPackage = null;
	
	private boolean applicationParsed = false;
	
	private Method sipAppKey = null;
	
	private ClassLoader classLoader;
	
	public ClassFileScanner(String docbase, CatalinaSipContext ctx) {
		this.docbase = docbase;
		this.sipContext = ctx;
	}
	
	/**
	 * Scan the application for annotations with the contextconfig classloader.
	 * It scans in the following locations :
	 * WEB-INF/classes
	 * WEB-INF/lib
	 * ../APP-INF/lib
	 * ../APP-INF/classes
	 * 
	 * @throws AnnotationVerificationException thrown if some annotations doesn't follow the restrictions given by the annotation contract
	 */
	public void scan() throws AnnotationVerificationException {
		ClassLoader cl = this.sipContext.getClass().getClassLoader();
		this.classLoader = sipContext.getLoader().getClassLoader();
//		this.classLoader.setResources(this.sipContext.getResources());
//		this.classLoader.setAntiJARLocking(true);
		
		if(logger.isInfoEnabled()) {
			logger.info("Annotations docBase : " + this.docbase);
		}
		
//		this.classLoader.setWorkDir(new File(this.docbase + "/tmp"));
		
		// Add this SAR/WAR's binary file from WEB-INF/classes and WEB-INF/lib		
//		this.classLoader.addRepository("/WEB-INF/classes/", new File(this.docbase + "/WEB-INF/classes/"));
//		this.classLoader.addJarDir(this.docbase + "/WEB-INF/lib/");
		//Add those only for EAR files
//		if(docbase.indexOf(".ear")!=-1) {
//			//Adding root dir to include jars located here like ejb modules and so on...
//			//Ideally we may want to parse the application.xml and get the jars that are defined in it...?
//			this.classLoader.addJarDir(this.docbase + "/../");
//			
//			// Try to add the EAR binaries as repositories
//			File earJarDir = new File(this.docbase + "/../APP-INF/lib");
//			File earClassesDir = new File(this.docbase + "/../APP-INF/classes");
//			if(earJarDir.exists())
//				this.classLoader.addJarDir(this.docbase + "/../APP-INF/lib");
//			if(earClassesDir.exists())
//				this.classLoader.addRepository(this.docbase + "/../APP-INF/classes");
//		}
		// TODO: Add META-INF classpath
			
		_scan(new File(this.docbase));
	}
	
	protected void _scan(File folder) throws AnnotationVerificationException {    	
        File[] files = folder.listFiles();
        if(files != null) {
	        for(int j = 0; j < files.length; j++) {
	            if(files[j].isDirectory()) {
	                _scan(files[j]);
	            } else if(files[j].getAbsolutePath().endsWith(".jar")) {
	            	scanJar(files[j].getAbsolutePath());
	            } else {
	            	analyzeClass(files[j].getAbsolutePath());
	            }
	        }
        }
    }
	
	private void scanJar(String path) throws AnnotationVerificationException {
		if(logger.isDebugEnabled()) {
    		logger.debug("scanning jar " + path + " for annotations");
    	}
		try {
			JarFile jar =new JarFile(path);
			Enumeration jarEntries = jar.entries();
			while (jarEntries.hasMoreElements()) {
				JarEntry jarEntry = jarEntries.nextElement();
				String entryName = jarEntry.getName();
								
				if(entryName.endsWith(".class")) {
					String className =  entryName.substring(0, entryName.indexOf(".class"));
					className = className.replace('/', '.');
					className = className.replace('\\', '.');
					try {
		    	    	Class clazz = Class.forName(className, false, this.classLoader);
		    	    	processAnnotations(clazz);
		    		} catch (Throwable e) {
		    			logger.debug("Failed to parse annotations for class " + className);
		    			if(logger.isDebugEnabled()) {
		    				logger.debug("Failed to parse annotations for class " + className, e);
		    			}
		    		}
				}
			}
		} catch (IOException e) {
			throw new AnnotationVerificationException("couldn't read the following jar file for parsing annotations " + path, e);
		} 
	}
    
    protected void analyzeClass(String path) throws AnnotationVerificationException {
    	if(logger.isDebugEnabled()) {
    		logger.debug("analyzing class " + path + " for annotations");
    	}
    	// TODO: must check if there are extra /// or \\\ or /./ in the path after classes/
    	int classesIndex = path.toLowerCase().lastIndexOf("classes/");
    	if(classesIndex < 0) classesIndex = path.toLowerCase().lastIndexOf("classes\\");
    	classesIndex += "classes/".length();
    	String classpath = path.substring(classesIndex);
    	classpath = classpath.replace('/', '.').replace('\\', '.');
    	if(classpath.endsWith(".class")) {
    		classpath = classpath.substring(0, classpath.length() - 6);
    		if(classpath.startsWith(".")) classpath = classpath.substring(1);
    		String className = classpath;
    		try {
    	    	Class clazz = Class.forName(className, false, this.classLoader);
    	    	processAnnotations(clazz);
    		} catch (Throwable e) {
    			logger.debug("Failed to parse annotations for class " + className);
    			if(logger.isDebugEnabled()) {
    				logger.debug("Failed to parse annotations for class " + className, e);
    			}
    		}
    	} 
    }
    
    protected void processAnnotations(Class clazz) throws AnnotationVerificationException {
    	if(logger.isDebugEnabled()) {
    		logger.debug("analyzing class " + clazz + " for annotations");
    	}    	
    	processListenerAnnotation(clazz);
		processServletAnnotation(clazz);
		processSipApplicationKeyAnnotation(clazz);
		processConcurrencyAnnotation(clazz);   
		if(clazz.toString().contains("package-info")) {
			if(logger.isDebugEnabled()) {
	    		logger.debug("scanning " + clazz.getCanonicalName() + " for @SipApplication annotation");
	    	}
			Package pack = clazz.getPackage();
			String packageName = pack.getName();

			SipApplication appData = getApplicationAnnotation(pack);
			if (appData != null) {
				if (this.parsedAnnotatedPackage != null
						&& !this.parsedAnnotatedPackage.equals(packageName)) {
					throw new IllegalStateException(
							"Cant have two different applications in a single context - "
									+ packageName + " and "
									+ this.parsedAnnotatedPackage);
				}
				
				if (this.parsedAnnotatedPackage == null) {
					this.parsedAnnotatedPackage = packageName;
					parseSipApplication(appData, packageName);
				}								
			} else {
				if(logger.isDebugEnabled()) {
		    		logger.debug("no @SipApplication annotation in " + clazz.getCanonicalName());
		    	}
			}
		}
	}

	protected void processListenerAnnotation(Class clazz) {
    	if(logger.isDebugEnabled()) {
    		logger.debug("scanning " + clazz.getCanonicalName() + " for listener annotations");
    	}
    	SipListener listener = (SipListener) clazz.getAnnotation(SipListener.class);
    	if(listener != null) {
    		if(logger.isDebugEnabled()) {
    			logger.debug("the following listener has been found as an annotation " + clazz.getCanonicalName());
    		}
    		sipContext.addSipApplicationListener(clazz.getCanonicalName());
    	}
    }
    
	protected void processSipApplicationKeyAnnotation(Class clazz) throws AnnotationVerificationException {
    	if(logger.isDebugEnabled()) {
    		logger.debug("scanning " + clazz.getCanonicalName() + " for sip application key annotation");
    	}
		Method[] methods = clazz.getMethods();
		for(Method method:methods) {
			if(method.getAnnotation(SipApplicationKey.class)!=null) {
				if(!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
					throw new AnnotationVerificationException(
							"A method annotated with the @SipApplicationKey annotation MUST be public and static");
				}
				if(!method.getGenericReturnType().equals(String.class)) {
					throw new AnnotationVerificationException(
							"A method annotated with the @SipApplicationKey annotation MUST return a String");
				}
				Type[] types = method.getGenericParameterTypes();
				if(types.length != 1 || !types[0].equals(SipServletRequest.class)) {
					throw new AnnotationVerificationException(
							"A method annotated with the @SipApplicationKey annotation MUST have a single argument of type SipServletRequest");
				}
				if(this.sipAppKey != null && !sipAppKey.equals(method)) {
				    // https://code.google.com/p/sipservlets/issues/detail?id=37
					throw new IllegalStateException(
						"More than one SipApplicationKey annotated method is not allowed.");
				}
				this.sipAppKey = method;
				if(logger.isDebugEnabled()) {
	    			logger.debug("the following @SipApplicationKey annotated method has been found " + method.toString());
	    		}
				sipContext.setSipApplicationKeyMethod(method);
			}
		}    	
    }
    
    
    protected void processServletAnnotation(Class clazz) {
    	if(logger.isDebugEnabled()) {
    		logger.debug("scanning " + clazz.getCanonicalName() + " for servlet annotations");
    	}
		SipServlet servlet = (SipServlet) clazz.getAnnotation(SipServlet.class);
		if (servlet == null)
			return;
		
		SipServletImpl parsedServletData = (SipServletImpl) sipContext.createWrapper();

		String appName = null;
		if (servlet.applicationName() == null
				|| servlet.applicationName().equals("")) {
			// String packageName = clazz.getCanonicalName().substring(0,
			// clazz.getCanonicalName().lastIndexOf('.'));
			// Wasted a whole day watching this line...
			Package pack = clazz.getPackage();
			String packageName = pack.getName();

			SipApplication appData = getApplicationAnnotation(pack);
			if (appData != null) {
				if (this.parsedAnnotatedPackage != null
						&& !this.parsedAnnotatedPackage.equals(packageName)) {
					throw new IllegalStateException(
							"Cant have two different applications in a single context - "
									+ packageName + " and "
									+ this.parsedAnnotatedPackage);
				}
				
				if (this.parsedAnnotatedPackage == null) {
					this.parsedAnnotatedPackage = packageName;
					parseSipApplication(appData, packageName);
				}
				appName = sipContext.getName();
			}
		}

		if (appName == null) {
			appName = servlet.applicationName();
		}
		if(sipContext.getApplicationName() == null && appName != null) {
			sipContext.setApplicationName(appName);
		}
		String name = null;
		if (servlet.name() == null || servlet.name().equals("")) {
			name = clazz.getSimpleName(); // if no name is specified deduce
											// from the classname
		} else {
			name = servlet.name();
		}
		
		if (sipContext.getMainServlet() == null
				|| sipContext.getMainServlet().equals("")) {
			sipContext.setMainServlet(name);
		}
		if(logger.isDebugEnabled()) {
			logger.debug("the following @SipServlet annotation has been found : ");
			logger.debug("Name,ServletName,DisplayName : " + name);
			logger.debug("Description : " + servlet.description());
			logger.debug("servletClass : " + clazz.getCanonicalName());
		}
		parsedServletData.setName(name);
		parsedServletData.setServletName(name);
		parsedServletData.setDisplayName(name);
		parsedServletData.setDescription(servlet.description());
		parsedServletData.setServletClass(clazz.getCanonicalName());
		parsedServletData.setLoadOnStartup(1);
		parsedServletData.setParent(sipContext);
		sipContext.addChild(parsedServletData);
		this.applicationParsed = true;
	}
    
    protected void parseSipApplication(SipApplication appData, String packageName) {
    	sipContext.setMainServlet(appData.mainServlet());
    	sipContext.setProxyTimeout(appData.proxyTimeout());
    	sipContext.setSipApplicationSessionTimeout(appData.sessionTimeout());
    	
    	if(appData.name() == null || appData.name().equals(""))
    		sipContext.setApplicationName(packageName);
    	else
    		sipContext.setApplicationName(appData.name());
    	
    	if(appData.displayName() == null || appData.displayName().equals(""))
    		sipContext.setDisplayName(packageName);
    	else
    		sipContext.setDisplayName(appData.displayName());
    	if(logger.isDebugEnabled()) {
			logger.debug("the following @SipApplication annotation has been found : ");
			logger.debug("ApplicationName : " + sipContext.getApplicationName());
			logger.debug("MainServlet : " + sipContext.getMainServlet());
		}
    	sipContext.setDescription(appData.description());
    	sipContext.setLargeIcon(appData.largeIcon());
    	sipContext.setSmallIcon(appData.smallIcon());
    	sipContext.setDistributable(appData.distributable());
    }
    
    protected SipApplication getApplicationAnnotation(Package pack) {
    	if(logger.isDebugEnabled()) {
			logger.debug("Analyzing " + pack + " for @SipApplication annotations");
    	}
    	if(pack == null) return null;    	    	
    	
    	SipApplication sipApp = (SipApplication) pack.getAnnotation(SipApplication.class);
    	if(sipApp != null) {
    		return sipApp;
    	}
    	return null;
    }

    /**
     * Check if the @ConcurrencyControl annotation is present in the package and if so process it 
     * @param clazz the clazz to check for @ConcurrencyControl annotation - only its package will be checked
     */
    protected void processConcurrencyAnnotation(Class clazz) {
    	if(sipContext.getConcurrencyControlMode() == null) {
	    	Package pack = clazz.getPackage();
	    	if(pack != null) {
				ConcurrencyControl concurrencyControl = pack.getAnnotation(ConcurrencyControl.class);
				if(concurrencyControl != null) {
					if(logger.isDebugEnabled()) {
						logger.debug("Concurrency control annotation found " + concurrencyControl.mode());
					}
					sipContext.setConcurrencyControlMode(concurrencyControl.mode());
				}
	    	}

    	}
	}

    
    /**
     * Shows if there is SipApplication annotation parsed and thur we dont need to
     * look at sip.xml to seearch descriptor info.
     * 
     * @return
     */
	public boolean isApplicationParsed() {
		return applicationParsed;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy