You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2006/05/05 19:49:08 UTC

svn commit: r400121 [1/4] - in /incubator/roller/trunk: ./ nbproject/ src/org/apache/roller/presentation/webservices/ src/org/apache/roller/util/ src/org/apache/roller/webservices/ src/org/apache/roller/webservices/adminapi/ src/org/apache/roller/webse...

Author: snoopdave
Date: Fri May  5 10:49:04 2006
New Revision: 400121

URL: http://svn.apache.org/viewcvs?rev=400121&view=rev
Log:
Moving webservices under own package, they're not tied to presentation

Added:
    incubator/roller/trunk/src/org/apache/roller/util/WSSEUtilities.java
    incubator/roller/trunk/src/org/apache/roller/webservices/
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AppUrl.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AtomAdminServlet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Authenticator.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BadRequestException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BasicAuthenticator.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Handler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/HandlerException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/InternalException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/IntrospectionHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotAllowedException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotFoundException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerMemberHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerUserHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerWeblogHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/UnauthorizedException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/WSSEAuthenticator.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/Entry.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/EntrySet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/LocaleString.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/MemberEntry.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/MemberEntrySet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/MissingElementException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/Service.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/UnexpectedRootElementException.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/UserEntry.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/UserEntrySet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/WeblogEntry.java
    incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/sdk/WeblogEntrySet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/AtomHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/AtomService.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/AtomServlet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/PubControlModule.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/PubControlModuleGenerator.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/PubControlModuleImpl.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/PubControlModuleParser.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/package.html
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/BaseAPIHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/BloggerAPIHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/RollerXMLRPCServlet.java
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/package.html
Removed:
    incubator/roller/trunk/src/org/apache/roller/presentation/webservices/
Modified:
    incubator/roller/trunk/build.xml
    incubator/roller/trunk/nbproject/project.xml
    incubator/roller/trunk/tests/org/apache/roller/presentation/xmlrpc/RollerXmlRpcServerTest.java

Modified: incubator/roller/trunk/build.xml
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/build.xml?rev=400121&r1=400120&r2=400121&view=diff
==============================================================================
--- incubator/roller/trunk/build.xml (original)
+++ incubator/roller/trunk/build.xml Fri May  5 10:49:04 2006
@@ -289,7 +289,7 @@
     </echo>
 
     <javac debug="${build.debug}" destdir="${build.compile_beans}"
-        excludes="org/apache/roller/presentation/**" >
+        excludes="org/apache/roller/presentation/**, org/apache/roller/webservices/**" >
         <src path="${basedir}/src" />
         <src path="${build.generated}/src.business"/>
         &custom-src-beans;
@@ -347,7 +347,7 @@
     <mkdir dir="${build.compile_web}"/>
     <antcall target="prepare.resin" />
     <javac debug="${build.debug}" destdir="${build.compile_web}"
-        includes="org/apache/roller/presentation/**, org/apache/roller/util/rome/**" >
+        includes="org/apache/roller/presentation/**, org/apache/roller/webservices/**, org/apache/roller/util/rome/**" >
         <src path="./src"  />
         <src path="${build.generated}/src.presentation"/>
         &custom-src-web;

Modified: incubator/roller/trunk/nbproject/project.xml
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/nbproject/project.xml?rev=400121&r1=400120&r2=400121&view=diff
==============================================================================
--- incubator/roller/trunk/nbproject/project.xml (original)
+++ incubator/roller/trunk/nbproject/project.xml Fri May  5 10:49:04 2006
@@ -63,6 +63,11 @@
                     <type>java</type>
                     <location>sandbox/atomadminprotocol/src</location>
                 </source-folder>
+                <source-folder>
+                    <label>sandbox/jdobackend/src</label>
+                    <type>java</type>
+                    <location>sandbox/jdobackend/src</location>
+                </source-folder>
             </folders>
             <ide-actions>
                 <action name="build">
@@ -154,6 +159,10 @@
                         <label>sandbox/atomadminprotocol/src</label>
                         <location>sandbox/atomadminprotocol/src</location>
                     </source-folder>
+                    <source-folder style="packages">
+                        <label>sandbox/jdobackend/src</label>
+                        <location>sandbox/jdobackend/src</location>
+                    </source-folder>
                     <source-file>
                         <location>build.xml</location>
                     </source-file>
@@ -180,6 +189,7 @@
                 <package-root>sandbox/atomprotocol/src</package-root>
                 <package-root>sandbox/atomprotocol/tests</package-root>
                 <package-root>sandbox/atomadminprotocol/src</package-root>
+                <package-root>sandbox/jdobackend/src</package-root>
                 <classpath mode="compile">tools/buildtime/junit-3.8.1.jar:tools/lib/activation.jar:tools/lib/commons-betwixt-1.0-beta-1.jar:tools/lib/commons-httpclient-2.0.2.jar:tools/lib/concurrent-1.3.2.jar:tools/lib/ekitapplet.jar:tools/lib/jazzy-core.jar:tools/lib/log4j-1.2.4.jar:tools/lib/lucene-1.4.3.jar:tools/lib/mail.jar:tools/lib/mm.mysql-2.0.14-bin.jar:tools/lib/taglibs-string.jar:tools/lib/velocity-1.4.jar:tools/lib/velocity-dep-1.4.jar:tools/lib/velocity-tools-1.1.jar:tools/lib/xmlrpc-1.2-b1.jar:tools/struts-1.2.4/lib/antlr.jar:tools/struts-1.2.4/lib/commons-beanutils.jar:tools/struts-1.2.4/lib/commons-collections.jar:tools/struts-1.2.4/lib/commons-digester.jar:tools/struts-1.2.4/lib/commons-fileupload.jar:tools/struts-1.2.4/lib/commons-lang-2.0.jar:tools/struts-1.2.4/lib/commons-logging.jar:tools/struts-1.2.4/lib/commons-validator.jar:tools/struts-1.2.4/lib/jakarta-oro.jar:tools/struts-1.2.4/lib/struts-el.jar:tools/struts-1.2.4/lib/struts.jar:tools/buildtime/mo
 ckrunner-0.35/lib/mockrunner-servlet.jar:tools/buildtime/mockrunner-0.35/lib/mockrunner-struts.jar:tools/buildtime/mockrunner-0.35/lib/mockrunner.jar:tools/buildtime/mockrunner-0.35/lib/nekohtml.jar:tools/standard-1.0.3/lib/jaxen-full.jar:tools/standard-1.0.3/lib/jstl.jar:tools/standard-1.0.3/lib/standard.jar:tools/buildtime/tomcat-5.0.28/servlet-api.jar:tools/buildtime/tomcat-5.0.28/jsp-api.jar:sandbox/atomprotocol/lib/rome-0.8.jar:sandbox/atomprotocol/lib/rome-fetcher-0.8.jar:tools/lib/jdom.jar:contrib/lib/ecs.jar:contrib/lib/jrcs-diff.jar:contrib/lib/JSPWiki.jar:contrib/lib/jython.jar:contrib/lib/oscache.jar:contrib/lib/radeox.jar:contrib/lib/textile4j-1.20.jar:tools/spring-1.2/acegi-security-0.9.0.jar:tools/spring-1.2/spring.jar:tools/lib/rome-0.8.jar:tools/lib/rome-fetcher-0.8.jar:tools/lib/commons-codec-1.3.jar:tools/hibernate-3.1/hibernate3.jar</classpath>
                 <source-level>1.4</source-level>
             </compilation-unit>

