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 2005/09/21 19:08:35 UTC
svn commit: r290745 - in
/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04:
./ AtomCollection.java AtomHandler.java AtomService.java AtomServlet.java
RollerAtomHandler.java WSSEUtilities.java package.html
Author: snoopdave
Date: Wed Sep 21 10:08:31 2005
New Revision: 290745
URL: http://svn.apache.org/viewcvs?rev=290745&view=rev
Log:
Starting new Atom Protocol impl
Added:
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomCollection.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomHandler.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomService.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomServlet.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/RollerAtomHandler.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/WSSEUtilities.java
incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/package.html
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomCollection.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomCollection.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomCollection.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomCollection.java Wed Sep 21 10:08:31 2005
@@ -0,0 +1,263 @@
+/*
+ * 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.atomapi04;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+
+/**
+ * Models an Atom collection.
+ *
+ * @author Dave Johnson
+ */
+/*
+ * Based on: draft-ietf-atompub-protocol-04.txt
+ *
+ * appCollection = element
+ * app:collection {
+ * attribute next { text } ?,
+ * appMember*
+ * }
+ *
+ * Here is an example Atom collection:
+ *
+ * <?xml version="1.0" encoding='utf-8'?>
+ * <collection xmlns="http://purl.org/atom/app#">
+ * <member href="http://example.org/1"
+ * hrefreadonly="http://example.com/1/bar"
+ * title="Sample 1"
+ * updated="2003-12-13T18:30:02Z" />
+ * <member href="http://example.org/2"
+ * hrefreadonly="http://example.com/2/bar"
+ * title="Sample 2"
+ * updated="2003-12-13T18:30:02Z" />
+ * <member href="http://example.org/3"
+ * hrefreadonly="http://example.com/3/bar"
+ * title="Sample 3"
+ * updated="2003-12-13T18:30:02Z" />
+ * <member href="http://example.org/4"
+ * title="Sample 4"
+ * updated="2003-12-13T18:30:02Z" />
+ * </collection>
+ */
+public class AtomCollection
+{
+ public static final Namespace ns =
+ Namespace.getNamespace("http://purl.org/atom/app#");
+
+ private static SimpleDateFormat df =
+ new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" );
+ private String next = null;
+ private List members = new ArrayList();
+
+ public AtomCollection()
+ {
+ }
+
+ /** URI of collection containing member elements updated earlier in time */
+ public String getNext()
+ {
+ return next;
+ }
+
+ public void setNext(String next)
+ {
+ this.next = next;
+ }
+
+ public List getMembers()
+ {
+ return members;
+ }
+
+ public void setMembers(List members)
+ {
+ this.members = members;
+ }
+
+ public void addMember(Member member)
+ {
+ members.add(member);
+ }
+
+ /** Models an Atom collection member */
+ /*
+ * appMember = element app:member { attribute title { text }, attribute href {
+ * text }, attribute hrefreadonly { text } ?, attribute updated { text } }
+ */
+ public static class Member
+ {
+ private String title;
+ private String href;
+ private String hrefreadonly;
+ private Date updated;
+
+ public Member()
+ {
+ }
+
+ /** Human readable title */
+ public String getTitle()
+ {
+ return title;
+ }
+
+ public void setTitle(String title)
+ {
+ this.title = title;
+ }
+
+ /** The URI used to edit the member source */
+ public String getHref()
+ {
+ return href;
+ }
+
+ public void setHref(String href)
+ {
+ this.href = href;
+ }
+
+ /** The URI for readonly access to member source */
+ public String getHrefreadonly()
+ {
+ return hrefreadonly;
+ }
+
+ public void setHrefreadonly(String hrefreadonly)
+ {
+ this.hrefreadonly = hrefreadonly;
+ }
+
+ /** Same as updated value of collection member */
+ public Date getUpdated()
+ {
+ return updated;
+ }
+
+ public void setUpdated(Date updated)
+ {
+ this.updated = updated;
+ }
+ }
+
+ /** Deserialize an Atom Collection XML document into an object */
+ public static AtomCollection documentToCollection(Document document)
+ throws Exception
+ {
+ AtomCollection collection = new AtomCollection();
+ Element root = document.getRootElement();
+ if (root.getAttribute("next") != null)
+ {
+ collection.setNext(root.getAttribute("next").getValue());
+ }
+ List mems = root.getChildren("member", ns);
+ Iterator iter = mems.iterator();
+ while (iter.hasNext())
+ {
+ Element e = (Element) iter.next();
+ collection.addMember(AtomCollection.elementToMember(e));
+ }
+ return collection;
+ }
+
+ /** Serialize an AtomCollection object into an XML document */
+ public static Document collectionToDocument(AtomCollection collection)
+ {
+ Document doc = new Document();
+ Element root = new Element("collection", ns);
+ doc.setRootElement(root);
+ if (collection.getNext() != null)
+ {
+ root.setAttribute("next", collection.getNext());
+ }
+ Iterator iter = collection.getMembers().iterator();
+ while (iter.hasNext())
+ {
+ Member member = (Member) iter.next();
+ root.addContent(AtomCollection.memberToElement(member));
+ }
+ return doc;
+ }
+
+ /** Deserialize an Atom collection member XML element into an object */
+ public static Member elementToMember(Element element) throws Exception
+ {
+ Member member = new Member();
+ member.setTitle(element.getAttribute("title").getValue());
+ member.setHref(element.getAttribute("href").getValue());
+ if (element.getAttribute("href") != null)
+ {
+ member.setHref(element.getAttribute("href").getValue());
+ }
+ member.setUpdated(df.parse(element.getAttribute("updated").getValue()));
+ return member;
+ }
+
+ /** Serialize a collection member into an XML element */
+ public static Element memberToElement(Member member)
+ {
+ Element element = new Element("member", ns);
+ element.setAttribute("title", member.getTitle()); // TODO: escape/strip HTML?
+ element.setAttribute("href", member.getHref());
+ if (member.getHrefreadonly() != null)
+ {
+ element.setAttribute("hrefreadonly", member.getHrefreadonly());
+ }
+ element.setAttribute("updated", df.format(member.getUpdated()));
+ return element;
+ }
+
+ /** Start and end date range */
+ public static class Range { Date start=null; Date end=null; }
+
+ /** Parse HTTP Range header into a start and end date range */
+ public static Range parseRange(String rangeString) throws ParseException
+ {
+ // Range: updated=<isodate>/<isodate>
+ // Range: updated=<isodate>/
+ // Range: updated=/<isodate>
+
+ Range range = new Range();
+ String[] split = rangeString.split("=");
+ if (split[1].startsWith("/"))
+ {
+ // we have only end date
+ range.end = df.parse(split[1].split("/")[1]);
+ }
+ else if (split[1].endsWith("/"))
+ {
+ // we have only start date
+ range.start = df.parse(split[1].split("/")[0]);
+ }
+ else
+ {
+ // both dates present
+ String[] dates = split[1].split("/");
+ range.start = df.parse(dates[0]);
+ range.end = df.parse(dates[1]);
+ }
+ return range;
+ }
+}
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomHandler.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomHandler.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomHandler.java Wed Sep 21 10:08:31 2005
@@ -0,0 +1,124 @@
+/*
+ * 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.atomapi04;
+
+import java.io.InputStream;
+import java.util.Date;
+
+import com.sun.syndication.feed.atom.Entry;
+
+/**
+ * Interface to be supported by an Atom server, expected lifetime: one request.
+ * AtomServlet calls this generic interface instead of Roller specific APIs.
+ * Does not impose any specific set of collections, just three collection types:
+ * entries, resources and categories. Implementations determine what collections
+ * of each type exist and what URIs are used to get and edit them.
+ * <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
+ */
+ public AtomCollection getCollection(String[] pathInfo) throws Exception;
+
+ /**
+ * Return collection restricted by date range
+ * @param pathInfo Used to determine which collection
+ * @param start Start date or null if none
+ * @param end End date or null of none
+ * @param offset Offset into query results (or -1 if none)
+ */
+ public AtomCollection getCollection(
+ String[] pathInfo, Date start, Date end, int offset)
+ 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 String postResource(String[] pathInfo, String name, String contentType,
+ InputStream is) throws Exception;
+
+ /**
+ * Update a resource.
+ * @param pathInfo Path info portion of URL
+ */
+ public void putResource(String[] pathInfo, String contentType,
+ InputStream is) throws Exception;
+
+ /**
+ * Delete resource specified by pathInfo.
+ * @param pathInfo Path info portion of URL
+ */
+ public void deleteResource(String[] pathInfo) throws Exception;
+
+ /**
+ * Get resource file path (so Servlet can determine MIME type).
+ * @param pathInfo Path info portion of URL
+ */
+ public String getResourceFilePath(String[] pathInfo) throws Exception;
+
+ public boolean isIntrospectionURI(String [] pathInfo);
+
+ public boolean isCollectionURI(String [] pathInfo);
+ public boolean isEntryCollectionURI(String [] pathInfo);
+ public boolean isResourceCollectionURI(String [] pathInfo);
+ public boolean isCategoryCollectionURI(String [] pathInfo);
+
+ public boolean isEntryURI(String[] pathInfo);
+ public boolean isResourceURI(String[] pathInfo);
+ public boolean isCategoryURI(String[] pathInfo);
+}
+
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomService.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomService.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomService.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomService.java Wed Sep 21 10:08:31 2005
@@ -0,0 +1,274 @@
+/*
+ * 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.atomapi04;
+
+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-04.txt
+ *
+ * appService =
+ * element app:service {
+ * (appWorkspace* & anyElement* )
+ * }
+ *
+ * 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
+ * contents="entries" title="My Blog Entries"
+ * href="http://example.org/reilly/feed" />
+ * <collection contents="generic"
+ * title="Documents" href="http://example.org/reilly/pic" />
+ * </workspace>
+ * <workspace title="Side Bar Blog">
+ * <collection contents="entries"
+ * title="Entries" href="http://example.org/reilly/feed" />
+ * <collection
+ * contents="http://example.net/booklist" title="Books"
+ * href="http://example.org/reilly/books" />
+ * </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 { attribute title { text },
+ * attribute contents { text }, attribute href { text }, anyElement* }
+ */
+ public static class Collection
+ {
+ private String title;
+ private String contents = "generic";
+ private String href;
+
+ public Collection()
+ {
+ }
+
+ /**
+ * Contents attribute conveys the nature of a collection's member
+ * resources. May be "entry" or "generic" and defaults to "generic"
+ */
+ public String getContents()
+ {
+ return contents;
+ }
+
+ public void setContents(String contents)
+ {
+ this.contents = contents;
+ }
+
+ /** 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());
+ if (element.getAttribute("href") != null)
+ {
+ collection.setContents(element.getAttribute("contents").getValue());
+ }
+ 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());
+ if (collection.getContents() != null)
+ {
+ element.setAttribute("contents", collection.getContents());
+ }
+ return element;
+ }
+}
\ No newline at end of file
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomServlet.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomServlet.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/AtomServlet.java Wed Sep 21 10:08:31 2005
@@ -0,0 +1,399 @@
+/*
+ * 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.atomapi04;
+
+import java.io.FileInputStream;
+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 org.roller.util.Utilities;
+
+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;
+
+/**
+ * Atom Servlet implements Atom by calling a Roller independent handler.
+ * @web.servlet name="AtomServlet"
+ * @web.servlet-mapping url-pattern="/atom04/*"
+ * @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)
+ {
+ 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);
+ 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
+ String ranges = req.getHeader("Range");
+ if (ranges == null) req.getParameter("Range");
+ AtomCollection col = null;
+ if (ranges != null)
+ {
+ // return a range of collection members
+ AtomCollection.Range range =
+ AtomCollection.parseRange(req.getHeader("Range"));
+ int offset = 0;
+ String offsetString = req.getParameter("offset");
+ if (offsetString != null)
+ {
+ offset = Integer.parseInt(offsetString);
+ }
+ col= handler.getCollection(
+ pathInfo, range.start, range.end, offset);
+ }
+ else
+ {
+ col= handler.getCollection(pathInfo);
+ }
+ // serialize collection to XML and write it out
+ Document doc = AtomCollection.collectionToDocument(col);
+ 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.isEntryURI(pathInfo))
+ {
+ // return an entry
+ Entry entry = handler.getEntry(pathInfo);
+ Writer writer = res.getWriter();
+ serializeEntry(entry, writer);
+ writer.close();
+ }
+ else if (handler.isResourceURI(pathInfo))
+ {
+ // return a resource
+ String absPath = handler.getResourceFilePath(pathInfo);
+ String type = getServletContext().getMimeType(absPath);
+ res.setContentType(type);
+ Utilities.copyInputToOutput(
+ new FileInputStream(absPath), res.getOutputStream());
+ }
+ 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.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.getLinks().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);
+ Writer writer = res.getWriter();
+ serializeEntry(savedEntry, writer);
+ writer.close();
+ }
+ else if (handler.isResourceCollectionURI(pathInfo))
+ {
+ // get incoming file name from HTTP header
+ String name = req.getHeader("Name");
+
+ // hand input stream of to hander to post file
+ String location = handler.postResource(
+ pathInfo, name, req.getContentType(), req.getInputStream());
+ res.setStatus(HttpServletResponse.SC_CREATED);
+ res.setHeader("Location", location);
+ }
+ 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.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
+ Writer writer = res.getWriter();
+ serializeEntry(updatedEntry, writer);
+ res.setStatus(HttpServletResponse.SC_OK);
+ writer.close();
+ }
+ else if (handler.isResourceCollectionURI(pathInfo))
+ {
+ // handle input stream to handler
+ handler.putResource(
+ pathInfo, req.getContentType(), req.getInputStream());
+ 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.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.isResourceURI(pathInfo))
+ {
+ handler.deleteResource(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.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,"/");
+ }
+
+ /**
+ * Utility method to make up for a Rome shortcoming:
+ * Rome can only serialize entire feeds, not individual elements
+ */
+ 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);
+ XMLOutputter outputter = new XMLOutputter();
+ outputter.setFormat(Format.getPrettyFormat());
+
+ StringWriter sw = new StringWriter(); // DEBUG
+ outputter.output(entryElement, sw); // DEBUG
+ System.out.println(sw.toString()); // DEBUG
+ writer.write(sw.toString()); // DEBUG
+
+ //outputter.output(entryElement, writer);
+ }
+
+ /**
+ * Utility method to make up for a Rome shortcoming:
+ * Rome can only parse Atom data with XML document root 'feed'
+ */
+ 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);
+
+ WireFeedInput input = new WireFeedInput();
+ Feed parsedFeed = (Feed)input.build(feedDoc);
+ return (Entry)parsedFeed.getEntries().get(0);
+ }
+}
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/RollerAtomHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/RollerAtomHandler.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/RollerAtomHandler.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/RollerAtomHandler.java Wed Sep 21 10:08:31 2005
@@ -0,0 +1,829 @@
+/*
+ * 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.atomapi04;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.util.RequestUtils;
+import org.roller.RollerException;
+import org.roller.model.FileManager;
+import org.roller.model.Roller;
+import org.roller.model.WeblogManager;
+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.LoginServlet;
+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.Link;
+import com.sun.syndication.io.impl.Base64;
+import org.roller.RollerException;
+
+/**
+ * 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()
+ * Collection-next URI /blog-name/<collection-name>/id 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
+ * categories categories
+ * soon:
+ * users user
+ * templates template
+ * </pre>
+ *
+ * @author David M Johnson
+ */
+public class RollerAtomHandler implements AtomHandler {
+ private HttpServletRequest mRequest;
+ private Roller mRoller;
+ private RollerContext mRollerContext;
+ private String mUsername;
+ 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 = RollerContext.getRoller(request);
+ mRollerContext = RollerContext.getRollerContext(request);
+
+ // TODO: decide what to do about authentication, is WSSE going to fly?
+ mUsername = authenticateWSSE(request);
+
+ if (mUsername != null) {
+ try {
+ UserData user = mRoller.getUserManager().getUser(mUsername);
+ mRoller.setUser(user);
+ } catch (Exception e) {
+ mLogger.error("ERROR: setting user", e);
+ }
+ }
+ // try
+ // {
+ // md5Helper = MessageDigest.getInstance("MD5");
+ // }
+ // catch (NoSuchAlgorithmException e)
+ // {
+ // mLogger.debug("ERROR creating MD5 helper", e);
+ // }
+ }
+
+ /**
+ * Return weblogHandle of authenticated user or null if there is none.
+ */
+ public String getAuthenticatedUsername() {
+ return mUsername;
+ }
+
+ //---------------------------------------------------------------- introspection
+
+ /**
+ * Return Atom service document for site, getting blog-name from pathInfo.
+ * Since a user can (currently) have only one blog, one workspace is returned.
+ * 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();
+ UserData user = mRoller.getUserManager().getUser(mUsername);
+ 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("Workspace: Collections for " + handle);
+ service.addWorkspace(workspace);
+
+ AtomService.Collection entryCol = new AtomService.Collection();
+ entryCol.setTitle("Collection: Weblog Entries for " + handle);
+ entryCol.setContents("entries");
+ entryCol.setHref(absUrl + "/atom/"+handle+"/entries");
+ workspace.addCollection(entryCol);
+
+ AtomService.Collection catCol = new AtomService.Collection();
+ catCol.setTitle("Collection: Categories for " + handle);
+ catCol.setContents("categories");
+ catCol.setHref(absUrl + "/atom/"+handle+"/categories");
+ workspace.addCollection(catCol);
+
+ AtomService.Collection uploadCol = new AtomService.Collection();
+ uploadCol.setTitle("Collection: File uploads for " + handle);
+ uploadCol.setContents("generic");
+ uploadCol.setHref(absUrl + "/atom/"+handle+"/resources");
+ workspace.addCollection(uploadCol);
+ }
+ }
+ return service;
+ }
+ throw new Exception("ERROR: bad URL in getIntrospection()");
+ }
+
+ //----------------------------------------------------------------- collections
+
+ /**
+ * Returns collection specified by pathInfo with no date range specified.
+ * Just calls the other getCollection(), but with offset = -1.
+ */
+ public AtomCollection getCollection(String[] pathInfo) throws Exception {
+ return getCollection(pathInfo, null, new Date(), -1);
+ }
+
+ /**
+ * Returns collection specified by pathInfo, constrained by a date range and
+ * starting at an offset within the collection.Returns 20 items at a time.
+ * <pre>
+ * Supports these three collection URI forms:
+ * /<blog-name>/entries
+ * /<blog-name>/resources
+ * /<blog-name>/categories
+ * </pre>
+ * @param pathInfo Path info from URI
+ * @param start Don't include members updated before this date (null allowed)
+ * @param end Don't include members updated after this date (null allowed)
+ * @param offset Offset within collection (for paging)
+ */
+ public AtomCollection getCollection(
+ String[] pathInfo, Date start, Date end, int offset)
+ throws Exception {
+ if (pathInfo.length > 0 && pathInfo[1].equals("entries")) {
+ return getCollectionOfEntries(pathInfo, start, end, offset);
+ } else if (pathInfo.length > 0 && pathInfo[1].equals("resources")) {
+ return getCollectionOfResources(pathInfo, start, end, offset);
+ } else if (pathInfo.length > 0 && pathInfo[1].equals("categories")) {
+ return getCollectionOfCategories(pathInfo, start, end, offset);
+ }
+ throw new Exception("ERROR: bad URL in getCollection()");
+ }
+
+ /**
+ * Helper method that returns collection of entries, called by getCollection().
+ */
+ public AtomCollection getCollectionOfEntries(
+ String[] pathInfo, Date start, Date end, int offset)
+ throws Exception {
+ String handle = pathInfo[0];
+ String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+ WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+ List entries = null;
+ if (canView(website)) {
+ if (pathInfo.length == 2) // handle /blogname/entries
+ {
+ // return most recent blog entries
+ if (offset == -1) {
+ entries = mRoller.getWeblogManager().getWeblogEntries(
+ website, // website
+ start, // startDate
+ end, // endDate
+ null, // catName
+ null, // status
+ new Integer(mMaxEntries + 1)); // maxEntries
+ } else {
+ entries = mRoller.getWeblogManager().getWeblogEntries(
+ website, // website
+ start, // startDate
+ end, // endDate
+ null, // catName
+ null, // status
+ offset, // offset (for range paging)
+ mMaxEntries + 1); // maxEntries
+ }
+ } else if (pathInfo.length == 3) // handle /blogname/entries/entryid
+ {
+ // return entries previous to entry specified by pathInfo
+ String entryid = pathInfo[2];
+ WeblogManager wmgr = mRoller.getWeblogManager();
+ WeblogEntryData entry = wmgr.retrieveWeblogEntry(entryid);
+ entries = wmgr.getPreviousEntries(entry, null, mMaxEntries + 1);
+ } else throw new Exception("ERROR: bad URL");
+
+ // build collection
+ AtomCollection col = new AtomCollection();
+ if (entries.size() > mMaxEntries) {
+ // there are more entries, so include next link
+ WeblogEntryData lastEntry =
+ (WeblogEntryData)entries.get(entries.size() - 1);
+ col.setNext(createNextLink(lastEntry, start, end, offset));
+ }
+ // add up to max entries to collection
+ int count = 0;
+ Iterator iter = entries.iterator();
+ while (iter.hasNext() && count++ < mMaxEntries) {
+ WeblogEntryData rollerEntry = (WeblogEntryData)iter.next();
+ AtomCollection.Member member = new AtomCollection.Member();
+ member.setTitle(rollerEntry.getDisplayTitle());
+ member.setUpdated(rollerEntry.getUpdateTime());
+ member.setHref(absUrl
+ + "/atom/" + handle + "/entry/" + rollerEntry.getId());
+ col.addMember(member);
+ }
+ return col;
+ }
+ throw new Exception("ERROR: not authorized");
+ }
+
+ /**
+ * Helper method that returns collection of resources, called by getCollection().
+ */
+ public AtomCollection getCollectionOfResources(
+ String[] pathInfo, Date start, Date end, int offset) 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);
+ if (canView(website)) {
+ AtomCollection col = new AtomCollection();
+ for (int i=0; i<files.length; i++) {
+ AtomCollection.Member member = new AtomCollection.Member();
+ member.setTitle(files[i].getName());
+ member.setUpdated(new Date(files[i].lastModified()));
+ member.setHref(absUrl
+ + "/atom/" + website.getHandle() + "/resource/" + files[i].getName() );
+ col.addMember(member);
+ }
+ return col;
+ }
+ throw new Exception("ERROR: not authorized");
+ }
+
+ /**
+ * Helper method that returns collection of categories, called by getCollection().
+ */
+ public AtomCollection getCollectionOfCategories(
+ String[] pathInfo, Date start, Date end, int offset) throws Exception {
+ String handle = pathInfo[0];
+ String absUrl = mRollerContext.getAbsoluteContextUrl(mRequest);
+ WebsiteData website = mRoller.getUserManager().getWebsiteByHandle(handle);
+ WeblogManager wmgr = mRoller.getWeblogManager();
+ List items = wmgr.getWeblogCategories(website);
+ if (canView(website)) {
+ AtomCollection col = new AtomCollection();
+ Iterator iter = items.iterator();
+ Date now = new Date();
+ while (iter.hasNext()) {
+ WeblogCategoryData item = (WeblogCategoryData)iter.next();
+ AtomCollection.Member member = new AtomCollection.Member();
+ String name = item.getPath();
+ if (name.equals("/")) continue;
+ member.setTitle(name);
+ member.setUpdated(now);
+ member.setHref(absUrl + "/atom/"
+ + website.getHandle() + "/category/" + item.getId());
+ col.addMember(member);
+ }
+ return col;
+ }
+ 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);
+ UserData creator = mRoller.getUserManager().getUser(mUsername);
+ if (canEdit(website)) {
+ // Save it and commit it
+ WeblogEntryData rollerEntry = createRollerEntry(website, entry);
+ rollerEntry.setCreator(creator);
+ rollerEntry.save();
+ mRoller.commit();
+
+ // Throttle one entry per second
+ // (MySQL timestamp has 1 sec resolution, damnit)
+ Thread.sleep(1000);
+
+ // TODO: ping the appropriate ping
+ // TODO: flush the cache on Atom post
+ //flushPageCache(mRequest);
+
+ 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().retrieveWeblogEntry(pathInfo[2]);
+ if (!canView(entry)) {
+ throw new Exception("ERROR not authorized to view entry");
+ } else if (entry != null) {
+ return createAtomEntry(entry);
+ }
+ throw new Exception("ERROR: entry not found");
+ }
+ 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().retrieveWeblogEntry(pathInfo[2]);
+ if (canEdit(rollerEntry)) {
+ rollerEntry.setTitle(entry.getTitle());
+
+ // TODO: don't assume type is HTML or TEXT
+ rollerEntry.setText(entry.getContent().getValue());
+
+ rollerEntry.setUpdateTime(new Timestamp(new Date().getTime()));
+ if (entry.getPublished() != null) {
+ rollerEntry.setPubTime(
+ new Timestamp(entry.getPublished().getTime()));
+ }
+ if (entry.getCategories() != null
+ && entry.getCategories().size() > 0) {
+ Category atomCat = (Category)entry.getCategories().get(0);
+ WeblogCategoryData cat =
+ mRoller.getWeblogManager().getWeblogCategoryByPath(
+ rollerEntry.getWebsite(), atomCat.getTerm());
+ if (cat != null) {
+ rollerEntry.setCategory(cat);
+ }
+ }
+ rollerEntry.save();
+ mRoller.commit();
+ 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().retrieveWeblogEntry(pathInfo[2]);
+ if (canEdit(rollerEntry)) {
+ rollerEntry.remove();
+ mRoller.commit();
+ 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 String postResource(String[] pathInfo,
+ String name, String contentType, InputStream is)
+ 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();
+ RollerMessages msgs = new RollerMessages();
+
+ // save to temp file
+ if (name == null) {
+ throw new Exception(
+ "ERROR[postResource]: No 'name' present in HTTP headers");
+ }
+ File 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, name, tempFile.length(), msgs)) {
+ // Then save the file
+ FileInputStream fis = new FileInputStream(tempFile);
+ fmgr.saveFile(website, name, tempFile.length(), fis);
+ fis.close();
+
+ // TODO: build URL to uploaded file should be done in FileManager
+ String uploadPath = RollerContext.getUploadPath(
+ mRequest.getSession(true).getServletContext());
+ uploadPath += "/" + website.getHandle() + "/" + name;
+ return RequestUtils.printableURL(
+ RequestUtils.absoluteURL(mRequest, uploadPath));
+ }
+ tempFile.delete();
+ throw new Exception("File upload denied because:" + msgs.toString());
+ } catch (Exception e) {
+ String msg = "ERROR in atom.postResource";
+ mLogger.error(msg,e);
+ throw new Exception(msg);
+ }
+ }
+ throw new Exception("ERROR not authorized to edit website");
+ }
+
+ /**
+ * Get absolute path to resource specified by path info.
+ */
+ public String getResourceFilePath(String[] pathInfo) throws Exception {
+ // ==> /<blogname>/resources/<filename>
+ String uploadPath = RollerContext.getUploadPath(
+ mRequest.getSession(true).getServletContext());
+ return uploadPath + File.separator + pathInfo[2];
+ }
+
+ /**
+ * Update resource specified by pathInfo using data from input stream.
+ * Expects pathInfo of form /blog-name/resources/name
+ */
+ public void putResource(String[] pathInfo,
+ String contentType, InputStream is) throws Exception {
+ if (pathInfo.length > 2) {
+ String name = pathInfo[2];
+ postResource(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 deleteResource(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, pathInfo[2]);
+ } catch (Exception e) {
+ String msg = "ERROR in atom.deleteResource";
+ mLogger.error(msg,e);
+ throw new Exception(msg);
+ }
+ }
+ 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 isResourceURI(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 isResourceCollectionURI(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.canSave();
+ } catch (Exception e) {
+ mLogger.error("ERROR: checking website.canSave()");
+ }
+ return false;
+ }
+
+ /**
+ * Return true if user is allowed to edit a website.
+ */
+ private boolean canEdit(WebsiteData website) {
+ try {
+ return website.canSave();
+ } catch (Exception e) {
+ mLogger.error("ERROR: checking website.canSave()");
+ }
+ 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().getUser(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.decode(credentials));
+ int p = userPass.indexOf(":");
+ if (p != -1) {
+ userID = userPass.substring(0, p);
+ UserData user = mRoller.getUserManager().getUser(userID);
+ String realpassword = LoginServlet.getEncryptedPassword(
+ request, user.getUserName(), user.getPassword());
+ password = userPass.substring(p+1);
+ if ( (!userID.trim().equals(user.getUserName()))
+ && (!password.trim().equals(realpassword))) {
+ valid = true;
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ mLogger.debug(e);
+ }
+ if (valid) return userID;
+ return null;
+ }
+
+ //----------------------------------------------------------- internal utilities
+
+ /**
+ * Create next member list suitable for use in entry collection.
+ * Puts state date, end date and off set in request parameters.
+ */
+ private String createNextLink(
+ WeblogEntryData entry, Date start, Date end, int offset) {
+ SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" );
+ String absUrl = mRollerContext.getAbsoluteContextUrl();
+ String url = absUrl + "/atom/" + mUsername + "/entries/" + entry.getId();
+ if (offset != -1 && start != null && end != null) {
+ url = url + "?Range=" + df.format(start) + "/" + df.format(end);
+ } else if (offset != -1 && start != null) {
+ url = url + "?Range=" + df.format(start) + "/";
+ } else if (offset != -1 && end != null) {
+ url = url + "?Range=/" + df.format(end);
+ }
+ if (offset != -1) {
+ url = url + "&offset=" + (offset + mMaxEntries);
+ }
+ return url;
+ }
+
+ /**
+ * 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());
+
+ atomEntry.setId( entry.getId());
+ atomEntry.setTitle( entry.getTitle());
+ atomEntry.setContent( content);
+ atomEntry.setPublished( entry.getPubTime());
+ atomEntry.setUpdated( entry.getUpdateTime());
+
+ List categories = new ArrayList();
+ Category atomCat = new Category();
+ atomCat.setTerm(entry.getCategory().getPath());
+ categories.add(atomCat);
+ atomEntry.setCategories(categories);
+
+ List links = new ArrayList();
+ Link altlink = new Link();
+ altlink.setRel("alternate");
+ altlink.setHref(entry.getPermaLink());
+ links.add(altlink);
+ atomEntry.setLinks(links);
+
+ return atomEntry;
+ }
+
+ /**
+ * 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());
+ rollerEntry.setText(entry.getContent().getValue());
+ rollerEntry.setPubTime(pubTime);
+ rollerEntry.setUpdateTime(updateTime);
+ rollerEntry.setWebsite(website);
+ rollerEntry.setStatus(WeblogEntryData.PUBLISHED);
+
+ List categories = entry.getCategories();
+ if (categories != null && categories.size() > 0) {
+ Category cat = (Category)categories.get(0);
+ System.out.println(cat.getTerm());
+ WeblogCategoryData rollerCat =
+ mRoller.getWeblogManager().getWeblogCategoryByPath(
+ website, cat.getTerm());
+ rollerEntry.setCategory(rollerCat);
+ } else {
+ rollerEntry.setCategory(website.getBloggerCategory());
+ }
+ return rollerEntry;
+ }
+}
Added: incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/WSSEUtilities.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/WSSEUtilities.java?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/WSSEUtilities.java (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/WSSEUtilities.java Wed Sep 21 10:08:31 2005
@@ -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.atomapi04;
+
+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/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/package.html?rev=290745&view=auto
==============================================================================
--- incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/package.html (added)
+++ incubator/roller/branches/roller_2.0/sandbox/atomprotocol/src/org/roller/presentation/atomapi04/package.html Wed Sep 21 10:08:31 2005
@@ -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>