You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2013/03/01 09:04:08 UTC

svn commit: r1451514 - in /sling/trunk/contrib/extensions/tenant: ./ src/main/java/org/apache/sling/tenant/ src/main/java/org/apache/sling/tenant/internal/ src/main/java/org/apache/sling/tenant/internal/console/

Author: fmeschbe
Date: Fri Mar  1 08:04:08 2013
New Revision: 1451514

URL: http://svn.apache.org/r1451514
Log:
SLING-2710 Define TenantManager API

- Add service interface as a provider type
  (not to be implemented by consumers)
- Implement TenantManager in the TenantProviderImpl
- Use latest Repository API to leverage repository CRUD
- Move tenant resolution for the AdapterFactory to the
  TenantAdapterFactory (just handing the configuration
  over on construction).

Added:
    sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/TenantManager.java
Modified:
    sling/trunk/contrib/extensions/tenant/pom.xml
    sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantAdapterFactory.java
    sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java
    sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java

Modified: sling/trunk/contrib/extensions/tenant/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tenant/pom.xml?rev=1451514&r1=1451513&r2=1451514&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tenant/pom.xml (original)
+++ sling/trunk/contrib/extensions/tenant/pom.xml Fri Mar  1 08:04:08 2013
@@ -82,7 +82,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.2.0</version>
+            <version>2.3.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -104,15 +104,9 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>commons-lang</groupId>
-            <artifactId>commons-lang</artifactId>
-            <version>2.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
-            <version>4.3.1</version>
+            <version>4.3.1</version><!--$NO-MVN-MAN-VER$-->
             <scope>provided</scope>
         </dependency>
         <dependency>

