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);