You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by Luca Arzeni <l....@iname.com> on 2014/06/17 11:44:01 UTC

Editing tapestry wiki to add a ClassPathUrlConverter for JBoss-eap-6.1.1.GA (Jboss-as-7.2.1)

package com.example.project.services;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This converter works for Tapestry 5.3.7 under jboss-eap-6.1.1.GA, which is based on jboss-as-7.2.1, so it should work
 * even under that release.
 * 
 * @author l dot arzeni -at- iname dot com
 */
public class ClasspathURLConverterJBoss612Eap implements ClasspathURLConverter {
	private final static String JBOSS_VFS_PREFIX="vfs";
	private final static String JBOSS_TEMPORARY_FOLDER="/opt/jboss-eap-6.1.1.GA/standalone/tmp";
	private final static String JBOSS_VFS_310_GET_PHYSICAL_FILE_METHOD_NAME="getPhysicalFile";

	private final static Logger logger = LoggerFactory.getLogger(ClasspathURLConverterJBoss612Eap.class);

	public URL convert(URL url) {
		assert (url != null)  : "Error: url to convert should not be null!";
		
        // If the URL is not a "vfs" URL (JBoss 5 and newer uses a Virtual File System),
		// let it pass without further processing
		if (! url.getProtocol().startsWith(JBOSS_VFS_PREFIX)) {
			return url; // leave the url unchanged
		}

		logger.debug("Processing url "+ url);
		if (logger.isDebugEnabled()) {
			printFolderContents(JBOSS_TEMPORARY_FOLDER);
		}
		
		return getPhysicalUrl(url);
	}

