You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2017/03/13 08:20:55 UTC

svn commit: r1786637 - in /sling/trunk/bundles/scripting/sightly: engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/ engine/src/main/java/org/apache/sling/s...

Author: radu
Date: Mon Mar 13 08:20:55 2017
New Revision: 1786637

URL: http://svn.apache.org/viewvc?rev=1786637&view=rev
Log:
SLING-6612 - HTL/Sightly sometimes fails to recompile an updated Java POJO located in the repository

* changed the compilation logic for repository POJO objects so that changes for which events are not
received still trigger a recompilation of the affected use objects

Modified:
    sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ResourceBackedPojoChangeMonitor.java
    sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyJavaCompilerService.java
    sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java
    sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java
    sling/trunk/bundles/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyJavaCompilerServiceTest.java
    sling/trunk/bundles/scripting/sightly/testing/src/test/java/org/apache/sling/scripting/sightly/it/SlingSpecificsSightlyIT.java

Modified: sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ResourceBackedPojoChangeMonitor.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ResourceBackedPojoChangeMonitor.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ResourceBackedPojoChangeMonitor.java (original)
+++ sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/ResourceBackedPojoChangeMonitor.java Mon Mar 13 08:20:55 2017
@@ -21,14 +21,12 @@ package org.apache.sling.scripting.sight
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-
 import javax.annotation.Nonnull;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
 import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChangeListener;
-import org.apache.sling.scripting.sightly.impl.engine.compiled.SourceIdentifier;
+import org.apache.sling.scripting.sightly.SightlyException;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -38,7 +36,6 @@ import org.slf4j.LoggerFactory;
         service = {ResourceBackedPojoChangeMonitor.class, ResourceChangeListener.class},
         property = {
                 ResourceChangeListener.PATHS + "=glob:**/*.java",
-                ResourceChangeListener.CHANGES + "=ADDED",
                 ResourceChangeListener.CHANGES + "=CHANGED",
                 ResourceChangeListener.CHANGES + "=REMOVED",
         }
@@ -53,38 +50,47 @@ public class ResourceBackedPojoChangeMon
     private SightlyEngineConfiguration sightlyEngineConfiguration = null;
 
     /**
+     * Records the usage of the Use-object available at the provided {@code path}.
+     *
+     * @param path      the path of the Use-object
+     * @param timestamp the timestamp when the object identified by the resource from {@code path} was last modified
+     */
+    public void recordLastModifiedTimestamp(String path, long timestamp) {
+        if (path == null) {
+            throw new SightlyException("Path value cannot be null.");
+        }
+        slyJavaUseMap.put(path, timestamp);
+    }
+
+    /**
      * Returns the last modified date for a Java Use-API object stored in the repository.
      *
-     * @param className the fully qualified class name
+     * @param path the {@code Resource} path of the Use-object
      * @return the Java Use-API file's last modified date or 0 if there's no information about this file
      */
