You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2019/06/07 11:10:14 UTC

[sling-org-apache-sling-feature-analyser] branch master updated: SLING-8474 : Improve scanner by caching scanned results

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

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git


The following commit(s) were added to refs/heads/master by this push:
     new 66f2eb9  SLING-8474 : Improve scanner by caching scanned results
66f2eb9 is described below

commit 66f2eb9a6d88eec8b5062f47477b5bd640ff6a53
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Jun 7 13:08:01 2019 +0200

    SLING-8474 : Improve scanner by caching scanned results
---
 .../sling/feature/scanner/ArtifactDescriptor.java  |  11 +-
 .../sling/feature/scanner/BundleDescriptor.java    |   7 +-
 .../sling/feature/scanner/ContainerDescriptor.java |   8 +-
 .../apache/sling/feature/scanner/Descriptor.java   |  13 ++-
 .../sling/feature/scanner/FeatureDescriptor.java   |   5 +
 .../apache/sling/feature/scanner/PackageInfo.java  |  11 +-
 .../org/apache/sling/feature/scanner/Scanner.java  | 118 ++++++++++++++-------
 7 files changed, 125 insertions(+), 48 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
index 85e149e..a9a75d6 100644
--- a/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
@@ -16,12 +16,17 @@
  */
 package org.apache.sling.feature.scanner;
 
-import org.apache.sling.feature.Artifact;
-
 import java.io.File;
 
