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/10/21 23:46:28 UTC

svn commit: r327589 [29/72] - in /incubator/roller/branches/roller_1.x: ./ contrib/ contrib/lib/ contrib/plugins/ contrib/plugins/src/ contrib/plugins/src/org/ contrib/plugins/src/org/roller/ contrib/plugins/src/org/roller/presentation/ contrib/plugins...

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerRequest.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerRequest.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerRequest.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerRequest.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,934 @@
+
+package org.roller.presentation;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.config.RollerRuntimeConfig;
+import org.roller.model.ParsedRequest;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.pojos.Template;
+import org.roller.model.UserManager;
+import org.roller.model.WeblogManager;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WebsiteData;
+import org.roller.util.DateUtil;
+import org.roller.util.Utilities;
+ 
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Access to objects and values specified by request. Parses out arguments from
+ * request URL needed for various parts of Roller and makes them available via
+ * getter methods.
+ * <br/><br/> 
+ * 
+ * These forms of pathinfo get special support:
+ * <br/><br/>
+ * 
+ * <pre>
+ * [username] - get default page for user for today's date 
+ * [username]/[date] - get default page for user for specified date 
+ * [username]/[pagelink] - get specified page for today's date 
+ * [username]/[pagelink]/[date] - get specified page for specified date
+ * [username]/[pagelink]/[anchor] - get specified page & entry (by anchor)
+ * [username]/[pagelink]/[date]/[anchor] - get specified page & entry (by anchor)
+ * </pre>
+ *  
+ * @author David M Johnson
+ */
+public class RollerRequest implements ParsedRequest
+{
+    //----------------------------------------------------------------- Fields
+    
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(RollerRequest.class);
+
+    private BookmarkData       mBookmark;
+    private ServletContext     mContext = null;    
+    private Date               mDate = null;
+    private String             mDateString = null;
+    private String             mPathInfo = null; 
+    private String             mPageLink = null;
+    private Template           mPage;
+    private PageContext        mPageContext = null;
+    private HttpServletRequest mRequest = null;
+    private WebsiteData        mWebsite;
+    private WeblogEntryData    mWeblogEntry;
+    private WeblogCategoryData mWeblogCategory;
+    private boolean           mIsDateSpecified = false;
+        
+    private static ThreadLocal mRollerRequestTLS = new ThreadLocal();
+    
+    public static final String ANCHOR_KEY             = "entry";
+    public static final String ANCHOR_KEY_OLD         = "anchor";
+    public static final String USERNAME_KEY           = "username";
+    public static final String WEBSITEID_KEY          = "websiteid";
+    public static final String FOLDERID_KEY           = "folderid";
+    public static final String PARENTID_KEY           = "parentid";
+    public static final String NEWSFEEDID_KEY         = "feedid";
+    public static final String PAGEID_KEY             = "pageid";
+    public static final String PAGELINK_KEY           = "pagelink";
+    public static final String PINGTARGETID_KEY       = "pingtargetid";
+    public static final String EXCERPTS_KEY           = "excerpts";
+    public static final String BOOKMARKID_KEY         = "bookmarkid";
+    public static final String REFERERID_KEY          = "refid";
+    public static final String WEBLOGENTRYID_KEY      = "entryid";
+    public static final String WEBLOGENTRY_COUNT      = "count";
+    public static final String WEBLOGCATEGORYNAME_KEY = "catname";
+    public static final String WEBLOGCATEGORYID_KEY   = "catid";
+    public static final String WEBLOGENTRIES_KEY      = "entries";
+    public static final String WEBLOGDAY_KEY          = "day";
+    public static final String WEBLOGCOMMENTID_KEY    = "catid";
+    public static final String LOGIN_COOKIE           = "sessionId";
+    
+    public static final String OWNING_USER            = "OWNING_USER";
+    
+    private static final String ROLLER_REQUEST        = "roller_request";
+    
+    private SimpleDateFormat mFmt = DateUtil.get8charDateFormat();
+
+    //----------------------------------------------------------- Constructors
+
+    /** Construct Roller request for a Servlet request */
+    public RollerRequest( HttpServletRequest req, ServletContext ctx )
+        throws RollerException
+    {
+        mRequest = req;
+        mContext = ctx;
+        init();
+    }
+    
+    //------------------------------------------------------------------------
+    /** Convenience */
+    public RollerRequest( ServletRequest req, ServletContext ctx ) 
+        throws RollerException
+    {
+        mRequest = (HttpServletRequest)req;
+        mContext = ctx;
+        init();
+    }
+    
+    //------------------------------------------------------------------------
+    public RollerRequest( PageContext pCtx) throws RollerException
+    {
+        mRequest = (HttpServletRequest) pCtx.getRequest();
+        mContext = pCtx.getServletContext();
+        mPageContext = pCtx;
+        init();
+    }
+
+    //------------------------------------------------------------------------
+    private void init() throws RollerException
+    {
+        mRollerRequestTLS.set(this);
+        if (mRequest.getContextPath().equals("/atom"))
+        {
+            return; // Atom servlet request needs no init
+        }
+        
+        // Bind persistence session to authenticated user, if we have one
+        RollerContext rctx = RollerContext.getRollerContext(mContext); 
+        Authenticator auth = rctx.getAuthenticator();
+        String userName = auth.getAuthenticatedUserName(mRequest);
+        if (userName != null)
+        {
+            UserManager userMgr = getRoller().getUserManager();
+            UserData currentUser = userMgr.getUser(userName);
+            getRoller().setUser(currentUser);
+        }
+        
+        // path info may be null, (e.g. on JSP error page)
+        mPathInfo = mRequest.getPathInfo();
+        mPathInfo = (mPathInfo!=null) ? mPathInfo : "";            
+        
+        String[] pathInfo = StringUtils.split(mPathInfo,"/");  
+        if ( pathInfo.length > 0 )
+        {
+            // Parse pathInfo and throw exception if it is invalid
+            if (!"j_security_check".equals(pathInfo[0]))
+            {
+                parsePathInfo( pathInfo );
+                return;
+            }
+        }
+
+        // Parse user, page, and entry from request params if possible
+        parseRequestParams();
+    }
+    
+    //------------------------------------------------------------------------
+    /** 
+     * Bind persistence session to specific user.
+     */
+    private void bindUser() throws RollerException
+    {
+    }
+    
+    //------------------------------------------------------------------------
+    /** Parse pathInfo and throw exception if it is invalid */
+    private void parsePathInfo( String[] pathInfo ) throws RollerException
+    {
+        try 
+        {
+            // If there is any path info, it must be in one of the 
+            // below formats or the request is considered to be invalid.
+            //
+            //   /username 
+            //   /username/datestring 
+            //   /username/pagelink
+            //   /username/pagelink/datestring
+            //   /username/pagelink/anchor (specific entry)
+            //   /username/pagelink/datestring/anchor (specific entry)
+            UserManager userMgr = getRoller().getUserManager();
+            mWebsite = userMgr.getWebsite(pathInfo[0]);
+            if (mWebsite != null)
+            {
+                if ( pathInfo.length == 1 )
+                {
+                    // we have the /username form of URL
+                    mDate = getDate(true);
+                    mDateString = DateUtil.format8chars(mDate);
+                    mPage = mWebsite.getDefaultPage();
+                }
+                else if ( pathInfo.length == 2 )
+                {
+                    mDate = parseDate(pathInfo[1]);
+                    if ( mDate == null ) // pre-jdk1.4 --> || mDate.getYear() <= 70 )
+                    {
+                        // we have the /username/pagelink form of URL
+                        mDate = getDate(true);
+                        mDateString = DateUtil.format8chars(mDate);
+                        mPageLink = pathInfo[1];
+                        mPage = mWebsite.getPageByLink(pathInfo[1]);
+                    }
+                    else
+                    {
+                        // we have the /username/datestring form of URL
+                        mDateString = pathInfo[1];
+                        mPage = mWebsite.getDefaultPage();
+                        mIsDateSpecified = true;
+                    }               
+                }
+                else if ( pathInfo.length == 3 )
+                {
+                    mPageLink = pathInfo[1];
+                    mPage = mWebsite.getPageByLink(pathInfo[1]);
+                    
+                    mDate = parseDate(pathInfo[2]);
+                    if ( mDate == null ) // pre-jdk1.4 --> || mDate.getYear() <= 70 )
+                    {
+                        // we have the /username/pagelink/anchor form of URL
+                        try
+                        {
+                            WeblogManager weblogMgr = getRoller().getWeblogManager();
+                            mWeblogEntry = weblogMgr.getWeblogEntryByAnchor(
+                                mWebsite, pathInfo[2]);
+                            mDate = mWeblogEntry.getPubTime();
+                            mDateString = DateUtil.format8chars(mDate);
+                        }
+                        catch (Exception damn)
+                        {
+                            // doesn't really matter, we've got the Page anyway
+                            mLogger.debug("Damn", damn);
+                        }
+                    }
+                    else
+                    {
+                        // we have the /username/pagelink/datestring form of URL
+                        mDateString = pathInfo[2];
+                        mIsDateSpecified = true;
+                    }
+                }
+                else if ( pathInfo.length == 4 )
+                {
+                    // we have the /username/pagelink/datestring/anchor form of URL
+                    mPageLink = pathInfo[1];
+                    mPage = mWebsite.getPageByLink(pathInfo[1]);
+                    
+                    mDate = parseDate(pathInfo[2]);
+                    mDateString = pathInfo[2];
+                    mIsDateSpecified = true;
+
+                    // we have the /username/pagelink/date/anchor form of URL
+                    WeblogManager weblogMgr = getRoller().getWeblogManager();
+                    mWeblogEntry = weblogMgr.getWeblogEntryByAnchor(
+                                    mWebsite, pathInfo[3]);
+                }                
+            }
+        }
+        catch ( Exception ignored )
+        {
+            mLogger.debug("Exception parsing pathInfo",ignored);
+        }
+        
+        if ( mWebsite==null || mDate==null || mPage==null )
+        {            
+            String msg = "Invalid pathInfo: "+StringUtils.join(pathInfo,"|");
+            mLogger.info(msg);                       
+            throw new RollerException(msg);
+        }
+    }       
+    
+    //------------------------------------------------------------------------
+    /** Parse user, page, and entry from request params if possible */
+    private void parseRequestParams()
+    {
+        try
+        {
+            // No path info means that we need to parse request params
+            
+            // First, look for user in the request params
+            UserManager userMgr = getRoller().getUserManager();            
+            String userName = mRequest.getParameter(USERNAME_KEY);
+            if ( userName == null )
+            {
+                // then try remote user
+                userName = mRequest.getRemoteUser(); 
+            }
+            
+            if ( userName != null )
+            {
+                mWebsite = userMgr.getWebsite(userName);
+            }
+            
+            // Look for page ID in request params
+            String pageId = mRequest.getParameter(RollerRequest.PAGEID_KEY);                    
+            if ( pageId != null )
+            {
+                mPage = userMgr.retrievePage(pageId);
+                /*
+                // We can use page to find the user, if we don't have one yet
+                if ( mWebsite == null )
+                {
+                    mWebsite = mPage.getWebsite();
+                }
+                 */                    
+            }
+            else if (mWebsite != null) 
+            {
+                mPage = mWebsite.getDefaultPage();
+            }
+                                       
+            // Look for day in request params 
+            String daystr = mRequest.getParameter(WEBLOGDAY_KEY);
+            if ( daystr != null )
+            {
+                mDate = parseDate(daystr);
+                if ( mDate != null )
+                {
+                    // we have the /username/datestring form of URL
+                    mDateString = daystr;
+                    mIsDateSpecified = true;
+                }               
+            }                  
+        }
+        catch ( Exception ignored )
+        {
+            mLogger.debug("Exception parsing request params",ignored);
+        }
+    }
+
+    //------------------------------------------------------------------------
+    /** Get HttpServletmRequest that is wrapped by this RollerRequest */
+    public PageContext getPageContext()
+    {
+        return mPageContext;
+    }
+    
+    public void setPageContext(PageContext p)
+    {
+        mPageContext = p;
+    }
+
+    //------------------------------------------------------------------------
+    /** Get HttpServletmRequest that is wrapped by this RollerRequest */
+    public HttpServletRequest getRequest()
+    {
+        return mRequest;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get the RollerRequest object that is stored in the request. Creates
+     * RollerRequest if one not found in mRequest.
+     */
+    public static RollerRequest getRollerRequest( 
+        HttpServletRequest r, ServletContext ctx ) throws RollerException
+    {
+        RollerRequest ret= (RollerRequest)r.getAttribute(ROLLER_REQUEST);
+        if ( ret == null )
+        {
+            ret = new RollerRequest(r,ctx);
+            r.setAttribute( ROLLER_REQUEST, ret );
+        }
+        return ret;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get the RollerRequest object that is stored in the request. Creates
+     * RollerRequest if one not found in mRequest.
+     */
+    public static RollerRequest getRollerRequest( HttpServletRequest r )
+    {
+        try
+        {
+            return getRollerRequest(r, RollerContext.getServletContext());
+        }
+        catch (Exception e)
+        {
+            mLogger.debug("Unable to create a RollerRequest", e);
+        }
+        return null;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get the RollerRequest object that is stored in the request. Creates
+     * RollerRequest if one not found in mRequest.
+     */
+    public static RollerRequest getRollerRequest( PageContext p )
+    {
+        HttpServletRequest r = (HttpServletRequest)p.getRequest();
+        RollerRequest ret = (RollerRequest)r.getAttribute(ROLLER_REQUEST);
+        if (ret == null)
+        {
+            try
+            {
+                ret = new RollerRequest( p );
+                r.setAttribute( ROLLER_REQUEST, ret );
+            }
+            catch (Exception e)
+            {
+                mLogger.debug("Unable to create a RollerRequest", e);
+            }
+        }
+        else
+        {
+            ret.setPageContext( p );
+        }
+        return ret;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get RollerRequest object for the current thread using EVIL MAGIC, 
+     * do not use unless you absolutely, positively, cannot use on of the 
+     * getRollerRequest() methods.
+     */
+    public static RollerRequest getRollerRequest()
+    {
+        return (RollerRequest)mRollerRequestTLS.get();
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get the RollerRequest object that is stored in the requeset.
+     * Creates RollerRequest if one not found in mRequest.
+     */
+    public ServletContext getServletContext()
+    {
+        return mContext;
+    }
+
+
+    public Roller getRoller()
+    {
+        return RollerFactory.getRoller();
+    }
+    
+    
+    //------------------------------------------------------------------------
+    /** Is mRequest's user the admin user? */
+    public boolean isAdminUser() throws RollerException
+    {
+        UserData user = getUser();
+        if (user != null && user.hasRole("admin")) 
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    //------------------------------------------------------------------------
+    /** Is mRequest's user authorized to edit the mRequested resource */
+    public boolean isUserAuthorizedToEdit() throws RollerException
+    {
+        // Make sure authenticated user is the owner of this item
+        // Session's principal's name must match user name in mRequest
+        
+        RollerContext rctx = RollerContext.getRollerContext(mContext); 
+        Authenticator auth = rctx.getAuthenticator();
+        
+        String userName = auth.getAuthenticatedUserName(mRequest);
+            
+        // TODO: A hack to be replaced by Object.canEdit()
+        UserData owningUser = null;
+        if (mRequest.getAttribute(OWNING_USER) != null)
+        {
+            owningUser = (UserData)mRequest.getAttribute(OWNING_USER);
+        }
+        else
+        {
+            owningUser = getUser(); 
+        }
+        
+        if (    userName != null 
+             && owningUser != null
+             && userName.equalsIgnoreCase( owningUser.getUserName() )
+             && auth.isAuthenticatedUserInRole(mRequest,"editor"))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Get user by name.
+     */
+    public UserData getUser( String userName ) throws Exception
+    {
+        return getRoller().getUserManager().getUser(userName);
+    }
+
+    //------------------------------------------------------------------------
+    
+    public boolean isDateSpecified()
+    {
+        return mIsDateSpecified;
+    }
+            
+    //------------------------------------------------------------------------
+    
+    public int getWeblogEntryCount()
+    {
+        // Get count of entries to return, or 20 if null
+        int count = 20;
+        if ( mRequest.getParameter("count") != null )
+        {
+            count= Utilities.stringToInt(mRequest.getParameter("count"));
+            if ( count==0 || count>50 )
+            {
+                count = 20;
+            } 
+        } 
+        return count; 
+    }         
+            
+    //------------------------------------------------------------------------
+    
+    /**
+     * Gets the date specified by the request, or null.
+     * @return Date
+     */
+    public Date getDate()
+    {
+        return getDate(false);
+    }
+    
+    //------------------------------------------------------------------------
+    /**
+     * Gets the date specified by the request
+     * @param orToday If no date specified, then use today's date.
+     * @return Date
+     */
+    public Date getDate( boolean orToday )
+    {
+        Date ret = null;
+        if ( mDate != null )
+        {
+            ret = mDate;
+        }
+        else if ( orToday )
+        {
+            ret = getToday();
+        }
+        return ret;
+    }
+ 
+    /**
+     * Gets the current date based on Website's Locale & Timezone.
+     * @return
+     */
+    private Date getToday()
+    {
+        Calendar todayCal = Calendar.getInstance();
+        if (this.getWebsite() != null)
+        {
+            todayCal = Calendar.getInstance(
+                    this.getWebsite().getTimeZoneInstance(),
+                    this.getWebsite().getLocaleInstance());
+        }
+        todayCal.setTime( new Date() );
+        return todayCal.getTime(); 
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the YYYYMMDD date string specified by the request, or null.
+     * @return String
+     */
+    public String getDateString()
+    {
+        return getDateString(false);
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the date specified by the request
+     * @param orToday If no date specified, then use today's date.
+     * @return Date
+     */
+    public String getDateString( boolean orToday )
+    {
+        String ret = null;
+        if ( mDateString != null )
+        {
+            ret = mDateString;
+        }
+        else if ( orToday )
+        {
+            Calendar todayCal = Calendar.getInstance();
+            if (this.getWebsite() != null)
+            {
+            	todayCal = Calendar.getInstance(
+                        this.getWebsite().getTimeZoneInstance(),
+                        this.getWebsite().getLocaleInstance());
+            }
+            todayCal.setTime( new Date() );
+            ret = mFmt.format(todayCal.getTime());            
+        }
+        return ret;
+    }
+    
+    //------------------------------------------------------------------------
+    /**
+     * Gets the path-info specified by the request, or null.
+     * @return String
+     */
+    public String getPathInfo()
+    {
+        return mPathInfo;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the page link name specified by the request, or null.
+     * @return String
+     */
+    public String getPageLink()
+    {
+        return mPageLink;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the BookmarkData specified by the request, or null.
+     * @return BookmarkData
+     */
+    public BookmarkData getBookmark( )
+    {
+        if ( mBookmark == null )
+        {
+            String id = getFromRequest(BOOKMARKID_KEY);
+            if ( id != null )
+            {
+                try
+                {
+                    mBookmark = 
+                        getRoller().getBookmarkManager().retrieveBookmark(id);
+                }
+                catch (RollerException e)
+                {
+                    mLogger.error("Getting bookmark from request",e);
+                }
+            }
+        }
+        return mBookmark;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the WeblogCategoryData specified by the request, or null.
+     * @return 
+     */
+    public WeblogCategoryData getWeblogCategory()
+    {
+        if ( mWeblogCategory == null )
+        {
+            String id = getFromRequest(WEBLOGCATEGORYID_KEY);
+            if ( id != null )
+            {
+                try
+                {
+                    mWeblogCategory = 
+                        getRoller().getWeblogManager().retrieveWeblogCategory(id);
+                }
+                catch (RollerException e)
+                {
+                    mLogger.error("Getting weblog category by id from request",e);
+                }
+            }
+            else if (StringUtils.isNotEmpty(id = getFromRequest(WEBLOGCATEGORYNAME_KEY)))
+            {
+                try
+                {
+                    mWeblogCategory = 
+                        getRoller().getWeblogManager().getWeblogCategoryByPath(
+                            getWebsite(), null, id);
+                }
+                catch (RollerException e)
+                {
+                    mLogger.error("Getting weblog category by name from request",e);
+                }
+            }
+        }
+        return mWeblogCategory;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the FolderData specified by the request, or null.
+     * @return FolderData
+     */
+    public FolderData getFolder( )
+    {
+        FolderData folder = null;
+        //if ( folder == null )
+        //{
+            String id = getFromRequest(FOLDERID_KEY);
+            if ( id != null )
+            {
+                try
+                {
+                    folder = 
+                        getRoller().getBookmarkManager().retrieveFolder(id);
+                }
+                catch (RollerException e)
+                {
+                    mLogger.error("Getting folder from request",e);
+                }
+            }
+        //}
+        return folder;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Gets the WeblogTemplate specified by the request, or null.
+     * @return WeblogTemplate
+     */
+    public Template getPage()
+    {
+        if (mPage == null)
+        {
+            String id = getFromRequest(PAGEID_KEY);
+            if ( id != null )
+            {
+                try
+                {
+                    mPage = getRoller().getUserManager().retrievePage(id);
+                }
+                catch (RollerException e)
+                {
+                    mLogger.error("Getting page from request",e);
+                }
+            }
+        }
+        return mPage;
+    }
+    
+    /**
+     * Allow comment servlet to inject page that it has chosen.
+     */
+    public void setPage(org.roller.pojos.Template page) 
+    {
+        mPage = page;
+    }
+    
+    //------------------------------------------------------------------------
+    /**
+     * Gets the Request URL specified by the request, or null.
+     * @return String
+     */
+    public String getRequestURL( )
+    {
+        return mRequest.getRequestURL().toString();
+    }
+    
+    //------------------------------------------------------------------------
+    
+    /**
+     * Gets the Referer URL specified by the request, or null.
+     * @return String
+     */
+    public String getRefererURL( )
+    {
+        return mRequest.getHeader("referer");
+    }
+    
+    /**
+     * Gets the UserData specified by the request, or null.
+     * @return UserData
+     */
+    public UserData getUser()
+    {
+        if (mWebsite != null) return mWebsite.getUser();
+        return null;
+    }
+
+    /**
+     * Gets the WebsiteData specified by the request, or null.
+     * @return WeblogCategory
+     */
+    public WebsiteData getWebsite()
+    {
+        return mWebsite;
+    }
+    public void setWebsite(WebsiteData wd)
+    {
+        mWebsite = wd;
+    }
+
+    /**
+     * Gets the WeblogEntryData specified by the request, or null.
+     * 
+     * Why is this done lazily in the parseRequestParameters() method?
+     * 
+     * Because: that method is called from init(), which is called from
+     * a ServletFilter, and sometimes request parameters are not available
+     * in a ServletFiler. They ARE available when the URL points to a JSP,
+     * but they ARE NOT available when the URL points to the PageServlet.
+     * This may be a Tomcat bug, I'm not sure.
+     * 
+     * @return WeblogEntryData
+     */
+    public WeblogEntryData getWeblogEntry( )
+    {
+        if ( mWeblogEntry == null )
+        {        
+            // Look for anchor or entry ID that identifies a specific entry 
+            String anchor = mRequest.getParameter(ANCHOR_KEY);
+            if (anchor == null) anchor = mRequest.getParameter(ANCHOR_KEY_OLD);
+            String entryid = mRequest.getParameter(WEBLOGENTRYID_KEY);
+            if (entryid == null) 
+            {
+                entryid = (String)mRequest.getAttribute(WEBLOGENTRYID_KEY);
+            }
+            try
+            {
+                if ( entryid != null )
+                {
+                    WeblogManager weblogMgr = getRoller().getWeblogManager();
+                    mWeblogEntry = weblogMgr.retrieveWeblogEntry(entryid);                
+                
+                    // We can use entry to find the website, if we don't have one
+                    if ( mWeblogEntry != null && mWebsite == null )
+                    {
+                        mWebsite = mWeblogEntry.getWebsite();
+                    }
+                } 
+                else if ( mWebsite != null && anchor != null )
+                {
+                    WeblogManager weblogMgr = getRoller().getWeblogManager();
+                    mWeblogEntry = weblogMgr.getWeblogEntryByAnchor(
+                        mWebsite,anchor);
+                }                             
+            }
+            catch (RollerException e)
+            {
+                mLogger.error("EXCEPTION getting weblog entry",e);
+                mLogger.error("user=" + mWebsite.getUser());
+                mLogger.error("anchor=" + anchor);
+                mLogger.error("entryid=" + entryid);
+            }
+        }           
+        return mWeblogEntry;
+    }
+
+    //------------------------------------------------------------------------
+
+    /** Get attribute from mRequest, and if that fails try session */
+    private String getFromRequest( String key )
+    {
+        String ret = (String)mRequest.getAttribute( key );
+        if ( ret == null )
+        {
+            ret = mRequest.getParameter( key );
+            if (ret == null && mRequest.getSession(false) != null)
+            {
+                ret = (String)mRequest.getSession().getAttribute( key );
+            }
+        }
+        return ret;
+    }
+
+    //------------------------------------------------------------------------
+
+    private Date parseDate( String dateString )
+    {
+        Date ret = null;        
+        if (   dateString!=null 
+            && dateString.length()==8
+            && StringUtils.isNumeric(dateString) )
+        {
+            ParsePosition pos = new ParsePosition(0);
+            ret = mFmt.parse( dateString, pos );
+            
+            // make sure the requested date is not in the future
+            Date today = getToday();
+            if (ret.after(today)) ret = today;
+            
+            // since a specific date was requested set time to end of day
+            ret = DateUtil.getEndOfDay(ret);
+        }
+        return ret;
+    }
+    
+    //------------------------------------------------------------------------
+
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        sb.append(getRequestURL());
+        sb.append(", ");
+        sb.append(getRefererURL());
+        sb.append("]");
+        return sb.toString();
+    }
+
+    //------------------------------------------------------------------------
+
+    /** 
+     * @see org.roller.pojos.ParsedRequest#isLinkbackEnabled()
+     */
+    public boolean isEnableLinkback()
+    {
+        return RollerRuntimeConfig.getBooleanProperty("site.linkbacks.enabled");
+    }
+}
+

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerSession.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerSession.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerSession.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/RollerSession.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,146 @@
+package org.roller.presentation;
+
+import org.apache.commons.collections.ArrayStack;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+import java.io.Serializable;
+
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Roller session handles session startup and shutdown.
+ * @web.listener
+ */
+public class RollerSession
+    implements HttpSessionListener, HttpSessionActivationListener, Serializable
+{
+    // Although we have no actual members, we implement Serializable to meet expectations of
+    // session attributes for some container configurations.
+    static final long serialVersionUID = 5890132909166913727L;
+
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(RollerSession.class);
+
+    public static final String ROLLER_SESSION = "org.roller.rollersession";
+    public static final String BREADCRUMB = "org.roller.breadcrumb";
+    public static final String ERROR_MESSAGE   = "rollererror_message";
+    public static final String STATUS_MESSAGE  = "rollerstatus_message";
+
+
+    //------------------------------------------------------------------------
+    /** Create session's Roller instance */
+    public void sessionCreated(HttpSessionEvent se)
+    {
+        // put this in session, so that we get HttpSessionActivationListener callbacks
+        se.getSession().setAttribute( ROLLER_SESSION, this );
+        
+        RollerContext rctx = RollerContext.getRollerContext(
+            se.getSession().getServletContext());
+        rctx.sessionCreated(se);           
+    }    
+
+    //------------------------------------------------------------------------
+    public void sessionDestroyed(HttpSessionEvent se)
+    {
+        RollerContext rctx = RollerContext.getRollerContext(
+            se.getSession().getServletContext());
+        rctx.sessionDestroyed(se);           
+        
+        clearSession(se);        
+    }
+
+    //------------------------------------------------------------------------
+    /** Init session as if it was new */
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Clear bread crumb trail.
+     * @param req the request
+     */
+    public static void clearBreadCrumbTrail( HttpServletRequest req )
+    { 
+        HttpSession ses = req.getSession(false);
+        if (ses != null && ses.getAttribute(BREADCRUMB) != null)
+        {
+            ArrayStack stack = (ArrayStack)ses.getAttribute(BREADCRUMB);
+            stack.clear();
+        }
+    }
+    
+    //------------------------------------------------------------------------
+    /**
+     * Store the url of the latest request stored in the session.
+     * @param useReferer If true try to return the "referer" header.
+     */
+    public static String getBreadCrumb( 
+        HttpServletRequest req, boolean useReferer )
+    {
+        String crumb = null;
+        
+        HttpSession ses = req.getSession(false);
+        if (ses != null && ses.getAttribute(BREADCRUMB) != null)
+        {
+            ArrayStack stack = (ArrayStack) ses.getAttribute(BREADCRUMB);
+            if (stack != null && !stack.empty())
+            {
+                crumb = (String)stack.peek();
+            }
+        }
+
+        if ( crumb == null && useReferer )
+        {
+            crumb = req.getHeader("referer");
+        }
+        
+        return crumb;
+    }
+    
+    //------------------------------------------------------------------------
+    /**
+     * Store the url of the latest request stored in the session.
+     * Else try to return the "referer" header.
+     */
+    public static String getBreadCrumb( HttpServletRequest req )
+    {
+        return getBreadCrumb(req,true);
+    }
+
+    //------------------------------------------------------------------------
+    /** Purge session before passivation. Because Roller currently does not
+      * support session recovery, failover, migration, or whatever you want
+      * to call it when sessions are saved and then restored at some later
+      * point in time.
+      */
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+        clearSession(se);
+    }
+
+    //------------------------------------------------------------------------    /*
+    private  void clearSession( HttpSessionEvent se )
+    {
+        HttpSession session = se.getSession();
+        try
+        {
+            session.removeAttribute( BREADCRUMB );
+        }
+        catch (Throwable e)
+        {
+            if (mLogger.isDebugEnabled())
+            {
+                // ignore purge exceptions
+                mLogger.debug("EXCEPTION PURGING session attributes",e);
+            }
+        }
+    }
+}
+

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/TurnoverReferersTask.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/TurnoverReferersTask.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/TurnoverReferersTask.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/TurnoverReferersTask.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,44 @@
+/*
+ * Created on Aug 16, 2003
+ */
+package org.roller.presentation;
+
+import java.util.TimerTask;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.RefererManager;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.model.ScheduledTask;
+
+/**
+ * @author aim4min
+ */
+public class TurnoverReferersTask extends TimerTask implements ScheduledTask
+{
+    private static Log mLogger = LogFactory.getFactory().getInstance(
+            TurnoverReferersTask.class);
+    private RefererManager refManager = null;
+    
+    public void init(Roller roller, String realPath) throws RollerException
+    {
+        refManager = roller.getRefererManager();
+    }
+    public void run()
+    {
+        if (refManager != null)
+            try
+            {
+                RollerFactory.getRoller().begin();
+                refManager.checkForTurnover(false, null);
+                RollerFactory.getRoller().commit();
+                RollerFactory.getRoller().release();
+            }
+            catch (RollerException e)
+            {
+                mLogger.error("Error while checking for referer turnover", e);
+            }
+    }
+}
\ No newline at end of file

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomCollection.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomCollection.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomCollection.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomCollection.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,262 @@
+/*
+ * 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.atomapi;
+
+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_1.x/src/org/roller/presentation/atomapi/AtomHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomHandler.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomHandler.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomHandler.java Fri Oct 21 14:27:36 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.atomapi;
+
+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_1.x/src/org/roller/presentation/atomapi/AtomService.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomService.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomService.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomService.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,273 @@
+/*
+ * 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.atomapi;
+
+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_1.x/src/org/roller/presentation/atomapi/AtomServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomServlet.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomServlet.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/atomapi/AtomServlet.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,389 @@
+/*
+ * 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.atomapi;
+
+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;
+
+/**
+ * Atom Servlet implements Atom by calling a Roller independent handler.
+ * @web.servlet name="AtomServlet"
+ * @web.servlet-mapping url-pattern="/atom/*"
+ * @author David M Johnson
+ */
+public class AtomServlet extends HttpServlet
+{
+    /** We use Rome to parse/generate Atom feeds and it does Atom format 0.3 */
+    public static final String FEED_TYPE = "atom_0.3"; 
+    
+    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) 
+                    {
+                        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);
+                    }
+                    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.getAlternateLinks().iterator();
+                    while (links.hasNext()) {
+                        Link link = (Link) links.next();
+                        if (link.getRel().equals("alternate")) {
+                            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());
+        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);
+    }
+}