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

svn commit: r398712 [18/32] - in /incubator/roller/trunk/src/org/apache: ./ roller/ roller/business/ roller/business/hibernate/ roller/business/referrers/ roller/business/runnable/ roller/business/search/ roller/business/search/operations/ roller/busin...

Added: incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchResultsPageModel.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchResultsPageModel.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchResultsPageModel.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchResultsPageModel.java Mon May  1 15:23:02 2006
@@ -0,0 +1,346 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * SearchResultsPageModel.java
+ *
+ * Created on September 23, 2005, 11:27 AM
+ */
+
+package org.apache.roller.presentation.search;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.collections.comparators.ReverseComparator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.Hits;
+import org.apache.roller.RollerException;
+import org.apache.roller.business.search.FieldConstants;
+import org.apache.roller.business.search.operations.SearchOperation;
+import org.apache.roller.model.IndexManager;
+import org.apache.roller.model.Roller;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.model.UserManager;
+import org.apache.roller.model.WeblogManager;
+import org.apache.roller.pojos.WeblogEntryComparator;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WeblogEntryWrapperComparator;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.pojos.wrapper.WeblogEntryDataWrapper;
+import org.apache.roller.presentation.RollerRequest;
+import org.apache.roller.util.DateUtil;
+import org.apache.roller.util.StringUtils;
+
+
+
+/**
+ * Encapsulate seach result in page model so it can be used from Velocity or JSP.
+ * @author Min (original code)
+ * @author Dave Johnson (encapsulation)
+ */
+public class SearchResultsPageModel {
+    
+    private String   term = "";
+    private Integer  hits = new Integer(0);
+    private Integer  offset = new Integer(0);
+    private Integer  limit = new Integer(0);
+    private TreeMap  results = new TreeMap();
+    private Set      categories = new TreeSet();
+    private boolean  websiteSpecificSearch = false;
+    private String   errorMessage = null;
+    private boolean  useWrappers = false;
+    
+    /* How many results to display */
+    private static int LIMIT = 10;
+    
+    /* Where to start fetching results */
+    private static int OFFSET = 0;
+    
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(SearchResultsPageModel.class);
+    private static ResourceBundle bundle = 
+        ResourceBundle.getBundle("ApplicationResources");          
+
+    public SearchResultsPageModel(HttpServletRequest request, boolean wrappers) { 
+        useWrappers = wrappers;
+        try {            
+            RollerRequest rreq = RollerRequest.getRollerRequest(request);
+            setWebsiteSpecificSearch(checkForWebsite(request));
+            
+            SearchOperation search =
+                new SearchOperation(RollerFactory.getRoller().getIndexManager());
+            search.setTerm(request.getParameter("q"));
+            setTerm(request.getParameter("q"));
+
+            WebsiteData website = null;
+            if (isWebsiteSpecificSearch()) {
+                website = rreq.getWebsite();
+                search.setWebsiteHandle(rreq.getWebsite().getHandle());
+            }
+
+            if (StringUtils.isNotEmpty(request.getParameter("c"))) {
+                search.setCategory(request.getParameter("c"));
+            }
+
+            // execute search
+            executeSearch(RollerFactory.getRoller(), search);
+
+            if (search.getResultsCount() == -1) {
+                // this means there has been a parsing (or IO) error
+                setErrorMessage(bundle.getString("error.searchProblem"));
+            } else {
+                // Convert the Hits into WeblogEntryData instances.
+                Hits hits = search.getResults();
+                setResults(convertHitsToEntries(rreq, website, hits));
+                setOffset((Integer)request.getAttribute("offset"));
+                setLimit((Integer)request.getAttribute("limit"));
+                if (request.getAttribute("categories") != null) {
+                    Set cats = (Set)request.getAttribute("categories");
+                    if (cats.size() > 0) {
+                        setCategories(cats);
+                    }
+                }
+            }
+            setHits(new Integer(search.getResultsCount()));
+            
+        } catch (IOException ex) {
+            mLogger.error("ERROR: initializing search page model");
+        } catch (RollerException ex) {
+            mLogger.error("ERROR: initializing search page model");
+        }
+    }
+    
+    private void executeSearch(Roller roller, SearchOperation search)
+        throws RollerException {
+        IndexManager indexMgr = roller.getIndexManager();
+        indexMgr.executeIndexOperationNow(search);
+        if (mLogger.isDebugEnabled()) {
+            mLogger.debug("numresults = " + search.getResultsCount());
+        }
+    }
+    
+    /** Look in PathInfo so req.getRemoteUser() doesn't interfere. */
+    private boolean checkForWebsite(HttpServletRequest request) {
+        if (StringUtils.isNotEmpty(
+                request.getParameter(RollerRequest.WEBLOG_KEY))) {
+            return true;
+        }        
+        String pathInfoStr = request.getPathInfo();
+        pathInfoStr = (pathInfoStr!=null) ? pathInfoStr : "";
+        
+        String[] pathInfo = StringUtils.split(pathInfoStr,"/");
+        if ( pathInfo.length > 0 ) {
+            return true; // is a user page
+        }
+        return false;
+    }
+  
+    /**
+     * Iterate over Hits and build sets of WeblogEntryData
+     * objects, placed into Date buckets (in reverse order).
+     * @param rreq
+     * @param website
+     * @param hits
+     * @throws RollerException
+     * @throws IOException
+     */
+    private TreeMap convertHitsToEntries(
+            RollerRequest rreq, WebsiteData website, Hits hits)
+            throws RollerException, IOException {
+        // determine offset (starting point)
+        int ioffset = useOffset(rreq.getRequest());
+        if (ioffset >= hits.length()) ioffset = OFFSET;
+        rreq.getRequest().setAttribute("offset", new Integer(ioffset));
+        
+        // determine limit (number of results to display)
+        int ilimit = useLimit(rreq.getRequest());
+        rreq.getRequest().setAttribute("limit", new Integer(ilimit));
+        if (ioffset + ilimit > hits.length()) ilimit = hits.length()-ioffset;
+        
+        boolean websiteSpecificSearch = checkForWebsite(rreq.getRequest());
+        TreeMap searchResults = new TreeMap(new ReverseComparator());
+        TreeSet categories = new TreeSet();
+        Roller roller = RollerFactory.getRoller();
+        UserManager userMgr = roller.getUserManager();
+        WeblogManager weblogMgr =roller.getWeblogManager();
+        WeblogEntryData entry;
+        Document doc = null;
+        String handle = null;
+        for (int i = ioffset; i < ioffset+ilimit; i++) {
+            entry = null; // reset for each iteration
+            
+            doc = hits.doc(i);
+            handle = doc.getField(FieldConstants.WEBSITE_HANDLE).stringValue();
+            
+            if (websiteSpecificSearch && website != null) {
+                // "wrong user" results have been reported
+                if (handle.equals(rreq.getWebsite().getHandle())) {
+                    // get real entry for display on user's site
+                    entry = weblogMgr.getWeblogEntry(
+                            doc.getField(FieldConstants.ID).stringValue() );
+                }
+            } else {
+                // if user is not enabled, website will be null
+                //entry = buildSearchEntry(website, doc);
+                entry = weblogMgr.getWeblogEntry(
+                        doc.getField(FieldConstants.ID).stringValue() );
+                if (doc.getField(FieldConstants.CATEGORY) != null) {
+                    categories.add(
+                            doc.getField(FieldConstants.CATEGORY).stringValue());
+                }
+            }
+            
+            // maybe null if search result returned inactive user
+            // or entry's user is not the requested user.
+            if (entry != null && useWrappers) {
+                addEntryWrapperToSearchResults(searchResults, WeblogEntryDataWrapper.wrap(entry));
+            } 
+            else if (entry != null && !useWrappers) {
+                addEntryToSearchResults(searchResults, entry);
+            } 
+        }
+        rreq.getRequest().setAttribute("categories", categories);
+        return searchResults;
+    }
+    
+    private void addEntryToSearchResults(
+            TreeMap searchResults, WeblogEntryData entry) {
+        // convert entry's each date to midnight (00m 00h 00s)
+        Date midnight = DateUtil.getStartOfDay( entry.getPubTime() );
+        
+        // ensure we do not get duplicates from Lucene by
+        // using a Set Collection.  Entries sorted by pubTime.
+        TreeSet set = (TreeSet) searchResults.get(midnight);
+        if (set == null) {
+            // date is not mapped yet, so we need a new Set
+            set = new TreeSet( new WeblogEntryComparator() );
+            searchResults.put(midnight, set);
+        }
+        set.add(entry);
+    }
+    
+    private void addEntryWrapperToSearchResults(
+            TreeMap searchResults, WeblogEntryDataWrapper entry) {
+        // convert entry's each date to midnight (00m 00h 00s)
+        Date midnight = DateUtil.getStartOfDay( entry.getPubTime() );
+        
+        // ensure we do not get duplicates from Lucene by
+        // using a Set Collection.  Entries sorted by pubTime.
+        TreeSet set = (TreeSet) searchResults.get(midnight);
+        if (set == null) {
+            // date is not mapped yet, so we need a new Set
+            set = new TreeSet( new WeblogEntryWrapperComparator() );
+            searchResults.put(midnight, set);
+        }
+        set.add(entry);
+    }
+    
+    private int useOffset(HttpServletRequest request) {
+        int offset = OFFSET;
+        if (request.getParameter("o") != null) {
+            try {
+                offset = Integer.valueOf(request.getParameter("o")).intValue();
+            } catch (NumberFormatException e) {
+                // Not a valid Integer
+            }
+        }
+        return offset;
+    }
+    
+    private int useLimit(HttpServletRequest request) {
+        int limit = LIMIT;
+        if (request.getParameter("n") != null) {
+            try {
+                limit = Integer.valueOf(request.getParameter("n")).intValue();
+            } catch (NumberFormatException e) {
+                // Not a valid Integer
+            }
+        }
+        return limit;
+    }
+
+    public String getTerm() {
+        return term;
+    }
+
+    public void setTerm(String term) {
+        this.term = term;
+    }
+
+    public Integer getHits() {
+        return hits;
+    }
+
+    public void setHits(Integer hits) {
+        this.hits = hits;
+    }
+
+    public Integer getOffset() {
+        return offset;
+    }
+
+    public void setOffset(Integer offset) {
+        this.offset = offset;
+    }
+
+    public Integer getLimit() {
+        return limit;
+    }
+
+    public void setLimit(Integer limit) {
+        this.limit = limit;
+    }
+
+    public TreeMap getResults() {
+        return results;
+    }
+
+    public void setResults(TreeMap results) {
+        this.results = results;
+    }
+
+    public Set getCategories() {
+        return categories;
+    }
+
+    public void setCategories(Set categories) {
+        this.categories = categories;
+    }
+
+    public boolean isWebsiteSpecificSearch() {
+        return websiteSpecificSearch;
+    }
+
+    public void setWebsiteSpecificSearch(boolean websiteSpecificSearch) {
+        this.websiteSpecificSearch = websiteSpecificSearch;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchServlet.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/search/SearchServlet.java Mon May  1 15:23:02 2006
@@ -0,0 +1,78 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.search;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.presentation.RollerRequest;
+import org.apache.roller.presentation.velocity.ContextLoader;
+import org.apache.roller.presentation.velocity.PageServlet;
+
+/**
+ * This servlet retrieves (and displays) search results.
+ *
+ * @web.servlet name="SearchServlet" load-on-startup="5"
+ * @web.servlet-init-param name="properties" value="/WEB-INF/velocity.properties"
+ * @web.servlet-mapping url-pattern="/search/*"
+ */
+public class SearchServlet extends PageServlet {
+    
+    static final long serialVersionUID = -2150090108300585670L;
+    
+    private static Log mLogger = LogFactory.getLog(SearchServlet.class);
+    
+    private boolean searchEnabled = true;
+    
+    
+    public void init(ServletConfig config) throws ServletException {
+        
+        super.init(config);
+        
+        // lookup if search is enabled
+        this.searchEnabled = RollerConfig.getBooleanProperty("search.enabled");
+    }
+    
+    /**
+     * Prepare the requested page for execution by setting content type
+     * and populating velocity context.
+     */
+    protected Template prepareForPageExecution(
+            Context ctx,
+            RollerRequest rreq,
+            HttpServletResponse response,
+            org.apache.roller.pojos.Template page)             
+        throws ResourceNotFoundException, RollerException {
+        
+        // search model executes search, makes results available to page
+        SearchResultsPageModel model = 
+            new SearchResultsPageModel(rreq.getRequest(), true);
+        ctx.put("searchResults", model);
+        return super.prepareForPageExecution(ctx, rreq, response, page);
+    }
+}
+
+

Added: incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentAuthenticatorServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentAuthenticatorServlet.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentAuthenticatorServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentAuthenticatorServlet.java Mon May  1 15:23:02 2006
@@ -0,0 +1,97 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * CommentAuthenticatorServlet.java
+ *
+ * Created on January 5, 2006, 12:37 PM
+ */
+
+package org.apache.roller.presentation.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.presentation.velocity.CommentAuthenticator;
+import org.apache.roller.presentation.velocity.DefaultCommentAuthenticator;
+
+
+/**
+ * The CommentAuthenticatorServlet is used for generating the html used for
+ * comment authentication.  This is done outside of the normal rendering process
+ * so that we can cache full pages and still set the comment authentication
+ * section dynamically.
+ *
+ * @web.servlet name="CommentAuthenticatorServlet"
+ * @web.servlet-mapping url-pattern="/CommentAuthenticatorServlet"
+ */
+public class CommentAuthenticatorServlet extends HttpServlet {
+    
+    private static Log mLogger = 
+        LogFactory.getLog(CommentAuthenticatorServlet.class);
+    
+    private CommentAuthenticator authenticator = null;
+    
+    
+    /**
+     * Handle incoming http GET requests.
+     *
+     * We only handle get requests.
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException {
+        
+        PrintWriter out = response.getWriter();
+        
+        response.setContentType("text/html");
+        out.println(this.authenticator.getHtml(null, request, response));
+    }
+    
+    
+    /** 
+     * Initialization.
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+        
+        // lookup the authenticator we are going to use and instantiate it
+        try {
+            String name = RollerConfig.getProperty("comment.authenticator.classname");
+            
+            Class clazz = Class.forName(name);
+            this.authenticator = (CommentAuthenticator) clazz.newInstance();
+            
+        } catch(Exception e) {
+            mLogger.error(e);
+            this.authenticator = new DefaultCommentAuthenticator();
+        }
+
+    }
+    
+    /** 
+     * Destruction.
+     */
+    public void destroy() {}
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentServlet.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/servlets/CommentServlet.java Mon May  1 15:23:02 2006
@@ -0,0 +1,558 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.servlets;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.naming.InitialContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.util.RequestUtils;
+import org.apache.roller.RollerException;
+import org.apache.roller.config.RollerConfig;
+import org.apache.roller.config.RollerRuntimeConfig;
+import org.apache.roller.model.IndexManager;
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.model.WeblogManager;
+import org.apache.roller.pojos.CommentData;
+import org.apache.roller.pojos.UserData;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.RollerContext;
+import org.apache.roller.presentation.RollerRequest;
+import org.apache.roller.presentation.RollerSession;
+import org.apache.roller.presentation.velocity.CommentAuthenticator;
+import org.apache.roller.presentation.weblog.formbeans.CommentFormEx;
+import org.apache.roller.util.SpamChecker;
+import org.apache.roller.util.MailUtil;
+import org.apache.roller.util.StringUtils;
+import org.apache.roller.presentation.cache.CacheManager;
+import org.apache.roller.presentation.velocity.DefaultCommentAuthenticator;
+import org.apache.roller.util.Utilities;
+
+/**
+ * The CommentServlet handles all incoming weblog entry comment posts.
+ * 
+ * We validate each incoming comment based on various comment settings and
+ * if all checks are passed then the comment is saved.
+ * 
+ * Incoming comments are tested against the MT Blacklist. If they are found
+ * to be spam, then they are marked as spam and hidden from view.
+ *
+ * If email notification is turned on, each new comment will result in an
+ * email sent to the blog owner and all who have commented on the same post.
+ * 
+ * @author Allen Gilliland
+ *
+ * @web.servlet name="CommentServlet"
+ * @web.servlet-mapping url-pattern="/comment/*"
+ */
+public class CommentServlet extends HttpServlet {
+    
+    private static Log mLogger = LogFactory.getLog(CommentServlet.class);
+    
+    private static final String EMAIL_ADDR_REGEXP = "^.*@.*[.].{2,}$";
+    
+    private ResourceBundle bundle =
+        ResourceBundle.getBundle("ApplicationResources");
+    
+    private CommentAuthenticator authenticator = null;
+    
+    
+    /** 
+     * Initialization.
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+        
+        // lookup the authenticator we are going to use and instantiate it
+        try {
+            String name = RollerConfig.getProperty("comment.authenticator.classname");
+            
+            Class clazz = Class.forName(name);
+            this.authenticator = (CommentAuthenticator) clazz.newInstance();
+            
+        } catch(Exception e) {
+            mLogger.error(e);
+            this.authenticator = new DefaultCommentAuthenticator();
+        }
+
+    }
+    
+    
+    /**
+     * Handle incoming http GET requests.
+     *
+     * The CommentServlet is not meant to handle GET requests, so we just
+     * redirect these request to the root of the webapp.
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException {
+        
+        // we should never get any GET requests, but just in case
+        response.sendRedirect(request.getContextPath());
+    }
+    
+    
+    /**
+     * Service incoming POST requests.
+     *
+     * Here we handle incoming comment postings.  We will collect the data,
+     * validate it, and save it.
+     */
+    public void doPost(HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException {
+        
+        boolean preview = false;
+        String error = null;
+        String message = null;
+        String entry_permalink = request.getContextPath();
+        
+        String method = request.getParameter("method");
+        if(method == null)
+            method = "post";
+        else if (method.equals("preview"))
+            preview = true;
+        
+        // parse request and validate
+        RollerRequest rreq = RollerRequest.getRollerRequest(request);
+        HttpSession session = request.getSession();
+        
+        // make sure we know the entry this comment is going to
+        WeblogEntryData entry = rreq.getWeblogEntry();
+        if (entry == null || entry.getId() == null) {
+            session.setAttribute(RollerSession.ERROR_MESSAGE, "Cannot post comment to null entry");
+            RequestDispatcher dispatcher = 
+                request.getRequestDispatcher(entry_permalink);
+            dispatcher.forward(request, response);
+            return;
+        }
+            
+        try {
+            // we know what our weblog entry is, so setup our permalink url
+            entry_permalink = entry.getPermaLink();
+            
+            mLogger.debug("Doing comment posting for entry = "+entry_permalink);
+            
+            // check if we even allow comments
+            if(!RollerRuntimeConfig.getBooleanProperty("users.comments.enabled"))
+                throw new Exception("Comments are disabled for this site.");
+            
+            if (!entry.getWebsite().getAllowComments().booleanValue() ||
+                    !entry.getCommentsStillAllowed())
+                throw new Exception("Comments not allowed on this entry");
+            
+            WebsiteData website = entry.getWebsite();
+            
+            // Construct our Comment object from the submitted data
+            WeblogManager mgr = RollerFactory.getRoller().getWeblogManager();
+            CommentFormEx cf = new CommentFormEx();
+            CommentData comment = new CommentData();
+            RequestUtils.populate(cf, request);
+            cf.copyTo(comment, request.getLocale());
+            
+            comment.setWeblogEntry(entry);
+            comment.setRemoteHost(request.getRemoteHost());
+            comment.setPostTime(new java.sql.Timestamp(System.currentTimeMillis()));
+            
+            cf.setWeblogEntry(entry);
+            cf.setPostTime(new java.sql.Timestamp(System.currentTimeMillis()));
+            
+            request.setAttribute("commentForm", cf);
+            request.setAttribute("blogEntry", entry);
+            
+            if (preview) {
+                message = "This is a comment preview only";
+                
+                // If comment contains blacklisted text, warn commenter
+                SpamChecker checker = new SpamChecker();
+                if (checker.checkComment(comment)) {
+                   error = bundle.getString("commentServlet.previewMarkedAsSpam");
+                   mLogger.debug("Comment marked as spam"); 
+                }
+                request.setAttribute("previewComments", "dummy");
+                mLogger.debug("Comment is a preview");
+                
+            } else {
+                if (this.authenticator.authenticate(comment, request)) {
+                    mLogger.debug("Comment passed authentication");
+                    
+                    // If comment contains blacklisted text, mark as spam
+                    SpamChecker checker = new SpamChecker();
+                    if (checker.checkComment(comment)) {
+                       comment.setSpam(Boolean.TRUE);
+                       error = bundle.getString("commentServlet.commentMarkedAsSpam");
+                       mLogger.debug("Comment marked as spam"); 
+                    }
+                     
+                    // If comment moderation is on, set comment as pending
+                    if (website.getCommentModerationRequired()) {
+                        comment.setPending(Boolean.TRUE);   
+                        comment.setApproved(Boolean.FALSE);
+                        message = bundle.getString("commentServlet.submittedToModerator");
+                    } else { 
+                        comment.setPending(Boolean.FALSE);   
+                        comment.setApproved(Boolean.TRUE);
+                    }
+                    
+                    mgr.saveComment(comment);
+                    RollerFactory.getRoller().flush();
+                    reindexEntry(entry);
+                    
+                    // Clear all caches associated with comment
+                    CacheManager.invalidate(comment);
+                    
+                    // Send email notifications
+                    RollerContext rc = RollerContext.getRollerContext();                                
+                    String rootURL = rc.getAbsoluteContextUrl(request);
+                    if (rootURL == null || rootURL.trim().length()==0) {
+                        rootURL = RequestUtils.serverURL(request) + request.getContextPath();
+                    }            
+                    sendEmailNotification(comment, rootURL);
+                    
+                } else {
+                    error = bundle.getString("error.commentAuthFailed");
+                    mLogger.debug("Comment failed authentication");
+                }
+            }
+        } catch (RollerException re) {
+            mLogger.error("ERROR posting comment", re);
+            error = re.getMessage();
+        } catch (Exception e) {
+            error = e.getMessage();
+        }
+        
+        // the work has been done, now send the user back to the entry page
+        if (error != null)
+            session.setAttribute(RollerSession.ERROR_MESSAGE, error);
+        if (message != null)
+            session.setAttribute(RollerSession.STATUS_MESSAGE, message);
+        
+        if(error == null && !preview) {
+            entry_permalink = request.getContextPath()+entry_permalink;            
+            mLogger.debug("comment complete, redirecting to "+entry_permalink);
+            response.sendRedirect(entry_permalink);
+        } else {
+            mLogger.debug("more work needed, forwarding to "+entry_permalink);
+            RequestDispatcher dispatcher = 
+                request.getRequestDispatcher(entry_permalink);
+            dispatcher.forward(request, response);
+        }
+    }
+    
+    
+    /**
+     * Re-index the WeblogEntry so that the new comment gets indexed.
+     */
+    private void reindexEntry(WeblogEntryData entry) 
+        throws RollerException {
+        
+        IndexManager manager = RollerFactory.getRoller().getIndexManager();
+        
+        // remove entry before (re)adding it, or in case it isn't Published
+        manager.removeEntryIndexOperation(entry);
+        
+        // if published, index the entry
+        if (entry.isPublished()) {
+            manager.addEntryIndexOperation(entry);
+        }
+    }
+        
+    /**
+     * Send email notification of comment.
+     *
+     * TODO: Make the addressing options configurable on a per-website basis.
+     */
+    public static void sendEmailNotification(CommentData cd, String rootURL) {
+        
+        // Send commment notifications in locale of server
+        ResourceBundle resources = ResourceBundle.getBundle("ApplicationResources");
+
+        WeblogEntryData entry = cd.getWeblogEntry();
+        WebsiteData site = entry.getWebsite();
+        UserData user = entry.getCreator();
+        
+        // Send e-mail to owner and subscribed users (if enabled)
+        boolean notify = RollerRuntimeConfig.getBooleanProperty("users.comments.emailnotify");
+        if (notify && site.getEmailComments().booleanValue()) {
+            mLogger.debug("Comment notification enabled ... preparing email");
+            
+            // Determine message and addressing options from init parameters
+            boolean separateMessages =
+                    RollerConfig.getBooleanProperty("comment.notification.separateOwnerMessage");
+            boolean hideCommenterAddrs =
+                    RollerConfig.getBooleanProperty("comment.notification.hideCommenterAddresses");
+            
+            //------------------------------------------
+            // --- Determine the "from" address
+            // --- Use either the site configured from address or the user's address
+            
+            String from =
+                    (StringUtils.isEmpty(site.getEmailFromAddress()))
+                    ? user.getEmailAddress()
+                    : site.getEmailFromAddress();
+            
+            //------------------------------------------
+            // --- Build list of email addresses to send notification to
+            
+            List comments = null;
+            try {
+                WeblogManager wMgr = RollerFactory.getRoller().getWeblogManager();
+                // get only approved, non spam comments
+                comments = entry.getComments(true, true); 
+            } catch(RollerException re) {
+                // should never happen
+                comments = new ArrayList();
+            }
+            
+            // Get all the subscribers to this comment thread
+            Set subscribers = new TreeSet();
+            for (Iterator it = comments.iterator(); it.hasNext();) {
+                CommentData comment = (CommentData) it.next();
+                if (!StringUtils.isEmpty(comment.getEmail())) {
+                    // If user has commented twice,
+                    // count the most recent notify setting
+                    if (comment.getNotify().booleanValue()) {
+                        // only add those with valid email
+                        if (comment.getEmail().matches(EMAIL_ADDR_REGEXP)) {
+                            subscribers.add(comment.getEmail());
+                        }
+                    } else {
+                        // remove user who doesn't want to be notified
+                        subscribers.remove(comment.getEmail());
+                    }
+                }
+            }
+            
+            // Form array of commenter addrs
+            String[] commenterAddrs = (String[])subscribers.toArray(new String[0]);
+            
+            //------------------------------------------
+            // --- Form the messages to be sent -
+            // For simplicity we always build separate owner and commenter messages even if sending a single one
+            
+            // Determine with mime type to use for e-mail
+            StringBuffer msg = new StringBuffer();
+            StringBuffer ownermsg = new StringBuffer();
+            boolean escapeHtml = RollerRuntimeConfig.getBooleanProperty("users.comments.escapehtml");
+            
+            if (!escapeHtml) {
+                msg.append("<html><body style=\"background: white; ");
+                msg.append(" color: black; font-size: 12px\">");
+            }
+            
+            if (!StringUtils.isEmpty(cd.getName())) {
+                msg.append(cd.getName() + " "
+                        + resources.getString("email.comment.wrote")+": ");
+            } else {
+                msg.append(resources.getString("email.comment.anonymous")+": ");
+            }
+            
+            msg.append((escapeHtml) ? "\n\n" : "<br /><br />");
+                        
+            msg.append((escapeHtml) ? Utilities.escapeHTML(cd.getContent()) 
+                : Utilities.transformToHTMLSubset(Utilities.escapeHTML(cd.getContent())));
+            
+            msg.append((escapeHtml) ? "\n\n----\n"
+                    : "<br /><br /><hr /><span style=\"font-size: 11px\">");
+            msg.append(resources.getString("email.comment.respond") + ": ");
+            msg.append((escapeHtml) ? "\n" : "<br />");
+
+            // Build link back to comment
+            StringBuffer commentURL = new StringBuffer(rootURL);
+            commentURL.append(entry.getPermaLink());
+            commentURL.append("#comments");
+            
+            if (escapeHtml) {
+                msg.append(commentURL.toString());
+            } else {
+                msg.append("<a href=\""+commentURL+"\">"+commentURL+"</a></span>");
+            }
+            
+            ownermsg.append(msg);
+            
+            // add link to weblog edit page so user can login to manage comments
+            ownermsg.append((escapeHtml) ? "\n\n----\n" :
+                "<br /><br /><hr /><span style=\"font-size: 11px\">");
+            ownermsg.append("Link to comment management page:");
+            ownermsg.append((escapeHtml) ? "\n" : "<br />");
+            
+            StringBuffer deleteURL = new StringBuffer(rootURL);
+            deleteURL.append("/editor/commentManagement.do?method=query&entryid=" + entry.getId());
+            
+            if (escapeHtml) {
+                ownermsg.append(deleteURL.toString());
+            } else {
+                ownermsg.append(
+                        "<a href=\"" + deleteURL + "\">" + deleteURL + "</a></span>");
+                msg.append("</Body></html>");
+                ownermsg.append("</Body></html>");
+            }
+            
+            String subject = null;
+            if ((subscribers.size() > 1) ||
+                    (StringUtils.equals(cd.getEmail(), user.getEmailAddress()))) {
+                subject= "RE: "+resources.getString("email.comment.title")+": ";
+            } else {
+                subject = resources.getString("email.comment.title") + ": ";
+            }
+            subject += entry.getTitle();
+            
+            //------------------------------------------
+            // --- Send message to email recipients
+            try {
+                javax.naming.Context ctx = (javax.naming.Context)
+                new InitialContext().lookup("java:comp/env");
+                Session session = (Session)ctx.lookup("mail/Session");
+                boolean isHtml = !escapeHtml;
+                if (separateMessages) {
+                    // Send separate messages to owner and commenters
+                    sendMessage(session, from,
+                            new String[]{user.getEmailAddress()}, null, null, subject, ownermsg.toString(), isHtml);
+                            if (commenterAddrs.length > 0) {
+                                // If hiding commenter addrs, they go in Bcc: otherwise in the To: of the second message
+                                String[] to = hideCommenterAddrs ? null : commenterAddrs;
+                                String[] bcc = hideCommenterAddrs ? commenterAddrs : null;
+                                sendMessage(session, from, to, null, bcc, subject, msg.toString(), isHtml);
+                                
+                            }
+                } else {
+                    // Single message.  User in To: header, commenters in either cc or bcc depending on hiding option
+                    String[] cc = hideCommenterAddrs ? null : commenterAddrs;
+                    String[] bcc = hideCommenterAddrs ? commenterAddrs : null;
+                    sendMessage(session, from, new String[]{user.getEmailAddress()}, cc, bcc, subject,
+                            ownermsg.toString(), isHtml);
+                }
+            } catch (javax.naming.NamingException ne) {
+                mLogger.error("Unable to lookup mail session.  Check configuration.  NamingException: " + ne.getMessage());
+            } catch (Exception e) {
+                mLogger.warn("Exception sending comment mail: " + e.getMessage());
+                // This will log the stack trace if debug is enabled
+                if (mLogger.isDebugEnabled()) {
+                    mLogger.debug(e);
+                }
+            }
+            
+            mLogger.debug("Done sending email message");
+            
+        } // if email enabled
+    }
+    
+    /**
+     * Send message to author of approved comment
+     *
+     * TODO: Make the addressing options configurable on a per-website basis.
+     */
+    public static void sendEmailApprovalNotification(CommentData cd, String rootURL) {
+        
+        // Send commment notifications in locale of server
+        ResourceBundle resources = ResourceBundle.getBundle("ApplicationResources");
+        
+        WeblogEntryData entry = cd.getWeblogEntry();
+        WebsiteData site = entry.getWebsite();
+        UserData user = entry.getCreator();
+            
+        // Only send email if email notificaiton is enabled
+        boolean notify = RollerRuntimeConfig.getBooleanProperty("users.comments.emailnotify");
+        if (notify && site.getEmailComments().booleanValue()) {
+            mLogger.debug("Comment notification enabled ... preparing email");
+            
+
+                                
+            //------------------------------------------
+            // --- Determine the "from" address
+            // --- Use either the site configured from address or the user's address
+            
+            String from =
+                    (StringUtils.isEmpty(site.getEmailFromAddress()))
+                    ? user.getEmailAddress()
+                    : site.getEmailFromAddress();
+                        
+            //------------------------------------------
+            // --- Form the message to be sent -
+            
+            String subject = resources.getString("email.comment.commentApproved");
+            
+            StringBuffer msg = new StringBuffer();
+            msg.append(resources.getString("email.comment.commentApproved"));
+
+            // Build link back to comment
+            StringBuffer commentURL = new StringBuffer(rootURL);
+            commentURL.append(entry.getPermaLink());
+            commentURL.append("#comments");
+            msg.append(commentURL.toString());
+            
+            //------------------------------------------
+            // --- Send message to author of approved comment
+            try {
+                javax.naming.Context ctx = (javax.naming.Context)
+                new InitialContext().lookup("java:comp/env");
+                Session session = (Session)ctx.lookup("mail/Session");
+                String[] cc = null;
+                String[] bcc = null;
+                sendMessage(session, from, 
+                    new String[] {cd.getEmail()}, 
+                    null, // cc
+                    null, // bcc
+                    subject, msg.toString(), false);
+            } catch (javax.naming.NamingException ne) {
+                mLogger.error("Unable to lookup mail session.  Check configuration.  NamingException: " + ne.getMessage());
+            } catch (Exception e) {
+                mLogger.warn("Exception sending comment mail: " + e.getMessage());
+                // This will log the stack trace if debug is enabled
+                if (mLogger.isDebugEnabled()) {
+                    mLogger.debug(e);
+                }
+            }
+            
+            mLogger.debug("Done sending email message");
+            
+        } // if email enabled
+    }
+    
+    
+    /*
+     * This is somewhat ridiculous, but avoids duplicating a bunch of logic 
+     * in the already messy sendEmailNotification.
+     */
+    static void sendMessage(Session session, String from, String[] to, String[] cc, String[] bcc, String subject,
+            String msg, boolean isHtml) throws MessagingException {
+        if (isHtml)
+            MailUtil.sendHTMLMessage(session, from, to, cc, bcc, subject, msg);
+        else
+            MailUtil.sendTextMessage(session, from, to, cc, bcc, subject, msg);
+    }
+    
+}
+

Added: incubator/roller/trunk/src/org/apache/roller/presentation/servlets/ResourceServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/servlets/ResourceServlet.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/servlets/ResourceServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/servlets/ResourceServlet.java Mon May  1 15:23:02 2006
@@ -0,0 +1,151 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.servlets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.util.Date;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.model.RollerFactory;
+
+
+/**
+ * Resources servlet.  Acts as a gateway to files uploaded by users.
+ *
+ * Since we keep uploaded resources in a location outside of the webapp
+ * context we need a way to serve them up.  This servlet assumes that
+ * resources are stored on a filesystem in the "uploads.dir" directory.
+ *
+ * @author Allen Gilliland
+ *
+ * @web.servlet name="ResourcesServlet"
+ * @web.servlet-mapping url-pattern="/resources/*"
+ */
+public class ResourceServlet extends HttpServlet {
+    
+    private static Log mLogger = LogFactory.getLog(ResourceServlet.class);
+    
+    private String upload_dir = null;
+    private ServletContext context = null;
+    
+    
+    public void init(ServletConfig config) throws ServletException {
+        
+        super.init(config);
+        
+        this.context = config.getServletContext();
+        
+        try {
+            this.upload_dir = RollerFactory.getRoller().getFileManager().getUploadDir();
+            mLogger.debug("upload dir is ["+this.upload_dir+"]");
+        } catch(Exception e) { mLogger.warn(e); }
+        
+    }
+    
+    
+    /** 
+     * Handles requests for user uploaded resources.
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        
+        String context = request.getContextPath();
+        String servlet = request.getServletPath();
+        String reqURI = request.getRequestURI();
+        
+        // URL decoding
+        
+        // Fix for ROL-1065: even though a + should mean space in a URL, folks 
+        // who upload files with plus signs expect them to work without 
+        // escaping. This is essentially what other systems do (e.g. JIRA) to 
+        // enable this.
+        reqURI = reqURI.replaceAll("\\+", "%2B");
+        
+        // now we really decode the URL
+        reqURI = URLDecoder.decode(reqURI, "UTF-8");
+        
+        // calculate the path of the requested resource
+        // we expect ... /<context>/<servlet>/path/to/resource
+        String reqResource = reqURI.substring(servlet.length() + context.length());
+        
+        // now we can formulate the *real* path to the resource on the filesystem
+        String resource_path = this.upload_dir + reqResource;
+        File resource = new File(resource_path);
+        
+        mLogger.debug("Resource requested ["+reqURI+"]");
+        mLogger.debug("Real path is ["+resource.getAbsolutePath()+"]");
+        
+        // do a quick check to make sure the resource exits, otherwise 404
+        if(!resource.exists() || !resource.canRead() || resource.isDirectory()) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        // make sure someone isn't trying to sneek outside the uploads dir
+        File uploadDir = new File(this.upload_dir);
+        if(!resource.getCanonicalPath().startsWith(uploadDir.getCanonicalPath())) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        // does the client already have this file?  if so, then 304
+        Date ifModDate = new Date(request.getDateHeader("If-Modified-Since"));
+        Date lastMod = new Date(resource.lastModified());
+        if(lastMod.compareTo(ifModDate) <= 0) {
+            mLogger.debug("Resource unmodified ... sending 304");
+            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            return;
+        }
+        
+        // looks like we'll be serving up the file ... lets set some headers
+        // set last-modified date so we can do if-modified-since checks
+        // set the content type based on whatever is in our web.xml mime defs
+        response.addDateHeader("Last-Modified", (new Date()).getTime());
+        response.setContentType(this.context.getMimeType(resource.getAbsolutePath()));
+        
+        // ok, lets serve up the file
+        byte[] buf = new byte[8192];
+        int length = 0;
+        OutputStream out = response.getOutputStream();
+        InputStream resource_file = new FileInputStream(resource);
+        while((length = resource_file.read(buf)) > 0)
+            out.write(buf, 0, length);
+        
+        // cleanup
+        out.close();
+        resource_file.close();
+    }
+    
+    
+    public void doPost(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        doGet(request, response);
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/servlets/TrackbackServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/servlets/TrackbackServlet.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/servlets/TrackbackServlet.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/servlets/TrackbackServlet.java Mon May  1 15:23:02 2006
@@ -0,0 +1,259 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * Created on Apr 13, 2003
+ */
+package org.apache.roller.presentation.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.sql.Timestamp;
+import java.util.Date;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.util.RequestUtils;
+import org.apache.roller.config.RollerRuntimeConfig;
+import org.apache.roller.pojos.CommentData;
+import org.apache.roller.util.SpamChecker;
+
+import org.apache.roller.model.RollerFactory;
+import org.apache.roller.model.WeblogManager;
+import org.apache.roller.pojos.WeblogEntryData;
+import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.presentation.RollerContext;
+import org.apache.roller.presentation.RollerRequest;
+import org.apache.roller.presentation.cache.CacheManager;
+import org.apache.roller.util.LinkbackExtractor;
+
+
+/**
+ * Roller's Trackback server implementation. POSTing to this Servlet will add a
+ * Trackback to a Weblog Entrty. For more info on Trackback, read the spec:
+ * <a href="http://www.movabletype.org/docs/mttrackback.html>MT Trackback</a>.
+ *
+ * @web.servlet name="TrackbackServlet"
+ * @web.servlet-mapping url-pattern="/trackback/*"
+ *
+ * @author David M Johnson
+ */
+public class TrackbackServlet extends HttpServlet { 
+    
+    private static Log logger = 
+        LogFactory.getFactory().getInstance(TrackbackServlet.class);
+        
+    /** Request parameter to indicate a trackback "tb" */
+    //private static final String TRACKBACK_PARAM = "tb";
+    
+    /** Request parameter for the trackback "title" */
+    private static final String TRACKBACK_TITLE_PARAM = "title";
+    
+    /** Request parameter for the trackback "excerpt" */
+    private static final String TRACKBACK_EXCERPT_PARAM = "excerpt";
+    
+    /** Request parameter for the trackback "url" */
+    private static final String TRACKBACK_URL_PARAM = "url";
+    
+    /** Request parameter for the trackback "blog_name" */
+    private static final String TRACKBACK_BLOG_NAME_PARAM = "blog_name";
+    
+    /** Key under which the trackback return code will be placed
+     * (example: on the request for the JSPDispatcher) */
+    public static final String TRACKBACK_RETURN_CODE =
+            "BLOJSOM_TRACKBACK_RETURN_CODE";
+    
+    /** Key under which the trackback error message will be placed
+     * (example: on the request for the JSPDispatcher) */
+    public static final String TRACKBACK_MESSAGE =
+            "BLOJSOM_TRACKBACK_MESSAGE";
+    
+    /** Trackback success page */
+    //private static final String TRACKBACK_SUCCESS_PAGE = "trackback-success";
+    
+    /** Trackback failure page */
+    //private static final String TRACKBACK_FAILURE_PAGE = "trackback-failure";
+    
+    /**
+     * Constructor.
+     */
+    public TrackbackServlet() {
+        super();
+    }
+    
+    /**
+     * POSTing to this Servlet will add a Trackback to a Weblog Entrty.
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse res)
+    throws ServletException, IOException {
+        doPost(req,res);
+    }
+    
+    /**
+     * POSTing to this Servlet will add a Trackback to a Weblog Entrty.
+     */
+    protected void doPost(HttpServletRequest req, HttpServletResponse res)
+        throws ServletException, IOException {
+        
+        try {
+            // insure that incoming data is parsed as UTF-8
+            req.setCharacterEncoding("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new ServletException("Can't set incoming encoding to UTF-8");
+        }
+        
+        String url = req.getParameter(TRACKBACK_URL_PARAM);
+        String title = req.getParameter(TRACKBACK_TITLE_PARAM);
+        String excerpt = req.getParameter(TRACKBACK_EXCERPT_PARAM);
+        String blogName = req.getParameter(TRACKBACK_BLOG_NAME_PARAM);
+        
+        if ((title == null) || "".equals(title)) {
+            title = url;
+        }
+        
+        if (excerpt == null) {
+            excerpt = "";
+        } else {
+            if (excerpt.length() >= 255) {
+                excerpt = excerpt.substring(0, 252);
+                excerpt += "...";
+            }
+        }
+        
+        String error = null;
+        boolean verified = true;
+        PrintWriter pw = new PrintWriter(res.getOutputStream());
+        try {
+            if(!RollerRuntimeConfig.getBooleanProperty("users.trackbacks.enabled")) {
+                error = "Trackbacks are disabled for this site";
+            } 
+            else if (title==null || url==null || excerpt==null || blogName==null) {
+                error = "title, url, excerpt, and blog_name not specified.";
+            } 
+            else {                
+                RollerRequest rreq = RollerRequest.getRollerRequest(req);
+                WeblogEntryData entry = rreq.getWeblogEntry();
+                WebsiteData website = entry.getWebsite();
+                boolean siteAllows = website.getAllowComments().booleanValue();
+                
+                if (entry!=null && siteAllows && entry.getCommentsStillAllowed()) {
+                    
+                    // Track trackbacks as comments
+                    CommentData comment = new CommentData();
+                    comment.setContent("[Trackback] "+excerpt);
+                    comment.setName(blogName);
+                    comment.setUrl(url);
+                    comment.setWeblogEntry(entry);
+                    comment.setNotify(Boolean.FALSE);
+                    comment.setPostTime(new Timestamp(new Date().getTime()));
+                    
+                    // If comment contains blacklisted text, mark as spam
+                    SpamChecker checker = new SpamChecker();
+                    if (checker.checkTrackback(comment)) {
+                       comment.setSpam(Boolean.TRUE);
+                       logger.debug("Trackback blacklisted: "+comment.getUrl()); 
+                       error = "REJECTED: trackback contains spam words";
+                    }
+                    // Else, if trackback verification is on...
+                    else if (RollerRuntimeConfig.getBooleanProperty(
+                           "site.trackbackVerification.enabled")) {
+                        
+                        // ...ensure trackbacker actually links to us
+                        RollerContext rctx= RollerContext.getRollerContext();
+                        String absurl = rctx.getAbsoluteContextUrl();
+                        LinkbackExtractor linkback = new LinkbackExtractor(
+                            comment.getUrl(), absurl + entry.getPermaLink());
+                        if (linkback.getExcerpt() == null) {
+                           comment.setPending(Boolean.TRUE);
+                           comment.setApproved(Boolean.FALSE);
+                           verified = false;
+                           // if we can't verify trackback, then reject it
+                           error = "REJECTED: trackback failed verification";
+                           logger.debug("Trackback failed verification: "+comment.getUrl());
+                        }
+                    }
+                    
+                    if (error == null) {
+                        // If comment moderation is on, set comment as pending
+                        if (verified && website.getCommentModerationRequired()) {
+                            comment.setPending(Boolean.TRUE);   
+                            comment.setApproved(Boolean.FALSE);
+                        } else if (verified) { 
+                            comment.setPending(Boolean.FALSE);   
+                            comment.setApproved(Boolean.TRUE);
+                        } 
+
+                        // save, commit, send response
+                        WeblogManager mgr = RollerFactory.getRoller().getWeblogManager();
+                        mgr.saveComment(comment);
+                        RollerFactory.getRoller().flush();
+
+                        // Clear all caches associated with comment
+                        CacheManager.invalidate(comment);
+
+                        // Send email notifications
+                        RollerContext rc = RollerContext.getRollerContext();                                
+                        String rootURL = rc.getAbsoluteContextUrl(req);
+                        if (rootURL == null || rootURL.trim().length()==0) {
+                            rootURL = RequestUtils.serverURL(req) + req.getContextPath();
+                        } 
+                        CommentServlet.sendEmailNotification(comment, rootURL);
+
+                        pw.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
+                        pw.println("<response>");
+                        pw.println("<error>0</error>");
+                        if (comment.getPending().booleanValue()) {
+                            pw.println("<message>Trackback sumitted to moderation</message>");
+                        } else {
+                            pw.println("<message>Trackback accepted</message>");
+                        }
+                        pw.println("</response>");
+                        pw.flush();
+                    }
+                    
+                } else if (entry!=null) {
+                    error = "Comments and Trackbacks are disabled for the entry you specified.";
+                } else {
+                    error = "Entry not specified.";
+                }
+            }
+            
+        } catch (Exception e) {
+            error = e.getMessage();
+            if ( error == null ) {
+                error = e.getClass().getName();
+            }
+        }
+        
+        if ( error!= null ) {
+            pw.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
+            pw.println("<response>");
+            pw.println("<error>1</error>");
+            pw.println("<message>ERROR: "+error+"</message>");
+            pw.println("</response>");
+            pw.flush();
+        }
+        res.flushBuffer();
+        
+        // TODO : FindBugs thinks 'pw' should close
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/apache/roller/presentation/tags/DateTag.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/tags/DateTag.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/tags/DateTag.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/tags/DateTag.java Mon May  1 15:23:02 2006
@@ -0,0 +1,142 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.roller.presentation.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.Globals;
+import org.apache.struts.action.ActionMapping;
+import org.apache.struts.util.RequestUtils;
+
+/**
+ * Struts-based date field tag that wraps Matt Kruze's JavaScript data chooser.
+ * @jsp.tag name="Date"
+ */
+public class DateTag extends TagSupport {
+    static final long serialVersionUID = 1485100916981692535L;
+    
+    // Unique key prefix keeps us from colliding with other tags and user data
+    public static final String KEY_PREFIX = "ZZZ_DATETAG_ZZZ";
+    
+    private String property = null;
+    private String dateFormat = null;
+    private Boolean readOnly = Boolean.FALSE;
+    private String formName = null;
+    
+    private static Log mLogger =
+            LogFactory.getFactory().getInstance(DateTag.class);
+    
+    /**
+     * Renders date field by calling a JSP page.
+     */
+    public int doStartTag() throws JspException {
+        
+        // Get form name
+        ActionMapping mapping =
+                (ActionMapping) pageContext.getRequest().getAttribute(
+                Globals.MAPPING_KEY);
+        if (formName == null) {
+            formName = mapping.getName();
+        }
+        
+        // Get value of form field
+        Object value =
+                RequestUtils.lookup(pageContext, formName, property, null);
+        if (value == null)
+            value = "";
+        
+        // put variables into request scope for view page
+        pageContext.getRequest().setAttribute(
+                KEY_PREFIX + "_formName",
+                formName);
+        pageContext.getRequest().setAttribute(
+                KEY_PREFIX + "_property",
+                property);
+        pageContext.getRequest().setAttribute(
+                KEY_PREFIX + "_dateFormat",
+                dateFormat);
+        pageContext.getRequest().setAttribute(
+                KEY_PREFIX + "_readOnly",
+                readOnly);
+        pageContext.getRequest().setAttribute(KEY_PREFIX + "_value", value);
+        
+        // dispatch to view page
+        try {
+            pageContext.include("/tags/date.jsp");
+        } catch (Exception e) {
+            // can't handle this here
+            throw new JspException("ERROR including date.jsp");
+        }
+        
+        // Don't evaluate content of tag, just continue processing this page
+        return (SKIP_BODY);
+    }
+    
+    /**
+     * Date format string to be used.
+     *
+     * @jsp.attribute required="true" rtexprvalue="true" type="java.lang.String"
+     */
+    public String getDateFormat() {
+        return dateFormat;
+    }
+    
+    public void setDateFormat(String dateFormat) {
+        this.dateFormat = dateFormat;
+    }
+
+    /**
+     * Name of form property represented.
+     * @jsp.attribute required="true" rtexprvalue="true" type="java.lang.String"
+     */
+    public String getProperty() {
+        return property;
+    }
+    
+    public void setProperty(String property) {
+        this.property = property;
+    }
+    
+    /**
+     * True if field should be readOnly.
+     * @jsp.attribute required="false" rtexprvalue="true" type="java.lang.Boolean"
+     */
+    public Boolean getReadOnly() {
+        return readOnly;
+    }
+        
+    public void setReadOnly(Boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+    
+    /**
+     * Form name, only needed when more than one form on page.
+     * @jsp.attribute required="false" rtexprvalue="true" type="java.lang.String"
+     */
+    public String getFormName() {
+        return formName;
+    }
+        
+    public void setFormName(String formName) {
+        this.formName = formName;
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/tags/HybridTag.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/tags/HybridTag.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/tags/HybridTag.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/tags/HybridTag.java Mon May  1 15:23:02 2006
@@ -0,0 +1,96 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+*  contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+/*
+ * HybridTag.java
+ *
+ * Created on February 10, 2002, 11:12 PM
+ */
+
+package org.apache.roller.presentation.tags;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * JSP tag designed to be used from JSP page or from Velocity page.
+ * Tag must be a standalone tag, design precludes contents.
+ * @author David M Johnson
+ */
+public abstract class HybridTag extends TagSupport 
+{
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(HybridTag.class);
+
+    public HybridTag() 
+    {
+    }
+
+	public String toString()
+	{
+        String ret = null;
+        try 
+        {
+            StringWriter sw = new StringWriter();
+            doStartTag( new PrintWriter( sw, true ));
+			// See, design precludes contents 
+            doEndTag( new PrintWriter( sw, true ));
+            ret = sw.toString();
+        }
+        catch (Exception e)
+        {
+            ret = "Exception in tag";
+            mLogger.error(ret,e);
+        }
+        return ret;
+	}
+    
+	public String emit()
+	{
+		return toString();
+	}
+
+	public int doStartTag() throws JspException 
+	{
+		return doStartTag( new PrintWriter( pageContext.getOut(), true) );
+	}
+
+
+	public int doEndTag() throws JspException 
+	{
+		return doEndTag( new PrintWriter( pageContext.getOut(), true) );
+	}
+
+	/** Default processing of the end tag returning SKIP_BODY. */
+	public int doStartTag( PrintWriter pw ) throws JspException
+	{
+		return SKIP_BODY;
+	}
+
+	/** Default processing of the end tag returning EVAL_PAGE. */
+	public int doEndTag( PrintWriter pw ) throws JspException
+	{
+		return EVAL_PAGE;
+	}
+
+}

Added: incubator/roller/trunk/src/org/apache/roller/presentation/tags/LinkParamTag.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/apache/roller/presentation/tags/LinkParamTag.java?rev=398712&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/presentation/tags/LinkParamTag.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/presentation/tags/LinkParamTag.java Mon May  1 15:23:02 2006
@@ -0,0 +1,192 @@
+/*
+ * $Header: /cvs/roller/roller/src/org/roller/presentation/tags/LinkParamTag.java,v 1.2 2004/08/18 03:19:34 lavandowska Exp $
+ * $Revision: 1.2 $
+ * $Date: 2004/08/18 03:19:34 $
+ *
+ * ====================================================================
+ */
+
+
+package org.apache.roller.presentation.tags;
+
+import org.apache.log4j.Category;
+import org.apache.struts.util.RequestUtils;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyContent;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+
+/**
+ * Implements a custom tag to add parameter to a href request.<br/>
+ * This tag is intended to be nested in a <code>hm:link</code> tag.
+ *
+ * Title:        BSquare
+ * Description:  Bsquare Projects
+ * Copyright:    Copyright (c) 2001
+ * Company:      HubMethods
+ * @author Eric Fesler
+ * @version 1.0
+ */
+
+public class LinkParamTag extends BodyTagSupport {
+    // ----------------------------------------------------- Logging
+    static Category cat = Category.getInstance(LinkParamTag.class);
+
+    // ----------------------------------------------------- Instance variables
+    /**
+     * The name of the request parameter
+     */
+    private String id = null;
+
+    /**
+     * The value of the request parameter
+     */
+    private String value = null;
+
+    /**
+     * The source bean
+     */
+    private String name = null;
+
+    /**
+     * The source bean property
+     */
+    private String property = null;
+
+    /**
+     * The scope of the source bean
+     */
+    private String scope = null;
+
+
+    // ----------------------------------------------------- Properties
+
+    /**
+     * Sets the request parameter tag name
+     *
+     * @param name the request parameter tag name
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the request parameter name
+     *
+     * @return the request parameter name
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Sets the request parameter value
+     *
+     * @param value the request parameter value
+     */
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the request parameter value
+     *
+     * @return the request parameter value
+     */
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * Sets the source bean name
+     * @param sourceBean the source bean name
+     */
+    public void setName ( String sourceBean) {
+        this.name = sourceBean;
+    }
+
+    /**
+     * Returns the source bean name
+     * @return the source bean name
+     */
+    public String getName () {
+        return this.name;
+    }
+
+    /**
+     * Sets the source bean property
+     * @param sourceProperty the source property
+     */
+    public void setProperty(String sourceProperty) {
+        this.property = sourceProperty;
+    }
+
+    /**
+     * Returns the source bean property
+     * @return the source property
+     */
+    public String getProperty() {
+        return property;
+    }
+
+    /**
+     * Set the source bean scope.
+     * @param sourceScope the source bean scope
+     */
+    public void setScope(String sourceScope) {
+        this.scope = sourceScope;
+    }
+
+    /**
+     * Returns the source bean scope
+     * @return the source bean scope
+     */
+    public String getScope() {
+        return this.scope;
+    }
+
+
+    // ------------------------------------------------------ Public Methods
+    /**
+     * Add the parameter and its value to the link tag
+     */
+    public int doEndTag() throws JspException {
+        // parent tag must be a LinkTag, gives access to methods in parent
+    LinkTag myparent = (LinkTag)javax.servlet.jsp.tagext.TagSupport.findAncestorWithClass(this, LinkTag.class);
+
+    if (myparent == null)
+        throw new JspException("linkparam tag not nested within link tag");
+        else {
+            BodyContent bodyContent = getBodyContent();
+            if (bodyContent != null && !bodyContent.getString().equals("")) {
+                setValue(bodyContent.getString());
+            }
+            else if (getValue() == null) setValue("null");
+//                throw new JspException("Unable to assign a value to the parameter: '" + getId() + "'");
+        myparent.addRequestParameter(getId(), getValue());
+    }
+    return SKIP_BODY;
+    }
+
+    /**
+     * Process the start tag
+     */
+    public int doStartTag() throws javax.servlet.jsp.JspException {
+
+        // Look up the requested property value
+        if (name != null) {
+            Object beanValue =
+                RequestUtils.lookup(pageContext, name, property, scope);
+            if (cat.isDebugEnabled()) cat.debug("Value is : '" + beanValue + "'");
+            if (beanValue == null)
+                return (EVAL_BODY_TAG);
+
+        // set the property as value
+            setValue(beanValue.toString());
+        }
+
+    // Continue processing this page
+    return (EVAL_BODY_TAG);
+
+    }
+}