    /**
     * To retrieve the archive physical path, we look for the rightmost "archive" component of the path.
     * Actually, since we assume to deploy an ear, we assume that we will find a "/lib/xxx.jar" or a "/yyy.war"
     * (the "/yyyy.war/WEB-INF/lib/zzz.jar" has not been tested, but should work).
     * 
     * When we have obtained the vfs path of the rightmost archive, we ask to vfs the physical path of the mounted
     * archive; then we append to it the "package" part of the url. 
     * 
     * @param i_vfsUrl the "vfs:" url to be analyzed
     * 
     * @return the url to the physical archive/component, null if there are errors
     * 
     * Examples:
     * JAR: 
     * IN:  vfs:/content/example.ear/lib/tapestry-core-5.3.7.jar/org/apache/tapestry5/corelib/components/OutputRaw.class
     * OUT: jar:file:/opt/jboss-eap-6.1.1.GA/standalone/tmp/vfs/deployment25da2658a1e1e74e/tapestry-core-5.3.7.jar-607f5c47254d148c/tapestry-core-5.3.7.jar!/org/apache/tapestry5/corelib/components/OutputRaw.class
     * 
     * WAR:
     * IN:  vfs:/content/example.ear/example.war/WEB-INF/classes/com/example/project/components/
     * OUT: file:/opt/jboss-eap-6.1.1.GA/standalone/tmp/vfs/deployment25da2658a1e1e74e/example.war-3c5a10a04f50bfe9/WEB-INF/classes/com/example/project/components/
     * 
     */
    private URL getPhysicalUrl(URL i_vfsUrl) {
    	assert (i_vfsUrl != null) : "i_vfsUrl cannot be null"; 
		logger.debug("i_vfsUrl="+ i_vfsUrl);

		boolean l_isJar=true;
		
		// Let's get the leaf archive virtual path and the components package path
						
		String l_archiveVfsSubstring=i_vfsUrl.toString().replaceAll("^(.*\\.jar)/.*$", "$1"); // greedy search, so we wil be sure to get the full vfs path of last jar element
		logger.debug("l_archiveVfsSubstring="+ l_archiveVfsSubstring);
		
		if (i_vfsUrl.toString().equals(l_archiveVfsSubstring)) {
			// if i_vfsUrl does NOT contains a jar archive, nothing to do, return the url unaltered
			logger.warn("i_vfsUrl="+ i_vfsUrl+" is not a jar archive, let's see if it's a war...");
			
			l_archiveVfsSubstring=i_vfsUrl.toString().replaceAll("^(.*\\.war)/.*$", "$1"); // greedy search, so we wil be sure to get the full vfs path of last jar element
			logger.debug("l_archiveVfsSubstring="+ l_archiveVfsSubstring);
			
			if (i_vfsUrl.toString().equals(l_archiveVfsSubstring)) {
				// if i_vfsUrl does NOT contains a jar archive, nothing to do, return the url unaltered
				logger.warn("i_vfsUrl="+ i_vfsUrl+" is not a war archive, returning unaltered");
		    	return i_vfsUrl;
			}
			l_isJar=false;
		}
		
		String l_packagePath=i_vfsUrl.toString().substring(l_archiveVfsSubstring.length());
		logger.debug("l_packagePath="+ l_packagePath);

		String l_archiveName=l_archiveVfsSubstring.replaceAll("^.*/", ""); // greedy search, so we wil be sure to strip the full vfs path until the last jar element
		logger.debug("l_archiveName="+ l_archiveName);
		
		URL l_archiveVFSUrl=null;
		try {
			l_archiveVFSUrl = new URL(l_archiveVfsSubstring);
			logger.debug("l_archiveVFSUrl="+ l_archiveVFSUrl);
		}
		catch (MalformedURLException l_exception) {
			logger.error("Cannot obtain l_archiveVFSUrl for i_vfsUrl="+ i_vfsUrl +", l_archiveVfsSubstring="+l_archiveVfsSubstring, l_exception);
			return null;
		}
		
		URLConnection l_archiveConnection=null;
		try {
			l_archiveConnection = l_archiveVFSUrl.openConnection();
			logger.debug("l_archiveConnection="+ l_archiveConnection);
		}
		catch (IOException l_exception) {
			logger.error("Cannot obtain l_archiveVFSUrl for i_vfsUrl="+ i_vfsUrl +", l_archiveVfsSubstring="+l_archiveVfsSubstring + ", l_archiveVFSUrl="+ l_archiveVFSUrl, l_exception);
			return null;
		}
		
		Object l_archiveVirtualDir=null;
		try {
			l_archiveVirtualDir = l_archiveConnection.getContent();
			logger.debug("l_archiveVirtualDir="+ l_archiveVirtualDir);
		}
		catch (IOException l_exception) {
			logger.error("Cannot obtain l_archiveVFSUrl for i_vfsUrl="+ i_vfsUrl +", l_archiveVfsSubstring="+l_archiveVfsSubstring + ", l_archiveVFSUrl="+ l_archiveVFSUrl + ", l_archiveConnection="+ l_archiveConnection, l_exception);
			return null;
		}
		
		// Use reflection so that we don't need JBoss in the classpath at compile time.
		File l_archivePhysicalDir = (File) invokerGetter(l_archiveVirtualDir, JBOSS_VFS_310_GET_PHYSICAL_FILE_METHOD_NAME);
		logger.debug("l_archivePhysicalDir="+ l_archivePhysicalDir);
		
		String l_physicalArchiveDirUri = l_archivePhysicalDir.toURI().toString();
		logger.debug("l_physicalArchiveDirUri="+ l_physicalArchiveDirUri);

		if (l_isJar) {
			String l_physicalArchiveUrl="jar:"+l_physicalArchiveDirUri.replaceAll("contents/$", l_archiveName)+"!"+l_packagePath;
			
			logger.info("JAR: i_vfsUrl="+ i_vfsUrl + " -> l_physicalArchiveUrl="+ l_physicalArchiveUrl);
			
			try {
				return new URL(l_physicalArchiveUrl);
			}
			catch (MalformedURLException l_exception) {
				logger.error("JAR: Cannot obtain l_archiveVFSUrl for i_vfsUrl="+ i_vfsUrl +", l_archiveVfsSubstring="+l_archiveVfsSubstring + ", l_archiveVFSUrl="+ l_archiveVFSUrl + ", l_archiveConnection="+ l_archiveConnection, l_exception);
				return null;
			}
		}
		
		// it's a war
		String l_physicalArchiveUrl=l_physicalArchiveDirUri+l_packagePath;
		
		logger.info("WAR: i_vfsUrl="+ i_vfsUrl + " -> l_physicalArchiveUrl="+ l_physicalArchiveUrl);
		
		try {
			return new URL(l_physicalArchiveUrl);
		}
		catch (MalformedURLException l_exception) {
			logger.error("WAR: Cannot obtain l_archiveVFSUrl for i_vfsUrl="+ i_vfsUrl +", l_archiveVfsSubstring="+l_archiveVfsSubstring + ", l_archiveVFSUrl="+ l_archiveVFSUrl + ", l_archiveConnection="+ l_archiveConnection, l_exception);
			return null;
		}
    }

