This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-fsclassloader.git

commit ac4bcb595ad3845116a9d09ade79ddf4efe51676
Author: Dan Klco <[email protected]>
AuthorDate: Fri Feb 17 19:06:12 2017 +0000

    SLING-5980: Deduplicating the FSClassLoader cache clearing functionality 
and adding a JMX interface
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1783452 
13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   8 +-
 .../commons/fsclassloader/FSClassLoaderMBean.java  |  59 ++
 .../fsclassloader/impl/FSClassLoaderMBeanImpl.java | 112 ++++
 .../fsclassloader/impl/FSClassLoaderProvider.java  | 549 +++++++++-------
 .../impl/FSClassLoaderWebConsole.java              | 691 +++++++++------------
 .../commons/fsclassloader/impl/ScriptFiles.java    |  89 +++
 6 files changed, 883 insertions(+), 625 deletions(-)

diff --git a/pom.xml b/pom.xml
index d79e248..f75ddc3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,7 +97,13 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.classloader</artifactId>
-            <version>1.3.0</version>
+            <version>1.3.9-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.1.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git 
a/src/main/java/org/apache/sling/commons/fsclassloader/FSClassLoaderMBean.java 
b/src/main/java/org/apache/sling/commons/fsclassloader/FSClassLoaderMBean.java
new file mode 100644
index 0000000..be49971
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/commons/fsclassloader/FSClassLoaderMBean.java
@@ -0,0 +1,59 @@
+/*
+ * #%L
+ * ACS AEM Commons Bundle
+ * %%
+ * Copyright (C) 2017 - Adobe
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package org.apache.sling.commons.fsclassloader;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * MBean interface for interacting with the FSClassLoader
+ */
+public interface FSClassLoaderMBean {
+
+       /**
+        * Clears the cache of compiled scripts from the FSClassLoader
+        */
+       void clearCache();
+
+       /**
+        * Gets the root directory at which the FSClassloader is creating it's
+        * cached files
+        * 
+        * @return the file system classloader root
+        */
+       String getFSClassLoaderRoot();
+
+       /**
+        * Get the total count of scripts in the FSClassLoader cache
+        * 
+        * @return the total number of scripts
+        * @throws IOException
+        */
+       int getCachedScriptCount() throws IOException;
+
+       /**
+        * Gets the scripts in the FSClassLoaderCache
+        * 
+        * @return the scripts from the FSClassLoaderCache
+        * @throws IOException
+        */
+       List<String> getCachedScripts() throws IOException;
+
+}
diff --git 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderMBeanImpl.java
 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderMBeanImpl.java
