You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:34:20 UTC

[sling-org-apache-sling-featureflags] 01/25: New Feature Flags prototype

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

rombert pushed a commit to annotated tag org.apache.sling.featureflags-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-featureflags.git

commit b660cb40e1ad5fea7b3b8cbd2683b673f2b45815
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Dec 19 01:21:55 2013 +0000

    New Feature Flags prototype
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/whiteboard/feature-flags@1552202 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  70 ++++++++
 .../extensions/featureflags/ExecutionContext.java  |  56 ++++++
 .../sling/extensions/featureflags/Feature.java     |  54 ++++++
 .../extensions/featureflags/FeatureProvider.java   |  57 ++++++
 .../featureflags/impl/ExecutionContextFilter.java  | 100 +++++++++++
 .../extensions/featureflags/impl/FeatureImpl.java  | 196 +++++++++++++++++++++
 .../featureflags/impl/ResourceAccessImpl.java      |  51 ++++++
 .../extensions/featureflags/package-info.java      |  30 ++++
 8 files changed, 614 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6a40896
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>18</version>
+    </parent>
+    
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.featureflags</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+    <name>Apache Sling Feature Flags</name>
+    
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.4.3-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.resourceaccesssecurity</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/ExecutionContext.java b/src/main/java/org/apache/sling/extensions/featureflags/ExecutionContext.java
new file mode 100644
index 0000000..8e00dfd
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/ExecutionContext.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class ExecutionContext {
+
+    private final ResourceResolver resourceResolver;
+
+    private final SlingHttpServletRequest request;
+
+    public static ExecutionContext fromRequest(final SlingHttpServletRequest request) {
+        return new ExecutionContext(request);
+    }
+
+    public static ExecutionContext fromResourceResolver(final ResourceResolver resourceResolver) {
+        return new ExecutionContext(resourceResolver);
+    }
+
+    private ExecutionContext(final ResourceResolver resourceResolver) {
+        this.request = null;
+        this.resourceResolver = resourceResolver;
+    }
+
+
+    private ExecutionContext(final SlingHttpServletRequest request) {
+        this.request = request;
+        this.resourceResolver = request.getResourceResolver();
+    }
+
+    public SlingHttpServletRequest getRequest() {
+        return this.request;
+    }
+
+    public ResourceResolver getResourceResolver() {
+        return this.resourceResolver;
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/Feature.java b/src/main/java/org/apache/sling/extensions/featureflags/Feature.java
new file mode 100644
index 0000000..ed901c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/Feature.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * The feature service is the central gateway for feature handling.
+ * It can be used to query the available features and to
+ * check whether a feature is enabled for the current execution
+ * context.
+ */
+@ProviderType
+public interface Feature {
+
+
+    /**
+     * Checks whether the feature is enabled for the given
+     * execution context.
+     *
+     * The actual check is delegated to the {@link FeatureProvider}
+     * providing the feature.
+     */
+    boolean isEnabled(String featureName, ExecutionContext context);
+
+    /** Get the list of active feature flags */
+    String[] getFeatureNames();
+
+    /**
+     * Checks whether a feature with the given name is available.
+     * A feature is available if there is a {@link FeatureProvider}
+     * for that feature.
+     * @param featureName
+     * @return
+     */
+    boolean isAvailable(String featureName);
+
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java b/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java
new file mode 100644
index 0000000..66264b9
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * A feature provider activates one more features.
+ */
+@ConsumerType
+public interface FeatureProvider {
+
+    /**
+     * Checks whether the feature is enabled for the current execution
+     * context.
+     */
+    boolean isEnabled(String featureName, ExecutionContext context);
+
+    /**
+     * Return the list of available features from this provider.
+     */
+    String [] getFeatureNames();
+
+    /**
+     * Returns the resource type mapping for a feature.
+     * This mapping is only used if {@link #isEnabled(String, ExecutionContext)}
+     * return true for the given feature/context.
+     */
+    Map<String, String> getResourceTypeMapping(String featureName);
+
+    /**
+     * Checks whether a resource should be hidden for a feature.
+     * This check is only executed if {@link #isEnabled(String, ExecutionContext)}
+     * return true for the given feature/context.
+     */
+    boolean hideResource(String featureName, Resource resource);
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java
new file mode 100644
index 0000000..7ee7b85
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags.impl;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.extensions.featureflags.ExecutionContext;
+import org.apache.sling.extensions.featureflags.Feature;
+
+@Component
+@Service(value=Filter.class)
+@Property(name="pattern", value="/.*")
+public class ExecutionContextFilter implements Filter {
+
+    private static ThreadLocal<ExecutionContextInfo> EXECUTION_CONTEXT;
+
+    @Reference
+    private Feature feature;
+
+    public static ExecutionContextInfo getCurrentExecutionContextInfo() {
+        final ThreadLocal<ExecutionContextInfo> local = EXECUTION_CONTEXT;
+        if ( local != null ) {
+            return local.get();
+        }
+        return null;
+    }
+
+    @Override
+    public void destroy() {
+        EXECUTION_CONTEXT = null;
+    }
+
+    @Override
+    public void doFilter(final ServletRequest req, final ServletResponse res,
+            final FilterChain chain)
+    throws IOException, ServletException {
+        final ThreadLocal<ExecutionContextInfo> local = EXECUTION_CONTEXT;
+        if ( local != null && req instanceof SlingHttpServletRequest ) {
+            local.set(new ExecutionContextInfo((SlingHttpServletRequest)req, feature));
+        }
+        try {
+            chain.doFilter(req, res);
+        } finally {
+            if ( local != null && req instanceof SlingHttpServletRequest ) {
+                local.set(null);
+            }
+        }
+    }
+
+    @Override
+    public void init(final FilterConfig config) throws ServletException {
+        EXECUTION_CONTEXT = new ThreadLocal<ExecutionContextInfo>();
+    }
+
+    public final class ExecutionContextInfo {
+
+        public final ExecutionContext context;
+        public final Set<String> enabledFeatures = new HashSet<String>();
+
+        public ExecutionContextInfo(final SlingHttpServletRequest req,
+                final Feature feature) {
+            this.context = ExecutionContext.fromRequest(req);
+            for(final String name : feature.getFeatureNames()) {
+                if ( feature.isEnabled(name, context) ) {
+                    enabledFeatures.add(name);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java
new file mode 100644
index 0000000..65abe86
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.extensions.featureflags.ExecutionContext;
+import org.apache.sling.extensions.featureflags.Feature;
+import org.apache.sling.extensions.featureflags.FeatureProvider;
+import org.osgi.framework.Constants;
+
+/**
+ * This service implements the feature handling.
+ * It keeps track of all {@link FeatureProvider} services.
+ */
+@Component
+@Service(value=Feature.class)
+@Reference(name="featureProvider",
+           cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
+           policy=ReferencePolicy.DYNAMIC,
+           referenceInterface=FeatureProvider.class)
+public class FeatureImpl implements Feature {
+
+    private final Map<String, List<FeatureProviderDescription>> providers = new HashMap<String, List<FeatureProviderDescription>>();
+
+    private Map<String, FeatureProviderDescription> activeProviders = new HashMap<String, FeatureProviderDescription>();
+
+    /**
+     * Bind a new feature provider
+     */
+    protected void bindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) {
+        final String[] features = provider.getFeatureNames();
+        if ( features != null && features.length > 0 ) {
+            final FeatureProviderDescription info = new FeatureProviderDescription(provider, props);
+            synchronized ( this.providers ) {
+                boolean changed = false;
+                for(final String n : features) {
+                    if ( n != null ) {
+                        final String name = n.trim();
+                        if ( name.length() > 0 ) {
+                            List<FeatureProviderDescription> candidates = this.providers.get(name);
+                            if ( candidates == null ) {
+                                candidates = new ArrayList<FeatureProviderDescription>();
+                                this.providers.put(name, candidates);
+                            }
+                            candidates.add(info);
+                            Collections.sort(candidates);
+                            changed = true;
+                        }
+                    }
+                }
+                if ( changed ) {
+                    this.calculateActiveProviders();
+                }
+            }
+        }
+    }
+
+    /**
+     * Unbind a feature provider
+     */
+    protected void unbindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) {
+        final String[] features = provider.getFeatureNames();
+        if ( features != null && features.length > 0 ) {
+            final FeatureProviderDescription info = new FeatureProviderDescription(provider, props);
+            synchronized ( this.providers ) {
+                boolean changed = false;
+                for(final String n : features) {
+                    if ( n != null ) {
+                        final String name = n.trim();
+                        if ( name.length() > 0 ) {
+                            final List<FeatureProviderDescription> candidates = this.providers.get(name);
+                            if ( candidates != null ) { // sanity check
+                                candidates.remove(info);
+                                if ( candidates.size() == 0 ) {
+                                    this.providers.remove(name);
+                                    changed = true;
+                                }
+                            }
+                        }
+                    }
+                }
+                if ( changed ) {
+                    this.calculateActiveProviders();
+                }
+            }
+        }
+    }
+
+    private void calculateActiveProviders() {
+        final Map<String, FeatureProviderDescription> activeMap = new HashMap<String, FeatureImpl.FeatureProviderDescription>();
+        for(final Map.Entry<String, List<FeatureProviderDescription>> entry : this.providers.entrySet()) {
+            activeMap.put(entry.getKey(), entry.getValue().get(0));
+        }
+        this.activeProviders = activeMap;
+    }
+
+    @Override
+    public boolean isEnabled(final String featureName, final ExecutionContext context) {
+        boolean result = false;
+        final FeatureProviderDescription desc = this.activeProviders.get(featureName);
+        if ( desc != null ) {
+            final FeatureProvider prod = desc.getProvider();
+            result = prod.isEnabled(featureName, context);
+        }
+        return result;
+    }
+
+    @Override
+    public String[] getFeatureNames() {
+        return this.activeProviders.keySet().toArray(new String[this.activeProviders.size()]);
+    }
+
+    @Override
+    public boolean isAvailable(final String featureName) {
+        return this.activeProviders.containsKey(featureName);
+    }
+
+
+    /**
+     * Internal class caching some provider infos like service id and ranking.
+     */
+    private final static class FeatureProviderDescription implements Comparable<FeatureProviderDescription> {
+
+        public FeatureProvider provider;
+        public final int ranking;
+        public final long serviceId;
+
+        public FeatureProviderDescription(final FeatureProvider provider, final Map<String, Object> props) {
+            this.provider = provider;
+            final Object sr = props.get(Constants.SERVICE_RANKING);
+            if ( sr == null || !(sr instanceof Integer)) {
+                this.ranking = 0;
+            } else {
+                this.ranking = (Integer)sr;
+            }
+            this.serviceId = (Long)props.get(Constants.SERVICE_ID);
+        }
+
+        @Override
+        public int compareTo(final FeatureProviderDescription o) {
+            if ( this.ranking < o.ranking ) {
+                return 1;
+            } else if (this.ranking > o.ranking ) {
+                return -1;
+            }
+            // If ranks are equal, then sort by service id in descending order.
+            return (this.serviceId < o.serviceId) ? -1 : 1;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if ( obj instanceof FeatureProviderDescription ) {
+                return ((FeatureProviderDescription)obj).serviceId == this.serviceId;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (serviceId ^ (serviceId >>> 32));
+            return result;
+        }
+
+        public FeatureProvider getProvider() {
+            return provider;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java
new file mode 100644
index 0000000..a3bed50
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.featureflags.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.extensions.featureflags.Feature;
+import org.apache.sling.resourceaccesssecurity.AllowingResourceAccessGate;
+import org.apache.sling.resourceaccesssecurity.ResourceAccessGate;
+
+@Component
+@Service(value=ResourceAccessGate.class)
+public class ResourceAccessImpl
+    extends AllowingResourceAccessGate
+    implements ResourceAccessGate {
+
+    @Reference
+    private Feature feature;
+
+    @Override
+    public GateResult canRead(final Resource resource) {
+        boolean available = true;
+        final ExecutionContextFilter.ExecutionContextInfo info = ExecutionContextFilter.getCurrentExecutionContextInfo();
+        if ( info != null ) {
+            for(final String name : info.enabledFeatures) {
+                // we can't check as Feature does not have the api (TODO - we deny for now)
+                available = false;
+                break;
+            }
+        }
+        return (available ? GateResult.DONTCARE : GateResult.DENIED);
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/featureflags/package-info.java b/src/main/java/org/apache/sling/extensions/featureflags/package-info.java
new file mode 100644
index 0000000..f1cdd16
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/featureflags/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides a service to interface which may be implemented by applications
+ * to get notified on cluster topology changes.
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+package org.apache.sling.extensions.featureflags;
+
+import aQute.bnd.annotation.Version;
+

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.