Added: incubator/roller/trunk/src/org/apache/roller/util/WSSEUtilities.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/util/WSSEUtilities.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/util/WSSEUtilities.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/util/WSSEUtilities.java Fri May  5 10:49:04 2006
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2005, Dave Johnson
+ * 
+ * Licensed 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.roller.util;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * Utilties to support WSSE authentication.
+ * @author Dave Johnson
+ */
+public class WSSEUtilities {
+    public static synchronized String generateDigest(
+            byte[] nonce, byte[] created, byte[] password) {
+        String result = null;
+        try {
+            MessageDigest digester = MessageDigest.getInstance("SHA");
+            digester.reset();
+            digester.update(nonce);
+            digester.update(created);
+            digester.update(password);
+            byte[] digest = digester.digest();
+            result = new String(base64Encode(digest));
+        }
+        catch (NoSuchAlgorithmException e) {
+            result = null;
+        }
+        return result;
+    }
+    public static byte[] base64Decode(String value) throws IOException {
+        return Base64.decodeBase64(value.getBytes("UTF-8"));
+    }
+    public static String base64Encode(byte[] value) {
+        return new String(Base64.encodeBase64(value));
+    }
+    public static String generateWSSEHeader(String userName, String password) 
+    throws UnsupportedEncodingException {  
+       
+        byte[] nonceBytes = Long.toString(new Date().getTime()).getBytes();
+        String nonce = new String(WSSEUtilities.base64Encode(nonceBytes));
+        
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        String created = sdf.format(new Date());
+        
+        String digest = WSSEUtilities.generateDigest(
+                nonceBytes, created.getBytes("UTF-8"), password.getBytes("UTF-8"));
+        
+        StringBuffer header = new StringBuffer("UsernameToken Username=\"");
+        header.append(userName);
+        header.append("\", ");
+        header.append("PasswordDigest=\"");
+        header.append(digest);
+        header.append("\", ");
+        header.append("Nonce=\"");
+        header.append(nonce);
+        header.append("\", ");
+        header.append("Created=\"");
+        header.append(created);
+        header.append("\"");
+        return header.toString();
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AppUrl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AppUrl.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AppUrl.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AppUrl.java Fri May  5 10:49:04 2006
@@ -0,0 +1,92 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+/**
+ * This class generates Atom Publishing Protocol (APP) URls.
+ */
+public class AppUrl {
+    private static final String ENDPOINT = "/app";
+    private static Pattern ID_PATTERN = Pattern.compile("^http://.*/(.*)/(?:entries|resources)$");
+    private static Pattern ENDPOINT_PATTERN = Pattern.compile("^(http://.*)/.*/(?:entries|resources)$");
+    
+    private URL entryUrl;
+    private URL resourceUrl;
+    private String handle;
+    
+    public AppUrl(String urlPrefix, String handle) throws MalformedURLException {
+        entryUrl = new URL(urlPrefix + ENDPOINT + "/" + handle + "/entries");
+        resourceUrl = new URL(urlPrefix + ENDPOINT + "/" + handle + "/resources");        
+    }    
+
+    public AppUrl(URL url) throws MalformedURLException {
+        handle = parseHandle(url);
+        URL endpoint = parseEndpoint(url);
+        
+        entryUrl = new URL(endpoint + "/" + handle + "/entries");
+        resourceUrl = new URL(endpoint + "/" + handle + "/resources");        
+    }    
+    
+    private String parseHandle(URL url) {
+        String urlString = url.toString();
+        String handle = null;
+        
+        Matcher m = ID_PATTERN.matcher(urlString);
+        
+        if (m.matches()) {
+            handle = m.group(1);
+        }
+        
+        return handle;
+    }
+    
+    private URL parseEndpoint(URL url) throws MalformedURLException {
+        String urlString = url.toString();
+        String endpointString = null;
+        
+        Matcher m = ENDPOINT_PATTERN.matcher(urlString);
+        
+        if (m.matches()) {
+            endpointString = m.group(1);
+        }
+        
+        URL endpoint = null;
+        if (endpointString != null) {
+            endpoint = new URL(endpointString);
+        }
+        
+        return endpoint;
+    }
+    
+    
+    public URL getEntryUrl() {
+        return entryUrl;
+    }
+
+    public URL getResourceUrl() {
+        return resourceUrl;
+    }
+    
+    public String getHandle() {
+        return handle;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AtomAdminServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AtomAdminServlet.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AtomAdminServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/AtomAdminServlet.java Fri May  5 10:49:04 2006
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2005 David M Johnson (For RSS and Atom In Action)
+ *
+ * Licensed 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.roller.webservices.adminapi;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jdom.Document;
+import org.jdom.output.XMLOutputter;
+import org.jdom.output.Format;
+import org.apache.roller.webservices.adminapi.sdk.EntrySet;
+
+/**
+ * Atom Admin Servlet implements the Atom Admin endpoint.
+ * This servlet simply delegates work to a particular handler object.
+ *
+ * @web.servlet name="AtomAdminServlet"
+ * @web.servlet-mapping url-pattern="/aapp/*"
+ *
+ * @author jtb
+ */
+public class AtomAdminServlet extends HttpServlet {
+    private static Log logger = LogFactory.getFactory().getInstance(AtomAdminServlet.class);
+    
+    /**
+     * Handles an Atom GET by calling handler and writing results to response.
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        try {
+            Handler handler = Handler.getHandler(req);
+            String userName = handler.getUserName();
+            
+            EntrySet c = handler.processGet();
+            
+            res.setStatus(HttpServletResponse.SC_OK);            
+            res.setContentType("application/xml; charset=utf8");
+            String s = c.toString();
+            Writer writer = res.getWriter();
+            writer.write(s);            
+            writer.close();            
+        } catch (HandlerException he) {
+            res.sendError(he.getStatus(), he.getMessage());
+            he.printStackTrace(res.getWriter());
+            logger.error(he);
+        }
+    }
+    
+    /**
+     * Handles an Atom POST by calling handler to identify URI, reading/parsing
+     * data, calling handler and writing results to response.
+     */
+    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        try {
+            Handler handler = Handler.getHandler(req);
+            String userName = handler.getUserName();
+            
+            EntrySet c = handler.processPost(new InputStreamReader(req.getInputStream()));
+            
+            res.setStatus(HttpServletResponse.SC_CREATED);            
+            res.setContentType("application/xml; charset=utf8");
+            String s = c.toString();
+            Writer writer = res.getWriter();
+            writer.write(s);            
+            writer.close();            
+        } catch (HandlerException he) {
+            res.sendError(he.getStatus(), he.getMessage());
+            he.printStackTrace(res.getWriter());
+            logger.error(he);
+        }
+    }
+    
+    /**
+     * Handles an Atom PUT by calling handler to identify URI, reading/parsing
+     * data, calling handler and writing results to response.
+     */
+    protected void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        try {
+            Handler handler = Handler.getHandler(req);
+            String userName = handler.getUserName();
+            
+            EntrySet c = handler.processPut(new InputStreamReader(req.getInputStream()));
+            
+            res.setStatus(HttpServletResponse.SC_OK);            
+            res.setContentType("application/xml; charset=utf8");
+            String s = c.toString();
+            Writer writer = res.getWriter();
+            writer.write(s);            
+            writer.close();            
+        } catch (HandlerException he) {
+            res.sendError(he.getStatus(), he.getMessage());
+            he.printStackTrace(res.getWriter());
+            logger.error(he);
+        }
+    }
+    
+    /**
+     * Handle Atom Admin DELETE by calling appropriate handler.
+     */
+    protected void doDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        try {
+            Handler handler = Handler.getHandler(req);
+            String userName = handler.getUserName();
+            
+            EntrySet es = handler.processDelete();
+            
+            res.setStatus(HttpServletResponse.SC_OK);                        
+            res.setContentType("application/xml; charset=utf8");
+            String s = es.toString();
+            Writer writer = res.getWriter();
+            writer.write(s);            
+            writer.close();                        
+        } catch (HandlerException he) {
+            res.sendError(he.getStatus(), he.getMessage());
+            he.printStackTrace(res.getWriter());
+            logger.error(he);
+        }
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Authenticator.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Authenticator.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Authenticator.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Authenticator.java Fri May  5 10:49:04 2006
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2005 David M Johnson (For RSS and Atom In Action)
+ *
+ * Licensed 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.roller.webservices.adminapi;
+
+import javax.servlet.http.HttpServletRequest;
+import org.apache.roller.RollerException;
+import org.apache.roller.model.Roller;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.pojos.UserData;
+
+/**
+ * TODO
+ *
+ * @author jtb
+ */
+abstract class Authenticator {
+    private HttpServletRequest request;
+    private Roller             roller;
+    private String             userName;
+    
+    /** Creates a new instance of HttpBasicAuthenticator */
+    public Authenticator(HttpServletRequest req) {
+        setRequest(req);
+        setRoller(RollerFactory.getRoller());
+    }
+    
+    public abstract void authenticate() throws HandlerException;
+    
+    /** 
+     * This method should be called by extensions of this class within their
+     * implementation of authenticate().
+     */
+    protected void verifyUser() throws HandlerException {
+        try {
+            UserData user = getRoller().getUserManager().getUserByUsername(getUserName());
+            if (user != null && user.hasRole("admin") && user.getEnabled().booleanValue()) {
+                // success! no exception
+            } else {
+                throw new UnauthorizedException("ERROR: User must have the admin role to use the AAPP endpoint: " + getUserName());
+            }
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not verify user: " + getUserName(), re);
+        }
+    }
+    
+    public HttpServletRequest getRequest() {
+        return request;
+    }
+    
+    protected void setRequest(HttpServletRequest request) {
+        this.request = request;
+    }
+    
+    public String getUserName() {
+        return userName;
+    }
+    
+    protected void setUserName(String userId) {
+        this.userName = userId;
+    }
+    
+    protected Roller getRoller() {
+        return roller;
+    }
+    
+    protected void setRoller(Roller roller) {
+        this.roller = roller;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BadRequestException.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BadRequestException.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BadRequestException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BadRequestException.java Fri May  5 10:49:04 2006
@@ -0,0 +1,34 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Indicates to client that a bad (syntactically incorrect)
+ * request has been made.
+ */
+public class BadRequestException extends HandlerException { 
+    public BadRequestException(String msg) {
+        super(msg);
+    }    
+    
+    public int getStatus() {
+        return HttpServletResponse.SC_BAD_REQUEST;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BasicAuthenticator.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BasicAuthenticator.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BasicAuthenticator.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/BasicAuthenticator.java Fri May  5 10:49:04 2006
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2005 David M Johnson (For RSS and Atom In Action)
+ *
+ * Licensed 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.roller.webservices.adminapi;
+
+import java.util.StringTokenizer;
+import javax.servlet.http.HttpServletRequest;
+import com.sun.syndication.io.impl.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.pojos.UserData;
+
+/**
+ * This class implements HTTP basic authentication for roller.
+ *
+ * @author jtb
+ */
+class BasicAuthenticator extends Authenticator {
+    /** Creates a new instance of HttpBasicAuthenticator */
+    public BasicAuthenticator(HttpServletRequest req) {
+        super(req);
+    }
+    
+    public void authenticate() throws HandlerException {
+        setUserName(null);
+        
+        String userName = null;
+        String password = null;
+        String authHeader = getRequest().getHeader("Authorization");
+        if (authHeader == null) {
+            throw new UnauthorizedException("ERROR: Authorization header was not set");
+        }
+        
+        try {
+            StringTokenizer st = new StringTokenizer(authHeader);
+            if (st.hasMoreTokens()) {
+                String basic = st.nextToken();
+                if (basic.equalsIgnoreCase("Basic")) {
+                    String credentials = st.nextToken();
+                    String userPass = new String(Base64.decode(credentials));
+                    int p = userPass.indexOf(":");
+                    if (p != -1) {
+                        userName = userPass.substring(0, p);
+                        UserData user = getRoller().getUserManager().getUserByUsername(userName);
+                        if (user == null) {
+                            throw new UnauthorizedException("ERROR: User does not exist: " + userName);
+                        }
+                        String realpassword = user.getPassword();
+                        password = userPass.substring(p+1);
+                        if ((userName.trim().equals(user.getUserName())) && (password.trim().equals(realpassword))) {
+                            setUserName(userName);
+                        }
+                    }
+                }
+            }
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not authorize user: " + userName, re);
+        }
+        if (getUserName() == null) {
+            throw new UnauthorizedException("ERROR: User is not authorized to use the AAPP endpoint: " + userName);
+        }
+        
+        // make sure the user has the admin role
+        verifyUser();
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Handler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Handler.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Handler.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/Handler.java Fri May  5 10:49:04 2006
@@ -0,0 +1,188 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * Handler.java
+ *
+ * Created on January 17, 2006, 12:44 PM
+ */
+package org.apache.roller.webservices.adminapi;
+
+import java.io.Reader;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.model.Roller;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.presentation.RollerContext;
+import org.apache.roller.util.StringUtils;
+import org.apache.roller.webservices.adminapi.sdk.EntrySet;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * This class is the abstract notion of an AAPP request handler.
+ * It processes HTTP requests for each of the four HTTP verbs:
+ * GET, POST, PUT, DELETE, for a given weblog resource.
+ *
+ * @author jtb
+ */
+abstract class Handler {
+    protected static final String ENDPOINT = "/aapp";
+    
+    static class URI {
+        private static Pattern PATHINFO_PATTERN = Pattern.compile("^/(users|weblogs|members)(?:/(.*))?$");
+        
+        private String type;
+        private String entryId;
+        
+        public URI(HttpServletRequest request) throws BadRequestException {
+            String pi = request.getPathInfo();
+            
+            if (pi == null || pi.length() == 0) {
+                type = null;
+                entryId = null;
+            } else {
+                Matcher m = PATHINFO_PATTERN.matcher(pi);
+                if (!m.matches()) {
+                    throw new BadRequestException("ERROR: Invalid path info: " + pi);
+                }
+                
+                type = m.group(1);
+                entryId = m.group(2);
+            }
+        }
+        
+        public String getType() {
+            return type;
+        }
+        
+        public String getEntryId() {
+            return entryId;
+        }
+        
+        public boolean isIntrospection() {
+            return getEntryId() == null && type == null;
+        }
+        
+        public boolean isCollection() {
+            return getEntryId() == null && type != null;
+        }
+        
+        public boolean isEntry() {
+            return getEntryId() != null && type != null;
+        }
+    }
+    
+    protected static final Log logger = LogFactory.getFactory().getInstance(Handler.class);
+    
+    private HttpServletRequest request;
+    private Roller roller;
+    private RollerContext rollerContext;
+    private String userName;
+    private URI uri;
+    private String urlPrefix;
+    
+    /** Get a Handler object implementation based on the given request. */
+    public static Handler getHandler(HttpServletRequest req) throws HandlerException {
+        boolean enabled = RollerConfig.getBooleanProperty("webservices.adminprotocol.enabled");
+        if (!enabled) {
+            throw new NotAllowedException("ERROR: Admin protocol not enabled");
+        }
+        
+        URI uri = new URI(req);
+        Handler handler;
+        
+        if (uri.isIntrospection()) {
+            handler = new IntrospectionHandler(req);
+        } else if (uri.isCollection() || uri.isEntry()) {
+            String type = uri.getType();
+            if (type.equals(EntrySet.Types.WEBLOGS)) {
+                handler = new RollerWeblogHandler(req);
+            } else if (type.equals(EntrySet.Types.USERS)) {
+                handler = new RollerUserHandler(req);
+            } else if (type.equals(EntrySet.Types.MEMBERS)) {
+                handler = new RollerMemberHandler(req);
+            } else {
+                throw new BadRequestException("ERROR: Unknown type: " + uri.getType());
+            }
+        } else {
+            throw new BadRequestException("ERROR: Unknown URI type");
+        }
+        
+        return handler;
+    }
+    
+    public Handler(HttpServletRequest request) throws HandlerException {
+        this.request = request;
+        this.uri = new URI(request);
+        this.rollerContext = RollerContext.getRollerContext();
+        this.roller = RollerFactory.getRoller();
+        this.urlPrefix = getRollerContext().getAbsoluteContextUrl(getRequest()) + ENDPOINT;
+        
+        // TODO: decide what to do about authentication, is WSSE going to fly?
+        //Authenticator auth = new WSSEAuthenticator(request);
+        Authenticator auth = new BasicAuthenticator(request);
+        auth.authenticate();        
+        setUserName(auth.getUserName());
+    }
+    
+    /**
+     * Get the authenticated user name.
+     * If this method returns null, then authentication has failed.
+     */
+    public String getUserName() {
+        return userName;
+    }
+    
+    private void setUserName(String userName) {
+        this.userName = userName;
+    }
+    
+    /** Process an HTTP GET request. */
+    public abstract EntrySet processGet() throws HandlerException;
+    /** Process an HTTP POST request. */
+    public abstract EntrySet processPost(Reader r) throws HandlerException;
+    /** Process an HTTP PUT request. */
+    public abstract EntrySet processPut(Reader r) throws HandlerException;
+    /** Process an HTTP DELETE request. */
+    public abstract EntrySet processDelete() throws HandlerException;
+    
+    protected URI getUri() {
+        return uri;
+    }
+    
+    protected HttpServletRequest getRequest() {
+        return request;
+    }
+    
+    protected RollerContext getRollerContext() {
+        return rollerContext;
+    }
+    
+    protected Roller getRoller() {
+        return roller;
+    }
+    
+    protected String getUrlPrefix() {
+        return urlPrefix;
+    }
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/HandlerException.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/HandlerException.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/HandlerException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/HandlerException.java Fri May  5 10:49:04 2006
@@ -0,0 +1,41 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+/**
+ * Abstract base class for all handler exceptions.
+ *
+ * Subclasses of this class allow handler implementations to indicate to
+ * callers a particular HTTP error type, while still providing
+ * a textual description of the problem.
+ * 
+ * Callers may use the 
+ * <code>getStatus()</code> method to discover the HTTP status
+ * code that should be returned to the client.
+ */
+public abstract class HandlerException extends Exception { 
+    public HandlerException(String msg) {
+        super(msg);
+    }    
+
+    public HandlerException(String msg, Throwable t) {
+        super(msg);
+    }    
+    
+    public abstract int getStatus();
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/InternalException.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/InternalException.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/InternalException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/InternalException.java Fri May  5 10:49:04 2006
@@ -0,0 +1,34 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Indicates to client that an internal error occured when processing
+ * the request.
+ */
+public class InternalException extends HandlerException { 
+    public InternalException(String msg, Throwable t) {
+        super(msg, t);
+    }    
+    
+    public int getStatus() {
+        return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/IntrospectionHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/IntrospectionHandler.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/IntrospectionHandler.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/IntrospectionHandler.java Fri May  5 10:49:04 2006
@@ -0,0 +1,99 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * IntrospectionHandler.java
+ *
+ * Created on January 17, 2006, 12:44 PM
+ */
+package org.apache.roller.webservices.adminapi;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.roller.webservices.adminapi.sdk.Entry;
+import org.apache.roller.webservices.adminapi.sdk.EntrySet;
+import org.apache.roller.webservices.adminapi.sdk.Service;
+
+/**
+ * This class handles requests for the AAPP introspection document.
+ * It only processes HTTP GET requests.
+ *
+ * @author jtb
+ */
+class IntrospectionHandler extends Handler {   
+    public IntrospectionHandler(HttpServletRequest request) throws HandlerException {
+        super(request);
+    }
+    
+    public EntrySet processGet() throws HandlerException {
+        if (getUri().isIntrospection()) {
+            return getIntrospection(getRequest());
+        } else {
+            throw new BadRequestException("ERROR: Unknown GET URI type");
+        }
+    }
+    
+    public EntrySet processPost(Reader r) {
+        throw new UnsupportedOperationException("ERROR: POST not supported in this handler");
+    }
+    
+    public EntrySet processPut(Reader r) {
+        throw new UnsupportedOperationException("ERROR: PUT not supported in this handler");
+    }
+    
+    public EntrySet processDelete() {
+        throw new UnsupportedOperationException("ERROR: DELETE not supported in this handler");
+    }
+    
+    private Service getIntrospection(HttpServletRequest req) {
+        String href = getUrlPrefix();
+        Service service = new Service(href);
+        
+        Service.Workspace workspace = new Service.Workspace();
+        workspace.setTitle("Workspace: Collections for administration");
+        workspace.setHref(service.getHref());
+        service.setEntries(new Entry[] { workspace });
+        
+        List workspaceCollections = new ArrayList();
+        
+        Service.Workspace.Collection weblogCol = new Service.Workspace.Collection();
+        weblogCol.setTitle("Collection: Weblog administration entries");
+        weblogCol.setMemberType(org.apache.roller.webservices.adminapi.sdk.Entry.Types.WEBLOG);
+        weblogCol.setHref(service.getHref() + "/" + org.apache.roller.webservices.adminapi.sdk.EntrySet.Types.WEBLOGS);
+        workspaceCollections.add(weblogCol);
+        
+        Service.Workspace.Collection userCol = new Service.Workspace.Collection();
+        userCol.setTitle("Collection: User administration entries");
+        userCol.setMemberType("user");
+        userCol.setHref(service.getHref() + "/" + org.apache.roller.webservices.adminapi.sdk.EntrySet.Types.USERS);
+        workspaceCollections.add(userCol);
+        
+        Service.Workspace.Collection memberCol = new Service.Workspace.Collection();
+        memberCol.setTitle("Collection: Member administration entries");
+        memberCol.setMemberType("member");
+        memberCol.setHref(service.getHref() + "/" + org.apache.roller.webservices.adminapi.sdk.EntrySet.Types.MEMBERS);
+        workspaceCollections.add(memberCol);
+        
+        workspace.setEntries((Entry[])workspaceCollections.toArray(new Entry[0]));
+        
+        return service;
+    }
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotAllowedException.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotAllowedException.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotAllowedException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotAllowedException.java Fri May  5 10:49:04 2006
@@ -0,0 +1,34 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Indicates to client that they are not allows to perform the requested
+ * operation on the requested resource.
+ */
+public class NotAllowedException extends HandlerException { 
+    public NotAllowedException(String msg) {
+        super(msg);
+    }    
+    
+    public int getStatus() {
+        return HttpServletResponse.SC_METHOD_NOT_ALLOWED;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotFoundException.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotFoundException.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotFoundException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/NotFoundException.java Fri May  5 10:49:04 2006
@@ -0,0 +1,34 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.webservices.adminapi;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Indicates to client that the requested resource was not found
+ * on the server.
+ */
+public class NotFoundException extends HandlerException { 
+    public NotFoundException(String msg) {
+        super(msg);
+    }    
+    
+    public int getStatus() {
+        return HttpServletResponse.SC_NOT_FOUND;
+    }
+}

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerMemberHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerMemberHandler.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerMemberHandler.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerMemberHandler.java Fri May  5 10:49:04 2006
@@ -0,0 +1,478 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * RollerMemberHandler.java
+ *
+ * Created on January 17, 2006, 12:44 PM
+ */
+package org.apache.roller.webservices.adminapi;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import org.jdom.Document;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.apache.roller.RollerException;
+import org.apache.roller.model.UserManager;
+import org.apache.roller.pojos.PermissionsData;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.util.cache.CacheManager;
+import org.apache.roller.webservices.adminapi.sdk.Entry;
+import org.apache.roller.webservices.adminapi.sdk.EntrySet;
+import org.apache.roller.webservices.adminapi.sdk.MemberEntry;
+import org.apache.roller.webservices.adminapi.sdk.MemberEntrySet;
+import org.apache.roller.webservices.adminapi.sdk.MissingElementException;
+import org.apache.roller.webservices.adminapi.sdk.UnexpectedRootElementException;
+
+/**
+ * This class handles requests concerning Roller weblog membership (groups).
+ */
+class RollerMemberHandler extends Handler {
+    static class MemberURI extends URI {
+        private String username;
+        private String handle;
+        
+        public MemberURI(HttpServletRequest req) throws BadRequestException {
+            super(req);
+            String entryId = getEntryId();
+            if (entryId == null) {
+                username = null;
+                handle = null;
+            } else {
+                String[] entryIds = entryId.split("/");
+                if (entryIds == null || entryIds.length == 0) {
+                    throw new BadRequestException("ERROR: Invalid path info: " + req.getPathInfo()); 
+                }
+                handle = entryIds[0];
+                if (entryIds.length > 1) {
+                    username = entryIds[1];
+                }
+            }
+        }
+        
+        public boolean hasUsername() {
+            return getUsername() != null;
+        }
+        
+        public String getUsername() {
+            return username;
+        }
+        
+        private void setUsername(String username) {
+            this.username = username;
+        }
+        
+        public String getHandle() {
+            return handle;
+        }
+        
+        private void setHandle(String handle) {
+            this.handle = handle;
+        }
+    }
+    
+    private URI memberUri;
+    
+    public RollerMemberHandler(HttpServletRequest request) throws HandlerException {
+        super(request);
+        memberUri = new MemberURI(request);
+    }
+    
+    protected URI getUri() {
+        return memberUri;
+    }
+    
+    public EntrySet processGet() throws HandlerException {
+        if (getUri().isCollection()) {
+            return getCollection();
+        } else if (getUri().isEntry()) {
+            return getEntry();
+        } else {
+            throw new BadRequestException("ERROR: Unknown GET URI type");
+        }
+    }
+    
+    public EntrySet processPost(Reader r) throws HandlerException {
+        if (getUri().isCollection()) {
+            return postCollection(r);
+        } else {
+            throw new BadRequestException("ERROR: Unknown POST URI type");
+        }
+    }
+    
+    public EntrySet processPut(Reader r) throws HandlerException {
+        if (getUri().isCollection()) {
+            return putCollection(r);
+        } else if (getUri().isEntry()) {
+            return putEntry(r);
+        } else {
+            throw new BadRequestException("ERROR: Unknown PUT URI type");
+        }
+    }
+    
+    public EntrySet processDelete() throws HandlerException {
+        if (getUri().isEntry()) {
+            return deleteEntry();
+        } else {
+            throw new BadRequestException("ERROR: Unknown DELETE URI type");
+        }
+    }
+    
+    private EntrySet getCollection() throws HandlerException {
+        // get all permissions: for all users, for all websites
+        try {
+            List users = getRoller().getUserManager().getUsers();
+            List perms = new ArrayList();
+            for (Iterator i = users.iterator(); i.hasNext(); ) {
+                UserData user = (UserData)i.next();
+                List permissions = getRoller().getUserManager().getAllPermissions(user);
+                for (Iterator j = permissions.iterator(); j.hasNext(); ) {
+                    PermissionsData pd = (PermissionsData)j.next();
+                    perms.add(pd);
+                }
+            }
+            EntrySet es = toMemberEntrySet((PermissionsData[])perms.toArray(new PermissionsData[0]));
+            return es;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not get member collection", re);
+        }
+    }
+    
+    private EntrySet getEntry() throws HandlerException {
+        MemberURI muri = (MemberURI)getUri();
+        String handle = muri.getHandle();
+        String username = muri.getUsername();
+        
+        try {
+            List perms;
+            if (username == null) {
+                //get all entries for the given website handle
+                WebsiteData wd = getRoller().getUserManager().getWebsiteByHandle(handle);
+                if (wd == null) {
+                    throw new NotFoundException("ERROR: Unknown weblog handle: " + handle);
+                }
+                perms = getRoller().getUserManager().getAllPermissions(wd);
+            } else {
+                //get all entries for the given website handle & username
+                WebsiteData wd = getRoller().getUserManager().getWebsiteByHandle(handle);
+                if (wd == null) {
+                    throw new NotFoundException("ERROR: Unknown weblog handle: " + handle);
+                }
+                UserData ud = getRoller().getUserManager().getUserByUsername(username);
+                if (ud == null) {
+                    throw new NotFoundException("ERROR: Unknown user name: " + username);
+                }
+                PermissionsData pd = getRoller().getUserManager().getPermissions(wd, ud);
+                if (pd == null) {
+                    throw new NotFoundException("ERROR: Could not get permissions for user name: " + username + ", handle: " + handle);
+                }
+                perms = Collections.singletonList(pd);
+            }
+            
+            EntrySet es = toMemberEntrySet((PermissionsData[])perms.toArray(new PermissionsData[0]));
+            return es;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not get entry for handle: " + handle + ", username: " + username, re);
+        }
+    }
+    
+    private EntrySet postCollection(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new MemberEntrySet(collectionDoc, getUrlPrefix());
+            c = createMembers((MemberEntrySet)c);
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not post collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not post collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not post collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not post collection", uree);            
+        }       
+    }
+    
+    private EntrySet putCollection(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new MemberEntrySet(collectionDoc, getUrlPrefix());
+            c = updateMembers((MemberEntrySet)c);
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not post collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not post collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not post collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not post collection", uree);            
+        }       
+    }
+    
+    private EntrySet putEntry(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new MemberEntrySet(collectionDoc, getUrlPrefix());
+            
+            if (c.getEntries().length > 1) {
+                throw new BadRequestException("ERROR: Cannot put >1 entries per request");
+            }
+            if (c.getEntries().length > 0) {
+                // only one entry
+                // if there's zero entries, this is a nop
+                MemberEntry entry = (MemberEntry)c.getEntries()[0];
+                
+                MemberURI muri = (MemberURI)getUri();
+                
+                // get handle
+                // if there's no handle in the entry, set it
+                // if the entry and URI handles do not match, exception
+                String handle = muri.getHandle();
+                if (entry.getHandle() == null) {
+                    entry.setHandle(handle);
+                } else if (!entry.getHandle().equals(handle)) {
+                    throw new BadRequestException("ERROR: URI and entry handle do not match");
+                }
+                
+                // get username
+                // if there's no name in the entry or the URI, exception
+                // if there's no name in the entry, set it
+                // if the names in the entry and URI do not match, exception
+                String username = muri.getUsername();
+                if (entry.getName() == null) {
+                    if (username == null) {
+                        throw new BadRequestException("ERROR: No user name in URI or entry");
+                    }
+                    entry.setName(username);
+                } else if (username != null && !entry.getName().equals(username)) {
+                    throw new BadRequestException("ERROR: URI and entry user name do not match");
+                }
+                
+                updateMembers((MemberEntrySet)c);
+            }
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not post collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not post collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not post collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not post collection", uree);            
+        }        
+    }
+    
+    private MemberEntrySet createMembers(MemberEntrySet c) throws HandlerException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            
+            List permissionsDatas= new ArrayList();
+            for (int i = 0; i < c.getEntries().length; i++) {
+                MemberEntry entry = (MemberEntry)c.getEntries()[i];
+                PermissionsData pd = toPermissionsData(entry);
+                mgr.savePermissions(pd);
+                permissionsDatas.add(pd);
+            }
+            getRoller().flush();
+            return toMemberEntrySet((PermissionsData[])permissionsDatas.toArray(new PermissionsData[0]));
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not create members", re);
+        }
+    }
+    
+    private PermissionsData toPermissionsData(MemberEntry entry) throws HandlerException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            UserData ud = mgr.getUserByUsername(entry.getName());
+            WebsiteData wd = mgr.getWebsiteByHandle(entry.getHandle());
+            PermissionsData pd = new PermissionsData();
+            pd.setUser(ud);
+            pd.setWebsite(wd);
+            pd.setPermissionMask(stringToMask(entry.getPermission()));
+            pd.setPending(false);
+            
+            return pd;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not convert to permissions data", re);
+        }
+    }
+    
+    private PermissionsData getPermissionsData(MemberEntry entry) throws HandlerException {
+        return getPermissionsData(entry.getHandle(), entry.getName());
+    }
+    
+    private PermissionsData getPermissionsData(String handle, String username) throws HandlerException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            UserData ud = mgr.getUserByUsername(username);
+            WebsiteData wd = mgr.getWebsiteByHandle(handle);
+            PermissionsData pd = mgr.getPermissions(wd, ud);
+            
+            return pd;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not get permissions data", re);
+        }
+    }
+    
+    private MemberEntrySet updateMembers(MemberEntrySet c) throws HandlerException {
+        try {
+            List permissionsDatas= new ArrayList();
+            for (int i = 0; i < c.getEntries().length; i++) {
+                MemberEntry entry = (MemberEntry)c.getEntries()[i];
+                PermissionsData pd = getPermissionsData(entry);
+                if (pd == null) {
+                    throw new NotFoundException("ERROR: Permissions do not exist for weblog handle: " + entry.getHandle() + ", user name: " + entry.getName());
+                }
+                updatePermissionsData(pd, entry);
+                permissionsDatas.add(pd);
+            }
+            getRoller().flush();
+            return toMemberEntrySet((PermissionsData[])permissionsDatas.toArray(new PermissionsData[0]));            
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not update members", re);
+        }
+    }
+    
+    private void updatePermissionsData(PermissionsData pd, MemberEntry entry) throws HandlerException {
+        // only permission can be updated
+        
+        if (entry.getPermission() != null) {
+            pd.setPermissionMask(stringToMask(entry.getPermission()));
+        }
+		
+		// TODO: does the permissions data need to be invalidated?
+        
+        try {
+            UserData ud = getRoller().getUserManager().getUserByUsername(entry.getName());
+            CacheManager.invalidate(ud);
+            WebsiteData wd = getRoller().getUserManager().getWebsiteByHandle(entry.getHandle());
+            CacheManager.invalidate(wd);
+            
+            UserManager mgr = getRoller().getUserManager();
+            mgr.savePermissions(pd);
+            
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not update permissions data", re);
+        }
+        
+    }
+    
+    private EntrySet deleteEntry() throws HandlerException {
+        MemberURI muri = (MemberURI)getUri();
+        
+        String handle = muri.getHandle();
+        String username = muri.getUsername();
+        
+        if (username == null) {
+            throw new BadRequestException("ERROR: No user name supplied in URI");
+        }
+        
+        try {
+            PermissionsData pd = getPermissionsData(handle, username);
+            PermissionsData[] pds;
+            if (pd == null) {
+                throw new NotFoundException("ERROR: Permissions do not exist for weblog handle: " + handle + ", user name: " + username);
+            }
+            pds = new PermissionsData[] { pd };
+            
+            UserManager mgr = getRoller().getUserManager();
+            mgr.removePermissions(pd);
+            
+            UserData ud = getRoller().getUserManager().getUserByUsername(username);
+            CacheManager.invalidate(ud);
+            WebsiteData wd = getRoller().getUserManager().getWebsiteByHandle(handle);
+            CacheManager.invalidate(wd);
+            getRoller().flush();
+            
+            EntrySet es = toMemberEntrySet(pds);
+            return es;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not delete entry", re);
+        }
+    }
+    
+    private MemberEntry toMemberEntry(PermissionsData pd) {
+        if (pd == null) {
+            throw new NullPointerException("ERROR: Null permission data not allowed");
+        }
+        MemberEntry me = new MemberEntry(pd.getWebsite().getHandle(), pd.getUser().getUserName(), getUrlPrefix());
+        me.setPermission(maskToString(pd.getPermissionMask()));
+        
+        return me;
+    }
+    private MemberEntrySet toMemberEntrySet(PermissionsData[] pds) {
+        if (pds == null) {
+            throw new NullPointerException("ERROR: Null permission data not allowed");
+        }
+        
+        List entries = new ArrayList();
+        for (int i = 0; i < pds.length; i++) {
+            PermissionsData pd = pds[i];
+            Entry entry = toMemberEntry(pd);
+            entries.add(entry);
+        }
+        MemberEntrySet mes = new MemberEntrySet(getUrlPrefix());
+        mes.setEntries((Entry[])entries.toArray(new Entry[0]));
+        
+        return mes;
+    }
+    
+    private static String maskToString(short mask) {
+        if (mask == PermissionsData.ADMIN) {
+            return MemberEntry.Permissions.ADMIN;
+        }
+        if (mask == PermissionsData.AUTHOR) {
+            return MemberEntry.Permissions.AUTHOR;
+        }
+        if (mask == PermissionsData.LIMITED) {
+            return MemberEntry.Permissions.LIMITED;
+        }
+        return null;
+    }
+    
+    
+    private static short stringToMask(String s) {
+        if (s == null) {
+            throw new NullPointerException("ERROR: Null string not allowed");
+        }
+        if (s.equalsIgnoreCase(MemberEntry.Permissions.ADMIN)) {
+            return PermissionsData.ADMIN;
+        }
+        if (s.equalsIgnoreCase(MemberEntry.Permissions.AUTHOR)) {
+            return PermissionsData.AUTHOR;
+        }
+        if (s.equalsIgnoreCase(MemberEntry.Permissions.LIMITED)) {
+            return PermissionsData.LIMITED;
+        }
+        return 0;
+    }
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerUserHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerUserHandler.java?rev=400121&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerUserHandler.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/adminapi/RollerUserHandler.java Fri May  5 10:49:04 2006
@@ -0,0 +1,336 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * RollerUserHandler.java
+ *
+ * Created on January 17, 2006, 12:44 PM
+ */
+package org.apache.roller.webservices.adminapi;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Date;
+import javax.servlet.http.HttpServletRequest;
+import org.jdom.Document;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.apache.roller.RollerException;
+import org.apache.roller.model.UserManager;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.util.cache.CacheManager;
+import org.apache.roller.webservices.adminapi.sdk.Entry;
+import org.apache.roller.webservices.adminapi.sdk.EntrySet;
+import org.apache.roller.webservices.adminapi.sdk.MissingElementException;
+import org.apache.roller.webservices.adminapi.sdk.UnexpectedRootElementException;
+import org.apache.roller.webservices.adminapi.sdk.UserEntry;
+import org.apache.roller.webservices.adminapi.sdk.UserEntrySet;
+
+/**
+ * This class handles request concerning Roller users.
+ *
+ * @author jtb
+ */
+class RollerUserHandler extends Handler {
+    public RollerUserHandler(HttpServletRequest request) throws HandlerException {
+        super(request);
+    }
+    
+    public EntrySet processGet() throws HandlerException {
+        if (getUri().isCollection()) {
+            return getCollection();
+        } else if (getUri().isEntry()) {
+            return getEntry();
+        } else {
+            throw new BadRequestException("ERROR: Unknown GET URI type");
+        }
+    }
+    
+    public EntrySet processPost(Reader r) throws HandlerException {
+        if (getUri().isCollection()) {
+            return postCollection(r);
+        } else {
+            throw new BadRequestException("ERROR: Unknown POST URI type");
+        }
+    }
+    
+    public EntrySet processPut(Reader r) throws HandlerException {
+        if (getUri().isCollection()) {
+            return putCollection(r);
+        } else if (getUri().isEntry()) {
+            return putEntry(r);
+        } else {
+            throw new BadRequestException("ERROR: Unknown PUT URI type");
+        }
+    }
+    
+    public EntrySet processDelete() throws HandlerException {
+        if (getUri().isEntry()) {
+            return deleteEntry();
+        } else {
+            throw new BadRequestException("ERROR: Unknown DELETE URI type");
+        }
+    }
+    
+    private EntrySet getCollection() throws HandlerException {
+        try {
+            List users = getRoller().getUserManager().getUsers();
+            if (users == null) {
+                users = java.util.Collections.EMPTY_LIST;
+            }
+            EntrySet es = toUserEntrySet((UserData[])users.toArray(new UserData[0]));
+            
+            return es;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not get user collection", re);
+        }
+    }
+    
+    private EntrySet getEntry() throws HandlerException {
+        try {
+            UserData ud = getRoller().getUserManager().getUserByUsername(getUri().getEntryId());
+            if (ud == null) {
+                throw new NotFoundException("ERROR: Unknown user: " + getUri().getEntryId());
+            }
+            UserData[] uds = new UserData[] { ud };
+            
+            EntrySet c = toUserEntrySet(uds);
+            return c;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not get user collection", re);
+        }
+    }
+    
+    private EntrySet postCollection(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new UserEntrySet(collectionDoc, getUrlPrefix());
+            c = createUsers((UserEntrySet)c);
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not post collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not post collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not post collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not post collection", uree);
+        }
+    }
+    
+    private EntrySet putCollection(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new UserEntrySet(collectionDoc, getUrlPrefix());
+            c = updateUsers((UserEntrySet)c);
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not put collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not put collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not put collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not put collection", uree);
+        }
+    }
+    
+    private EntrySet putEntry(Reader r) throws HandlerException {
+        try {
+            SAXBuilder builder = new SAXBuilder();
+            Document collectionDoc = builder.build(r);
+            EntrySet c = new UserEntrySet(collectionDoc, getUrlPrefix());
+            
+            if (c.getEntries().length > 1) {
+                throw new BadRequestException("ERROR: Cannot put >1 entries per request");
+            }
+            if (c.getEntries().length > 0) {
+                UserEntry entry = (UserEntry)c.getEntries()[0];
+                if (entry.getName() != null && !entry.getName().equals(getUri().getEntryId())) {
+                    throw new BadRequestException("ERROR: Content name does not match URI name");
+                }
+                entry.setName(getUri().getEntryId());
+                updateUsers((UserEntrySet)c);
+            }
+            
+            return c;
+        } catch (JDOMException je) {
+            throw new InternalException("ERROR: Could not post collection", je);
+        } catch (IOException ioe) {
+            throw new InternalException("ERROR: Could not post collection", ioe);
+        } catch (MissingElementException mee) {
+            throw new InternalException("ERROR: Could not post collection", mee);
+        } catch (UnexpectedRootElementException uree) {
+            throw new InternalException("ERROR: Could not post collection", uree);
+        }
+    }
+    
+    private UserEntrySet createUsers(UserEntrySet c) throws HandlerException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            
+            List userDatas = new ArrayList();
+            for (int i = 0; i < c.getEntries().length; i++) {
+                UserEntry entry = (UserEntry)c.getEntries()[i];
+                if (entry.getDateCreated() == null) {
+                    // if no creation date supplied, add it
+                    entry.setDateCreated(new Date());
+                }
+                UserData ud = toUserData(entry);
+                mgr.addUser(ud);
+                userDatas.add(ud);
+            }
+            getRoller().flush();
+            return toUserEntrySet((UserData[])userDatas.toArray(new UserData[0]));
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not create users: " + c, re);
+        }
+    }
+    
+    private UserEntrySet updateUsers(UserEntrySet c) throws NotFoundException, InternalException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            
+            List userDatas = new ArrayList();
+            for (int i = 0; i < c.getEntries().length; i++) {
+                UserEntry entry = (UserEntry)c.getEntries()[i];
+                UserData ud = mgr.getUserByUsername(entry.getName());
+                if (ud == null) {
+                    throw new NotFoundException("ERROR: Uknown user: " + entry.getName());
+                }
+                updateUserData(ud, entry);
+                
+                mgr.saveUser(ud);
+                CacheManager.invalidate(ud);
+                userDatas.add(ud);
+            }
+            getRoller().flush();
+            return toUserEntrySet((UserData[])userDatas.toArray(new UserData[0]));
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not update users: " + c, re);
+        }
+    }
+    
+    private void updateUserData(UserData ud, UserEntry entry) {
+        // user name cannot be updated
+        
+        if (entry.getFullName() != null) {
+            ud.setFullName(entry.getFullName());
+        }
+        if (entry.getPassword() != null) {
+            ud.setPassword(entry.getPassword());
+        }
+        if (entry.getLocale() != null) {
+            ud.setLocale(entry.getLocale().toString());
+        }
+        if (entry.getTimezone() != null) {
+            ud.setTimeZone(entry.getTimezone().getID());
+        }
+        if (entry.getEmailAddress() != null) {
+            ud.setEmailAddress(entry.getEmailAddress());
+        }
+    }
+    
+    private EntrySet deleteEntry() throws HandlerException {
+        try {
+            UserManager mgr = getRoller().getUserManager();
+            UserData ud = mgr.getUserByUsername(getUri().getEntryId());
+            
+            if (ud == null) {
+                throw new NotFoundException("ERROR: Uknown user: " + getUri().getEntryId());
+            }
+            // don't allow deletion of the currently authenticated user
+            if (ud.getUserName().equals(getUserName())) {
+                throw new NotAllowedException("ERROR: Can't delete authenticated user: " + getUserName());
+            }
+            
+            UserData[] uds = new UserData[] { ud };
+            mgr.removeUser(ud);
+            
+            CacheManager.invalidate(ud);
+            getRoller().flush();
+            EntrySet es = toUserEntrySet(uds);
+            return es;
+        } catch (RollerException re) {
+            throw new InternalException("ERROR: Could not delete entry: " + getUri().getEntryId(), re);
+        }
+    }
+    
+    private UserEntry toUserEntry(UserData ud) {
+        if (ud == null) {
+            throw new NullPointerException("ERROR: Null user data not allowed");
+        }
+        
+        // password field is not set
+        // we never return password field
+        
+        UserEntry ue = new UserEntry(ud.getUserName(), getUrlPrefix());
+        ue.setFullName(ud.getFullName());
+        ue.setLocale(ud.getLocale());
+        ue.setTimezone(ud.getTimeZone());
+        ue.setEmailAddress(ud.getEmailAddress());
+        ue.setDateCreated(ud.getDateCreated());
+        
+        return ue;
+    }
+    
+    private UserEntrySet toUserEntrySet(UserData[] uds) {
+        if (uds == null) {
+            throw new NullPointerException("ERROR: Null user data not allowed");
+        }
+        UserEntrySet ues = new UserEntrySet(getUrlPrefix());
+        
+        List entries = new ArrayList();
+        for (int i = 0; i < uds.length; i++) {
+            UserData ud = uds[i];
+            Entry entry = toUserEntry(ud);
+            entries.add(entry);
+        }
+        ues.setEntries((Entry[])entries.toArray(new Entry[0]));
+        
+        return ues;
+    }
+    
+    /** This object, as a Roller UserData object. */
+    public UserData toUserData(UserEntry ue) {
+        if (ue == null) {
+            throw new NullPointerException("ERROR: Null user entry not allowed");
+        }
+        
+        //
+        // if any of the entry fields are null, the set below amounts
+        // to a no-op.
+        //
+        UserData ud = new UserData();
+        ud.setUserName(ue.getName());
+        ud.setFullName(ue.getFullName());
+        ud.setPassword(ue.getPassword());
+        ud.setEmailAddress(ue.getEmailAddress());
+        ud.setLocale(ue.getLocale().toString());
+        ud.setTimeZone(ue.getTimezone().getID());
+        ud.setDateCreated(ue.getDateCreated());
+        
+        return ud;
+    }
+}
+