roller-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From snoopd...@apache.org
Subject svn commit: r395633 [1/2] - in /incubator/roller/trunk: custom/ sandbox/atomprotocol/ src/org/roller/presentation/webservices/ src/org/roller/presentation/webservices/atomprotocol/ src/org/roller/presentation/webservices/xmlrpc/ src/org/roller/presenta...
Date Thu, 20 Apr 2006 16:22:56 GMT
Author: snoopdave
Date: Thu Apr 20 09:22:49 2006
New Revision: 395633

URL: http://svn.apache.org/viewcvs?rev=395633&view=rev
Log:
Moving Atom Pub. Protocol implementation from sandbox to src, disabled by default with warning in roller.properties

Added:
    incubator/roller/trunk/src/org/roller/presentation/webservices/
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomHandler.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomService.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomServlet.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModule.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleGenerator.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleImpl.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleParser.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/RollerAtomHandler.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/WSSEUtilities.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/package.html
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/BaseAPIHandler.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/BloggerAPIHandler.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/MetaWeblogAPIHandler.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/RollerXMLRPCServlet.java
    incubator/roller/trunk/src/org/roller/presentation/webservices/xmlrpc/package.html
Removed:
    incubator/roller/trunk/sandbox/atomprotocol/
    incubator/roller/trunk/src/org/roller/presentation/xmlrpc/
Modified:
    incubator/roller/trunk/custom/custom-dbscripts.xmlf
    incubator/roller/trunk/custom/custom-gen-beans.xmlf
    incubator/roller/trunk/custom/custom-gen-web.xmlf
    incubator/roller/trunk/custom/custom-jars.xmlf
    incubator/roller/trunk/custom/custom-post-dbtest.xmlf
    incubator/roller/trunk/custom/custom-pre-dbtest.xmlf
    incubator/roller/trunk/custom/custom-src-beans.xmlf
    incubator/roller/trunk/custom/custom-src-web.xmlf
    incubator/roller/trunk/custom/custom-web.xmlf
    incubator/roller/trunk/tests/org/roller/business/WeblogManagerTest.java
    incubator/roller/trunk/web/WEB-INF/classes/roller.properties
    incubator/roller/trunk/web/WEB-INF/classes/rome.properties

Modified: incubator/roller/trunk/custom/custom-dbscripts.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-dbscripts.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-dbscripts.xmlf (original)
+++ incubator/roller/trunk/custom/custom-dbscripts.xmlf Thu Apr 20 09:22:49 2006
@@ -1,5 +1,5 @@
 
-<!-- Include SQL files in dbscript substitution
+<!-- Include SQL files in dbscript substitution, for example:
    <fileset dir="./sandbox/planetroller/metadata/database">     
       <include name="createdb-planet-raw.sql" />
       <include name="droptables-planet-raw.sql" />

Modified: incubator/roller/trunk/custom/custom-gen-beans.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-gen-beans.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-gen-beans.xmlf (original)
+++ incubator/roller/trunk/custom/custom-gen-beans.xmlf Thu Apr 20 09:22:49 2006
@@ -1,8 +1,8 @@
 
-<!--  Custom classes to include in Hibernate mapping file generation 
+<!--  Custom classes to include in Hibernate mapping file generation, for example:
 <fileset dir="${basedir}/custom/src" includes="**/*Data.java" /> 
 
 <fileset dir="${basedir}/sandbox/planetroller/src" 
     includes="**/*Data.java,**/*Assoc.java" />
 -->
-    
\ No newline at end of file
+    

Modified: incubator/roller/trunk/custom/custom-gen-web.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-gen-web.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-gen-web.xmlf (original)
+++ incubator/roller/trunk/custom/custom-gen-web.xmlf Thu Apr 20 09:22:49 2006
@@ -1,10 +1,9 @@
 
 <!--  Include experimental protocols in build
-<fileset dir="${basedir}/sandbox/atomprotocol/src" includes="**/*Servlet.java" />
 <fileset dir="${basedir}/sandbox/atomadminprotocol/src" includes="**/*Servlet.java" />
 -->
 
-<!--  Custom classes to include in Struts and web generation 
+<!--  Custom classes to include in Struts and web generation, for example:
 <fileset dir="${basedir}/custom/src" includes="**/*Data.java" />
 <fileset dir="${basedir}/sandbox/planetroller/src" includes="**/*Data.java" />
 <fileset dir="${basedir}/sandbox/planetroller/src" includes="**/*Action.java" />

Modified: incubator/roller/trunk/custom/custom-jars.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-jars.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-jars.xmlf (original)
+++ incubator/roller/trunk/custom/custom-jars.xmlf Thu Apr 20 09:22:49 2006
@@ -1,7 +1,4 @@
 <!-- 
-Custom jars to be added to both rollerbeans and rollerweb compiles 
--->
-
-<!-- Uncomment this to compile experimental JDO backend
+Custom jars to be added to both rollerbeans and rollerweb compiles, for example:
 <fileset dir="${basedir}/sandbox/jdobackend/lib" includes="*.jar" />
- -->
+-->

Modified: incubator/roller/trunk/custom/custom-post-dbtest.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-post-dbtest.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-post-dbtest.xmlf (original)
+++ incubator/roller/trunk/custom/custom-post-dbtest.xmlf Thu Apr 20 09:22:49 2006
@@ -1,5 +1,5 @@
 
-<!-- add post-test tasks to test-hibernate target of build.xml
+<!-- add post-test tasks to test-hibernate target of build.xml 
     <sql driver="org.hsqldb.jdbcDriver"
          url="jdbc:hsqldb:hsql://localhost:3219"
          userid="sa" password=""

Modified: incubator/roller/trunk/custom/custom-pre-dbtest.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-pre-dbtest.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-pre-dbtest.xmlf (original)
+++ incubator/roller/trunk/custom/custom-pre-dbtest.xmlf Thu Apr 20 09:22:49 2006
@@ -6,4 +6,4 @@
         src="./build/roller/WEB-INF/dbscripts/hsql/createdb-planet.sql"
         classpath="${ro.tools}/buildtime/hsqldb.jar" />
 -->
-        
\ No newline at end of file
+        

Modified: incubator/roller/trunk/custom/custom-src-beans.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-src-beans.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-src-beans.xmlf (original)
+++ incubator/roller/trunk/custom/custom-src-beans.xmlf Thu Apr 20 09:22:49 2006
@@ -5,4 +5,4 @@
 
 <!-- Uncomment this to compile experimental JDO backend
 <src path="${basedir}/sandbox/jdobackend/src" />
- -->
\ No newline at end of file
+ -->

Modified: incubator/roller/trunk/custom/custom-src-web.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-src-web.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-src-web.xmlf (original)
+++ incubator/roller/trunk/custom/custom-src-web.xmlf Thu Apr 20 09:22:49 2006
@@ -1,7 +1,6 @@
 
 <!--  Include experimental Atom Protocol impl. in build. 
 <src path="${basedir}/sandbox/atomadminprotocol/src" />
-<src path="${basedir}/sandbox/atomprotocol/src" />
 -->
 
 

Modified: incubator/roller/trunk/custom/custom-web.xmlf
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/custom/custom-web.xmlf?rev=395633&r1=395632&r2=395633&view=diff
==============================================================================
--- incubator/roller/trunk/custom/custom-web.xmlf (original)
+++ incubator/roller/trunk/custom/custom-web.xmlf Thu Apr 20 09:22:49 2006
@@ -1,13 +1,4 @@
 