new file mode 100644
index 0000000..d9afe3c
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderMBeanImpl.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.commons.fsclassloader.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.commons.fsclassloader.FSClassLoaderMBean;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the FSClassLoaderMBean interface
+ */
+public class FSClassLoaderMBeanImpl implements FSClassLoaderMBean {
+       private final BundleContext context;
+       private final FSClassLoaderProvider fsClassLoaderProvider;
+       private static final Logger log = 
LoggerFactory.getLogger(FSClassLoaderMBeanImpl.class);
+
+       public FSClassLoaderMBeanImpl(final FSClassLoaderProvider 
fsClassLoaderProvider, final BundleContext context) {
+               this.fsClassLoaderProvider = fsClassLoaderProvider;
+               this.context = context;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.apache.sling.commons.fsclassloader.FSClassLoaderMBean#
+        * getCachedScriptCount()
+        */
+       @Override
+       public int getCachedScriptCount() throws IOException {
+               return getScripts().size();
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.apache.sling.commons.fsclassloader.FSClassLoaderMBean#
+        * getCachedScripts()
+        */
+       @Override
+       public List<String> getCachedScripts() {
+               List<String> scripts = new ArrayList<String>();
+               scripts.addAll(getScripts());
+               Collections.sort(scripts);
+               return scripts;
+       }
+
+       private Collection<String> getScripts() {
+               Collection<String> scripts = new HashSet<String>();
+               try {
+                       Map<String, ScriptFiles> s = new LinkedHashMap<String, 
ScriptFiles>();
+                       File root = new File(context.getDataFile(""), 
"classes");
+                       if (root != null) {
+                               FSClassLoaderWebConsole.readFiles(root, root, 
s);
+                       }
+                       scripts = s.keySet();
+               } catch (Exception e) {
+                       log.warn("Exception retrieving scripts from 
FSClassLoader", e);
+               }
+               return scripts;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see
+        * 
org.apache.sling.commons.fsclassloader.FSClassLoaderMBean#clearCache()
+        */
+       @Override
+       public void clearCache() {
+               fsClassLoaderProvider.delete("");
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.apache.sling.commons.fsclassloader.FSClassLoaderMBean#
+        * getFSClassLoaderRoot()
+        */
+       @Override
+       public String getFSClassLoaderRoot() {
+               return new File(context.getDataFile(""), 
"classes").getAbsolutePath();
+       }
+
+}
diff --git 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderProvider.java
 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderProvider.java
index ee6b6bc..2b63f8d 100644
--- 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderProvider.java
+++ 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderProvider.java
@@ -28,7 +28,13 @@ import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -37,10 +43,17 @@ import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.commons.classloader.ClassLoaderWriter;
+import org.apache.sling.commons.classloader.ClassLoaderWriterListener;
 import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
+import org.apache.sling.commons.fsclassloader.FSClassLoaderMBean;
+import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,240 +64,304 @@ import org.slf4j.LoggerFactory;
  *
  */
 @Component
-@Service(value={ClassLoaderWriter.class}, serviceFactory = true)
-@Property( name=Constants.SERVICE_RANKING, intValue=100)
-public class FSClassLoaderProvider
-    implements ClassLoaderWriter {
-
-    /** File root */
-    private File root;
-
-    /** File root URL */
-    private URL rootURL;
-
-    /** Current class loader */
-    private FSDynamicClassLoader loader;
-
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    @Reference(
-            referenceInterface = DynamicClassLoaderManager.class,
-            bind = "bindDynamicClassLoaderManager",
-            unbind = "unbindDynamicClassLoaderManager")
-    private ServiceReference dynamicClassLoaderManager;
-
-    /** The bundle asking for this service instance */
-    private Bundle callerBundle;
-
-    /**
-     * Activate this component.
-     * Create the root directory.
-     * @param componentContext
-     * @throws MalformedURLException
-     */
-    @Activate
-    protected void activate(final ComponentContext componentContext) throws 
MalformedURLException {
-        // get the file root
-        this.root = new 
File(componentContext.getBundleContext().getDataFile(""), "classes");
-        this.root.mkdirs();
-        this.rootURL = this.root.toURI().toURL();
-        this.callerBundle = componentContext.getUsingBundle();
-    }
-
-    /**
-     * Deactivate this component.
-     * Create the root directory.
-     */
-    @Deactivate
-    protected void deactivate() {
-        this.root = null;
-        this.rootURL = null;
-        this.destroyClassLoader();
-    }
-
-    /**
-     * Called to handle binding the DynamicClassLoaderManager service
-     * reference
-     */
-    @SuppressWarnings("unused")
-    private void bindDynamicClassLoaderManager(final ServiceReference ref) {
-        this.dynamicClassLoaderManager = ref;
-    }
-
-    /**
-     * Called to handle unbinding of the DynamicClassLoaderManager service
-     * reference
-     */
-    @SuppressWarnings("unused")
-    private void unbindDynamicClassLoaderManager(final ServiceReference ref) {
-        if (this.dynamicClassLoaderManager == ref) {
-            this.dynamicClassLoaderManager = null;
-        }
-    }
-
-    private void destroyClassLoader() {
-        final ClassLoader rcl = this.loader;
-        if (rcl != null) {
-            this.loader = null;
-
-            final ServiceReference localDynamicClassLoaderManager = 
this.dynamicClassLoaderManager;
-            final Bundle localCallerBundle = this.callerBundle;
-            if ( localDynamicClassLoaderManager != null && localCallerBundle 
!= null ) {
-                
localCallerBundle.getBundleContext().ungetService(localDynamicClassLoaderManager);
-            }
-        }
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getClassLoader()
-     */
-    public ClassLoader getClassLoader() {
-        synchronized ( this ) {
-            if ( loader == null || !loader.isLive() ) {
-                this.destroyClassLoader();
-                // get the dynamic class loader for the bundle using this
-                // class loader writer
-                final DynamicClassLoaderManager dclm = 
(DynamicClassLoaderManager) this.callerBundle.getBundleContext().getService(
-                    this.dynamicClassLoaderManager);
-
-                loader = new FSDynamicClassLoader(new URL[] {this.rootURL}, 
dclm.getDynamicClassLoader());
-            }
-            return this.loader;
-        }
-    }
-
-    private void checkClassLoader(final String filePath) {
-        if ( filePath.endsWith(".class") ) {
-            // remove store directory and .class
-            final String path = 
filePath.substring(this.root.getAbsolutePath().length() + 1, filePath.length() 
- 6);
-            // convert to a class name
-            final String className = path.replace(File.separatorChar, '.');
-
-            synchronized ( this ) {
-                final FSDynamicClassLoader currentLoader = this.loader;
-                if ( currentLoader != null ) {
-                    currentLoader.check(className);
-                }
-            }
-        }
-    }
-
-    //---------- SCR Integration ----------------------------------------------
-
-    private boolean deleteRecursive(final File f, final List<String> names) {
-        if ( f.isDirectory() ) {
-            for(final File c : f.listFiles()) {
-                if ( !deleteRecursive(c, names) ) {
-                    return false;
-                }
-            }
-        }
-        names.add(f.getAbsolutePath());
-        return f.delete();
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#delete(java.lang.String)
-     */
-    public boolean delete(final String name) {
-        final String path = cleanPath(name);
-        final File file = new File(path);
-        if ( file.exists() ) {
-            final List<String> names = new ArrayList<String>();
-            final boolean result = deleteRecursive(file, names);
-            logger.debug("Deleted {} : {}", name, result);
-            if ( result ) {
-                for(final String n : names ) {
-                    this.checkClassLoader(n);
-                }
-            }
-
-            return result;
-        }
-        // file does not exist so we return false
-        return false;
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getOutputStream(java.lang.String)
-     */
-    public OutputStream getOutputStream(final String name) {
-        logger.debug("Get stream for {}", name);
-        final String path = cleanPath(name);
-        final File file = new File(path);
-        final File parentDir = file.getParentFile();
-        if ( !parentDir.exists() ) {
-            parentDir.mkdirs();
-        }
-        try {
-            if ( file.exists() ) {
-                this.checkClassLoader(path);
-            }
-            return new FileOutputStream(path);
-        } catch (FileNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#rename(java.lang.String, 
java.lang.String)
-     */
-    public boolean rename(final String oldName, final String newName) {
-        logger.debug("Rename {} to {}", oldName, newName);
-        final String oldPath = cleanPath(oldName);
-        final String newPath = cleanPath(newName);
-        final File old = new File(oldPath);
-        final boolean result = old.renameTo(new File(newPath));
-        if ( result ) {
-            this.checkClassLoader(oldPath);
-            this.checkClassLoader(newPath);
-        }
-        return result;
-    }
-
-    /**
-     * Clean the path by converting slashes to the correct format
-     * and prefixing the root directory.
-     * @param path The path
-     * @return The file path
-     */
-    private String cleanPath(String path) {
-        // replace backslash by slash
-        path = path.replace('\\', '/');
-
-        // cut off trailing slash
-        while (path.endsWith("/")) {
-            path = path.substring(0, path.length() - 1);
-        }
-        if ( File.separatorChar != '/') {
-            path = path.replace('/', File.separatorChar);
-        }
-        return this.root.getAbsolutePath() + path;
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getInputStream(java.lang.String)
-     */
-    public InputStream getInputStream(final String name)
-    throws IOException {
-        logger.debug("Get input stream of {}", name);
-        final String path = cleanPath(name);
-        final File file = new File(path);
-        return new FileInputStream(file);
-    }
-
-    /**
-     * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getLastModified(java.lang.String)
-     */
-    public long getLastModified(final String name) {
-        logger.debug("Get last modified of {}", name);
-        final String path = cleanPath(name);
-        final File file = new File(path);
-        if ( file.exists() ) {
-            return file.lastModified();
-        }
-
-        // fallback to "non-existant" in case of problems
-        return -1;
-    }
+@Service(value = { ClassLoaderWriter.class }, serviceFactory = true)
+@Property(name = Constants.SERVICE_RANKING, intValue = 100)
+public class FSClassLoaderProvider implements ClassLoaderWriter {
+
+       private static final String LISTENER_FILTER = "(" + 
Constants.OBJECTCLASS + "="
+                       + ClassLoaderWriterListener.class.getName() + ")";
+
+       /** File root */
+       private File root;
+
+       /** File root URL */
+       private URL rootURL;
+
+       /** Current class loader */
+       private FSDynamicClassLoader loader;
+
+       private static ServiceListener classLoaderWriterServiceListener;
+
+       private Map<Long, ServiceReference<ClassLoaderWriterListener>> 
classLoaderWriterListeners = new HashMap<Long, 
ServiceReference<ClassLoaderWriterListener>>();
+
+       private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+       @Reference(referenceInterface = DynamicClassLoaderManager.class, bind = 
"bindDynamicClassLoaderManager", unbind = "unbindDynamicClassLoaderManager")
+       private ServiceReference dynamicClassLoaderManager;
+
+       /** The bundle asking for this service instance */
+       private Bundle callerBundle;
+
+       private static ServiceRegistration<?> mbeanRegistration;
+
+       /**
+        * Activate this component. Create the root directory.
+        * 
+        * @param componentContext
+        * @throws MalformedURLException
+        * @throws InvalidSyntaxException
+        * @throws MalformedObjectNameException
+        */
+       @Activate
+       protected void activate(final ComponentContext componentContext)
+                       throws MalformedURLException, InvalidSyntaxException, 
MalformedObjectNameException {
+               // get the file root
+               this.root = new 
File(componentContext.getBundleContext().getDataFile(""), "classes");
+               this.root.mkdirs();
+               this.rootURL = this.root.toURI().toURL();
+               this.callerBundle = componentContext.getUsingBundle();
+
+               classLoaderWriterListeners.clear();
+               if (this.classLoaderWriterServiceListener != null) {
+                       
componentContext.getBundleContext().removeServiceListener(classLoaderWriterServiceListener);
+                       classLoaderWriterServiceListener = null;
+               }
+               classLoaderWriterServiceListener = new ServiceListener() {
+                       @Override
+                       public void serviceChanged(ServiceEvent event) {
+                               ServiceReference<ClassLoaderWriterListener> 
reference = (ServiceReference<ClassLoaderWriterListener>) event
+                                               .getServiceReference();
+                               if (event.getType() == ServiceEvent.MODIFIED || 
event.getType() == ServiceEvent.REGISTERED) {
+                                       
classLoaderWriterListeners.put(getId(reference), reference);
+                               } else {
+                                       
classLoaderWriterListeners.remove(getId(reference));
+                               }
+                       }
+
+                       private Long 
getId(ServiceReference<ClassLoaderWriterListener> reference) {
+                               return 
PropertiesUtil.toLong(reference.getProperty(Constants.SERVICE_ID), -1);
+                       }
+               };
+               
componentContext.getBundleContext().addServiceListener(classLoaderWriterServiceListener,
 LISTENER_FILTER);
+
+               // handle the MBean Installation
+               if (mbeanRegistration != null) {
+                       mbeanRegistration.unregister();
+                       mbeanRegistration = null;
+               }
+               Hashtable<String, String> jmxProps = new Hashtable<String, 
String>();
+               jmxProps.put("type", "ClassLoader");
+               jmxProps.put("name", "FSClassLoader");
+
+               final Hashtable<String, Object> mbeanProps = new 
Hashtable<String, Object>();
+               mbeanProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling 
FSClassLoader Controller Service");
+               mbeanProps.put(Constants.SERVICE_VENDOR, "The Apache Software 
Foundation");
+               mbeanProps.put("jmx.objectname", new 
ObjectName("org.apache.sling.classloader", jmxProps));
+               mbeanRegistration = 
componentContext.getBundleContext().registerService(FSClassLoaderMBean.class.getName(),
+                               new FSClassLoaderMBeanImpl(this, 
componentContext.getBundleContext()), mbeanProps);
+       }
+
+       /**
+        * Deactivate this component. Create the root directory.
+        */
+       @Deactivate
+       protected void deactivate(ComponentContext componentContext) {
+               this.root = null;
+               this.rootURL = null;
+               this.destroyClassLoader();
+               if (this.classLoaderWriterServiceListener != null) {
+                       
componentContext.getBundleContext().removeServiceListener(classLoaderWriterServiceListener);
+               }
+               if (mbeanRegistration != null) {
+                       mbeanRegistration.unregister();
+                       mbeanRegistration = null;
+               }
+       }
+
+       /**
+        * Called to handle binding the DynamicClassLoaderManager service 
reference
+        */
+       @SuppressWarnings("unused")
+       private void bindDynamicClassLoaderManager(final ServiceReference ref) {
+               this.dynamicClassLoaderManager = ref;
+       }
+
+       /**
+        * Called to handle unbinding of the DynamicClassLoaderManager service
+        * reference
+        */
+       @SuppressWarnings("unused")
+       private void unbindDynamicClassLoaderManager(final ServiceReference 
ref) {
+               if (this.dynamicClassLoaderManager == ref) {
+                       this.dynamicClassLoaderManager = null;
+               }
+       }
+
+       private void destroyClassLoader() {
+               final ClassLoader rcl = this.loader;
+               if (rcl != null) {
+                       this.loader = null;
+
+                       final ServiceReference localDynamicClassLoaderManager = 
this.dynamicClassLoaderManager;
+                       final Bundle localCallerBundle = this.callerBundle;
+                       if (localDynamicClassLoaderManager != null && 
localCallerBundle != null) {
+                               
localCallerBundle.getBundleContext().ungetService(localDynamicClassLoaderManager);
+                       }
+               }
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getClassLoader()
+        */
+       public ClassLoader getClassLoader() {
+               synchronized (this) {
+                       if (loader == null || !loader.isLive()) {
+                               this.destroyClassLoader();
+                               // get the dynamic class loader for the bundle 
using this
+                               // class loader writer
+                               final DynamicClassLoaderManager dclm = 
(DynamicClassLoaderManager) this.callerBundle.getBundleContext()
+                                               
.getService(this.dynamicClassLoaderManager);
+
+                               loader = new FSDynamicClassLoader(new URL[] { 
this.rootURL }, dclm.getDynamicClassLoader());
+                       }
+                       return this.loader;
+               }
+       }
+
+       private void checkClassLoader(final String filePath) {
+               if (filePath.endsWith(".class")) {
+                       // remove store directory and .class
+                       final String path = 
filePath.substring(this.root.getAbsolutePath().length() + 1, filePath.length() 
- 6);
+                       // convert to a class name
+                       final String className = 
path.replace(File.separatorChar, '.');
+
+                       synchronized (this) {
+                               final FSDynamicClassLoader currentLoader = 
this.loader;
+                               if (currentLoader != null) {
+                                       currentLoader.check(className);
+                               }
+                       }
+               }
+       }
+
+       // ---------- SCR Integration 
----------------------------------------------
+
+       private boolean deleteRecursive(final File f, final List<String> names) 
{
+               if (f.isDirectory()) {
+                       for (final File c : f.listFiles()) {
+                               if (!deleteRecursive(c, names)) {
+                                       return false;
+                               }
+                       }
+               }
+               names.add(f.getAbsolutePath());
+               return f.delete();
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#delete(java.lang.String)
+        */
+       public boolean delete(final String name) {
+               final String path = cleanPath(name);
+               final File file = new File(path);
+               if (file.exists()) {
+                       final List<String> names = new ArrayList<String>();
+                       final boolean result = deleteRecursive(file, names);
+                       logger.debug("Deleted {} : {}", name, result);
+                       if (result) {
+                               for (final String n : names) {
+                                       this.checkClassLoader(n);
+                               }
+                               for 
(ServiceReference<ClassLoaderWriterListener> reference : 
classLoaderWriterListeners.values()) {
+                                       if (reference != null) {
+                                               ClassLoaderWriterListener 
listener = callerBundle.getBundleContext().getService(reference);
+                                               if (listener != null) {
+                                                       
listener.onClassLoaderClear(name);
+                                               } else {
+                                                       logger.warn("Found 
ClassLoaderWriterListener Service reference with no service bound");
+                                               }
+                                       }
+                               }
+                       }
+
+                       return result;
+               }
+               // file does not exist so we return false
+               return false;
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getOutputStream(java.lang.String)
+        */
+       public OutputStream getOutputStream(final String name) {
+               logger.debug("Get stream for {}", name);
+               final String path = cleanPath(name);
+               final File file = new File(path);
+               final File parentDir = file.getParentFile();
+               if (!parentDir.exists()) {
+                       parentDir.mkdirs();
+               }
+               try {
+                       if (file.exists()) {
+                               this.checkClassLoader(path);
+                       }
+                       return new FileOutputStream(path);
+               } catch (FileNotFoundException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#rename(java.lang.String,
+        *      java.lang.String)
+        */
+       public boolean rename(final String oldName, final String newName) {
+               logger.debug("Rename {} to {}", oldName, newName);
+               final String oldPath = cleanPath(oldName);
+               final String newPath = cleanPath(newName);
+               final File old = new File(oldPath);
+               final boolean result = old.renameTo(new File(newPath));
+               if (result) {
+                       this.checkClassLoader(oldPath);
+                       this.checkClassLoader(newPath);
+               }
+               return result;
+       }
+
+       /**
+        * Clean the path by converting slashes to the correct format and 
prefixing
+        * the root directory.
+        * 
+        * @param path
+        *            The path
+        * @return The file path
+        */
+       private String cleanPath(String path) {
+               // replace backslash by slash
+               path = path.replace('\\', '/');
+
+               // cut off trailing slash
+               while (path.endsWith("/")) {
+                       path = path.substring(0, path.length() - 1);
+               }
+               if (File.separatorChar != '/') {
+                       path = path.replace('/', File.separatorChar);
+               }
+               return this.root.getAbsolutePath() + path;
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getInputStream(java.lang.String)
+        */
+       public InputStream getInputStream(final String name) throws IOException 
{
+               logger.debug("Get input stream of {}", name);
+               final String path = cleanPath(name);
+               final File file = new File(path);
+               return new FileInputStream(file);
+       }
+
+       /**
+        * @see 
org.apache.sling.commons.classloader.ClassLoaderWriter#getLastModified(java.lang.String)
+        */
+       public long getLastModified(final String name) {
+               logger.debug("Get last modified of {}", name);
+               final String path = cleanPath(name);
+               final File file = new File(path);
+               if (file.exists()) {
+                       return file.lastModified();
+               }
+
+               // fallback to "non-existant" in case of problems
+               return -1;
+       }
 }
diff --git 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderWebConsole.java
 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderWebConsole.java
index eccbc29..0affacc 100644
--- 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderWebConsole.java
+++ 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/FSClassLoaderWebConsole.java
@@ -53,394 +53,309 @@ import org.slf4j.LoggerFactory;
  */
 @Component
 @Service
-@Properties({
-        @Property(name = "service.description", value = "Web Console for the 
FileSystem Class Loader"),
-        @Property(name = "service.vendor", value = "The Apache Software 
Foundation"),
-        @Property(name = "felix.webconsole.label", value = 
FSClassLoaderWebConsole.APP_ROOT),
-        @Property(name = "felix.webconsole.title", value = "File System Class 
Loader"),
-        @Property(name = "felix.webconsole.css", value = { 
FSClassLoaderWebConsole.RES_LOC
-                + "/prettify.css" }),
-        @Property(name = "felix.webconsole.category", value = "Sling") })
+@Properties({ @Property(name = "service.description", value = "Web Console for 
the FileSystem Class Loader"),
+               @Property(name = "service.vendor", value = "The Apache Software 
Foundation"),
+               @Property(name = "felix.webconsole.label", value = 
FSClassLoaderWebConsole.APP_ROOT),
+               @Property(name = "felix.webconsole.title", value = "File System 
Class Loader"),
+               @Property(name = "felix.webconsole.css", value = { 
FSClassLoaderWebConsole.RES_LOC + "/prettify.css" }),
+               @Property(name = "felix.webconsole.category", value = "Sling") 
})
 public class FSClassLoaderWebConsole extends AbstractWebConsolePlugin {
 
-    static final String APP_ROOT = "fsclassloader";
-
-    static final String RES_LOC = APP_ROOT + "/res/ui";
-    static final String POST_PARAM_CLEAR_CLASSLOADER = "clear";
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(FSClassLoaderWebConsole.class);
-
-    @Reference(target = 
"(component.name=org.apache.sling.commons.fsclassloader.impl.FSClassLoaderProvider)")
-    private ClassLoaderWriter classLoaderWriter;
-
-    /**
-     * Represents a set of class, java and deps files for a script.
-     */
-    private static class ScriptFiles {
-
-        /**
-         * Gets the script associated with the file.
-         *
-         * @param file
-         *            the file to find the associate script
-         * @return the associated script
-         */
-        public static String getScript(File file) {
-            String relative = file.getAbsolutePath().substring(
-                    root.getAbsolutePath().length());
-            String script = remove(relative, "/org/apache/jsp");
-            script = remove(script, ".class");
-            script = remove(script, ".java");
-            script = remove(script, ".deps");
-            if (File.separatorChar == '\\') {
-                script = script.replace(File.separatorChar, '/');
-            }
-            return StringUtils.substringBeforeLast(script, "_") + "."
-                    + StringUtils.substringAfterLast(script, "_");
-        }
-
-        private static String remove(String orig, String rem) {
-            return orig.replace(rem, "");
-        }
-
-        private final String classFile;
-        private final String depsFile;
-
-        private final String javaFile;
-
-        private final String script;
-
-        public ScriptFiles(File file) {
-            script = getScript(file);
-
-            String relative = file.getAbsolutePath().substring(
-                    root.getAbsolutePath().length());
-
-            relative = remove(relative, ".class");
-            relative = remove(relative, ".deps");
-            relative = remove(relative, ".java");
-            classFile = relative + ".class";
-            depsFile = relative + ".deps";
-            javaFile = relative + ".java";
-        }
-
-        public String getClassFile() {
-            return classFile;
-        }
-
-        public String getDepsFile() {
-            return depsFile;
-        }
-
-        public String getJavaFile() {
-            return javaFile;
-        }
-
-        public String getScript() {
-            return script;
-        }
-
-    }
-
-    /**
-     * The root under which the class files are under
-     */
-    private static File root;
-
-    /**
-     * The serialization UID
-     */
-    private static final long serialVersionUID = -5728679635644481848L;
-
-    /**
-     * The servlet configuration
-     */
-    private ServletConfig config;
-
-    /**
-     * Activate this component. Create the root directory.
-     *
-     * @param componentContext the component context
-     * @throws MalformedURLException
-     */
-    @Activate
-    @SuppressWarnings("unused")
-    protected void activate(final ComponentContext componentContext)
-            throws MalformedURLException {
-        // get the file root
-        root = new File(componentContext.getBundleContext().getDataFile(""),
-                "classes");
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#destroy()
-     */
-    public void destroy() {
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest,
-     * javax.servlet.ServletResponse)
-     */
-    protected void doGet(HttpServletRequest request,
-            HttpServletResponse response) throws ServletException, IOException 
{
-        String file = request.getParameter("download");
-        File toDownload = new File(root + file);
-        if (!StringUtils.isEmpty(file)) {
-            if (isValid(toDownload)) {
-                InputStream is = null;
-                try {
-                    is = new FileInputStream(toDownload);
-                    response.setHeader("Content-disposition",
-                            "attachment; filename=" + toDownload.getName());
-                    IOUtils.copy(is, response.getOutputStream());
-                } finally {
-                    IOUtils.closeQuietly(is);
-                    IOUtils.closeQuietly(response.getOutputStream());
-                }
-            } else {
-                response.sendError(404, "File " + file + " not found");
-            }
-        } else if (request.getRequestURI().endsWith(RES_LOC + 
"/prettify.css")) {
-            response.setContentType("text/css");
-            IOUtils.copy(
-                    getClass().getClassLoader().getResourceAsStream(
-                            "/res/ui/prettify.css"), 
response.getOutputStream());
-        } else if (request.getRequestURI().endsWith(RES_LOC + "/prettify.js")) 
{
-            response.setContentType("application/javascript");
-            IOUtils.copy(
-                    getClass().getClassLoader().getResourceAsStream(
-                            "/res/ui/prettify.js"), 
response.getOutputStream());
-        } else if (request.getRequestURI().endsWith(RES_LOC + 
"/fsclassloader.js")) {
-            response.setContentType("application/javascript");
-            IOUtils.copy(
-                    getClass().getClassLoader().getResourceAsStream(
-                            "/res/ui/fsclassloader.js"), 
response.getOutputStream());
-        }
-        else {
-            super.doGet(request, response);
-        }
-    }
-
-    @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
-        String clear = req.getParameter(POST_PARAM_CLEAR_CLASSLOADER);
-        boolean shouldClear = Boolean.parseBoolean(clear);
-        if (shouldClear) {
-            if (classLoaderWriter != null) {
-                boolean result = classLoaderWriter.delete("");
-                if (result) {
-                    resp.getWriter().write("{ \"status\" : \"success\" }");
-                    resp.setStatus(HttpServletResponse.SC_OK);
-                } else {
-                    resp.getWriter().write("{ \"status\" : \"failure\", 
\"message\" : \"unable to clear classloader; check server log\" }");
-                    
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-                }
-            } else {
-                LOG.error("Cannot get a reference to 
org.apache.sling.commons.fsclassloader.impl.FSClassLoaderProvider");
-                resp.getWriter().write("{ \"status\" : \"failure\", 
\"message\" : \"unable to clear classloader; check server log\" }");
-                resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            }
-        } else {
-            resp.getWriter().write("{ \"status\" : \"failure\", \"message\" : 
\"invalid command\" }");
-            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getLabel()
-     */
-    @Override
-    public String getLabel() {
-        return "fsclassloader";
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#getServletConfig()
-     */
-    public ServletConfig getServletConfig() {
-        return this.config;
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#getServletInfo()
-     */
-    public String getServletInfo() {
-        return "";
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getTitle()
-     */
-    @Override
-    public String getTitle() {
-        return "File System Class Loader";
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
-     */
-    public void init(ServletConfig config) throws ServletException {
-        this.config = config;
-    }
-
-    /**
-     * Checks whether the specified file is a file and is underneath the root
-     * directory.
-     *
-     * @param file
-     *            the file to check
-     * @return false if not a file or not under the root directory, true
-     *         otherwise
-     * @throws IOException
-     */
-    private boolean isValid(File file) throws IOException {
-        if (file.isFile()) {
-            File parent = file.getCanonicalFile().getAbsoluteFile()
-                    .getParentFile();
-            while (parent != null) {
-                if (parent.getCanonicalPath().equals(root.getCanonicalPath())) 
{
-                    return true;
-                }
-                parent = parent.getParentFile();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Reads all of the files under the current file.
-     *
-     * @param file
-     *            the root file
-     * @param scripts
-     *            the map of scripts
-     * @throws IOException
-     *             an exception occurs reading the files
-     */
-    private void readFiles(File file, Map<String, ScriptFiles> scripts)
-            throws IOException {
-        if (file.isDirectory()) {
-            File[] children = file.listFiles();
-            if (children != null) {
-                for (File f : children) {
-                    readFiles(f, scripts);
-                }
-            }
-        } else {
-            String script = ScriptFiles.getScript(file);
-            if (!scripts.containsKey(script)
-                    && file.getName().endsWith(".java")) {
-                scripts.put(script, new ScriptFiles(file));
-            }
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see
-     * org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax
-     * .servlet.http.HttpServletRequest, 
javax.servlet.http.HttpServletResponse)
-     */
-    @Override
-    protected void renderContent(HttpServletRequest request,
-            HttpServletResponse response) throws ServletException, IOException 
{
-        Map<String, ScriptFiles> scripts = new LinkedHashMap<String, 
ScriptFiles>();
-        readFiles(root, scripts);
-
-        Writer w = response.getWriter();
-
-        w.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + RES_LOC
-                + "/prettify.css\"></link>");
-        w.write("<script type=\"text/javascript\" src=\"" + RES_LOC
-                + "/prettify.js\"></script>");
-        w.write("<script type=\"text/javascript\" src=\"" + RES_LOC
-                + "/fsclassloader.js\"></script>");
-        w.write("<script>$(document).ready(prettyPrint);</script>");
-        w.write("<style>.prettyprint ol.linenums > li { list-style-type: 
decimal; } pre.prettyprint { white-space: pre-wrap; }</style>");
-        String file = request.getParameter("view");
-        File toView = new File(root + file);
-        w.write("<div id=\"classes\">");
-        if (!StringUtils.isEmpty(file)) {
-            if (isValid(toView)) {
-
-                w.write("<p class=\"statline ui-state-highlight\">Viewing 
Script: "
-                        + root + file + "</p><br/><br/>");
-
-                ScriptFiles scriptFiles = new ScriptFiles(toView);
-
-                w.write("<table class=\"nicetable ui-widget\">");
-                w.write("<tr class=\"header ui-widget-header\">");
-                w.write("<th>Script</th>");
-                w.write("<th>Class</th>");
-                w.write("<th>Deps</th>");
-                w.write("<th>Java</th>");
-                w.write("</tr>");
-                w.write("<tr class=\"ui-state-default\">");
-                w.write("<td>" + scriptFiles.getScript() + "</td>");
-                w.write("<td>[<a href=\"?download="
-                        + scriptFiles.getClassFile()
-                        + "\" target=\"_blank\">download</a>]</td>");
-                w.write("<td>[<a href=\"?download="
-                        + scriptFiles.getDepsFile()
-                        + "\" target=\"_blank\">download</a>]</td>");
-                w.write("<td>[<a href=\"?download="
-                        + scriptFiles.getJavaFile()
-                        + "\" target=\"_blank\">download</a>]</td>");
-                w.write("</tr>");
-                w.write("</table><br/><br/>");
-                InputStream is = null;
-                try {
-                    is = new FileInputStream(toView);
-                    String contents = IOUtils.toString(is, "UTF-8");
-                    w.write("<pre class=\"prettyprint linenums\">");
-                    w.write(StringEscapeUtils.escapeHtml4(contents));
-                    w.write("</pre>");
-                } finally {
-                    IOUtils.closeQuietly(is);
-                }
-            } else {
-                response.sendError(404, "File " + file + " not found");
-            }
-        } else {
-            w.write("<p class=\"statline ui-state-highlight\">File System 
ClassLoader Root: "
-                    + root + " <span style=\"float: right\"><button 
type='button' id='clear'>Clear Class Loader</button></span></p>");
-            if (scripts.values().size() > 0 ) {
-                w.write("<table class=\"nicetable ui-widget 
fsclassloader-has-classes\">");
-            } else {
-                w.write("<table class=\"nicetable ui-widget\">");
-            }
-            w.write("<tr class=\"header ui-widget-header\">");
-            w.write("<th>View</th>");
-            w.write("<th>Script</th>");
-            w.write("</tr>");
-            int i = 0;
-            for (ScriptFiles scriptFiles : scripts.values()) {
-                w.write("<tr class=\"" + (i % 2 == 0 ? "even" : "odd")
-                        + " ui-state-default\">");
-                w.write("<td>[<a href=\"?view=" + scriptFiles.getJavaFile()
-                        + "\">view</a>]</td>");
-                w.write("<td>" + scriptFiles.getScript() + "</td>");
-                w.write("</tr>");
-                i++;
-            }
-            w.write("</table>");
-        }
-        w.write("</div>");
-    }
+       static final String APP_ROOT = "fsclassloader";
+
+       static final String RES_LOC = APP_ROOT + "/res/ui";
+       static final String POST_PARAM_CLEAR_CLASSLOADER = "clear";
+
+       private static final Logger LOG = 
LoggerFactory.getLogger(FSClassLoaderWebConsole.class);
+
+       @Reference(target = 
"(component.name=org.apache.sling.commons.fsclassloader.impl.FSClassLoaderProvider)")
+       private ClassLoaderWriter classLoaderWriter;
+
+       /**
+        * The root under which the class files are under
+        */
+       private File root;
+
+       /**
+        * The serialization UID
+        */
+       private static final long serialVersionUID = -5728679635644481848L;
+
+       /**
+        * The servlet configuration
+        */
+       private ServletConfig config;
+
+       /**
+        * Activate this component. Create the root directory.
+        *
+        * @param componentContext
+        *            the component context
+        * @throws MalformedURLException
+        */
+       @Activate
+       protected void activate(final ComponentContext componentContext) throws 
MalformedURLException {
+               // get the file root
+               root = new 
File(componentContext.getBundleContext().getDataFile(""), "classes");
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see javax.servlet.Servlet#destroy()
+        */
+       public void destroy() {
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest,
+        * javax.servlet.ServletResponse)
+        */
+       protected void doGet(HttpServletRequest request, HttpServletResponse 
response)
+                       throws ServletException, IOException {
+               String file = request.getParameter("download");
+               File toDownload = new File(root + file);
+               if (!StringUtils.isEmpty(file)) {
+                       if (isValid(toDownload)) {
+                               InputStream is = null;
+                               try {
+                                       is = new FileInputStream(toDownload);
+                                       
response.setHeader("Content-disposition", "attachment; filename=" + 
toDownload.getName());
+                                       IOUtils.copy(is, 
response.getOutputStream());
+                               } finally {
+                                       IOUtils.closeQuietly(is);
+                                       
IOUtils.closeQuietly(response.getOutputStream());
+                               }
+                       } else {
+                               response.sendError(404, "File " + file + " not 
found");
+                       }
+               } else if (request.getRequestURI().endsWith(RES_LOC + 
"/prettify.css")) {
+                       response.setContentType("text/css");
+                       
IOUtils.copy(getClass().getClassLoader().getResourceAsStream("/res/ui/prettify.css"),
+                                       response.getOutputStream());
+               } else if (request.getRequestURI().endsWith(RES_LOC + 
"/prettify.js")) {
+                       response.setContentType("application/javascript");
+                       
IOUtils.copy(getClass().getClassLoader().getResourceAsStream("/res/ui/prettify.js"),
+                                       response.getOutputStream());
+               } else if (request.getRequestURI().endsWith(RES_LOC + 
"/fsclassloader.js")) {
+                       response.setContentType("application/javascript");
+                       
IOUtils.copy(getClass().getClassLoader().getResourceAsStream("/res/ui/fsclassloader.js"),
+                                       response.getOutputStream());
+               } else {
+                       super.doGet(request, response);
+               }
+       }
+
+       @Override
+       protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+               String clear = req.getParameter(POST_PARAM_CLEAR_CLASSLOADER);
+               boolean shouldClear = Boolean.parseBoolean(clear);
+               if (shouldClear) {
+                       if (classLoaderWriter != null) {
+                               boolean result = classLoaderWriter.delete("");
+                               if (result) {
+                                       resp.getWriter().write("{ \"status\" : 
\"success\" }");
+                                       
resp.setStatus(HttpServletResponse.SC_OK);
+                               } else {
+                                       resp.getWriter().write(
+                                                       "{ \"status\" : 
\"failure\", \"message\" : \"unable to clear classloader; check server log\" 
}");
+                                       
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                               }
+                       } else {
+                               LOG.error(
+                                               "Cannot get a reference to 
org.apache.sling.commons.fsclassloader.impl.FSClassLoaderProvider");
+                               resp.getWriter().write(
+                                               "{ \"status\" : \"failure\", 
\"message\" : \"unable to clear classloader; check server log\" }");
+                               
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                       }
+               } else {
+                       resp.getWriter().write("{ \"status\" : \"failure\", 
\"message\" : \"invalid command\" }");
+                       resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getLabel()
+        */
+       @Override
+       public String getLabel() {
+               return "fsclassloader";
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see javax.servlet.Servlet#getServletConfig()
+        */
+       public ServletConfig getServletConfig() {
+               return this.config;
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see javax.servlet.Servlet#getServletInfo()
+        */
+       public String getServletInfo() {
+               return "";
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getTitle()
+        */
+       @Override
+       public String getTitle() {
+               return "File System Class Loader";
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+        */
+       public void init(ServletConfig config) throws ServletException {
+               this.config = config;
+       }
+
+       /**
+        * Checks whether the specified file is a file and is underneath the 
root
+        * directory.
+        *
+        * @param file
+        *            the file to check
+        * @return false if not a file or not under the root directory, true
+        *         otherwise
+        * @throws IOException
+        */
+       private boolean isValid(File file) throws IOException {
+               if (file.isFile()) {
+                       File parent = 
file.getCanonicalFile().getAbsoluteFile().getParentFile();
+                       while (parent != null) {
+                               if 
(parent.getCanonicalPath().equals(root.getCanonicalPath())) {
+                                       return true;
+                               }
+                               parent = parent.getParentFile();
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Reads all of the files under the current file.
+        *
+        * @param current
+        *            the current file
+        * @param root
+        *            the root file
+        * @param scripts
+        *            the map of scripts
+        * @throws IOException
+        *             an exception occurs reading the files
+        */
+       protected static void readFiles(File current, File root, Map<String, 
ScriptFiles> scripts) throws IOException {
+               if (current.isDirectory()) {
+                       File[] children = current.listFiles();
+                       if (children != null) {
+                               for (File f : children) {
+                                       readFiles(f, root, scripts);
+                               }
+                       }
+               } else {
+                       String script = ScriptFiles.getScript(root, current);
+                       if (!scripts.containsKey(script) && 
current.getName().endsWith(".java")) {
+                               scripts.put(script, new ScriptFiles(root, 
current));
+                       }
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see
+        * 
org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax
+        * .servlet.http.HttpServletRequest, 
javax.servlet.http.HttpServletResponse)
+        */
+       @Override
+       protected void renderContent(HttpServletRequest request, 
HttpServletResponse response)
+                       throws ServletException, IOException {
+               Map<String, ScriptFiles> scripts = new LinkedHashMap<String, 
ScriptFiles>();
+               readFiles(root, root, scripts);
+
+               Writer w = response.getWriter();
+
+               w.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + 
RES_LOC + "/prettify.css\"></link>");
+               w.write("<script type=\"text/javascript\" src=\"" + RES_LOC + 
"/prettify.js\"></script>");
+               w.write("<script type=\"text/javascript\" src=\"" + RES_LOC + 
"/fsclassloader.js\"></script>");
+               w.write("<script>$(document).ready(prettyPrint);</script>");
+               w.write("<style>.prettyprint ol.linenums > li { 
list-style-type: decimal; } pre.prettyprint { white-space: pre-wrap; 
}</style>");
+               String file = request.getParameter("view");
+               File toView = new File(root + file);
+               w.write("<div id=\"classes\">");
+               if (!StringUtils.isEmpty(file)) {
+                       if (isValid(toView)) {
+
+                               w.write("<p class=\"statline 
ui-state-highlight\">Viewing Script: " + root + file + "</p><br/><br/>");
+
+                               ScriptFiles scriptFiles = new ScriptFiles(root, 
toView);
+
+                               w.write("<table class=\"nicetable 
ui-widget\">");
+                               w.write("<tr class=\"header 
ui-widget-header\">");
+                               w.write("<th>Script</th>");
+                               w.write("<th>Class</th>");
+                               w.write("<th>Deps</th>");
+                               w.write("<th>Java</th>");
+                               w.write("</tr>");
+                               w.write("<tr class=\"ui-state-default\">");
+                               w.write("<td>" + scriptFiles.getScript() + 
"</td>");
+                               w.write("<td>[<a href=\"?download=" + 
scriptFiles.getClassFile()
+                                               + "\" 
target=\"_blank\">download</a>]</td>");
+                               w.write("<td>[<a href=\"?download=" + 
scriptFiles.getDepsFile()
+                                               + "\" 
target=\"_blank\">download</a>]</td>");
+                               w.write("<td>[<a href=\"?download=" + 
scriptFiles.getJavaFile()
+                                               + "\" 
target=\"_blank\">download</a>]</td>");
+                               w.write("</tr>");
+                               w.write("</table><br/><br/>");
+                               InputStream is = null;
+                               try {
+                                       is = new FileInputStream(toView);
+                                       String contents = IOUtils.toString(is, 
"UTF-8");
+                                       w.write("<pre class=\"prettyprint 
linenums\">");
+                                       
w.write(StringEscapeUtils.escapeHtml4(contents));
+                                       w.write("</pre>");
+                               } finally {
+                                       IOUtils.closeQuietly(is);
+                               }
+                       } else {
+                               response.sendError(404, "File " + file + " not 
found");
+                       }
+               } else {
+                       w.write("<p class=\"statline ui-state-highlight\">File 
System ClassLoader Root: " + root
+                                       + " <span style=\"float: 
right\"><button type='button' id='clear'>Clear Class 
Loader</button></span></p>");
+                       if (scripts.values().size() > 0) {
+                               w.write("<table class=\"nicetable ui-widget 
fsclassloader-has-classes\">");
+                       } else {
+                               w.write("<table class=\"nicetable 
ui-widget\">");
+                       }
+                       w.write("<tr class=\"header ui-widget-header\">");
+                       w.write("<th>View</th>");
+                       w.write("<th>Script</th>");
+                       w.write("</tr>");
+                       int i = 0;
+                       for (ScriptFiles scriptFiles : scripts.values()) {
+                               w.write("<tr class=\"" + (i % 2 == 0 ? "even" : 
"odd") + " ui-state-default\">");
+                               w.write("<td>[<a href=\"?view=" + 
scriptFiles.getJavaFile() + "\">view</a>]</td>");
+                               w.write("<td>" + scriptFiles.getScript() + 
"</td>");
+                               w.write("</tr>");
+                               i++;
+                       }
+                       w.write("</table>");
+               }
+               w.write("</div>");
+       }
 }
diff --git 
a/src/main/java/org/apache/sling/commons/fsclassloader/impl/ScriptFiles.java 
b/src/main/java/org/apache/sling/commons/fsclassloader/impl/ScriptFiles.java
new file mode 100644
index 0000000..80bde37
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/fsclassloader/impl/ScriptFiles.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.fsclassloader.impl;
+
+import java.io.File;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Represents a set of class, java and deps files for a script.
+ */
+public class ScriptFiles {
+
+       /**
+        * Gets the script associated with the file.
+        *
+        * @param file
+        *            the file to find the associate script
+        * @return the associated script
+        */
+       public static String getScript(File root, File file) {
+               String relative = 
file.getAbsolutePath().substring(root.getAbsolutePath().length());
+               String script = remove(relative, "/org/apache/jsp");
+               script = remove(script, ".class");
+               script = remove(script, ".java");
+               script = remove(script, ".deps");
+               if (File.separatorChar == '\\') {
+                       script = script.replace(File.separatorChar, '/');
+               }
+               return StringUtils.substringBeforeLast(script, "_") + "." + 
StringUtils.substringAfterLast(script, "_");
+       }
+
+       private static String remove(String orig, String rem) {
+               return orig.replace(rem, "");
+       }
+
+       private final String classFile;
+       private final String depsFile;
+
+       private final String javaFile;
+
+       private final String script;
+
+       public ScriptFiles(final File root, final File file) {
+               script = getScript(root, file);
+
+               String relative = 
file.getAbsolutePath().substring(root.getAbsolutePath().length());
+
+               relative = remove(relative, ".class");
+               relative = remove(relative, ".deps");
+               relative = remove(relative, ".java");
+               classFile = relative + ".class";
+               depsFile = relative + ".deps";
+               javaFile = relative + ".java";
+       }
+
+       public String getClassFile() {
+               return classFile;
+       }
+
+       public String getDepsFile() {
+               return depsFile;
+       }
+
+       public String getJavaFile() {
+               return javaFile;
+       }
+
+       public String getScript() {
+               return script;
+       }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"[email protected]" <[email protected]>.

Reply via email to