-    public long getLastModifiedDateForJavaUseObject(String className) {
-        if (className == null) {
+    public long getLastModifiedDateForJavaUseObject(String path) {
+        if (path == null) {
             return 0;
         }
-        Long date = slyJavaUseMap.get(className);
+        Long date = slyJavaUseMap.get(path);
         return date != null ? date : 0;
     }
 
-    public void clearJavaUseObject(String className) {
-        if (StringUtils.isNotEmpty(className)) {
-            slyJavaUseMap.remove(className);
-        }
-    }
-
     @Override
     public void onChange(@Nonnull List<ResourceChange> changes) {
         for (ResourceChange change : changes) {
             String path = change.getPath();
             ResourceChange.ChangeType changeType = change.getType();
-            SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, path);
             switch (changeType) {
-                case ADDED:
                 case CHANGED:
-                    slyJavaUseMap.put(sourceIdentifier.getFullyQualifiedClassName(), System.currentTimeMillis());
+                    if (slyJavaUseMap.containsKey(path)) {
+                        slyJavaUseMap.put(path, System.currentTimeMillis());
+                    }
                     break;
                 case REMOVED:
-                    slyJavaUseMap.remove(sourceIdentifier.getFullyQualifiedClassName());
+                    if (slyJavaUseMap.containsKey(path)) {
+                        slyJavaUseMap.remove(path);
+                    }
                     break;
                 default:
                     break;

Modified: sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyJavaCompilerService.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyJavaCompilerService.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyJavaCompilerService.java (original)
+++ sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/SightlyJavaCompilerService.java Mon Mar 13 08:20:55 2017
@@ -29,7 +29,6 @@ import java.util.regex.Pattern;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.commons.classloader.ClassLoaderWriter;
 import org.apache.sling.commons.compiler.CompilationResult;
 import org.apache.sling.commons.compiler.CompilationUnit;
@@ -59,8 +58,7 @@ import org.slf4j.LoggerFactory;
 public class SightlyJavaCompilerService {
 
     private static final Logger LOG = LoggerFactory.getLogger(SightlyJavaCompilerService.class);
-
-    public static final Pattern PACKAGE_DECL_PATTERN = Pattern.compile("(\\s*)package\\s+([a-zA-Z_$][a-zA-Z\\d_$]*\\.?)+;");
+    private static final Pattern PACKAGE_DECL_PATTERN = Pattern.compile("(\\s*)package\\s+([a-zA-Z_$][a-zA-Z\\d_$]*\\.?)+;");
 
     @Reference
     private ClassLoaderWriter classLoaderWriter = null;
@@ -80,48 +78,38 @@ public class SightlyJavaCompilerService
     private Options options;
 
     /**
-     * This method returns an Object instance based on a class that is either found through regular classloading mechanisms or on-the-fly
-     * compilation. In case the requested class does not denote a fully qualified classname, this service will try to find the class through
-     * Sling's servlet resolution mechanism and compile the class on-the-fly if required.
+     * This method returns an Object instance based on a {@link Resource}-backed class that is either found through regular classloading
+     * mechanisms or on-the-fly compilation. In case the requested class does not denote a fully qualified class name, this service will
+     * try to find the class through Sling's resource resolution mechanism and compile the class on-the-fly if required.
      *
      * @param renderContext the render context
      * @param className     name of class to use for object instantiation
-     * @return object instance of the requested class
+     * @return object instance of the requested class or {@code null} if the specified class is not backed by a {@link Resource}
      */
-    public Object getInstance(RenderContext renderContext, String className) {
+    public Object getResourceBackedUseObject(RenderContext renderContext, String className) {
         LOG.debug("Attempting to load class {}.", className);
-        if (className.contains(".")) {
-            if (resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(className) > 0) {
-                // it looks like the POJO comes from the repo and it was changed since it was last loaded
-                LOG.debug("Class {} identifies a POJO from the repository that was changed since the last time it was instantiated.",
-                        className);
-                Object result = compileRepositoryJavaClass(scriptingResourceResolverProvider.getRequestScopedResourceResolver(), className);
-                resourceBackedPojoChangeMonitor.clearJavaUseObject(className);
-                return result;
-            }
-            try {
-                // the object either comes from a bundle or from the repo but it was not registered by the ResourceBackedPojoChangeMonitor
-                LOG.debug("Attempting to load class {} from the classloader cache.", className);
-                return classLoaderWriter.getClassLoader().loadClass(className).newInstance();
-            } catch (Exception e) {
-                // the object definitely doesn't come from a bundle so we should attempt to compile it from the repo
-                LOG.debug("Class {} identifies a POJO from the repository and it needs to be compiled.", className);
-                return compileRepositoryJavaClass(scriptingResourceResolverProvider.getRequestScopedResourceResolver(), className);
-            }
-        } else {
-            Resource pojoResource = ScriptUtils.resolveScript(
-                    scriptingResourceResolverProvider.getRequestScopedResourceResolver(),
-                    renderContext,
-                    className + ".java"
-            );
-            if (pojoResource != null) {
-                SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, pojoResource.getPath());
-                String fqcn = sourceIdentifier.getFullyQualifiedClassName();
-                LOG.debug("Class {} has FQCN {}.", className, fqcn);
-                return getInstance(renderContext, fqcn);
+        try {
+            if (className.contains(".")) {
+                Resource pojoResource =
+                        SourceIdentifier.getPOJOFromFQCN(scriptingResourceResolverProvider.getRequestScopedResourceResolver()
+                                , null, className);
+                if (pojoResource != null) {
+                    return getUseObjectAndRecompileIfNeeded(pojoResource);
+                }
+            } else {
+                Resource pojoResource = ScriptUtils.resolveScript(
+                        scriptingResourceResolverProvider.getRequestScopedResourceResolver(),
+                        renderContext,
+                        className + ".java"
+                );
+                if (pojoResource != null) {
+                    return getUseObjectAndRecompileIfNeeded(pojoResource);
+                }
             }
+        } catch (Exception e) {
+            throw new SightlyException("Cannot obtain an instance for class " + className + ".", e);
         }
-        throw new SightlyException("Cannot find class " + className + ".");
+        return null;
     }
 
     /**
@@ -133,69 +121,77 @@ public class SightlyJavaCompilerService
      */
     public Object compileSource(SourceIdentifier sourceIdentifier, String sourceCode) {
         try {
-            return internalCompileSource(sourceIdentifier, sourceCode);
-        } catch (Exception e) {
-            throw new SightlyException(e);
-        }
-    }
-
-    private Object internalCompileSource(SourceIdentifier sourceIdentifier, String sourceCode) throws Exception {
-        String fqcn = sourceIdentifier.getFullyQualifiedClassName();
-        if (sightlyEngineConfiguration.keepGenerated()) {
-            String path = "/" + fqcn.replaceAll("\\.", "/") + ".java";
-            OutputStream os = classLoaderWriter.getOutputStream(path);
-            IOUtils.write(sourceCode, os, "UTF-8");
-            IOUtils.closeQuietly(os);
-        }
-        String[] sourceCodeLines = sourceCode.split("\\r\\n|[\\n\\x0B\\x0C\\r\\u0085\\u2028\\u2029]");
-        boolean foundPackageDeclaration = false;
-        for (String line : sourceCodeLines) {
-            Matcher matcher = PACKAGE_DECL_PATTERN.matcher(line);
-            if (matcher.matches()) {
-                /**
+            String fqcn = sourceIdentifier.getFullyQualifiedClassName();
+            if (sightlyEngineConfiguration.keepGenerated()) {
+                String path = "/" + fqcn.replaceAll("\\.", "/") + ".java";
+                OutputStream os = classLoaderWriter.getOutputStream(path);
+                IOUtils.write(sourceCode, os, "UTF-8");
+                IOUtils.closeQuietly(os);
+            }
+            String[] sourceCodeLines = sourceCode.split("\\r\\n|[\\n\\x0B\\x0C\\r\\u0085\\u2028\\u2029]");
+            boolean foundPackageDeclaration = false;
+            for (String line : sourceCodeLines) {
+                Matcher matcher = PACKAGE_DECL_PATTERN.matcher(line);
+                if (matcher.matches()) {
+                /*
                  * This matching might return false positives like:
                  * // package a.b.c;
                  *
                  * where from a syntactic point of view the source code doesn't have a package declaration and the expectancy is that our
                  * SightlyJavaCompilerService will add one.
                  */
-                foundPackageDeclaration = true;
-                break;
+                    foundPackageDeclaration = true;
+                    break;
+                }
             }
-        }
 
-        if (!foundPackageDeclaration) {
-            sourceCode = "package " + sourceIdentifier.getPackageName() + ";\n" + sourceCode;
-        }
+            if (!foundPackageDeclaration) {
+                sourceCode = "package " + sourceIdentifier.getPackageName() + ";\n" + sourceCode;
+            }
 
-        CompilationUnit compilationUnit = new SightlyCompilationUnit(sourceCode, fqcn);
-        long start = System.currentTimeMillis();
-        CompilationResult compilationResult = javaCompiler.compile(new CompilationUnit[]{compilationUnit}, options);
-        long end = System.currentTimeMillis();
-        List<CompilerMessage> errors = compilationResult.getErrors();
-        if (errors != null && errors.size() > 0) {
-            throw new SightlyException(createErrorMsg(errors));
-        }
-        if (compilationResult.didCompile()) {
-            LOG.debug("Class {} was compiled in {}ms.", fqcn, end - start);
+            CompilationUnit compilationUnit = new SightlyCompilationUnit(sourceCode, fqcn);
+            long start = System.currentTimeMillis();
+            CompilationResult compilationResult = javaCompiler.compile(new CompilationUnit[]{compilationUnit}, options);
+            long end = System.currentTimeMillis();
+            List<CompilerMessage> errors = compilationResult.getErrors();
+            if (errors != null && errors.size() > 0) {
+                throw new SightlyException(createErrorMsg(errors));
+            }
+            if (compilationResult.didCompile()) {
+                LOG.debug("Class {} was compiled in {}ms.", fqcn, end - start);
+            }
+            /*
+             * the class loader might have become dirty, so let the {@link ClassLoaderWriter} decide which class loader to return
+             */
+            return classLoaderWriter.getClassLoader().loadClass(fqcn).newInstance();
+        } catch (Exception e) {
+            throw new SightlyException(e);
         }
-        /**
-         * the class loader might have become dirty, so let the {@link ClassLoaderWriter} decide which class loader to return
-         */
-        return classLoaderWriter.getClassLoader().loadClass(fqcn).newInstance();
     }
 
-    private Object compileRepositoryJavaClass(ResourceResolver resolver, String className) {
-        Resource pojoResource = SourceIdentifier.getPOJOFromFQCN(resolver, sightlyEngineConfiguration.getBundleSymbolicName(), className);
-        if (pojoResource != null) {
-            try {
-                SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, pojoResource.getPath());
+    private Object getUseObjectAndRecompileIfNeeded(Resource pojoResource)
+            throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
+        SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, pojoResource.getPath());
+        long sourceLastModifiedDateFromCache =
+                resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoResource.getPath());
+        long classLastModifiedDate = classLoaderWriter.getLastModified("/" + sourceIdentifier.getFullyQualifiedClassName()
+                .replaceAll("\\.", "/") + ".class");
+        if (sourceLastModifiedDateFromCache == 0) {
+            // first access; let's check the real last modified date of the source
+            long sourceLastModifiedDate = pojoResource.getResourceMetadata().getModificationTime();
+            resourceBackedPojoChangeMonitor.recordLastModifiedTimestamp(pojoResource.getPath(), sourceLastModifiedDate);
+            if (classLastModifiedDate < 0 || sourceLastModifiedDate > classLastModifiedDate) {
+                return compileSource(sourceIdentifier, IOUtils.toString(pojoResource.adaptTo(InputStream.class), "UTF-8"));
+            } else {
+                return classLoaderWriter.getClassLoader().loadClass(sourceIdentifier.getFullyQualifiedClassName()).newInstance();
+            }
+        } else {
+            if (sourceLastModifiedDateFromCache > classLastModifiedDate) {
                 return compileSource(sourceIdentifier, IOUtils.toString(pojoResource.adaptTo(InputStream.class), "UTF-8"));
-            } catch (IOException e) {
-                throw new SightlyException(String.format("Unable to compile class %s from %s.", className, pojoResource.getPath()), e);
+            } else {
+                return classLoaderWriter.getClassLoader().loadClass(sourceIdentifier.getFullyQualifiedClassName()).newInstance();
             }
         }
-        throw new SightlyException("Cannot find a file corresponding to class " + className + " in the repository.");
     }
 
     @Activate

Modified: sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java (original)
+++ sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java Mon Mar 13 08:20:55 2017
@@ -39,17 +39,16 @@ import org.apache.sling.scripting.sightl
  */
 public class SourceIdentifier implements ClassInfo {
 
-    public static final Pattern MANGLED_CHAR_PATTERN = Pattern.compile("(.*)(__[0-9a-f]{4}__)(.*)");
-
+    private static final Pattern MANGLED_CHAR_PATTERN = Pattern.compile("(.*)(__[0-9a-f]{4}__)(.*)");
     private SightlyEngineConfiguration engineConfiguration;
     private String scriptName;
     private String simpleClassName;
     private String packageName;
     private String fullyQualifiedClassName;
 
-    public SourceIdentifier(SightlyEngineConfiguration engineConfiguration, String scriptName) {
+    public SourceIdentifier(SightlyEngineConfiguration engineConfiguration, String resourcePath) {
         this.engineConfiguration = engineConfiguration;
-        this.scriptName = scriptName;
+        this.scriptName = resourcePath;
     }
 
     @Override
@@ -98,6 +97,15 @@ public class SourceIdentifier implements
         return fullyQualifiedClassName;
     }
 
+    /**
+     * Given a {@code fullyQualifiedClassName} and optionally a sub-package that should be stripped ({@code slashSubpackage}), this
+     * method will try to locate a {@code Resource} in the repository that provides the source code for the Java class.
+     *
+     * @param resolver                a resource resolver with access to the script paths
+     * @param slashSubpackage         an optional sub-package that will be stripped from the {@code fullyQualifiedClassName}
+     * @param fullyQualifiedClassName the FQCN
+     * @return the {@code Resource} backing the class, or {@code null} if one cannot be found
+     */
     public static Resource getPOJOFromFQCN(ResourceResolver resolver, String slashSubpackage, String fullyQualifiedClassName) {
         String className = fullyQualifiedClassName;
         StringBuilder pathElements = new StringBuilder("/");
@@ -151,9 +159,9 @@ public class SourceIdentifier implements
      * the {@code originalPath}
      */
     private static Set<String> getPossiblePojoPaths(String originalPath) {
-        Set<String> possiblePaths = new LinkedHashSet<String>();
+        Set<String> possiblePaths = new LinkedHashSet<>();
         possiblePaths.add(originalPath);
-        Map<Integer, String> chars = new HashMap<Integer, String>();
+        Map<Integer, String> chars = new HashMap<>();
         AmbiguousPathSymbol[] symbols = AmbiguousPathSymbol.values();
         for (AmbiguousPathSymbol symbol : symbols) {
             String pathCopy = originalPath.substring(0, originalPath.lastIndexOf("/"));
@@ -171,7 +179,7 @@ public class SourceIdentifier implements
             }
         }
         if (chars.size() > 0) {
-            ArrayList<AmbiguousPathSymbol[]> possibleArrangements = new ArrayList<AmbiguousPathSymbol[]>();
+            ArrayList<AmbiguousPathSymbol[]> possibleArrangements = new ArrayList<>();
             populateArray(possibleArrangements, new AmbiguousPathSymbol[chars.size()], 0);
             Integer[] indexes = chars.keySet().toArray(new Integer[chars.size()]);
             for (AmbiguousPathSymbol[] arrangement : possibleArrangements) {

Modified: sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java (original)
+++ sling/trunk/bundles/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/JavaUseProvider.java Mon Mar 13 08:20:55 2017
@@ -28,8 +28,8 @@ import org.apache.sling.api.SlingHttpSer
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.commons.classloader.ClassLoaderWriter;
-import org.apache.sling.scripting.sightly.impl.engine.SightlyJavaCompilerService;
 import org.apache.sling.scripting.sightly.impl.engine.ResourceBackedPojoChangeMonitor;
+import org.apache.sling.scripting.sightly.impl.engine.SightlyJavaCompilerService;
 import org.apache.sling.scripting.sightly.impl.utils.BindingsUtils;
 import org.apache.sling.scripting.sightly.pojo.Use;
 import org.apache.sling.scripting.sightly.render.RenderContext;
@@ -39,8 +39,6 @@ import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.Designate;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -89,50 +87,39 @@ public class JavaUseProvider implements
 
         Object result;
         try {
-            LOG.debug("Attempting to load class {} from the classloader cache.", identifier);
-            Class<?> cls = classLoaderWriter.getClassLoader().loadClass(identifier);
-            if (resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(identifier) > 0) {
-                // the object is a POJO that was changed in the repository but not recompiled;
-                LOG.debug("Class {} is available in the classloader cache but it needs to be recompiled.", identifier);
-                result = sightlyJavaCompilerService.getInstance(renderContext, identifier);
+            result = sightlyJavaCompilerService.getResourceBackedUseObject(renderContext, identifier);
+            if (result != null) {
                 if (result instanceof Use) {
                     ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
                 }
                 return ProviderOutcome.success(result);
-            }
-            // attempt OSGi service load
-            result = sling.getService(cls);
-            if (result != null) {
-                return ProviderOutcome.success(result);
-            }
-            result = request.adaptTo(cls);
-            if (result == null) {
-                Resource resource = BindingsUtils.getResource(globalBindings);
-                result = resource.adaptTo(cls);
-            }
-            if (result != null) {
-                return ProviderOutcome.success(result);
             } else {
-                /**
-                 * the object was cached by the class loader but it's not adaptable from {@link Resource} or {@link
-                 * SlingHttpServletRequest}; attempt to load it like a regular POJO that optionally could implement {@link Use}
-                 */
-                result = cls.newInstance();
-                if (result instanceof Use) {
-                    ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
+                LOG.debug("Attempting to load class {} from the classloader cache.", identifier);
+                Class<?> cls = classLoaderWriter.getClassLoader().loadClass(identifier);
+                // attempt OSGi service load
+                result = sling.getService(cls);
+                if (result != null) {
+                    return ProviderOutcome.success(result);
+                }
+                result = request.adaptTo(cls);
+                if (result == null) {
+                    Resource resource = BindingsUtils.getResource(globalBindings);
+                    result = resource.adaptTo(cls);
+                }
+                if (result != null) {
+                    return ProviderOutcome.success(result);
+                } else {
+                    /*
+                     * the object was cached by the class loader but it's not adaptable from {@link Resource} or {@link
+                     * SlingHttpServletRequest}; attempt to load it like a regular POJO that optionally could implement {@link Use}
+                     */
+                    result = cls.newInstance();
+                    if (result instanceof Use) {
+                        ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
+                    }
+                    return ProviderOutcome.notNullOrFailure(result);
                 }
-                return ProviderOutcome.notNullOrFailure(result);
-            }
-        } catch (ClassNotFoundException e) {
-            /**
-             * this object is either not exported from a bundle, or it's a POJO from the repository that wasn't loaded before
-             */
-            LOG.debug("Class {} was not found in the classloader cache.", identifier);
-            result = sightlyJavaCompilerService.getInstance(renderContext, identifier);
-            if (result instanceof Use) {
-                ((Use) result).init(BindingsUtils.merge(globalBindings, arguments));
             }
-            return ProviderOutcome.success(result);
         } catch (Exception e) {
             // any other exception is an error
             return ProviderOutcome.failure(e);

Modified: sling/trunk/bundles/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyJavaCompilerServiceTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyJavaCompilerServiceTest.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyJavaCompilerServiceTest.java (original)
+++ sling/trunk/bundles/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyJavaCompilerServiceTest.java Mon Mar 13 08:20:55 2017
@@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHa
 
 import org.apache.commons.io.IOUtils;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.commons.classloader.ClassLoaderWriter;
 import org.apache.sling.commons.compiler.CompilationResult;
@@ -36,6 +37,7 @@ import org.apache.sling.scripting.api.re
 import org.apache.sling.scripting.sightly.impl.engine.ResourceBackedPojoChangeMonitor;
 import org.apache.sling.scripting.sightly.impl.engine.SightlyEngineConfiguration;
 import org.apache.sling.scripting.sightly.impl.engine.SightlyJavaCompilerService;
+import org.apache.sling.scripting.sightly.impl.engine.compiled.SourceIdentifier;
 import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl;
 import org.junit.After;
 import org.junit.Before;
@@ -52,85 +54,136 @@ public class SightlyJavaCompilerServiceT
 
     private SightlyJavaCompilerService compiler;
     private ResourceBackedPojoChangeMonitor resourceBackedPojoChangeMonitor;
+    private ClassLoaderWriter classLoaderWriter;
+    private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
 
     @Before
     public void setUp() throws Exception {
-        compiler = new SightlyJavaCompilerService();
+        compiler = spy(new SightlyJavaCompilerService());
         resourceBackedPojoChangeMonitor = spy(new ResourceBackedPojoChangeMonitor());
         SightlyEngineConfiguration sightlyEngineConfiguration = mock(SightlyEngineConfiguration.class);
+        when(sightlyEngineConfiguration.getBundleSymbolicName()).thenReturn("org.apache.sling.scripting.sightly");
+        when(sightlyEngineConfiguration.getScratchFolder()).thenReturn("/org/apache/sling/scripting/sightly");
         Whitebox.setInternalState(compiler, "sightlyEngineConfiguration", sightlyEngineConfiguration);
         Whitebox.setInternalState(compiler, "resourceBackedPojoChangeMonitor", resourceBackedPojoChangeMonitor);
+        classLoaderWriter = Mockito.mock(ClassLoaderWriter.class);
+        ClassLoader classLoader = Mockito.mock(ClassLoader.class);
+        when(classLoaderWriter.getClassLoader()).thenReturn(classLoader);
     }
 
     @After
     public void tearDown() throws Exception {
         compiler = null;
         resourceBackedPojoChangeMonitor = null;
+        classLoaderWriter = null;
     }
 
     @Test
-    /**
-     * Tests that class names whose packages contain underscores are correctly expanded to JCR paths containing symbols that might be
-     * replaced with an underscores in order to obey Java naming conventions.
-     */
-    public void testGetInstanceForPojoFromRepoWithAmbigousPath() throws Exception {
-        String pojoPath = "/apps/my-project/test_components/a/Pojo.java";
-        String className = "apps.my_project.test_components.a.Pojo";
-        getInstancePojoTest(pojoPath, className);
+    public void testDiskCachedUseObject() throws Exception {
+        String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
+        String className = "apps.myproject.testcomponents.a.Pojo";
+        scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
+        Resource pojoResource = Mockito.mock(Resource.class);
+        when(pojoResource.getPath()).thenReturn(pojoPath);
+        ResourceMetadata mockMetadata = Mockito.mock(ResourceMetadata.class);
+        when(mockMetadata.getModificationTime()).thenReturn(1l);
+        when(pojoResource.getResourceMetadata()).thenReturn(mockMetadata);
+        when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
+        when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
+        when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(2l);
+        getInstancePojoTest(className);
+        /*
+         * assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
+         * object
+         */
+        verify(compiler, never()).compileSource(any(SourceIdentifier.class), anyString());
     }
 
     @Test
-    /**
-     * Tests that class names whose package names don't contain underscores are correctly expanded to JCR paths.
-     */
-    public void testGetInstanceForPojoFromRepo() throws Exception {
+    public void testObsoleteDiskCachedUseObject() throws Exception {
         String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
         String className = "apps.myproject.testcomponents.a.Pojo";
-        getInstancePojoTest(pojoPath, className);
+        scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
+        Resource pojoResource = Mockito.mock(Resource.class);
+        when(pojoResource.getPath()).thenReturn(pojoPath);
+        ResourceMetadata mockMetadata = Mockito.mock(ResourceMetadata.class);
+        when(mockMetadata.getModificationTime()).thenReturn(2l);
+        when(pojoResource.getResourceMetadata()).thenReturn(mockMetadata);
+        when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
+        when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
+        when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(1l);
+        getInstancePojoTest(className);
+        /*
+         * assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
+         * object
+         */
+        verify(compiler).compileSource(any(SourceIdentifier.class), anyString());
     }
 
     @Test
-    public void testGetInstanceForCachedPojoFromRepo() throws Exception {
-        final String pojoPath = "/apps/my-project/test_components/a/Pojo.java";
-        final String className = "apps.my_project.test_components.a.Pojo";
-        Map<String, Long> slyJavaUseMap = new ConcurrentHashMap<String, Long>() {{
-            put(className, System.currentTimeMillis());
-        }};
-        Whitebox.setInternalState(resourceBackedPojoChangeMonitor, "slyJavaUseMap", slyJavaUseMap);
-        getInstancePojoTest(pojoPath, className);
-        verify(resourceBackedPojoChangeMonitor).clearJavaUseObject(className);
-    }
-
-    private void getInstancePojoTest(String pojoPath, String className) throws Exception {
-        RenderContextImpl renderContext = Mockito.mock(RenderContextImpl.class);
-        ScriptingResourceResolverProvider scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
+    public void testMemoryCachedUseObject() throws Exception {
+        String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
+        String className = "apps.myproject.testcomponents.a.Pojo";
+        scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
         Resource pojoResource = Mockito.mock(Resource.class);
         when(pojoResource.getPath()).thenReturn(pojoPath);
+        when(resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoPath)).thenReturn(1l);
+        when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
+        when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
+        when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(2l);
+        getInstancePojoTest(className);
+        /*
+         * assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
+         * object
+         */
+        verify(compiler, never()).compileSource(any(SourceIdentifier.class), anyString());
+    }
+
+    @Test
+    public void testObsoleteMemoryCachedUseObject() throws Exception {
+        String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
+        String className = "apps.myproject.testcomponents.a.Pojo";
+        scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
         ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
         when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
-        when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
+        Resource pojoResource = Mockito.mock(Resource.class);
+        when(pojoResource.getPath()).thenReturn(pojoPath);
+        when(resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoPath)).thenReturn(2l);
         when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
+        when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
+        when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(1l);
+        getInstancePojoTest(className);
+        /*
+         * assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
+         * object
+         */
+        verify(compiler).compileSource(any(SourceIdentifier.class), anyString());
+    }
+
+    private void getInstancePojoTest(String className) throws Exception {
+        RenderContextImpl renderContext = Mockito.mock(RenderContextImpl.class);
+
+
         JavaCompiler javaCompiler = Mockito.mock(JavaCompiler.class);
         CompilationResult compilationResult = Mockito.mock(CompilationResult.class);
         when(compilationResult.getErrors()).thenReturn(new ArrayList<CompilerMessage>());
         when(javaCompiler.compile(Mockito.any(CompilationUnit[].class), Mockito.any(Options.class))).thenReturn(compilationResult);
-        ClassLoaderWriter clw = Mockito.mock(ClassLoaderWriter.class);
-        ClassLoader classLoader = Mockito.mock(ClassLoader.class);
-        when(clw.getClassLoader()).thenReturn(classLoader);
-        when(classLoader.loadClass(className)).thenAnswer(new Answer<Object>() {
+        when(classLoaderWriter.getClassLoader().loadClass(className)).thenAnswer(new Answer<Object>() {
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
                 return MockPojo.class;
             }
         });
-        SightlyEngineConfiguration sightlyEngineConfiguration = mock(SightlyEngineConfiguration.class);
-        when(sightlyEngineConfiguration.getBundleSymbolicName()).thenReturn("org.apache.sling.scripting.sightly");
-        when(sightlyEngineConfiguration.getScratchFolder()).thenReturn("/org/apache/sling/scripting/sightly");
-        Whitebox.setInternalState(compiler, "classLoaderWriter", clw);
+        Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
         Whitebox.setInternalState(compiler, "javaCompiler", javaCompiler);
         Whitebox.setInternalState(compiler, "scriptingResourceResolverProvider", scriptingResourceResolverProvider);
-        Whitebox.setInternalState(compiler, "sightlyEngineConfiguration", sightlyEngineConfiguration);
-        Object obj = compiler.getInstance(renderContext, className);
+        Object obj = compiler.getResourceBackedUseObject(renderContext, className);
         assertTrue("Expected to obtain a " + MockPojo.class.getName() + " object.", obj instanceof MockPojo);
     }
 }

Modified: sling/trunk/bundles/scripting/sightly/testing/src/test/java/org/apache/sling/scripting/sightly/it/SlingSpecificsSightlyIT.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/sightly/testing/src/test/java/org/apache/sling/scripting/sightly/it/SlingSpecificsSightlyIT.java?rev=1786637&r1=1786636&r2=1786637&view=diff
==============================================================================
--- sling/trunk/bundles/scripting/sightly/testing/src/test/java/org/apache/sling/scripting/sightly/it/SlingSpecificsSightlyIT.java (original)
+++ sling/trunk/bundles/scripting/sightly/testing/src/test/java/org/apache/sling/scripting/sightly/it/SlingSpecificsSightlyIT.java Mon Mar 13 08:20:55 2017
@@ -28,6 +28,7 @@ import org.apache.http.entity.mime.Multi
 import org.apache.http.entity.mime.content.InputStreamBody;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -220,6 +221,27 @@ public class SlingSpecificsSightlyIT {
     }
 
     @Test
+    public void testRepositoryPojoUpdateDirty() throws Exception {
+        String url = launchpadURL + SLING_JAVA_USE_POJO_UPDATE;
+        String pageContent = client.getStringContent(url, 200);
+        assertEquals("original", HTMLExtractor.innerHTML(url + System.currentTimeMillis(), pageContent, "#repopojo"));
+
+        uploadFile("RepoPojo.java.updated", "RepoPojo.java", "/apps/sightly/scripts/use");
+        Thread.sleep(1000);
+        restartSightlyEngineBundle();
+
+        pageContent = client.getStringContent(url, 200);
+        assertEquals("updated", HTMLExtractor.innerHTML(url + System.currentTimeMillis(), pageContent, "#repopojo"));
+
+        uploadFile("RepoPojo.java.original", "RepoPojo.java", "/apps/sightly/scripts/use");
+        Thread.sleep(1000);
+        restartSightlyEngineBundle();
+
+        pageContent = client.getStringContent(url, 200);
+        assertEquals("original", HTMLExtractor.innerHTML(url + System.currentTimeMillis(), pageContent, "#repopojo"));
+    }
+
+    @Test
     public void testScriptUpdate() throws Exception {
         String url = launchpadURL + SLING_SCRIPT_UPDATE;
         String pageContent = client.getStringContent(url, 200);
@@ -335,6 +357,25 @@ public class SlingSpecificsSightlyIT {
         assertEquals("true", HTMLExtractor.innerHTML(url, pageContent, "#newline"));
     }
 
+    private void restartSightlyEngineBundle() throws InterruptedException, IOException {
+        HttpClient httpClient = HttpClientBuilder.create().build();
+        HttpPost post = new HttpPost(launchpadURL + "/system/console/bundles/org.apache.sling.scripting.sightly");
+        // Stop bundle
+        FormEntityBuilder formBuilder = FormEntityBuilder.create();
+        post.setHeader("Authorization", "Basic YWRtaW46YWRtaW4=");
+        formBuilder.addParameter("action", "stop");
+        post.setEntity(formBuilder.build());
+        httpClient.execute(post);
+        Thread.sleep(1000);
+        // Start bundle
+        formBuilder = FormEntityBuilder.create();
+        post.setHeader("Authorization", "Basic YWRtaW46YWRtaW4=");
+        formBuilder.addParameter("action", "start");
+        post.setEntity(formBuilder.build());
+        httpClient.execute(post);
+        Thread.sleep(1000);
+    }
+
     private void uploadFile(String fileName, String serverFileName, String url) throws IOException {
         HttpClient httpClient = HttpClientBuilder.create().build();
         HttpPost post = new HttpPost(launchpadURL + url);