Added: sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/TenantManager.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/TenantManager.java?rev=1451514&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/TenantManager.java (added)
+++ sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/TenantManager.java Fri Mar  1 08:04:08 2013
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * The <code>TenantManager</code> service interface defines the API that
+ * administrative tools will use to created, update or remove Tenants.
+ * <p>
+ * The implementation will make use of
+ * {@link org.apache.sling.tenant.spi.TenantCustomizer} services to customize
+ * management of tenants.
+ * <p>
+ * Tenant properties can be created, modified, and removed with the
+ * {@link #setProperty(Tenant, String, Object)},
+ * {@link #setProperties(Tenant, Map)} and
+ * {@link #removeProperties(Tenant, String...)} methods. Please note that every
+ * call to any of these methods causes the
+ * {@link org.apache.sling.tenant.spi.TenantCustomizer} services to be called.
+ * To limit these calls for multiple changes the
+ * {@link #setProperties(Tenant, Map)} method should be called.
+ */
+@ProviderType
+public interface TenantManager {
+
+    /**
+     * Creates a new tenant with the given tenant ID and the initial set of
+     * properties.
+     * <p>
+     * After creating the tenant, the
+     * {@link org.apache.sling.tenant.spi.TenantCustomizer#setup(Tenant, org.apache.sling.api.resource.ResourceResolver)}
+     * method is called to allow customizers to configure additional properties.
+     * <p>
+     * Before returning the newly created tenant object the data is persisted.
+     *
+     * @param tenantId The name of the new tenant. This name must not be
+     *            {@code null} or an empty string. It must also not be the same
+     *            name as that of an existing tenant.
+     * @param properties An optional map of initial properties. This may be
+     *            {@code null} to not preset any properties. It is recommended,
+     *            though, that this map contain at least the
+     *            {@link Tenant#PROP_NAME} and {@link Tenant#PROP_DESCRIPTION}
+     *            properties.
+     * @return The newly created {@link Tenant} instance.
+     * @throws NullPointerException if {@code tenantId} is {@code null}.
+     * @throws IllegalArgumentException if a tenant with the same
+     *             {@code tentantId} already exists.
+     */
+    Tenant create(String tenantId, Map<String, Object> properties);
+
+    /**
+     * Sets a single property of the tenant to a new value or removes the
+     * property if the value is {@code null}.
+     * <p>
+     * Before returning the
+     * {@link org.apache.sling.tenant.spi.TenantCustomizer#setup(Tenant, org.apache.sling.api.resource.ResourceResolver)}
+     * method is called to allow customizers to configure additional properties.
+     *
+     * @param tenant The tenant whose property is to be set or remove.
+     * @param name The name of the property to set or remove.
+     * @param value The new value of the property. If this value is {@code null}
+     *            the property is actually removed.
+     * @throws NullPointerException if {@code tenant} or {@code name} is
+     *             {@code null}.
+     */
+    void setProperty(Tenant tenant, String name, Object value);
+
+    /**
+     * Sets or removes multiple properties on the tenant.
+     * <p>
+     * Before returning the
+     * {@link org.apache.sling.tenant.spi.TenantCustomizer#setup(Tenant, org.apache.sling.api.resource.ResourceResolver)}
+     * method is called to allow customizers to configure additional properties.
+     *
+     * @param tenant The tenant whose properties are to be modified.
+     * @param properties The map of properties to set or remove. A property
+     *            whose value is {@code null} is removed from the tenant.
+     * @throws NullPointerException if {@code tenant} or {@code properties} is
+     *             {@code null}.
+     */
+    void setProperties(Tenant tenant, Map<String, Object> properties);
+
+    /**
+     * Removes one or more properties from the tenant.
+     * <p>
+     * Before returning the
+     * {@link org.apache.sling.tenant.spi.TenantCustomizer#setup(Tenant, org.apache.sling.api.resource.ResourceResolver)}
+     * method is called to allow customizers to configure additional properties
+     * unless the {@code properties} parameter is {@code null} or empty.
+     *
+     * @param tenant The tenant from which to remove properties.
+     * @param properties The list of properties to be removed. If this is
+     *            {@code null} or empty, nothing happens and the
+     *            {@link org.apache.sling.tenant.spi.TenantCustomizer} is not
+     *            called.
+     * @throws NullPointerException if {@code tenant} is {@code null}.
+     */
+    void removeProperties(Tenant tenant, String... propertyNames);
+
+    /**
+     * Removes the given tenant.
+     * <p>
+     * Before returning the
+     * {@link org.apache.sling.tenant.spi.TenantCustomizer#remove(Tenant, org.apache.sling.api.resource.ResourceResolver)}
+     * method is called to allow customizers to implement further cleanup upon
+     * tenant removal.
+     *
+     * @param tenant The tenant to remove.
+     * @throws NullPointerException if {@code tenant} is {@code null}
+     */
+    void remove(Tenant tenant);
+}

Modified: sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantAdapterFactory.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantAdapterFactory.java?rev=1451514&r1=1451513&r2=1451514&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantAdapterFactory.java (original)
+++ sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantAdapterFactory.java Fri Mar  1 08:04:08 2013
@@ -18,8 +18,12 @@
  */
 package org.apache.sling.tenant.internal;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.jcr.Session;
 
@@ -57,9 +61,16 @@ class TenantAdapterFactory implements Ad
 
 	private final ServiceRegistration<?> service;
 
-	TenantAdapterFactory(final BundleContext bundleContext, final TenantProviderImpl tenantProvider) {
+    private final List<Pattern> pathPatterns;
+
+	TenantAdapterFactory(final BundleContext bundleContext, final TenantProviderImpl tenantProvider, final String[] pathMatchers) {
 	    this.tenantProvider = tenantProvider;
 
+        this.pathPatterns = new ArrayList<Pattern>();
+        for (String matcherStr : pathMatchers) {
+            this.pathPatterns.add(Pattern.compile(matcherStr));
+        }
+
 	    Dictionary<String, Object> props = new Hashtable<String, Object>();
 	    props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling JCR Tenant Adapter");
 	    props.put(AdapterFactory.ADAPTER_CLASSES, new String[]{ TENANT_CLASS.getName() });
@@ -124,7 +135,7 @@ class TenantAdapterFactory implements Ad
 	private <AdapterType> AdapterType getAdapter(String path,
 			Class<AdapterType> type) {
 		if (type == TENANT_CLASS) {
-			Tenant tenant = tenantProvider.resolveTenantByPath(path);
+			Tenant tenant = resolveTenantByPath(path);
 
 			if (tenant != null) {
 				return (AdapterType) tenant;
@@ -135,4 +146,20 @@ class TenantAdapterFactory implements Ad
 		return null;
 	}
 
+    private Tenant resolveTenantByPath(String path) {
+        // find matching path identifier
+        for (Pattern pathPattern : pathPatterns) {
+            Matcher matcher = pathPattern.matcher(path);
+            if (matcher.find()) {
+                // assuming that first group is tenantId in the path, we can
+                // make group number configurable.
+                if (matcher.groupCount() >= 1) {
+                    String tenantId = matcher.group(1);
+                    return this.tenantProvider.getTenant(tenantId);
+                }
+            }
+        }
+        return null;
+    }
+
 }

Modified: sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java?rev=1451514&r1=1451513&r2=1451514&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java (original)
+++ sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/TenantProviderImpl.java Fri Mar  1 08:04:08 2013
@@ -21,19 +21,13 @@ package org.apache.sling.tenant.internal
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.lang.StringUtils;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -44,18 +38,17 @@ import org.apache.felix.scr.annotations.
 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.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 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.TenantManager;
 import org.apache.sling.tenant.TenantProvider;
 import org.apache.sling.tenant.internal.console.WebConsolePlugin;
 import org.apache.sling.tenant.spi.TenantCustomizer;
@@ -84,7 +77,7 @@ import org.slf4j.LoggerFactory;
         referenceInterface = TenantCustomizer.class,
         cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
         policy = ReferencePolicy.DYNAMIC)
-public class TenantProviderImpl implements TenantProvider {
+public class TenantProviderImpl implements TenantProvider, TenantManager {
 
     /** default log */
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -109,10 +102,6 @@ public class TenantProviderImpl implemen
             description = "Defines tenants path matcher i.e. /content/sample/([^/]+)/*, used while resolving path to tenant")
     private static final String TENANT_PATH_MATCHER = "tenant.path.matcher";
 
-    private String[] pathMatchers;
-
-    private List<Pattern> pathPatterns = new ArrayList<Pattern>();
-
     private String tenantRootPath = JCR_TENANT_ROOT;
 
     @Reference
@@ -122,20 +111,10 @@ public class TenantProviderImpl implemen
 
     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) {
-            this.pathPatterns.add(Pattern.compile(matcherStr));
-        }
-
-        this.adapterFactory = new TenantAdapterFactory(bundleContext, this);
+        this.adapterFactory = new TenantAdapterFactory(bundleContext, this, PropertiesUtil.toStringArray(properties.get(TENANT_PATH_MATCHER), DEFAULT_PATH_MATCHER));
         this.plugin = new WebConsolePlugin(bundleContext, this);
     }
 
@@ -152,10 +131,12 @@ public class TenantProviderImpl implemen
         }
     }
 
+    @SuppressWarnings("unused")
     private synchronized void bindTenantSetup(TenantCustomizer action, Map<String, Object> config) {
         registeredTenantHandlers.put(ServiceUtil.getComparableForServiceRanking(config), action);
     }
 
+    @SuppressWarnings("unused")
     private synchronized void unbindTenantSetup(TenantCustomizer action, Map<String, Object> config) {
         registeredTenantHandlers.remove(ServiceUtil.getComparableForServiceRanking(config));
     }
@@ -164,23 +145,14 @@ public class TenantProviderImpl implemen
         return registeredTenantHandlers.values();
     }
 
-    public Tenant getTenant(String tenantId) {
-        if (StringUtils.isBlank(tenantId)) {
-            return null;
-        }
-
-        final ResourceResolver adminResolver = getAdminResolver();
-        if (adminResolver != null) {
-            try {
-                Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
-
-                Resource tenantRes = tenantRootRes.getChild(tenantId);
-                if (tenantRes != null) {
-                    return new TenantImpl(tenantRes);
+    public Tenant getTenant(final String tenantId) {
+        if (tenantId != null && tenantId.length() > 0) {
+            return call(new ResourceResolverTask<Tenant>() {
+                public Tenant call(ResourceResolver resolver) {
+                    Resource tenantRes = getTenantResource(resolver, tenantId);
+                    return (tenantRes != null) ? new TenantImpl(tenantRes) : null;
                 }
-            } finally {
-                adminResolver.close();
-            }
+            });
         }
 
         // in case of some problem
@@ -188,208 +160,246 @@ public class TenantProviderImpl implemen
     }
 
     public Iterator<Tenant> getTenants() {
-        final ResourceResolver adminResolver = getAdminResolver();
-        if (adminResolver != null) {
+        return getTenants(null);
+    }
+
+    public Iterator<Tenant> getTenants(final String tenantFilter) {
+        final Filter filter;
+        if (tenantFilter != null && tenantFilter.length() > 0) {
             try {
-                Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
+                filter = FrameworkUtil.createFilter(tenantFilter);
+            } catch (InvalidSyntaxException e) {
+                throw new IllegalArgumentException(e.getMessage(), e);
+            }
+        } else {
+            filter = null;
+        }
+
+        Iterator<Tenant> result = call(new ResourceResolverTask<Iterator<Tenant>>() {
+            public Iterator<Tenant> call(ResourceResolver resolver) {
+                Resource tenantRootRes = resolver.getResource(tenantRootPath);
+
+                List<Tenant> tenantList = new ArrayList<Tenant>();
+                Iterator<Resource> tenantResourceList = tenantRootRes.listChildren();
+                while (tenantResourceList.hasNext()) {
+                    Resource tenantRes = tenantResourceList.next();
 
-                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));
+                    if (filter == null || filter.matches(ResourceUtil.getValueMap(tenantRes))) {
+                        TenantImpl tenant = new TenantImpl(tenantRes);
+                        tenantList.add(tenant);
                     }
-                    return tenantList.iterator();
                 }
-            } finally {
-                adminResolver.close();
+                return tenantList.iterator();
             }
+        });
+
+        if (result == null) {
+            // no filter or no resource resolver for calling
+            result = Collections.<Tenant> emptyList().iterator();
         }
 
-        // in case of some problem return an empty iterator
-        return Collections.<Tenant> emptyList().iterator();
+        return result;
     }
 
-    /**
-     * 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);
+    public Tenant create(final String tenantId, final Map<String, Object> properties) {
+        return call(new ResourceResolverTask<Tenant>() {
+            public Tenant call(ResourceResolver adminResolver) {
+                try {
+                    // create the tenant
+                    Resource tenantRes = createTenantResource(adminResolver, tenantId, properties);
+                    TenantImpl tenant = new TenantImpl(tenantRes);
+                    customizeTenant(tenantRes, tenant);
+                    adminResolver.commit();
 
-                if (tenantRootRes == null) {
-                    // create the root path
-                    JcrUtils.getOrCreateByPath(tenantRootPath, null, adminSession);
-                    tenantRootRes = adminResolver.getResource(tenantRootPath);
-                }
+                    // refresh tenant instance, as it copies property from
+                    // resource
+                    tenant.loadProperties(tenantRes);
 
-                // check if tenantId already exists
-                Resource child = tenantRootRes.getChild(tenantId);
+                    return tenant;
 
-                if (child != null) {
-                    throw new PersistenceException("Tenant already exists with Id " + tenantId);
+                } catch (PersistenceException e) {
+                    log.error("create: Failed creating Tenant {}", tenantId, e);
+                } finally {
+                    adminResolver.close();
                 }
 
-                // create the tenant
-                Node rootNode = tenantRootRes.adaptTo(Node.class);
-                Node tenantNode = rootNode.addNode(tenantId);
-                tenantNode.setProperty(Tenant.PROP_NAME, name);
-                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()) {
-                    try {
-                        Map<String, Object> props = ts.setup(tenant, adminResolver);
-                        if (props != null) {
-                            tenantProps.putAll(props);
+                // no new tenant in case of problems
+                return null;
+            }
+        });
+    }
+
+    public void remove(final Tenant tenant) {
+        call(new ResourceResolverTask<Void>() {
+            public Void call(ResourceResolver resolver) {
+                try {
+                    Resource tenantRes = getTenantResource(resolver, tenant.getId());
+                    if (tenantRes != null) {
+                        // call tenant setup handler
+                        for (TenantCustomizer ts : getTenantHandlers()) {
+                            try {
+                                ts.remove(tenant, resolver);
+                            } catch (Exception e) {
+                                log.info("removeTenant: Unexpected problem calling TenantCustomizer " + ts, e);
+                            }
                         }
-                    } catch (Exception e) {
-                        log.info("addTenant: Unexpected problem calling TenantCustomizer " + ts, e);
+
+                        resolver.delete(tenantRes);
+                        resolver.commit();
                     }
+                } catch (PersistenceException e) {
+                    log.error("remove({}): Cannot persist Tenant removal", tenant.getId(), e);
                 }
-                // 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);
-            } finally {
-                adminResolver.close();
+                return null;
             }
-        }
-
-        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;
+    public void setProperty(final Tenant tenant, final String name, final Object value) {
+        updateProperties(tenant, new PropertiesUpdater() {
+            public void update(ModifiableValueMap properties) {
+                if (value != null) {
+                    properties.put(name, value);
+                } else {
+                    properties.remove(name);
                 }
+            }
+        });
+    }
 
-                // 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()) {
-                        try {
-                            ts.remove(tenant, adminResolver);
-                        } catch (Exception e) {
-                            log.info("removeTenant: Unexpected problem calling TenantCustomizer " + ts, e);
-                        }
+    public void setProperties(final Tenant tenant, final Map<String, Object> properties) {
+        updateProperties(tenant, new PropertiesUpdater() {
+            public void update(ModifiableValueMap vm) {
+                for (Entry<String, Object> entry : properties.entrySet()) {
+                    if (entry.getValue() != null) {
+                        vm.put(entry.getKey(), entry.getValue());
+                    } else {
+                        vm.remove(entry.getKey());
                     }
-
-                    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 void removeProperties(final Tenant tenant, final String... propertyNames) {
+        updateProperties(tenant, new PropertiesUpdater() {
+            public void update(ModifiableValueMap properties) {
+                for (String name : propertyNames) {
+                    properties.remove(name);
+                }
+            }
+        });
     }
 
-    public Iterator<Tenant> getTenants(String tenantFilter) {
-        if (StringUtils.isBlank(tenantFilter)) {
-            return null;
+    @SuppressWarnings("serial")
+    private Resource createTenantResource(final ResourceResolver resolver, final String tenantId,
+            final Map<String, Object> properties) throws PersistenceException {
+
+        // check for duplicate first
+        if (getTenantResource(resolver, tenantId) != null) {
+            throw new PersistenceException("Tenant '" + tenantId + "' already exists");
         }
 
-        final ResourceResolver adminResolver = getAdminResolver();
-        if (adminResolver != null) {
-            try {
-                Resource tenantRootRes = adminResolver.getResource(tenantRootPath);
+        Resource tenantRoot = resolver.getResource(tenantRootPath);
 
-                List<Tenant> tenantList = new ArrayList<Tenant>();
-                Iterator<Resource> tenantResourceList = tenantRootRes.listChildren();
-                while (tenantResourceList.hasNext()) {
-                    Resource tenantRes = tenantResourceList.next();
-                    ValueMap vm = ResourceUtil.getValueMap(tenantRes);
+        if (tenantRoot == null) {
+            Resource current = resolver.getResource("/");
+            if (current == null) {
+                throw new PersistenceException("Cannot get root Resource");
+            }
 
-                    Filter filter = FrameworkUtil.createFilter(tenantFilter);
-                    if (filter.matches(vm)) {
-                        TenantImpl tenant = new TenantImpl(tenantRes);
-                        tenantList.add(tenant);
-                    }
+            String[] segments = this.tenantRootPath.split("/");
+            for (String segment : segments) {
+                Resource child = current.getChild(segment);
+                if (child == null) {
+                    child = resolver.create(current, segment, new HashMap<String, Object>() {
+                        {
+                            put("jcr:primaryType", "sling:Folder");
+                        }
+                    });
                 }
-                return tenantList.iterator();
-            } catch (InvalidSyntaxException e) {
-                throw new IllegalArgumentException(e.getMessage(), e);
-            } finally {
-                adminResolver.close();
             }
+
+            tenantRoot = current;
         }
 
-        // in case of some problem return an empty iterator
-        return Collections.<Tenant> emptyList().iterator();
+        return resolver.create(tenantRoot, tenantId, properties);
     }
 
-    /**
-     * Helper for the {@link JcrTenantAdapterFactory} to resolve any resource
-     * path to a tenant.
-     */
-    Tenant resolveTenantByPath(String path) {
-        // find matching path identifier
-        for (Pattern pathPattern : pathPatterns) {
-            Matcher matcher = pathPattern.matcher(path);
-            if (matcher.find()) {
-                // assuming that first group is tenantId in the path, we can
-                // make group number configurable.
-                if (matcher.groupCount() >= 1) {
-                    String tenantId = matcher.group(1);
-                    return getTenant(tenantId);
+    private Resource getTenantResource(final ResourceResolver resolver, final String tenantId) {
+        return resolver.getResource(tenantRootPath + "/" + tenantId);
+    }
+
+    private void customizeTenant(final Resource tenantRes, final Tenant tenant) {
+
+        // call tenant setup handler
+        Map<String, Object> tenantProps = tenantRes.adaptTo(ModifiableValueMap.class);
+        if (tenantProps == null) {
+            log.warn(
+                "create({}): Cannot get ModifiableValueMap for new tenant; will not store changed properties of TenantCustomizers",
+                tenant.getId());
+            tenantProps = new HashMap<String, Object>();
+        }
+
+        for (TenantCustomizer ts : getTenantHandlers()) {
+            try {
+                Map<String, Object> props = ts.setup(tenant, tenantRes.getResourceResolver());
+                if (props != null) {
+                    tenantProps.putAll(props);
                 }
+            } catch (Exception e) {
+                log.info("addTenant: Unexpected problem calling TenantCustomizer " + ts, e);
             }
         }
-        return null;
     }
 
-    private ResourceResolver getAdminResolver() {
+    private <T> T call(ResourceResolverTask<T> task) {
+        ResourceResolver resolver = null;
+        T result = null;
+
         try {
-            return factory.getAdministrativeResourceResolver(null);
+            resolver = factory.getAdministrativeResourceResolver(null);
+            result = task.call(resolver);
         } catch (LoginException le) {
             // unexpected, thus ignore
+        } finally {
+            if (resolver != null) {
+                resolver.close();
+            }
         }
 
-        return null;
+        return result;
+    }
+
+    private void updateProperties(final Tenant tenant, final PropertiesUpdater updater) {
+        call(new ResourceResolverTask<Void>() {
+            public Void call(ResourceResolver resolver) {
+                try {
+                    Resource tenantRes = getTenantResource(resolver, tenant.getId());
+                    if (tenantRes != null) {
+                        updater.update(tenantRes.adaptTo(ModifiableValueMap.class));
+                        customizeTenant(tenantRes, tenant);
+                        resolver.commit();
+
+                        if (tenant instanceof TenantImpl) {
+                            ((TenantImpl) tenant).loadProperties(tenantRes);
+                        }
+                    }
+                } catch (PersistenceException pe) {
+                    log.error("setProperty({}): Cannot persist Tenant removal", tenant.getId(), pe);
+                }
+
+                return null;
+            }
+        });
+    }
+
+    private static interface ResourceResolverTask<T> {
+        T call(ResourceResolver resolver);
+    }
+
+    private static interface PropertiesUpdater {
+        void update(ModifiableValueMap properties);
     }
 }

Modified: sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java?rev=1451514&r1=1451513&r2=1451514&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java (original)
+++ sling/trunk/contrib/extensions/tenant/src/main/java/org/apache/sling/tenant/internal/console/WebConsolePlugin.java Fri Mar  1 08:04:08 2013
@@ -21,6 +21,7 @@ package org.apache.sling.tenant.internal
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
 
@@ -29,7 +30,6 @@ 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;
@@ -96,11 +96,11 @@ public class WebConsolePlugin extends Ht
         String msg = null;
         final String cmd = req.getParameter("action");
         if ("create".equals(cmd)) {
-            try {
-                Tenant t = this.createTenant(req);
+            Tenant t = this.createTenant(req);
+            if (t != null) {
                 msg = String.format("Created Tenant %s (%s)", t.getName(), t.getDescription());
-            } catch (PersistenceException pe) {
-                msg = "Cannot create tenant: " + pe.getMessage();
+            } else {
+                msg = "Cannot create tenant";
             }
         } else if ("remove".equals(cmd)) {
             this.removeTenant(req);
@@ -119,9 +119,13 @@ public class WebConsolePlugin extends Ht
         resp.sendRedirect(redirectTo);
     }
 
-    private void removeTenant(HttpServletRequest request) throws PersistenceException {
-        String tenantId = request.getParameter(REQ_PRM_TENANT_ID);
-        tenantProvider.removeTenant(tenantId);
+    private void removeTenant(HttpServletRequest request) {
+        final String tenantId = request.getParameter(REQ_PRM_TENANT_ID);
+        final Tenant tenant = this.tenantProvider.getTenant(tenantId);
+
+        if (tenant != null) {
+            this.tenantProvider.remove(tenant);
+        }
     }
 
     private void printForm(final PrintWriter pw, final Tenant t, final String buttonLabel, final String cmd) {
@@ -129,12 +133,18 @@ public class WebConsolePlugin extends Ht
             + "%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);
+    @SuppressWarnings("serial")
+    private Tenant createTenant(HttpServletRequest request) {
+        final String tenantName = request.getParameter(REQ_PRM_TENANT_NAME);
+        final String tenantId = request.getParameter(REQ_PRM_TENANT_ID);
+        final String tenantDesc = request.getParameter(REQ_PRM_TENANT_DESC);
+
+        return tenantProvider.create(tenantId, new HashMap<String, Object>() {
+            {
+                put(Tenant.PROP_NAME, tenantName);
+                put(Tenant.PROP_DESCRIPTION, tenantDesc);
+            }
+        });
     }
 
     @Override