-<!-- Need Atom PubControl Module for Atom protocol support
-<copy todir="${build.stage_web}/WEB-INF/classes" overwrite="true">
-   <fileset dir="${basedir}/sandbox/atomprotocol">
-      <include name="rome.properties" />
-   </fileset>
-</copy>
--->
-
-
 <!-- Example: copy custom files into the Roller web context
 <copy todir="${build.stage_web}" overwrite="true">
    <fileset dir="${basedir}/sandbox/planetroller/web">

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomHandler.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomHandler.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomHandler.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,109 @@
+/*
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import java.io.InputStream;
+import java.util.Date;
+
+import com.sun.syndication.feed.atom.Entry;
+import com.sun.syndication.feed.atom.Feed;
+
+/**
+ * Interface to be supported by an Atom server, expected lifetime: one request.
+ * AtomServlet calls this generic interface instead of Roller specific APIs. 
+ * <p />
+ * Designed to be Roller independent.
+ * 
+ * @author David M Johnson
+ */
+public interface AtomHandler
+{   
+    /** Get username of authenticated user */
+    public String getAuthenticatedUsername();    
+
+    /**
+     * Return introspection document
+     */
+    public AtomService getIntrospection(String[] pathInfo) throws Exception;
+    
+    /**
+     * Return collection
+     * @param pathInfo Used to determine which collection and range
+     */   
+    public Feed getCollection(String[] pathInfo) throws Exception;
+    
+    /**
+     * Create a new entry specified by pathInfo and posted entry.
+     * @param pathInfo Path info portion of URL
+     */
+    public Entry postEntry(String[] pathInfo, Entry entry) throws Exception;
+
+    /**
+     * Get entry specified by pathInfo.
+     * @param pathInfo Path info portion of URL
+     */
+    public Entry getEntry(String[] pathInfo) throws Exception;
+    
+    /**
+     * Update entry specified by pathInfo and posted entry.
+     * @param pathInfo Path info portion of URL
+     */
+    public Entry putEntry(String[] pathInfo, Entry entry) throws Exception;
+
+    /**
+     * Delete entry specified by pathInfo.
+     * @param pathInfo Path info portion of URL
+     */
+    public void deleteEntry(String[] pathInfo) throws Exception;
+    
+    /**
+     * Create a new resource specified by pathInfo, contentType, and binary data
+     * @param pathInfo Path info portion of URL
+     * @param contentType MIME type of uploaded content
+     * @param data Binary data representing uploaded content
+     */
+    public Entry postMedia(String[] pathInfo, String name, String contentType, 
+            InputStream is) throws Exception;
+
+    /**
+     * Update a resource.
+     * @param pathInfo Path info portion of URL
+     */
+    public Entry putMedia(String[] pathInfo, String contentType, 
+            InputStream is) throws Exception;
+    
+    /**
+     * Delete resource specified by pathInfo.
+     * @param pathInfo Path info portion of URL
+     */
+    public void deleteMedia(String[] pathInfo) throws Exception;
+    
+    /**
+     * Get resrouce entry.
+     * @param pathInfo Path info portion of URL
+     */
+    public Entry getMedia(String[] pathInfo) throws Exception;
+        
+    public boolean isIntrospectionURI(String [] pathInfo);  
+ 
+    public boolean isCollectionURI(String [] pathInfo);   
+    public boolean isEntryCollectionURI(String [] pathInfo);   
+    public boolean isMediaCollectionURI(String [] pathInfo);   
+    
+    public boolean isEntryURI(String[] pathInfo);
+    public boolean isMediaURI(String[] pathInfo);
+}
+

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomService.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomService.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomService.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomService.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,247 @@
+/*
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jdom.filter.Filter;
+
+/**
+ * This class models an Atom workspace.
+ * @author Dave Johnson
+ */
+/* Based on: draft-ietf-atompub-protocol-08.txt
+ * 
+ * appService =
+ *    element app:service {
+ *       appCommonAttributes,
+ *       ( appWorkspace+ 
+ *         & extensionElement* )
+ *    }
+ *
+ * Here is an example Atom workspace:
+ * 
+ * <?xml version="1.0" encoding='utf-8'?>
+ * <service xmlns="http://purl.org/atom/app#">
+ *   <workspace title="Main Site" >
+ *     <collection
+ *       title="My Blog Entries"
+ *       href="http://example.org/reilly/main" >
+ *       <member-type>entry</member-type>
+ *     </collection>
+ *     <collection
+ *       title="Pictures"
+ *       href="http://example.org/reilly/pic" >
+ *       <member-type>media</member-type>
+ *     </collection>
+ *   </workspace>
+ * </service>
+ */
+public class AtomService {
+    public static final Namespace ns =
+            Namespace.getNamespace("http://purl.org/atom/app#");
+    
+    private List workspaces = new ArrayList();
+    
+    public AtomService() {
+    }
+    
+    public void addWorkspace(AtomService.Workspace workspace) {
+        workspaces.add(workspace);
+    }
+    
+    public List getWorkspaces() {
+        return workspaces;
+    }
+    
+    public void setWorkspaces(List workspaces) {
+        this.workspaces = workspaces;
+    }
+    
+    /**
+     * This class models an Atom workspace.
+     *
+     * @author Dave Johnson
+     */
+    /*
+     * appWorkspace = element app:workspace { attribute title { text }, (
+     * appCollection* & anyElement* ) }
+     */
+    public static class Workspace {
+        private String title       = null;
+        private List   collections = new ArrayList();
+        
+        public Workspace() {
+        }
+        
+        public List getCollections() {
+            return collections;
+        }
+        
+        public void setCollections(List collections) {
+            this.collections = collections;
+        }
+        
+        public void addCollection(AtomService.Collection col) {
+            collections.add(col);
+        }
+        
+        /** Workspace must have a human readable title */
+        public String getTitle() {
+            return title;
+        }
+        
+        public void setTitle(String title) {
+            this.title = title;
+        }
+    }
+    
+    /**
+     * This class models an Atom workspace collection.    
+     * @author Dave Johnson
+     */
+    /* appCollection =
+     *       element app:collection {
+     *          appCommonAttributes,
+     *          attribute title { text },
+     *          attribute href { text },
+     *          ( appMemberType
+     *            & appListTemplate
+     *            & extensionElement* )
+     *       }
+     */
+    public static class Collection {
+        private String title = null;
+        private String memberType = "entry"; // or "media"
+        private String listTemplate = null;
+        private String href = null;
+        
+        public Collection() {
+        }
+        
+        /**
+         * Member type May be "entry" or "media".
+         */
+        public String getMemberType() {
+            return memberType;
+        }
+        
+        public void setMemberType(String memberType) {
+            this.memberType = memberType;
+        }
+        
+        /** The URI of the collection */
+        public String getHref() {
+            return href;
+        }
+        
+        public void setHref(String href) {
+            this.href = href;
+        }
+        
+        /** Must have human readable title */
+        public String getTitle() {
+            return title;
+        }
+        
+        public void setTitle(String title) {
+            this.title = title;
+        }
+    }
+    
+    /** Deserialize an Atom service XML document into an object */
+    public static AtomService documentToService(Document document) {
+        AtomService service = new AtomService();
+        Element root = document.getRootElement();
+        List spaces = root.getChildren("workspace", ns);
+        Iterator iter = spaces.iterator();
+        while (iter.hasNext()) {
+            Element e = (Element) iter.next();
+            service.addWorkspace(AtomService.elementToWorkspace(e));
+        }
+        return service;
+    }
+    
+    /** Serialize an AtomService object into an XML document */
+    public static Document serviceToDocument(AtomService service) {
+        Document doc = new Document();
+        Element root = new Element("service", ns);
+        doc.setRootElement(root);
+        Iterator iter = service.getWorkspaces().iterator();
+        while (iter.hasNext()) {
+            AtomService.Workspace space = (AtomService.Workspace) iter.next();
+            root.addContent(AtomService.workspaceToElement(space));
+        }
+        return doc;
+    }
+    
+    /** Deserialize a Atom workspace XML element into an object */
+    public static AtomService.Workspace elementToWorkspace(Element element) {
+        AtomService.Workspace space = new AtomService.Workspace();
+        space.setTitle(element.getAttribute("title").getValue());
+        List collections = element.getChildren("collection", ns);
+        Iterator iter = collections.iterator();
+        while (iter.hasNext()) {
+            Element e = (Element) iter.next();
+            space.addCollection(AtomService.elementToCollection(e));
+        }
+        return space;
+    }
+    
+    /** Serialize an AtomService.Workspace object into an XML element */
+    public static Element workspaceToElement(Workspace space) {
+        Namespace ns = Namespace.getNamespace("http://purl.org/atom/app#");
+        Element element = new Element("workspace", ns);
+        element.setAttribute("title", space.getTitle());
+        Iterator iter = space.getCollections().iterator();
+        while (iter.hasNext()) {
+            AtomService.Collection col = (AtomService.Collection) iter.next();
+            element.addContent(collectionToElement(col));
+        }
+        return element;
+    }
+    
+    /** Deserialize an Atom service collection XML element into an object */
+    public static AtomService.Collection elementToCollection(Element element) {
+        AtomService.Collection collection = new AtomService.Collection();
+        collection.setTitle(element.getAttribute("title").getValue());
+        collection.setHref(element.getAttribute("href").getValue());
+        Element memberType = element.getChild("member-type",  ns);
+        if (memberType != null) {
+            collection.setMemberType(memberType.getText());
+        }
+        return collection;
+    }
+    
+    /** Serialize an AtomService.Collection object into an XML element */
+    public static Element collectionToElement(AtomService.Collection collection) {
+        Namespace ns = Namespace.getNamespace("http://purl.org/atom/app#");
+        Element element = new Element("collection", ns);
+        element.setAttribute("title", collection.getTitle());
+        element.setAttribute("href", collection.getHref());
+        
+        Element memberType = new Element("member-type", ns);
+        memberType.setText(collection.getMemberType());
+        element.addContent(memberType);
+        
+        return element;
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomServlet.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomServlet.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/AtomServlet.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,375 @@
+/*
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+
+import com.sun.syndication.feed.atom.Entry;
+import com.sun.syndication.feed.atom.Feed;
+import com.sun.syndication.feed.atom.Link;
+import com.sun.syndication.io.FeedException;
+import com.sun.syndication.io.WireFeedInput;
+import com.sun.syndication.io.WireFeedOutput;
+import java.io.StringWriter;
+import org.jdom.Namespace;
+import org.roller.config.RollerConfig;
+
+/**
+ * Atom Servlet implements Atom by calling a Roller independent handler.
+ * @web.servlet name="AtomServlet"
+ * @web.servlet-mapping url-pattern="/app/*"
+ * @author David M Johnson
+ */
+public class AtomServlet extends HttpServlet {
+    public static final String FEED_TYPE = "atom_1.0";
+    
+    private static Log mLogger =
+            LogFactory.getFactory().getInstance(AtomServlet.class);
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * Create an Atom request handler.
+     * TODO: make AtomRequestHandler implementation configurable.
+     */
+    private AtomHandler createAtomRequestHandler(HttpServletRequest request) 
+    throws ServletException {
+        boolean enabled = RollerConfig.getBooleanProperty(
+            "webservices.atomprotocol.enable");
+        if (!enabled) {
+            throw new ServletException("ERROR: Atom protocol not enabled");
+        }
+        return new RollerAtomHandler(request);
+    }
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * Handles an Atom GET by calling handler and writing results to response.
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse res)
+    throws ServletException, IOException {
+        AtomHandler handler = createAtomRequestHandler(req);
+        String userName = handler.getAuthenticatedUsername();
+        if (userName != null) {
+            String[] pathInfo = getPathInfo(req);
+            try {
+                if (handler.isIntrospectionURI(pathInfo)) {
+                    // return an Atom Service document
+                    AtomService service = handler.getIntrospection(pathInfo);
+                    Document doc = AtomService.serviceToDocument(service);
+                    res.setContentType("application/xml; charset=utf8");
+                    Writer writer = res.getWriter();
+                    XMLOutputter outputter = new XMLOutputter();
+                    outputter.setFormat(Format.getPrettyFormat());
+                    outputter.output(doc, writer);
+                    writer.close();
+                    res.setStatus(HttpServletResponse.SC_OK);
+                } else if (handler.isCollectionURI(pathInfo)) {
+                    // return a collection
+                    Feed col = handler.getCollection(pathInfo);
+                    col.setFeedType(FEED_TYPE);
+                    WireFeedOutput wireFeedOutput = new WireFeedOutput();
+                    Document feedDoc = wireFeedOutput.outputJDom(col);
+                    res.setContentType("application/atom+xml; charset=utf8");
+                    Writer writer = res.getWriter();
+                    XMLOutputter outputter = new XMLOutputter();
+                    outputter.setFormat(Format.getPrettyFormat());
+                    outputter.output(feedDoc, writer);
+                    writer.close();
+                    res.setStatus(HttpServletResponse.SC_OK);
+                } else if (handler.isEntryURI(pathInfo)) {
+                    // return an entry
+                    Entry entry = handler.getEntry(pathInfo);
+                    if (entry != null) {
+                        Writer writer = res.getWriter();
+                        res.setContentType("application/atom+xml; charset=utf8");
+                        serializeEntry(entry, writer);
+                        writer.close();
+                    } else {
+                        res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                    }
+                } else if (handler.isMediaURI(pathInfo)) {
+                    // return a resource entry
+                    Entry entry = handler.getMedia(pathInfo);
+                    if (entry != null) {
+                        Writer writer = res.getWriter();
+                        res.setContentType("application/atom+xml; charset=utf8");
+                        serializeEntry(entry, writer);
+                        writer.close();
+                    } else {
+                        res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                    }
+                } else {
+                    res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                }
+            } catch (Exception e) {
+                res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                e.printStackTrace(res.getWriter());
+                mLogger.error(e);
+            }
+        } else {
+            res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
+            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * 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 {
+        AtomHandler handler = createAtomRequestHandler(req);
+        String userName = handler.getAuthenticatedUsername();
+        if (userName != null) {
+            String[] pathInfo = getPathInfo(req);
+            try {
+                if (handler.isEntryCollectionURI(pathInfo)) {
+                            
+                    // parse incoming entry
+                    Entry unsavedEntry = parseEntry(
+                        new InputStreamReader(req.getInputStream()));
+                     
+                    // call handler to post it
+                    Entry savedEntry = handler.postEntry(pathInfo, unsavedEntry);
+                    Iterator links = savedEntry.getAlternateLinks().iterator();
+                     
+                    // return alternate link as Location header
+                    while (links.hasNext()) {
+                        Link link = (Link) links.next();
+                        if (link.getRel().equals("alternate") || link.getRel() == null) {
+                            res.addHeader("Location", link.getHref());
+                            break;
+                        }
+                    }
+                    // write entry back out to response
+                    res.setStatus(HttpServletResponse.SC_CREATED);
+                    res.setContentType("application/atom+xml; charset=utf8");
+                    Writer writer = res.getWriter();
+                    serializeEntry(savedEntry, writer);
+                    writer.close(); 
+                } else if (handler.isMediaCollectionURI(pathInfo)) {
+                    // get incoming file name from HTTP header
+                    String name = req.getHeader("Name");
+                    
+                    // hand input stream of to hander to post file
+                    Entry resource = handler.postMedia(
+                        pathInfo, name, req.getContentType(), req.getInputStream());
+                    res.setStatus(HttpServletResponse.SC_CREATED);
+                    com.sun.syndication.feed.atom.Content content = 
+                        (com.sun.syndication.feed.atom.Content)resource.getContents().get(0);
+                    res.setHeader("Location", content.getSrc());
+                    Writer writer = res.getWriter();
+                    serializeEntry(resource, writer);
+                    writer.close(); 
+                } else {
+                    res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                }
+            } catch (Exception e) {
+                res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                e.printStackTrace(res.getWriter());
+                mLogger.error(e);
+            }
+        } else {
+            res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
+            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * 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 {
+        AtomHandler handler = createAtomRequestHandler(req);
+        String userName = handler.getAuthenticatedUsername();
+        if (userName != null) {
+            String[] pathInfo = getPathInfo(req);
+            try {
+                if (handler.isEntryURI(pathInfo)) {
+                    // parse incoming entry
+                    Entry unsavedEntry = parseEntry(
+                            new InputStreamReader(req.getInputStream()));
+                    
+                    // call handler to put entry
+                    Entry updatedEntry = handler.putEntry(pathInfo, unsavedEntry);
+                    
+                    // write entry back out to response
+                    res.setContentType("application/atom+xml; charset=utf8");
+                    Writer writer = res.getWriter();
+                    serializeEntry(updatedEntry, writer);
+                    res.setStatus(HttpServletResponse.SC_OK);
+                    writer.close();
+                } else if (handler.isMediaURI(pathInfo)) {
+                    // hand input stream to handler
+                    Entry updatedEntry = handler.putMedia(
+                        pathInfo, req.getContentType(), req.getInputStream());
+                                        
+                    // write entry back out to response
+                    res.setContentType("application/atom+xml; charset=utf8");
+                    Writer writer = res.getWriter();
+                    serializeEntry(updatedEntry, writer);
+                    writer.close();
+                    res.setStatus(HttpServletResponse.SC_OK);
+                } else {
+                    res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                }
+            } catch (Exception e) {
+                res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                e.printStackTrace(res.getWriter());
+                mLogger.error(e);
+            }
+        } else {
+            res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
+            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * Handle Atom DELETE by calling appropriate handler.
+     */
+    protected void doDelete(HttpServletRequest req, HttpServletResponse res)
+    throws ServletException, IOException {
+        AtomHandler handler = createAtomRequestHandler(req);
+        String userName = handler.getAuthenticatedUsername();
+        if (userName != null) {
+            String[] pathInfo = getPathInfo(req);
+            try {
+                if (handler.isEntryURI(pathInfo)) {
+                    handler.deleteEntry(pathInfo);
+                    res.setStatus(HttpServletResponse.SC_OK);
+                } else if (handler.isMediaURI(pathInfo)) {
+                    handler.deleteMedia(pathInfo);
+                    res.setStatus(HttpServletResponse.SC_OK);
+                } else {
+                    res.setStatus(HttpServletResponse.SC_NOT_FOUND);
+                }
+            } catch (Exception e) {
+                res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                e.printStackTrace(res.getWriter());
+                mLogger.error(e);
+            }
+        } else {
+            res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
+            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+    
+    //-----------------------------------------------------------------------------
+    /**
+     * Convenience method to return the PathInfo from the request.
+     */
+    protected String[] getPathInfo(HttpServletRequest request) {
+        String mPathInfo = request.getPathInfo();
+        mPathInfo = (mPathInfo!=null) ? mPathInfo : "";
+        return StringUtils.split(mPathInfo,"/");
+    }
+    
+    /**
+     * Serialize entry to writer.
+     */
+    public static void serializeEntry(Entry entry, Writer writer)
+    throws IllegalArgumentException, FeedException, IOException {
+        // Build a feed containing only the entry
+        List entries = new ArrayList();
+        entries.add(entry);
+        Feed feed1 = new Feed();
+        feed1.setFeedType(AtomServlet.FEED_TYPE);
+        feed1.setEntries(entries);
+        
+        // Get Rome to output feed as a JDOM document
+        WireFeedOutput wireFeedOutput = new WireFeedOutput();
+        Document feedDoc = wireFeedOutput.outputJDom(feed1);
+        
+        // Grab entry element from feed and get JDOM to serialize it
+        Element entryElement= (Element)feedDoc.getRootElement().getChildren().get(0);
+        
+        // Add our own namespaced element, so we can determine if we can 
+        // count on client to preserve foreign markup as it should.
+        Element rollerElement = new Element("atom-draft", 
+            "http://rollerweblogger.org/namespaces/app");
+        rollerElement.setText("8");
+        entryElement.addContent(rollerElement);
+        
+        XMLOutputter outputter = new XMLOutputter();
+        outputter.setFormat(Format.getPrettyFormat());
+        
+        if (mLogger.isDebugEnabled()) {
+            StringWriter sw = new StringWriter();
+            outputter.output(entryElement, sw); 
+            mLogger.debug(sw.toString());
+            writer.write(sw.toString()); 
+        } else {
+            outputter.output(entryElement, writer);
+        }        
+    }
+    
+    /**
+     * Parse entry from reader.
+     */
+    public static Entry parseEntry(Reader rd)
+    throws JDOMException, IOException, IllegalArgumentException, FeedException {
+        // Parse entry into JDOM tree
+        SAXBuilder builder = new SAXBuilder();
+        Document entryDoc = builder.build(rd);
+        Element fetchedEntryElement = entryDoc.getRootElement();
+        fetchedEntryElement.detach();
+        
+        // Put entry into a JDOM document with 'feed' root so that Rome can handle it
+        Feed feed = new Feed();
+        feed.setFeedType(FEED_TYPE);
+        WireFeedOutput wireFeedOutput = new WireFeedOutput();
+        Document feedDoc = wireFeedOutput.outputJDom(feed);
+        feedDoc.getRootElement().addContent(fetchedEntryElement);
+        
+        // Check for our special namespaced element. If it's there, then we 
+        // know that client is not preserving foreign markup.
+        Namespace ns = Namespace.getNamespace(
+            "http://rollerweblogger.org/namespaces/app");
+        Element rollerElement = fetchedEntryElement.getChild("atom-draft", ns);
+        if (rollerElement == null) {
+            mLogger.debug("Client is NOT preserving foreign markup");
+        }
+        
+        WireFeedInput input = new WireFeedInput();
+        Feed parsedFeed = (Feed)input.build(feedDoc);
+        return (Entry)parsedFeed.getEntries().get(0);
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModule.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModule.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModule.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModule.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc.
+ *
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import com.sun.syndication.feed.module.Module;
+
+public interface PubControlModule extends Module {
+    public static final String URI = "http://purl.org/atom/app#";
+    public Boolean getDraft();
+    public void setDraft(Boolean draft);
+}

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleGenerator.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleGenerator.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleGenerator.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleGenerator.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc.
+ *
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jdom.Element;
+import org.jdom.Namespace;
+
+import com.sun.syndication.feed.module.Module;
+import com.sun.syndication.io.ModuleGenerator;
+
+public class PubControlModuleGenerator implements ModuleGenerator {
+    private static final Namespace PUBCONTROL_NS  = 
+        Namespace.getNamespace("app", PubControlModule.URI);
+
+    public String getNamespaceUri() {
+        return PubControlModule.URI;
+    }
+
+    private static final Set NAMESPACES;
+
+    static {
+        Set nss = new HashSet();
+        nss.add(PUBCONTROL_NS);
+        NAMESPACES = Collections.unmodifiableSet(nss);
+    }
+
+    public Set getNamespaces() {
+        return NAMESPACES;
+    }
+
+    public void generate(Module module, Element element) {
+        PubControlModule m = (PubControlModule)module;
+        if (m.getDraft() != null) {
+            String draft = m.getDraft().booleanValue() ? "yes" : "no";
+            Element control = new Element("control", PUBCONTROL_NS);
+            control.addContent(generateSimpleElement("draft", draft));
+            element.addContent(control);
+        }
+    }
+
+    protected Element generateSimpleElement(String name, String value)  {
+        Element element = new Element(name, PUBCONTROL_NS);
+        element.addContent(value);
+        return element;
+    }
+
+}

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleImpl.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleImpl.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc.
+ *
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import com.sun.syndication.feed.module.ModuleImpl;
+
+public class PubControlModuleImpl extends ModuleImpl implements PubControlModule {
+    private Boolean _draft;
+
+    public PubControlModuleImpl() {
+        super(PubControlModule.class,PubControlModule.URI);
+    }
+    public Boolean getDraft() {
+        return _draft;
+    }
+    public void setDraft(Boolean draft) {
+        _draft = draft;
+    }
+    public Class getInterface() {
+        return PubControlModule.class;
+    }
+    public void copyFrom(Object obj) {
+        PubControlModule m = (PubControlModule)obj;
+        setDraft(m.getDraft());
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleParser.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleParser.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleParser.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/PubControlModuleParser.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc.
+ *
+ * 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.roller.presentation.webservices.atomprotocol;
+
+import org.jdom.Element;
+import org.jdom.Namespace;
+
+import com.sun.syndication.feed.module.Module;
+import com.sun.syndication.io.ModuleParser;
+
+public class PubControlModuleParser implements ModuleParser {
+
+    public String getNamespaceUri() {
+        return PubControlModule.URI;
+    }
+
+    public Namespace getContentNamespace() {
+        return Namespace.getNamespace(PubControlModule.URI);
+    }
+    public Module parse(Element elem) {
+        boolean foundSomething = false;
+        PubControlModule m = new PubControlModuleImpl();
+        Element e = elem.getChild("control", getContentNamespace());
+        if (e != null) {
+            Element draftElem = e.getChild("draft", getContentNamespace());
+            if (draftElem != null) {
+                if ("yes".equals(draftElem.getText())) m.setDraft(Boolean.TRUE); 
+                if ("no".equals(draftElem.getText())) m.setDraft(Boolean.FALSE);                
+            }
+        }
+        return m.getDraft()!=null ? m : null;
+    }
+}
+

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/RollerAtomHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/RollerAtomHandler.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/RollerAtomHandler.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/RollerAtomHandler.java Thu Apr 20 09:22:49 2006
@@ -0,0 +1,828 @@
+/*
+ * 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.roller.presentation.webservices.atomprotocol;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Collections;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.model.FileManager;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.pojos.UserData;
+import org.roller.pojos.PermissionsData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WebsiteData;
+import org.roller.presentation.RollerContext;
+import org.roller.util.RollerMessages;
+import org.roller.util.Utilities;
+
+import com.sun.syndication.feed.atom.Content;
+import com.sun.syndication.feed.atom.Category;
+import com.sun.syndication.feed.atom.Entry;
+import com.sun.syndication.feed.atom.Feed;
+import com.sun.syndication.feed.atom.Link;
+import com.sun.syndication.feed.atom.Person;
+import javax.activation.FileTypeMap;
+import org.roller.RollerException;
+import org.roller.config.RollerConfig;
+import org.roller.model.WeblogManager;
+import org.roller.presentation.cache.CacheManager;
+
+/**
+ * Roller's Atom Protocol implementation.
+ * <pre>
+ * Here are the URIs suppored:
+ *
+ *    URI type             URI form                        Handled by
+ *    --------             --------                        ----------
+ *    Introspection URI    /                               getIntrosection()
+ *    Collection URI       /blog-name/<collection-name>    getCollection()
+ *    Member URI           /blog-name/<object-name>        post<object-name>()
+ *    Member URI           /blog-name/<object-name>/id     get<object-name>()
+ *    Member URI           /blog-name/<object-name>/id     put<object-name>()
+ *    Member URI           /blog-name/<object-name>/id     delete<object-name>()
+ *
+ *    Until group blogging is supported weblogHandle == blogname.
+ *
+ *    Collection-names   Object-names
+ *    ----------------   ------------
+ *       entries           entry
+ *       resources         resource
+ * </pre>
+ *
+ * @author David M Johnson
+ */
+public class RollerAtomHandler implements AtomHandler {
+    private HttpServletRequest mRequest;
+    private Roller             mRoller;
+    private RollerContext      mRollerContext;
+    private UserData           user;
+    private int                mMaxEntries = 20;
+    //private MessageDigest    md5Helper = null;
+    //private MD5Encoder       md5Encoder = new MD5Encoder();
+    
+    private static Log mLogger =
+            LogFactory.getFactory().getInstance(RollerAtomHandler.class);
+    
+    //---------------------------------------------------------------- construction
+    
+    /**
+     * Create Atom handler for a request and attempt to authenticate user.
+     * If user is authenticated, then getAuthenticatedUsername() will return
+     * then user's name, otherwise it will return null.
+     */
+    public RollerAtomHandler(HttpServletRequest request) {
+        mRequest = request;
+        mRoller = RollerFactory.getRoller();
+        mRollerContext = RollerContext.getRollerContext();
+        
+        // TODO: decide what to do about authentication, is WSSE going to fly?
+        //mUsername = authenticateWSSE(request);
+        String mUsername = authenticateBASIC(request);
+        
+        if (mUsername != null) {
+            try {
+                this.user = mRoller.getUserManager().getUserByUsername(mUsername);
+            } catch (Exception e) {
+                mLogger.error("ERROR: setting user", e);
+            }
+        }
+    }
+    
+    /**
+     * Return weblogHandle of authenticated user or null if there is none.
+     */
+    public String getAuthenticatedUsername() {
+        return this.user.getUserName();
+    }
+    
+    //---------------------------------------------------------------- introspection
+    
+    /**
+     * Return Atom service document for site, getting blog-name from pathInfo.
+     * The workspace will contain collections for entries, categories and resources.
+     */
+    public AtomService getIntrospection(String[] pathInfo) throws Exception {
+        if (pathInfo.length == 0) {
+            String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+            AtomService service = new AtomService();
+            List perms = mRoller.getUserManager().getAllPermissions(user);
+            if (perms != null) {
+                for (Iterator iter=perms.iterator(); iter.hasNext();) {
+                    PermissionsData perm = (PermissionsData)iter.next();
+                    String handle = perm.getWebsite().getHandle();
+                    AtomService.Workspace workspace = new AtomService.Workspace();
+                    workspace.setTitle(Utilities.removeHTML(perm.getWebsite().getName()));
+                    service.addWorkspace(workspace);
+                    
+                    AtomService.Collection entryCol = new AtomService.Collection();
+                    entryCol.setTitle("Weblog Entries");
+                    entryCol.setMemberType("entry");
+                    entryCol.setHref(absUrl + "/app/"+handle+"/entries");
+                    workspace.addCollection(entryCol);
+                                        
+                    AtomService.Collection uploadCol = new AtomService.Collection();
+                    uploadCol.setTitle("Media Files");
+                    uploadCol.setMemberType("media");
+                    uploadCol.setHref(absUrl + "/app/"+handle+"/resources");
+                    workspace.addCollection(uploadCol);
+                }
+            }
+            return service;
+        }
+        throw new Exception("ERROR: bad URL in getIntrospection()");
+    }
+    
+    //----------------------------------------------------------------- collections
+    
+    /**
+     * Return collection specified by pathinfo.
+     * <pre>
+     * Supports these URI forms:
+     *    /<blog-name>/entries
+     *    /<blog-name>/entries/offset
+     *    /<blog-name>/resources
+     *    /<blog-name>/resources/offset
+     * </pre>
+     */
+    public Feed getCollection(String[] pathInfo) throws Exception {
+        int start = 0;
+        if (pathInfo.length > 2) {
+            try { 
+                String s = pathInfo[2].trim();
+                start = Integer.parseInt(s);
+            } catch (Throwable t) {
+                mLogger.warn("Unparsable range: " + pathInfo[2]);
+            }
+        }
+        if (pathInfo.length > 0 && pathInfo[1].equals("entries")) {
+            return getCollectionOfEntries(pathInfo, start, mMaxEntries);
+        } else if (pathInfo.length > 0 && pathInfo[1].equals("resources")) {
+            return getCollectionOfResources(pathInfo, start, mMaxEntries);
+        }
+        throw new Exception("ERROR: bad URL in getCollection()");
+    }
+    
+    /**
+     * Helper method that returns collection of entries, called by getCollection().
+     */
+    public Feed getCollectionOfEntries(
+            String[] pathInfo, int start, int max) throws Exception {
+        String handle = pathInfo[0];
+        String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        List entries = null;
+        if (canView(website)) {
+            entries = mRoller.getWeblogManager().getWeblogEntries(
+                    website,           // website
+                    null,              // startDate
+                    null,              // endDate
+                    null,              // catName
+                    null,              // status
+                    "updateTime",      // sortby
+                    start,             // offset (for range paging)
+                    max + 1);          // maxEntries
+            Feed feed = new Feed();
+            feed.setTitle("Entries for blog[" + handle + "]");
+            List atomEntries = new ArrayList();
+            int count = 0;
+            for (Iterator iter = entries.iterator(); iter.hasNext() && count < mMaxEntries; count++) {
+                WeblogEntryData rollerEntry = (WeblogEntryData)iter.next();
+                atomEntries.add(createAtomEntry(rollerEntry));
+            }
+            List links = new ArrayList();
+            if (entries.size() > max) { // add next link
+                int nextOffset = start + max; 
+                String url = absUrl + "/app/" + website.getHandle() + "/entries/" + nextOffset;
+                Link nextLink = new Link();
+                nextLink.setRel("next");
+                nextLink.setHref(url);
+                links.add(nextLink);
+            }
+            if (start > 0) { // add previous link
+                int prevOffset = start > max ? start - max : 0;
+                String url = absUrl + "/app/" +website.getHandle() + "/entries/" + prevOffset;
+                Link prevLink = new Link();
+                prevLink.setRel("previous");
+                prevLink.setHref(url);
+                links.add(prevLink);
+            }
+            if (links.size() > 0) feed.setOtherLinks(links);
+            feed.setEntries(atomEntries);
+            return feed;
+        }
+        throw new Exception("ERROR: not authorized");
+    }
+    
+    /**
+     * Helper method that returns collection of resources, called by getCollection().
+     */
+    public Feed getCollectionOfResources(
+            String[] pathInfo, int start, int max) throws Exception {
+        String handle = pathInfo[0];
+        String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        FileManager fmgr = mRoller.getFileManager();
+        File[] files = fmgr.getFiles(website.getHandle());
+        if (canView(website)) {            
+            Feed feed = new Feed();
+            List atomEntries = new ArrayList();
+            int count = 0;
+            if (files != null && start < files.length) {
+                for (int i=start; i<(start + max) && i<(files.length); i++) {                   
+                    Entry entry = createAtomResourceEntry(website, files[i]);
+                    atomEntries.add(entry);
+                    count++;
+                }
+            }
+            if (start + count < files.length) { // add next link
+                int nextOffset = start + max; 
+                String url = absUrl + "/app/" + website.getHandle() + "/resources/" + nextOffset;
+                Link nextLink = new Link();
+                nextLink.setRel("next");
+                nextLink.setHref(url);
+                List next = new ArrayList();
+                next.add(nextLink);
+                feed.setOtherLinks(next);
+            }
+            if (start > 0) { // add previous link
+                int prevOffset = start > max ? start - max : 0;
+                String url = absUrl + "/app/" +website.getHandle() + "/resources/" + prevOffset;
+                Link prevLink = new Link();
+                prevLink.setRel("previous");
+                prevLink.setHref(url);
+                List prev = new ArrayList();
+                prev.add(prevLink);
+                feed.setOtherLinks(prev);
+            }
+            feed.setEntries(atomEntries);
+            return feed;
+        }
+        throw new Exception("ERROR: not authorized");
+    }   
+    
+    //--------------------------------------------------------------------- entries
+    
+    /**
+     * Create entry in the entry collection (a Roller blog has only one).
+     */
+    public Entry postEntry(String[] pathInfo, Entry entry) throws Exception {
+        // authenticated client posted a weblog entry
+        String handle = pathInfo[0];
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        if (canEdit(website)) {
+            // Save it and commit it
+            WeblogManager mgr = mRoller.getWeblogManager();
+            WeblogEntryData rollerEntry = createRollerEntry(website, entry);
+            rollerEntry.setCreator(this.user);
+            mgr.saveWeblogEntry(rollerEntry);
+            
+            // Throttle one entry per second
+            // (MySQL timestamp has 1 sec resolution, damnit)
+            Thread.sleep(1000);
+            
+            CacheManager.invalidate(website);
+            if (rollerEntry.isPublished()) {
+                mRoller.getIndexManager().addEntryReIndexOperation(rollerEntry);
+            }            
+            return createAtomEntry(rollerEntry);
+        }
+        throw new Exception("ERROR not authorized to edit website");
+    }
+    
+    /**
+     * Retrieve entry, URI like this /blog-name/entry/id
+     */
+    public Entry getEntry(String[] pathInfo) throws Exception {
+        if (pathInfo.length == 3) // URI is /blogname/entries/entryid
+        {
+            WeblogEntryData entry =
+                mRoller.getWeblogManager().getWeblogEntry(pathInfo[2]);
+            if (entry != null && !canView(entry)) {
+                throw new Exception("ERROR not authorized to view entry");
+            } else if (entry != null) {
+                return createAtomEntry(entry);
+            }
+            return null;
+        }
+        throw new Exception("ERROR: bad URI");
+    }
+    
+    /**
+     * Update entry, URI like this /blog-name/entry/id
+     */
+    public Entry putEntry(String[] pathInfo, Entry entry) throws Exception {
+        if (pathInfo.length == 3) // URI is /blogname/entries/entryid
+        {
+            WeblogEntryData rollerEntry =
+                    mRoller.getWeblogManager().getWeblogEntry(pathInfo[2]);
+            if (canEdit(rollerEntry)) {
+                WeblogManager mgr = mRoller.getWeblogManager();
+                
+                WeblogEntryData rawUpdate = createRollerEntry(rollerEntry.getWebsite(), entry);
+                rollerEntry.setPubTime(rawUpdate.getPubTime());
+                rollerEntry.setUpdateTime(rawUpdate.getUpdateTime());
+                rollerEntry.setText(rawUpdate.getText());
+                rollerEntry.setStatus(rawUpdate.getStatus());
+                rollerEntry.setCategory(rawUpdate.getCategory());
+                rollerEntry.setTitle(rawUpdate.getTitle());
+                
+                mgr.saveWeblogEntry(rollerEntry);
+                
+                CacheManager.invalidate(rollerEntry.getWebsite());
+                if (rollerEntry.isPublished()) {
+                    mRoller.getIndexManager().addEntryReIndexOperation(rollerEntry);
+                }   
+                return createAtomEntry(rollerEntry);
+            }
+            throw new Exception("ERROR not authorized to put entry");
+        }
+        throw new Exception("ERROR: bad URI");
+    }
+    
+    /**
+     * Delete entry, URI like this /blog-name/entry/id
+     */
+    public void deleteEntry(String[] pathInfo) throws Exception {
+        if (pathInfo.length == 3) // URI is /blogname/entries/entryid
+        {
+            WeblogEntryData rollerEntry =
+                    mRoller.getWeblogManager().getWeblogEntry(pathInfo[2]);
+            if (canEdit(rollerEntry)) {
+                WeblogManager mgr = mRoller.getWeblogManager();
+                mgr.removeWeblogEntry(rollerEntry);
+                return;
+            }
+            throw new Exception("ERROR not authorized to delete entry");
+        }
+        throw new Exception("ERROR: bad URI");
+    }
+    
+    //-------------------------------------------------------------------- resources
+    
+    /**
+     * Create new resource in generic collection (a Roller blog has only one).
+     * TODO: can we avoid saving temporary file?
+     * TODO: do we need to handle mutli-part MIME uploads?
+     * TODO: use Jakarta Commons File-upload?
+     */
+    public Entry postMedia(String[] pathInfo, 
+            String name, String contentType, InputStream is)
+            throws Exception {
+        // authenticated client posted a weblog entry
+        File tempFile = null;
+        RollerMessages msgs = new RollerMessages();
+        String handle = pathInfo[0];
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        if (canEdit(website) && pathInfo.length > 1) {
+            // save to temp file
+            if (name == null) {
+                throw new Exception("ERROR[postResource]: No 'name' present in HTTP headers");
+            }
+            try {
+                FileManager fmgr = mRoller.getFileManager();
+                tempFile = File.createTempFile(name,"tmp");
+                FileOutputStream fos = new FileOutputStream(tempFile);
+                Utilities.copyInputToOutput(is, fos);
+                fos.close();
+                
+                // If save is allowed by Roller system-wide policies
+                if (fmgr.canSave(website.getHandle(), name, tempFile.length(), msgs)) {
+                    // Then save the file
+                    FileInputStream fis = new FileInputStream(tempFile);
+                    fmgr.saveFile(website.getHandle(), name, tempFile.length(), fis);
+                    fis.close();
+                    
+                    File resource = new File(fmgr.getUploadDir() + File.separator + name);
+                    return createAtomResourceEntry(website, resource);
+                }
+
+            } catch (Exception e) {
+                String msg = "ERROR in atom.postResource";
+                mLogger.error(msg,e);
+                throw new Exception(msg);
+            } finally {
+                if (tempFile != null) tempFile.delete();
+            }
+        }
+        throw new Exception("File upload denied because:" + msgs.toString());
+    }
+    
+    /**
+     * Get absolute path to resource specified by path info.
+     */
+    public Entry getMedia(String[] pathInfo) throws Exception {
+        String handle = pathInfo[0];
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        String uploadPath = RollerFactory.getRoller().getFileManager().getUploadUrl();
+        File resource = new File(uploadPath + File.separator + pathInfo[2]);
+        return createAtomResourceEntry(website, resource);
+    }
+    
+    /**
+     * Update resource specified by pathInfo using data from input stream.
+     * Expects pathInfo of form /blog-name/resources/name
+     */
+    public Entry putMedia(String[] pathInfo,
+            String contentType, InputStream is) throws Exception {
+        if (pathInfo.length > 2) {
+            String name = pathInfo[2];
+            return postMedia(pathInfo, name, contentType, is);
+        }
+        throw new Exception("ERROR: bad pathInfo");
+    }
+    
+    /**
+     * Delete resource specified by pathInfo.
+     * Expects pathInfo of form /blog-name/resources/name
+     */
+    public void deleteMedia(String[] pathInfo) throws Exception {
+        // authenticated client posted a weblog entry
+        String handle = pathInfo[0];
+        WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+        if (canEdit(website) && pathInfo.length > 1) {
+            try {
+                FileManager fmgr = mRoller.getFileManager();
+                fmgr.deleteFile(website.getHandle(), pathInfo[2]);
+            } catch (Exception e) {
+                String msg = "ERROR in atom.deleteResource";
+                mLogger.error(msg,e);
+                throw new Exception(msg);
+            }
+        }
+        else throw new Exception("ERROR not authorized to edit website");
+    }
+    
+    //------------------------------------------------------------------ URI testers
+    
+    /**
+     * True if URL is the introspection URI.
+     */
+    public boolean isIntrospectionURI(String[] pathInfo) {
+        if (pathInfo.length==0) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a entry URI.
+     */
+    public boolean isEntryURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("entry")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a resource URI.
+     */
+    public boolean isMediaURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("resource")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a category URI.
+     */
+    public boolean isCategoryURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("category")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a collection URI of any sort.
+     */
+    public boolean isCollectionURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("entries")) return true;
+        if (pathInfo.length > 1 && pathInfo[1].equals("resources")) return true;
+        if (pathInfo.length > 1 && pathInfo[1].equals("categories")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a entry collection URI.
+     */
+    public boolean isEntryCollectionURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("entries")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a resource collection URI.
+     */
+    public boolean isMediaCollectionURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("resources")) return true;
+        return false;
+    }
+    
+    /**
+     * True if URL is a category collection URI.
+     */
+    public boolean isCategoryCollectionURI(String[] pathInfo) {
+        if (pathInfo.length > 1 && pathInfo[1].equals("categories")) return true;
+        return false;
+    }
+    
+    //------------------------------------------------------------------ permissions
+    
+    /**
+     * Return true if user is allowed to edit an entry.
+     */
+    private boolean canEdit(WeblogEntryData entry) {
+        try {
+            return entry.hasWritePermissions(this.user);
+        } catch (Exception e) {
+            mLogger.error("ERROR: checking website.canSave()");
+        }
+        return false;
+    }
+    
+    /**
+     * Return true if user is allowed to create/edit weblog entries and file uploads in a website.
+     */
+    private boolean canEdit(WebsiteData website) {
+        try {
+            return website.hasUserPermissions(this.user, PermissionsData.AUTHOR);
+        } catch (Exception e) {
+            mLogger.error("ERROR: checking website.hasUserPermissions()");
+        }
+        return false;
+    }
+    
+    /**
+     * Return true if user is allowed to view an entry.
+     */
+    private boolean canView(WeblogEntryData entry) {
+        return canEdit(entry);
+    }
+    
+    /**
+     * Return true if user is allowed to view a website.
+     */
+    private boolean canView(WebsiteData website) {
+        return canEdit(website);
+    }
+    
+    //-------------------------------------------------------------- authentication
+    
+    /**
+     * Perform WSSE authentication based on information in request.
+     * Will not work if Roller password encryption is turned on.
+     */
+    protected String authenticateWSSE(HttpServletRequest request) {
+        String wsseHeader = request.getHeader("X-WSSE");
+        if (wsseHeader == null) return null;
+        
+        String ret = null;
+        String userName = null;
+        String created = null;
+        String nonce = null;
+        String passwordDigest = null;
+        String[] tokens = wsseHeader.split(",");
+        for (int i = 0; i < tokens.length; i++) {
+            int index = tokens[i].indexOf('=');
+            if (index != -1) {
+                String key = tokens[i].substring(0, index).trim();
+                String value = tokens[i].substring(index + 1).trim();
+                value = value.replaceAll("\"", "");
+                if (key.startsWith("UsernameToken")) {
+                    userName = value;
+                } else if (key.equalsIgnoreCase("nonce")) {
+                    nonce = value;
+                } else if (key.equalsIgnoreCase("passworddigest")) {
+                    passwordDigest = value;
+                } else if (key.equalsIgnoreCase("created")) {
+                    created = value;
+                }
+            }
+        }
+        String digest = null;
+        try {
+            UserData user = mRoller.getUserManager().getUserByUsername(userName);
+            digest = WSSEUtilities.generateDigest(
+                    WSSEUtilities.base64Decode(nonce),
+                    created.getBytes("UTF-8"),
+                    user.getPassword().getBytes("UTF-8"));
+            if (digest.equals(passwordDigest)) {
+                ret = userName;
+            }
+        } catch (Exception e) {
+            mLogger.error("ERROR in wsseAuthenticataion: " + e.getMessage(), e);
+        }
+        return ret;
+    }
+    
+    /**
+     * Untested (and currently unused) implementation of BASIC authentication
+     */
+    public String authenticateBASIC(HttpServletRequest request) {
+        boolean valid = false;
+        String userID = null;
+        String password = null;
+        try {
+            String authHeader = request.getHeader("Authorization");
+            if (authHeader != null) {
+                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.decodeBase64(credentials.getBytes()));
+                        int p = userPass.indexOf(":");
+                        if (p != -1) {
+                            userID = userPass.substring(0, p);
+                            UserData user = mRoller.getUserManager().getUserByUsername(userID);                                                        
+                            boolean enabled = user.getEnabled().booleanValue();
+                            if (enabled) {    
+                                // are passwords encrypted?
+                                RollerContext rollerContext = 
+                                    RollerContext.getRollerContext();
+                                String encrypted = 
+                                    RollerConfig.getProperty("passwds.encryption.enabled");
+                                password = userPass.substring(p+1);
+                                if ("true".equalsIgnoreCase(encrypted)) {
+                                    password = Utilities.encodePassword(password, 
+                                        RollerConfig.getProperty("passwds.encryption.algorithm"));
+                                }
+                                valid = user.getPassword().equals(password);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            mLogger.debug(e);
+        }
+        if (valid) return userID;
+        return null;
+    }
+    
+    //----------------------------------------------------------- internal utilities
+    
+    /**
+     * Create a Rome Atom entry based on a Roller entry.
+     * Content is escaped.
+     * Link is stored as rel=alternate link.
+     */
+    private Entry createAtomEntry(WeblogEntryData entry) {
+        Entry atomEntry = new Entry();
+        Content content = new Content();
+        content.setType(Content.HTML);
+        content.setValue(entry.getText());
+        List contents = new ArrayList();
+        contents.add(content);
+        
+        String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+        atomEntry.setId(        absUrl + entry.getPermaLink());
+        atomEntry.setTitle(     entry.getTitle());
+        atomEntry.setContents(  contents);
+        atomEntry.setPublished( entry.getPubTime());
+        atomEntry.setUpdated(   entry.getUpdateTime());        
+
+        UserData creator = entry.getCreator();        
+        Person author = new Person();
+        author.setName(         creator.getUserName());   
+        author.setEmail(        creator.getEmailAddress());                        
+        atomEntry.setAuthors(   Collections.singletonList(author));
+        
+        List categories = new ArrayList();
+        Category atomCat = new Category();
+        atomCat.setTerm(entry.getCategory().getPath());
+        categories.add(atomCat);
+        atomEntry.setCategories(categories);
+        
+        Link altlink = new Link();
+        altlink.setRel("alternate");
+        altlink.setHref(absUrl + entry.getPermaLink());
+        List altlinks = new ArrayList();
+        altlinks.add(altlink);
+        atomEntry.setAlternateLinks(altlinks);
+        
+        Link editlink = new Link();
+        editlink.setRel("edit");
+        editlink.setHref(absUrl + "/app/"
+            + entry.getWebsite().getHandle() + "/entry/" + entry.getId());
+        List otherlinks = new ArrayList();
+        otherlinks.add(editlink);
+        atomEntry.setOtherLinks(otherlinks);
+        
+        List modules = new ArrayList();
+        PubControlModule pubControl = new PubControlModuleImpl();
+        pubControl.setDraft(
+            new Boolean(!WeblogEntryData.PUBLISHED.equals(entry.getStatus())));
+        modules.add(pubControl);
+        atomEntry.setModules(modules);
+        
+        return atomEntry;
+    }
+    
+    private Entry createAtomResourceEntry(WebsiteData website, File file) { 
+        String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+        String editURI = absUrl
+                + "/app/" + website.getHandle()
+                + "/resource/" + file.getName();
+        String viewURI = absUrl
+                + "/resources/" + website.getHandle()
+                + "/" + file.getName();
+        FileTypeMap map = FileTypeMap.getDefaultFileTypeMap();
+        String contentType = map.getContentType(file);
+
+        Entry entry = new Entry();
+        entry.setTitle(file.getName());
+        entry.setUpdated(new Date(file.lastModified()));
+
+        Link editlink = new Link();
+        editlink.setRel("edit");
+        editlink.setHref(editURI);
+        List otherlinks = new ArrayList();
+        otherlinks.add(editlink);
+        entry.setOtherLinks(otherlinks);
+
+        Content content = new Content();
+        content.setSrc(viewURI);
+        content.setType(contentType);
+        List contents = new ArrayList();
+        contents.add(content);
+        entry.setContents(contents);
+        
+        return entry;
+    }
+    
+    /**
+     * Create a Roller weblog entry based on a Rome Atom entry object
+     */
+    private WeblogEntryData createRollerEntry(WebsiteData website, Entry entry)
+    throws RollerException {
+        
+        Timestamp current = new Timestamp(System.currentTimeMillis());
+        Timestamp pubTime = current;
+        Timestamp updateTime = current;
+        if (entry.getPublished() != null) {
+            pubTime = new Timestamp( entry.getPublished().getTime() );
+        }
+        if (entry.getUpdated() != null) {
+            updateTime = new Timestamp( entry.getUpdated().getTime() );
+        }
+        WeblogEntryData rollerEntry = new WeblogEntryData();
+        rollerEntry.setTitle(entry.getTitle());
+        if (entry.getContents() != null && entry.getContents().size() > 0) {
+            Content content = (Content)entry.getContents().get(0);
+            rollerEntry.setText(content.getValue());
+        }
+        rollerEntry.setPubTime(pubTime);
+        rollerEntry.setUpdateTime(updateTime);
+        rollerEntry.setWebsite(website);
+               
+        PubControlModule control = 
+            (PubControlModule)entry.getModule("http://purl.org/atom/app#");
+        if (control!=null && control.getDraft()!=null && control.getDraft().booleanValue()) {
+            rollerEntry.setStatus(WeblogEntryData.DRAFT);
+        } else {
+            rollerEntry.setStatus(WeblogEntryData.PUBLISHED);
+        }
+        
+        // Atom supports multiple cats, Roller supports one/entry
+        // so here we take accept the first category that exists
+        List categories = entry.getCategories();
+        if (categories != null && categories.size() > 0) {
+            for (int i=0; i<categories.size(); i++) {
+                Category cat = (Category)categories.get(i);
+                WeblogCategoryData rollerCat =
+                    mRoller.getWeblogManager().getWeblogCategoryByPath(
+                    website, cat.getTerm());
+                if (rollerCat != null) {
+                    rollerEntry.setCategory(rollerCat);
+                    break;
+                }
+            }
+        } else {
+            rollerEntry.setCategory(website.getBloggerCategory());
+        }
+        return rollerEntry;
+    }
+}

Added: incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/WSSEUtilities.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/WSSEUtilities.java?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/WSSEUtilities.java (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/WSSEUtilities.java Thu Apr 20 09:22:49 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.roller.presentation.webservices.atomprotocol;
+
+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/roller/presentation/webservices/atomprotocol/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/package.html?rev=395633&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/package.html (added)
+++ incubator/roller/trunk/src/org/roller/presentation/webservices/atomprotocol/package.html Thu Apr 20 09:22:49 2006
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+ROME-based Atom Protocol implementation.
+
+</body>
+</html>



Mime
View raw message