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/10/18 23:25:46 UTC
[sling-org-apache-sling-tenant] 05/44: SLING-2676 Simple tenant
administration through the Web Console (thanks Amit Gupta for providing the
patch).
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tenant.git
commit d12fcf0d29bf981370d48f0e1b70017a7dc9cce9
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Nov 29 13:14:21 2012 +0000
SLING-2676 Simple tenant administration through the Web Console (thanks Amit Gupta for providing the patch).
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1415151 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 11 ++
.../sling/tenant/internal/TenantProviderImpl.java | 144 +++++++++++++--
.../tenant/internal/console/WebConsolePlugin.java | 197 +++++++++++++++++++++
.../apache/sling/tenant/spi/TenantCustomizer.java | 71 ++++++++
.../org/apache/sling/tenant/spi/package-info.java | 24 +++
5 files changed, 437 insertions(+), 10 deletions(-)
diff --git a/pom.xml b/pom.xml
index 8d68c85..f4b7c7b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr-commons</artifactId>
+ <version>2.4.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.0</version>
@@ -118,6 +124,11 @@
<artifactId>bndlib</artifactId>
<scope>provided</scope>
</dependency>
+ <!-- Webconsole -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
<!-- Testing -->
<dependency>
diff --git a/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java b/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java
index 4ab2c47..45a7933 100644
--- a/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java
+++ b/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java
@@ -19,10 +19,13 @@
package org.apache.sling.tenant.internal;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -38,8 +41,12 @@ import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
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.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
@@ -47,8 +54,11 @@ import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.commons.osgi.ServiceUtil;
import org.apache.sling.tenant.Tenant;
import org.apache.sling.tenant.TenantProvider;
+import org.apache.sling.tenant.internal.console.WebConsolePlugin;
+import org.apache.sling.tenant.spi.TenantCustomizer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
@@ -61,11 +71,17 @@ import org.osgi.framework.InvalidSyntaxException;
@Component(
metatype = true,
label = "Apache Sling JCR Tenant Provider",
- description = "Service responsible for providing Tenants")
+ description = "Service responsible for providing Tenants",
+ immediate = true)
@Service
@Properties(value = {
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Apache Sling JCR Tenant Provider")
})
+@Reference(
+ name = "tenantSetup",
+ referenceInterface = TenantCustomizer.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC)
public class TenantProviderImpl implements TenantProvider {
/**
* Root path for tenant
@@ -77,6 +93,9 @@ public class TenantProviderImpl implements TenantProvider {
private static final String[] DEFAULT_PATH_MATCHER = {};
+ private SortedMap<Comparable<Object>, TenantCustomizer> registeredTenantHandlers = new TreeMap<Comparable<Object>, TenantCustomizer>(
+ Collections.reverseOrder());
+
@Property(
value = {},
unbounded = PropertyUnbounded.ARRAY,
@@ -95,10 +114,15 @@ public class TenantProviderImpl implements TenantProvider {
private TenantAdapterFactory adapterFactory;
+ private WebConsolePlugin plugin;
+
+ private BundleContext bundleContext;
+
@Activate
private void activate(final BundleContext bundleContext, final Map<String, Object> properties) {
this.tenantRootPath = PropertiesUtil.toString(properties.get(TENANT_ROOT), JCR_TENANT_ROOT);
this.pathMatchers = PropertiesUtil.toStringArray(properties.get(TENANT_PATH_MATCHER), DEFAULT_PATH_MATCHER);
+ this.bundleContext = bundleContext;
this.pathPatterns.clear();
for (String matcherStr : this.pathMatchers) {
@@ -106,6 +130,7 @@ public class TenantProviderImpl implements TenantProvider {
}
this.adapterFactory = new TenantAdapterFactory(bundleContext, this);
+ this.plugin = new WebConsolePlugin(bundleContext, this);
}
@Deactivate
@@ -114,6 +139,23 @@ public class TenantProviderImpl implements TenantProvider {
this.adapterFactory.dispose();
this.adapterFactory = null;
}
+
+ if (this.plugin != null) {
+ this.plugin.dispose();
+ this.plugin = null;
+ }
+ }
+
+ private synchronized void bindTenantSetup(TenantCustomizer action, Map<String, Object> config) {
+ registeredTenantHandlers.put(ServiceUtil.getComparableForServiceRanking(config), action);
+ }
+
+ private synchronized void unbindTenantSetup(TenantCustomizer action, Map<String, Object> config) {
+ registeredTenantHandlers.remove(ServiceUtil.getComparableForServiceRanking(config));
+ }
+
+ private synchronized Collection<TenantCustomizer> getTenantHandlers() {
+ return registeredTenantHandlers.values();
}
public Tenant getTenant(String tenantId) {
@@ -145,13 +187,15 @@ public class TenantProviderImpl implements TenantProvider {
try {
Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
- List<Tenant> tenantList = new ArrayList<Tenant>();
- Iterator<Resource> tenantResourceList = tenantRootRes.listChildren();
- while (tenantResourceList.hasNext()) {
- Resource tenantRes = tenantResourceList.next();
- tenantList.add(new TenantImpl(tenantRes));
+ if (tenantRootRes != null) {
+ List<Tenant> tenantList = new ArrayList<Tenant>();
+ Iterator<Resource> tenantResourceList = tenantRootRes.listChildren();
+ while (tenantResourceList.hasNext()) {
+ Resource tenantRes = tenantResourceList.next();
+ tenantList.add(new TenantImpl(tenantRes));
+ }
+ return tenantList.iterator();
}
- return tenantList.iterator();
} finally {
adminResolver.close();
}
@@ -161,11 +205,27 @@ public class TenantProviderImpl implements TenantProvider {
return Collections.<Tenant> emptyList().iterator();
}
- public Tenant addTenant(String name, String tenantId) throws PersistenceException {
+ /**
+ * Creates a new tenant (not exposed as part of the api)
+ *
+ * @param name
+ * @param tenantId
+ * @param description
+ * @return
+ * @throws PersistenceException
+ */
+ public Tenant addTenant(String name, String tenantId, String description) throws PersistenceException {
final ResourceResolver adminResolver = getAdminResolver();
if (adminResolver != null) {
try {
Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
+ Session adminSession = adminResolver.adaptTo(Session.class);
+
+ if (tenantRootRes == null) {
+ // create the root path
+ JcrUtils.getOrCreateByPath(tenantRootPath, null, adminSession);
+ tenantRootRes = adminResolver.getResource(tenantRootPath);
+ }
// check if tenantId already exists
Resource child = tenantRootRes.getChild(tenantId);
@@ -177,8 +237,27 @@ public class TenantProviderImpl implements TenantProvider {
Node rootNode = tenantRootRes.adaptTo(Node.class);
Node tenantNode = rootNode.addNode(tenantId);
tenantNode.setProperty(Tenant.PROP_NAME, name);
- adminResolver.adaptTo(Session.class).save();
- return new TenantImpl(adminResolver.getResource(tenantNode.getPath()));
+ tenantNode.setProperty(Tenant.PROP_DESCRIPTION, description);
+
+ Resource resource = adminResolver.getResource(tenantNode.getPath());
+ Tenant tenant = new TenantImpl(resource);
+ PersistableValueMap tenantProps = resource.adaptTo(PersistableValueMap.class);
+ // call tenant setup handler
+ for (TenantCustomizer ts : getTenantHandlers()) {
+ Map<String, Object> props = ts.setup(tenant, adminResolver);
+ if (props != null) {
+ tenantProps.putAll(props);
+ }
+ }
+ // save the properties
+ tenantProps.save();
+
+ // save the session
+ adminSession.save();
+ // refersh tenant instance, as it copies property from
+ // resource
+ tenant = new TenantImpl(resource);
+ return tenant;
}
} catch (RepositoryException e) {
throw new PersistenceException("Unexpected RepositoryException while adding tenant", e);
@@ -190,6 +269,51 @@ public class TenantProviderImpl implements TenantProvider {
throw new PersistenceException("Cannot create the tenant");
}
+ /**
+ * Removes the tenant (not exposed as part of the api)
+ *
+ * @param tenantId tenant identifier
+ * @return
+ * @throws PersistenceException
+ */
+ public void removeTenant(String tenantId) throws PersistenceException {
+ final ResourceResolver adminResolver = getAdminResolver();
+ if (adminResolver != null) {
+ try {
+ Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
+
+ if (tenantRootRes == null) {
+ // if tenant home is null just return
+ return;
+ }
+
+ // check if tenantId already exists
+ Resource tenantRes = tenantRootRes.getChild(tenantId);
+
+ if (tenantRes != null) {
+ Node tenantNode = tenantRes.adaptTo(Node.class);
+ Tenant tenant = new TenantImpl(tenantRes);
+ // call tenant setup handler
+ for (TenantCustomizer ts : getTenantHandlers()) {
+ ts.remove(tenant, adminResolver);
+ }
+
+ tenantNode.remove();
+ adminResolver.adaptTo(Session.class).save();
+ return;
+ }
+ // if there was no tenant found, just return
+ return;
+ } catch (RepositoryException e) {
+ throw new PersistenceException("Unexpected RepositoryException while removing tenant", e);
+ } finally {
+ adminResolver.close();
+ }
+ }
+
+ throw new PersistenceException("Cannot remove the tenant");
+ }
+
public Iterator<Tenant> getTenants(String tenantFilter) {
if (StringUtils.isBlank(tenantFilter)) {
return null;
diff --git a/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java b/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java
new file mode 100644
index 0000000..f3eb471
--- /dev/null
+++ b/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java
@@ -0,0 +1,197 @@
+/*
+ * 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.tenant.internal.console;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.tenant.Tenant;
+import org.apache.sling.tenant.internal.TenantProviderImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * This is a webconsole plugin displaying the active queues, some statistics and
+ * the configurations.
+ */
+public class WebConsolePlugin extends HttpServlet {
+
+ private static final long serialVersionUID = -6983227434841706385L;
+
+ private static final String LABEL = "tenants";
+
+ private static final String TITLE = "Tenant Administration";
+
+ private static final String CATEGORY = "Sling";
+
+ /** tenant name parameter */
+ private static final String REQ_PRM_TENANT_NAME = "tenantName";
+
+ /** tenant id parameter */
+ private static final String REQ_PRM_TENANT_ID = "tenantId";
+
+ /** tenant description parameter */
+ private static final String REQ_PRM_TENANT_DESC = "tenantDesc";
+
+ private TenantProviderImpl tenantProvider;
+
+ private final ServiceRegistration<?> service;
+
+ /** Escape the output for html. */
+ private String escape(final String text) {
+ if (text == null) {
+ return "";
+ }
+ return text.replace("&", "&").replace("<", "<").replace(">", ">");
+ }
+
+ public WebConsolePlugin(final BundleContext bundleContext, final TenantProviderImpl tenantProvider) {
+ this.tenantProvider = tenantProvider;
+
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Tenant Management Console");
+ props.put("felix.webconsole.label", LABEL);
+ props.put("felix.webconsole.title", TITLE);
+ props.put("felix.webconsole.category", CATEGORY);
+ // props.put("felix.webconsole.configprinter.modes", new String[]{"zip",
+ // "txt"});
+
+ this.service = bundleContext.registerService(Servlet.class.getCanonicalName(), this, props);
+ }
+
+ public void dispose() {
+ if (this.service != null) {
+ this.service.unregister();
+ }
+ }
+
+ @Override
+ protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
+ String msg = null;
+ final String cmd = req.getParameter("action");
+ if ("create".equals(cmd)) {
+ try {
+ Tenant t = this.createTenant(req);
+ msg = String.format("Created Tenant %s (%s)", t.getName(), t.getDescription());
+ } catch (PersistenceException pe) {
+ msg = "Cannot create tenant: " + pe.getMessage();
+ }
+ } else if ("remove".equals(cmd)) {
+ this.removeTenant(req);
+ } else {
+ msg = "Unknown command";
+ }
+
+ final String path = LABEL;
+ final String redirectTo;
+ if (msg == null) {
+ redirectTo = path;
+ } else {
+ redirectTo = path + "?message=" + msg;
+ }
+
+ resp.sendRedirect(redirectTo);
+ }
+
+ private void removeTenant(HttpServletRequest request) throws PersistenceException {
+ String tenantId = request.getParameter(REQ_PRM_TENANT_ID);
+ tenantProvider.removeTenant(tenantId);
+ }
+
+ private void printForm(final PrintWriter pw, final Tenant t, final String buttonLabel, final String cmd) {
+ pw.printf("<button class='ui-state-default ui-corner-all' onclick='javascript:cmdsubmit(\"%s\", \"%s\");'>"
+ + "%s</button>", cmd, (t != null ? t.getId() : ""), buttonLabel);
+ }
+
+ private Tenant createTenant(HttpServletRequest request) throws PersistenceException {
+ String tenantName = request.getParameter(REQ_PRM_TENANT_NAME);
+ String tenantId = request.getParameter(REQ_PRM_TENANT_ID);
+ String tenantDesc = request.getParameter(REQ_PRM_TENANT_DESC);
+
+ return tenantProvider.addTenant(tenantName, tenantId, tenantDesc);
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
+ final PrintWriter pw = res.getWriter();
+
+ pw.println("<form method='POST' name='cmd'>" + "<input type='hidden' name='action' value=''/>"
+ + "<input type='hidden' name='tenantId' value=''/>" + "</form>");
+ pw.println("<script type='text/javascript'>");
+ pw.println("function cmdsubmit(action, tenantId) {" + " document.forms['cmd'].action.value = action;"
+ + " document.forms['cmd'].tenantId.value = tenantId;" + " document.forms['cmd'].submit();" + "} "
+ + "function createsubmit() {" + " document.forms['editorForm'].submit();" + "} " + "</script>");
+ pw.printf("<p class='statline ui-state-highlight'>Apache Sling Tenant Support</p>");
+
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+ pw.println("<span style='float: left; margin-left: 1em'>Add New Tenant </span>");
+ pw.println("<button class='ui-state-default ui-corner-all' onclick='javascript:createsubmit();'> Create </button></div></td></tr>");
+ pw.println("</div>");
+ pw.println("<table id='editortable' class='nicetable'><tbody>");
+
+ pw.println("<tr width='100%'><td colspan='2'><form id='editorForm' method='POST'>");
+ pw.println("<input name='action' type='hidden' value='create' class='ui-state-default ui-corner-all'>");
+ pw.println("<table border='0' width='100%'><tbody>");
+ pw.println("<tr><td style='width: 30%;'>Identifier</td><td>");
+ pw.println("<div><input name='tenantId' type='text' value=''></div>");
+ pw.println("</td></tr>");
+ pw.println("<tr><td style='width: 30%;'>Name</td><td>");
+ pw.println("<div><input name='tenantName' type='text' value=''></div>");
+ pw.println("</td></tr>");
+ pw.println("<tr><td style='width: 30%;'>Description</td><td>");
+ pw.println("<div><input name='tenantDesc' type='text' value=''></div>");
+ pw.println("</td></tr>");
+ pw.println("</tbody></table></form>");
+ pw.println("</tbody></table>");
+
+ Iterator<Tenant> tenants = this.tenantProvider.getTenants();
+ int count = 0;
+ while (tenants.hasNext()) {
+ count++;
+ Tenant tenant = tenants.next();
+ if (count == 1) {
+ pw.printf("<p class='statline ui-state-highlight'>Registered Tenants</p>");
+ }
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+ pw.printf("<span style='float: left; margin-left: 1em'>Tenant : %s </span>", escape(tenant.getName()));
+ this.printForm(pw, tenant, "Remove", "remove");
+ pw.println("</div>");
+ pw.println("<table class='nicetable'><tbody>");
+
+ pw.printf("<tr><td style='width: 30%%;'>Identifier</td><td>%s</td></tr>", escape(tenant.getId()));
+ pw.printf("<tr><td style='width: 30%%;'>Name</td><td>%s</td></tr>", escape(tenant.getName()));
+ pw.printf("<tr><td style='width: 30%%;'>Description</td><td>%s</td></tr>", escape(tenant.getDescription()));
+ pw.println("</tbody></table>");
+ }
+ // no existing tenants
+ if (count == 0) {
+ pw.printf("<p class='statline ui-state-highlight'>There are not registered tenants</p>");
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/tenant/spi/TenantCustomizer.java b/src/main/java/org/apache/sling/tenant/spi/TenantCustomizer.java
new file mode 100644
index 0000000..f25edf6
--- /dev/null
+++ b/src/main/java/org/apache/sling/tenant/spi/TenantCustomizer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.tenant.spi;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.tenant.Tenant;
+
+/**
+ * This is a service interface which services are called by the WebConsole
+ * plugin (or admin tool) to complete the Tenant setup.
+ */
+public interface TenantCustomizer {
+
+ /**
+ * Method called to create or update the given tenant. The method may return
+ * additional properties to be added to the Tenant's property list. The
+ * ResourceResolver allows for access to the persistence.
+ * <p>
+ * The {@code ResourceResolver.commit} method must not be called by this
+ * method.
+ * <p>
+ * This method is not expected to throw an exception. Any exception thrown
+ * is logged but otherwise ignored.
+ *
+ * @param tenant The {@link Tenant} to be configured by this call
+ * @param resolver The {@code ResourceResolver} providing access to the
+ * persistence for further setup. Note, that this
+ * {@code resolver} will have administrative privileges.
+ * @return Additional properties to be added to the tenant. These properties
+ * may later be accessed through the {@linkplain Tenant tenant's}
+ * property accessor methods. {@code null} or an empty map may be
+ * returned to not add properties.
+ */
+ public Map<String, Object> setup(Tenant tenant, ResourceResolver resolver);
+
+ /**
+ * Called to remove the setup for the given Tenant. This reverts all changes
+ * done by the #setup method. The ResourceResolver allows for access to the
+ * persistence.
+ * <p>
+ * The {@code ResourceResolver.commit} method must not be called by this
+ * method.
+ * <p>
+ * This method is not expected to throw an exception. Any exception thrown
+ * is logged but otherwise ignored.
+ *
+ * @param tenant The {@link Tenant} about to be removed
+ * @param resolver The {@code ResourceResolver} providing access to the
+ * persistence for further cleanup. Note, that this
+ * {@code resolver} will have administrative privileges.
+ */
+ public void remove(Tenant tenant, ResourceResolver resolver);
+}
diff --git a/src/main/java/org/apache/sling/tenant/spi/package-info.java b/src/main/java/org/apache/sling/tenant/spi/package-info.java
new file mode 100644
index 0000000..fa458c7
--- /dev/null
+++ b/src/main/java/org/apache/sling/tenant/spi/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0")
+package org.apache.sling.tenant.spi;
+
+import aQute.bnd.annotation.Version;
+
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.