	/**
	 * Utility method to retrieve informations from an object, using reflection in a safe way
	 * 
	 * @param i_target
	 *            the object on which the method must be invoked
	 * @param i_getterName
	 *            the name of the method to invoke (it must be a getter, taking no parameter)
	 * @return the result of the getter method, or null if there are errors
	 */
	private Object invokerGetter(Object i_target, String i_getterName) {
		assert (i_target != null) : "Object cannot be null";
		assert (i_getterName != null) : "getter cannot be null";
		assert (!i_getterName.isEmpty()) : "getter cannot be empty";

		Class<?> type = i_target.getClass();
		Method method = null;
		try {
			// at first try to find the getter within public methods...
			method = type.getMethod(i_getterName);
		}
		catch (NoSuchMethodException l_exception1) {
			logger.warn("Unable to invoke getMethod for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ". Details: " + l_exception1 + ", trying with getDeclaredMethod");

			if (logger.isDebugEnabled()) {
				logger.debug("=== getMethods: available getter methods are: ===");
				Method[] l_methods = type.getMethods();
				for (int i = 0; i < l_methods.length; i++) {
					if (l_methods[i].getName().startsWith("get")) {
						logger.debug("getMethods: " + l_methods[i].toString());
					}
				}
			}

			try {
				// sometimes jboss tries to hide implementation details, so let's try hacking with non public methods...
				method = type.getDeclaredMethod(i_getterName);
			}
			catch (Throwable l_exception2) {
				logger.warn("Error: unable to invoke getDeclaredMethod for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ". Details: " + l_exception2 + ", returning null", l_exception2);
				if (logger.isDebugEnabled()) {
					logger.debug("=== getDeclaredMethods: available getter methods are: ===");
					Method[] l_methods = type.getDeclaredMethods();
					for (int i = 0; i < l_methods.length; i++) {
						if (l_methods[i].getName().startsWith("get")) {
							logger.debug("getDeclaredMethod: " + l_methods[i].toString());
						}
					}
				}

				logger.error("Error: unable to invoke getMethod or getDeclaredMethod for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ", returning null");
				return null;
			}

			try {
				method.setAccessible(true);
			}
			catch (Throwable l_exception3) {
				logger.error("Error: unable to invoke setAccessible for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ". Details: " + l_exception3 + ", returning null", l_exception3);
				return null;
			}
		}
		catch (Throwable l_exception) {
			logger.error("Error: unable to invoke getMethod for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ". Details: " + l_exception + ", returning null", l_exception);
			return null;
		}

		try {
			return method.invoke(i_target);
		}
		catch (Throwable l_exception) {
			logger.error("Error: unable to execute method.invoke for " + i_getterName + " on object " + i_target + " of class " + i_target.getClass() + ". Details: " + l_exception + ", returning null", l_exception);
			return null;
		}
	}

	/**
	 * Utility method to show contents of temporary folders. This way we can check if we are building the correct path
	 * for tapestry resources
	 * 
	 * @param i_folderPath
	 *            the full path of the folder whose contents must be shown
	 */
	private void printFolderContents(String i_folderPath) {
		ArrayList<File> l_files = new ArrayList<File>();
		listFolderTree(i_folderPath, l_files);

		for (File l_file : l_files) {
			logger.debug(l_file.getAbsolutePath());
		}
	}

	/**
	 * Utility method to recursively collect all files and subfolders contained in a folder
	 * 
	 * @param i_folderPath
	 *            the full path of the folder whose contents must be examined
	 * @param o_foundFiles
	 *            (output) the list of all files and subfolders contained in l_folderPath
	 */
	private void listFolderTree(String i_folderPath, List<File> o_foundFiles) {
		File l_folder = new File(i_folderPath);

		// get all the files from a directory
		File[] l_filesArray = l_folder.listFiles();
		for (File l_file : l_filesArray) {
			if (l_file.isFile()) {
				o_foundFiles.add(l_file);
			}
			else if (l_file.isDirectory()) {
				listFolderTree(l_file.getAbsolutePath(), o_foundFiles);
			}
		}
	}
}