+import org.apache.sling.feature.Artifact;
+
 /**
- * Information about an artifact
+ * Information about an artifact.
+ *
+ * Note that this implementation is not synchronized. If multiple threads access
+ * a descriptor concurrently, and at least one of the threads modifies the
+ * descriptor structurally, it must be synchronized externally. However, once a
+ * descriptor is locked, it is safe to access it concurrently.
  */
 public abstract class ArtifactDescriptor extends Descriptor {
     protected ArtifactDescriptor(String name) {
diff --git a/src/main/java/org/apache/sling/feature/scanner/BundleDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/BundleDescriptor.java
index 006f158..caa59fb 100644
--- a/src/main/java/org/apache/sling/feature/scanner/BundleDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/BundleDescriptor.java
@@ -21,7 +21,12 @@ import java.util.jar.Manifest;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
 
 /**
- * Information about a bundle
+ * Information about a bundle.
+ *
+ * Note that this implementation is not synchronized. If multiple threads access
+ * a descriptor concurrently, and at least one of the threads modifies the
+ * descriptor structurally, it must be synchronized externally. However, once a
+ * descriptor is locked, it is safe to access it concurrently.
  */
 public abstract class BundleDescriptor extends ArtifactDescriptor implements Comparable<BundleDescriptor> {
     protected BundleDescriptor(String name) {
diff --git a/src/main/java/org/apache/sling/feature/scanner/ContainerDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/ContainerDescriptor.java
index 33a17a7..7440117 100644
--- a/src/main/java/org/apache/sling/feature/scanner/ContainerDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/ContainerDescriptor.java
@@ -21,8 +21,12 @@ import java.util.HashSet;
 import java.util.Set;
 
 /**
- * Information about a container (feature/application).
- * This is the aggregated information.
+ * Information about a container (feature). This is the aggregated information.
+ *
+ * Note that this implementation is not synchronized. If multiple threads access
+ * a descriptor concurrently, and at least one of the threads modifies the
+ * descriptor structurally, it must be synchronized externally. However, once a
+ * descriptor is locked, it is safe to access it concurrently.
  */
 public abstract class ContainerDescriptor extends Descriptor {
 
diff --git a/src/main/java/org/apache/sling/feature/scanner/Descriptor.java b/src/main/java/org/apache/sling/feature/scanner/Descriptor.java
index 59dbb3d..3235e6b 100644
--- a/src/main/java/org/apache/sling/feature/scanner/Descriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/Descriptor.java
@@ -16,15 +16,20 @@
  */
 package org.apache.sling.feature.scanner;
 
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
 /**
- * A descriptor holds information about requirements and capabilities
+ * A descriptor holds information about requirements and capabilities.
+ *
+ * Note that this implementation is not synchronized. If multiple threads access
+ * a descriptor concurrently, and at least one of the threads modifies the
+ * descriptor structurally, it must be synchronized externally. However, once a
+ * descriptor is locked, it is safe to access it concurrently.
  */
 public abstract class Descriptor  {
     private final String name;
diff --git a/src/main/java/org/apache/sling/feature/scanner/FeatureDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/FeatureDescriptor.java
index 9fe1c9d..bc0332e 100644
--- a/src/main/java/org/apache/sling/feature/scanner/FeatureDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/FeatureDescriptor.java
@@ -20,6 +20,11 @@ import org.apache.sling.feature.Feature;
 
 /**
  * Information about a feature.
+ *
+ * Note that this implementation is not synchronized. If multiple threads access
+ * a descriptor concurrently, and at least one of the threads modifies the
+ * descriptor structurally, it must be synchronized externally. However, once a
+ * descriptor is locked, it is safe to access it concurrently.
  */
 public abstract class FeatureDescriptor extends ContainerDescriptor {
     private final Feature feature;
diff --git a/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java b/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
index 9979aab..369d68f 100644
--- a/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
+++ b/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
@@ -17,11 +17,18 @@
 package org.apache.sling.feature.scanner;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 
+/**
+ * A package info object contains information about a package, its name, its
+ * version and the uses constraints.
+ *
+ * A package info object is immutable.
+ */
 public class PackageInfo implements Comparable<PackageInfo> {
 
     private final boolean optional;
@@ -30,14 +37,14 @@ public class PackageInfo implements Comparable<PackageInfo> {
     private final Set<String> uses;
 
     public PackageInfo(final String name, final String version, final boolean optional) {
-        this(name, version, optional, Collections.emptySet());
+        this(name, version, optional, null);
     }
 
     public PackageInfo(final String name, final String version, final boolean optional, Set<String> uses) {
         this.name = name;
         this.version = version;
         this.optional = optional;
-        this.uses = uses;
+        this.uses = uses == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(uses));
     }
 
     public String getName() {
diff --git a/src/main/java/org/apache/sling/feature/scanner/Scanner.java b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
index b28b7ca..a2d7920 100644
--- a/src/main/java/org/apache/sling/feature/scanner/Scanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
@@ -16,11 +16,19 @@
  */
 package org.apache.sling.feature.scanner;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Bundles;
 import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.ArtifactProvider;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
@@ -28,22 +36,20 @@ import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
 import org.apache.sling.feature.scanner.spi.ExtensionScanner;
 import org.apache.sling.feature.scanner.spi.FrameworkScanner;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-
 /**
- * The scanner is a service that scans items and provides descriptions for these.
- * The following items can be scanned individually
+ * The scanner is a service that scans items and provides descriptions for
+ * these. The following items can be scanned individually
  * <ul>
- *   <li>A bundle artifact
- *   <li>An extension (requires {@link ExtensionScanner}s)
- *   <li>A feature (requires {@link ExtensionScanner}s)
- *   <li>A framework (requires {@link FrameworkScanner}s)
+ * <li>A bundle artifact
+ * <li>A feature (requires {@link ExtensionScanner}s)
+ * <li>A framework (requires {@link FrameworkScanner}s)
  * </ul>
+ *
+ * The scanner uses an internal cache for the scanned results, subsequent scan
+ * calls with the same input will be directly served from the cache. The cache
+ * is an in memory cache and its lifetime is bound to the lifetime of the used
+ * scanner instance.
+ *
  */
 public class Scanner {
 
@@ -53,6 +59,9 @@ public class Scanner {
 
     private final List<FrameworkScanner> frameworkScanners;
 
+    /** The in memory cache for the scanned descriptors. */
+    private final Map<String, Object> cache = new ConcurrentHashMap<>();
+
     /**
      * Create a new scanner
      *
@@ -105,18 +114,25 @@ public class Scanner {
      * @throws IOException If something goes wrong or the provided artifact is not a bundle.
      */
     public BundleDescriptor scan(final Artifact bundle, final int startLevel) throws IOException {
-        final File file = artifactProvider.provide(bundle.getId());
-        if ( file == null ) {
-            throw new IOException("Unable to find file for " + bundle.getId());
-        }
+        final String key = bundle.getId().toMvnId().concat(":").concat(String.valueOf(startLevel));
+        BundleDescriptor desc = (BundleDescriptor) this.cache.get(key);
+        if (desc == null) {
+            final File file = artifactProvider.provide(bundle.getId());
+            if (file == null) {
+                throw new IOException("Unable to find file for " + bundle.getId());
+            }
 
-        return new BundleDescriptorImpl(bundle, file, startLevel);
+            desc = new BundleDescriptorImpl(bundle, file, startLevel);
+            this.cache.put(key, desc);
+        }
+        return desc;
     }
 
     /**
-     * Get all bundle descriptors for a feature / application
+     * Get all bundle descriptors
+     *
      * @param bundles The bundles
-     * @param desc The descriptor
+     * @param desc    The container descriptor
      * @throws IOException If something goes wrong or no suitable scanner is found.
      */
     private void getBundleInfos(final Bundles bundles, final ContainerDescriptor desc)
@@ -129,9 +145,16 @@ public class Scanner {
         }
     }
 
-    private void scan(final Feature f, final Extensions extensions, final ContainerDescriptor desc)
+    /**
+     * Scan all extensions of a feature
+     *
+     * @param f    The feature
+     * @param desc The container descriptor
+     * @throws IOException If something goes wrong or no suitable scanner is found.
+     */
+    private void scanExtensions(final Feature f, final ContainerDescriptor desc)
     throws IOException {
-        for(final Extension ext : extensions) {
+        for (final Extension ext : f.getExtensions()) {
             ContainerDescriptor extDesc = null;
             for(final ExtensionScanner scanner : this.extensionScanners) {
                 extDesc = scanner.scan(f, ext, this.artifactProvider);
@@ -153,6 +176,11 @@ public class Scanner {
         }
     }
 
+    /**
+     * Compact the container description
+     * 
+     * @param desc The contaier description
+     */
     private void compact(final ContainerDescriptor desc) {
         // TBD remove all import packages / dynamic import packages which are resolved by this bundle set
         // same with requirements
@@ -167,15 +195,21 @@ public class Scanner {
      * @throws IOException If something goes wrong or a scanner is missing
      */
     public FeatureDescriptor scan(final Feature feature) throws IOException {
-        final FeatureDescriptorImpl desc = new FeatureDescriptorImpl(feature);
+        final String key = feature.getId().toMvnId();
+
+        FeatureDescriptorImpl desc = (FeatureDescriptorImpl) this.cache.get(key);
+        if (desc == null) {
+            desc = new FeatureDescriptorImpl(feature);
 
-        getBundleInfos(feature.getBundles(), desc);
-        scan(feature, feature.getExtensions(), desc);
+            getBundleInfos(feature.getBundles(), desc);
+            scanExtensions(feature, desc);
 
-        compact(desc);
+            compact(desc);
 
-        desc.lock();
+            desc.lock();
 
+            this.cache.put(key, desc);
+        }
         return desc;
     }
 
@@ -188,17 +222,29 @@ public class Scanner {
      * @throws IOException If something goes wrong or a scanner is missing
      */
     public BundleDescriptor scan(final ArtifactId framework, final Map<String,String> props) throws IOException {
-        BundleDescriptor fwk = null;
-        for(final FrameworkScanner scanner : this.frameworkScanners) {
-            fwk = scanner.scan(framework, props, artifactProvider);
-            if ( fwk != null ) {
-                break;
+        final StringBuilder sb = new StringBuilder();
+        sb.append(framework.toMvnId());
+        if (props != null) {
+            final Map<String, String> sortedMap = new TreeMap<String, String>(props);
+            for (final Map.Entry<String, String> entry : sortedMap.entrySet()) {
+                sb.append(":").append(entry.getKey()).append("=").append(entry.getValue());
             }
         }
-        if ( fwk == null ) {
-            throw new IOException("No scanner found for framework " + framework.toMvnId());
+        final String key = sb.toString();
+        BundleDescriptor desc = (BundleDescriptor) this.cache.get(key);
+        if (desc == null) {
+            for (final FrameworkScanner scanner : this.frameworkScanners) {
+                desc = scanner.scan(framework, props, artifactProvider);
+                if (desc != null) {
+                    break;
+                }
+            }
+            if (desc == null) {
+                throw new IOException("No scanner found for framework " + framework.toMvnId());
+            }
+            this.cache.put(key, desc);
         }
 
-        return fwk;
+        return desc;
     }
 }