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>.