diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/CMS.java b/ccm-cms/src/main/java/com/arsdigita/cms/CMS.java new file mode 100755 index 000000000..7547a26c2 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/CMS.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2002-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms; + + +import org.apache.log4j.Logger; + +/** + *
+ * A central location for commonly used CMS services and their accessories.
+ * + *+ * Context. {@link #getContext()} fetches the context record ({@link + * com.arsdigita.kernel.KernelContext}) of the current thread.
+ * + * @author Daniel Berrange + * @see com.arsdigita.kernel.Kernel + * @version $Id$ + */ +public abstract class CMS { + + /** + * Private Logger instance for debugging purpose. + */ + private static final Logger s_log = Logger.getLogger(CMS.class); + + /** + * The CMS XML namespace. + */ + public final static String CMS_XML_NS = "http://www.arsdigita.com/cms/1.0"; + + /** + * Constant string used as key for creating Workspace (content-center) as a + * legacy application. + */ + public static final String WORKSPACE_PACKAGE_KEY = "content-center"; + + /** + * Constant string used as key for creating service package as a legacy + * application. + */ + public final static String SERVICE_PACKAGE_KEY = "cms-service"; + + static final CMSContext s_initialContext = new CMSContext(); + + private static final ThreadLocal s_context = new ThreadLocal() { + + @Override + public Object initialValue() { + return s_initialContext; + } + + }; + + /** + * Get the context record of the current thread. + * + * @post return != null + */ + public static final CMSContext getContext() { + return (CMSContext) s_context.get(); + } + + static final void setContext(CMSContext context) { + s_context.set(context); + } + + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/CMSContext.java b/ccm-cms/src/main/java/com/arsdigita/cms/CMSContext.java new file mode 100755 index 000000000..77f6a355a --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/CMSContext.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2002-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms; + +import com.arsdigita.util.Assert; + +import org.apache.log4j.Logger; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; + + +/** + *The entry point into all the global state that CCM CMS code expects to + * have available to it when running, e.g. the current content section, + * current item + * + *
This is a session object that provides an environment in which + * code can execute. The CMSContext contains all session-specific + * variables. One session object is maintained per thread.
+ * + *Accessors of this class will assert that the item it returned is
+ * not null. If the caller wants to handle the case where an item is
+ * null explicitly, then use the hasContentItem and hasContentSection
+ * methods first.
+ *
+ * @see com.arsdigita.kernel.KernelContext
+ * @see com.arsdigita.cms.CMS
+ *
+ * @author Daniel Berrange
+ * @version $Id$
+ */
+public final class CMSContext {
+
+ private static final Logger s_log = Logger.getLogger(CMSContext.class);
+
+ private ContentSection m_section = null;
+ private ContentItem m_item = null;
+ private SecurityManager m_security = null;
+
+ CMSContext() {
+ // Empty
+ }
+
+ public final String getDebugInfo() {
+ final String info = "Current state of " + this + ":\n" +
+ " getContentSection() -> " + getContentSection() + "\n" +
+ " getContentItem() -> " + getContentItem() + "\n" +
+ " getSecurityManager() -> " + getSecurityManager();
+
+ return info;
+ }
+
+ final CMSContext copy() {
+ final CMSContext result = new CMSContext();
+
+ result.m_section = m_section;
+ result.m_item = m_item;
+ result.m_security = m_security;
+
+ return result;
+ }
+
+ /**
+ * Checks if a content section is available
+ * @return true if a content section is available
+ */
+ public final boolean hasContentSection() {
+ return m_section != null;
+ }
+
+ /**
+ * Gets the current content section
+ * not anymore: hasContentSection() == true
+ * @return the currently selected content section
+ */
+ public final ContentSection getContentSection() {
+ // removing this which is not true when viewing category pages
+ //Assert.exists(m_section, "section");
+ return m_section;
+ }
+
+ /**
+ * Sets the current content section
+ * @param section the new content section
+ */
+ public final void setContentSection(final ContentSection section) {
+ m_section = section;
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Content section set to " + section);
+ }
+ }
+
+ /**
+ * Checks if a content item is available
+ * @return true if a content item is available
+ */
+ public final boolean hasContentItem() {
+ return m_item != null;
+ }
+
+ /**
+ * Returns the current content item
+ * @pre hasContentItem() == true
+ * @return the current content item
+ */
+ public final ContentItem getContentItem() {
+ // removing this which is necessarily true in ContentList
+ //Assert.exists(m_item, "item");
+ if (s_log.isDebugEnabled() && m_item == null) {
+ s_log.debug("Content item is null");
+ }
+ return m_item;
+ }
+
+ /**
+ * Sets the current content item
+ * @param item the new content item
+ */
+ public final void setContentItem(final ContentItem item) {
+ m_item = item;
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Content item set to " + item);
+ }
+ }
+
+ /**
+ * Checks if there is a CMS
+ * The CMS Dispatcher serves all request made within a content section. This
+ * dispatcher is called by the Subsite dispatcher.
+ * Here are the steps for a request to
+ * http://yourserver/cms/cheese in excruciating detail:
+ * A client sends a request to the web server, which passes it on to the global
+ * ACS dispatcher.
+ * The global ACS dispatcher examines the first part of the URL, notices that
+ * CMS is mounted at /cms and hands the request to the CMS
+ * dispatcher.
+ * The CMS dispatcher determines whether a Page has been registered to
+ * the URL /cheese in this section via its
+ * {@link com.arsdigita.cms.dispatcher.PageResolver}.
+ * Since no page is registered to the URL, the CMS dispatcher asks the content
+ * section (via its {@link com.arsdigita.cms.dispatcher.ItemResolver}) for a
+ * content item for /cheese in this content section. The result of this
+ * process is a {@link com.arsdigita.cms.ContentItem} object.
+ * The CMS dispatcher asks the content section for a Page
+ * to use as the "master template" for this item. The content section may apply
+ * item-, type-, or request-specific rules to make this decision (for example,
+ * check a user preference for normal or accessible style, or a query parameter
+ * for a printable version).
+ * The CMS dispatcher hands the master Page object to the
+ * {@link com.arsdigita.sitenode.SiteNodePresentationManager} to serve the
+ * page.
+ * The presentation manager asks the master Page object for an XML
+ * document representing the data for the page.
+ * The master template begins walking through its component hierarchy,
+ * converting each component to XML by calling its
+ * generateXML method. The component responsible for rendering the
+ * content item uses an {@link com.arsdigita.cms.dispatcher.XMLGenerator} to
+ * convert the content item to XML.
+ * The presentation manager receives the completed XML document, and selects an
+ * XSL transformer to use for generating the HTML. The stylesheet on which the
+ * transformer is based contains templates for all styles and all content types
+ * in the content section, in particular those from the file
+ * cms-item.xsl. A CMSPage is a Bebop {@link com.arsdigita.bebop.Page}
+ * implementation of the {@link com.arsdigita.cms.dispatcher.ResourceHandler}
+ * interface. It stores the current {@link com.arsdigita.cms.ContentSection} and, if
+ * applicable, the {@link com.arsdigita.cms.ContentItem} in the page state as
+ * request local objects. Components that are part of the CMSPage
+ * may access these objects by calling:
+ * This In general, the process for resolving a template involves two
+ * steps: The ItemResolver is responsible for mapping a URL in a
+ * particular content section to a content item. As an example, here is the item resolution process for a request to
+ * http://yourserver/cms/cheese: The item resolver would be asked to map the URL stub /cheese
+ * in the content section mounted at /cms to a content item. To
+ * this end, the dispatcher calls the getItem method, passing in
+ * the {@link com.arsdigita.cms.ContentSection} and the URL stub for the
+ * item within the section, /cheese in our example. As a final
+ * argument, the dispatcher passes either ContentItem.DRAFT or
+ * ContentItem.LIVE to the ItemResolver, depending on
+ * whether the returned item should be the live version (for public pages)
+ * or the draft version (for previewing). A {@link com.arsdigita.cms.dispatcher.CMSPage} used for serving
+ * content items. This page contains a When a client
+ * retrieves this URL, the URL is resolved to point to a specific
+ * language instance of the item referenced here, i.e. this URL
+ * will be resolved to a language-specific URL
+ * internally.
+ *
+ * @param section the Only a specific language
+ * instance can be previewed, meaning there no language
+ * negotiation is involved when a request is made to a URL that
+ * has been generated by this method.
+ *
+ * @param section The This class contains methods for registering and resolving {@link
+ * ResourceHandler CMS resources} in a specific content section. The PageResolver includes methods for caching resource
+ * mappings. The default XMLGenerator implementation.
+ * Many sites offer alternative views of the same content item depending on
+ * device or browser, or on user preference. For example, a site may have
+ * "plain" and "fancy" versions of its pages. The fancy versions would be the
+ * defaults, while the plain versions would be appropriate for users with
+ * low-bandwidth connections, older browsers, or a distaste for flashy
+ * appurtenances. In this the case the selection might be made based on a
+ * cookie.
+ * Another common example is the "printable" version of a page. In this case a
+ * query variable might be more appropriate. This class provides many utility functions for the Content Management
+ * System. Generates XML representing a Content Item. As the last step of servicing a page, the
+ * {@link com.arsdigita.cms.dispatcher.MasterPage} will go through the
+ * hierarchy of its components and ask each of them to convert themselves
+ * to XML. A MasterPage contains a special component that knows how to ask
+ * its content section for the XML generator that should be applied. The
+ * XML generator's The Item Search page.
+ * The Content Center main page.
+ * You do not need to extend this class unless your dispatcher is also
+ * a servlet and is mounted in web.xml. In any given ACS installation, you
+ * generally only have one servlet that is mounted through web.xml, and that is
+ * usually the
+ * When a request comes in:
+ *
+ *
+ * If the concrete file is a directory, then we require that the directory
+ * have a welcome file like index.*; this prevents us from serving directory
+ * listings. For directories we return STATIC_FILE if there is a welcome
+ * file, otherwise return NOT_FOUND.
+ *
+ * @return STATIC_FILE if the current request points to a concrete static
+ * file (non-JSP) or a directory that has a welcome file. returns
+ * JSP_FILE if it corresponds to a dynamic JSP file. returns
+ * NOT_FOUND otherwise.
+ */
+ private int concreteFileType(HttpServletRequest req)
+ throws ServletException, IOException {
+
+ String path = DispatcherHelper.getCurrentResourcePath(req);
+
+ ServletContext sctx = this.getServletContext();
+ File realFile = new File(sctx.getRealPath(path));
+ if (realFile.exists() && (!realFile.isDirectory() || hasWelcomeFile(
+ realFile))) {
+ // yup. Go there, bypass the site map.
+ // we have a concrete file so no forwarding to
+ // rewrite the request URL is necessary.
+ if (realFile.getName().endsWith(".jsp")) {
+ return JSP_FILE;
+ } else {
+ return STATIC_FILE;
+ }
+ } else {
+ return NOT_FOUND;
+ }
+ }
+
+ /**
+ * returns true if dir is a directory and has a welcome file like index.*.
+ *
+ * @pre dir.isDirectory()
+ */
+ private boolean hasWelcomeFile(File dir) {
+ if (!dir.isDirectory()) {
+ throw new IllegalArgumentException("dir must be a directory");
+ }
+ String[] files = dir.list();
+ for (int i = 0; i < files.length; i++) {
+ if (m_welcomeFiles.indexOf(files[i]) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean trailingSlashRedirect(HttpServletRequest req,
+ HttpServletResponse resp)
+ throws IOException {
+ String path = DispatcherHelper.getCurrentResourcePath(req);
+ // first, see if we have an extension
+ if (path.lastIndexOf(".") <= path.lastIndexOf("/")) {
+ // maybe no extension. check if there's a trailing
+ // slash already.
+ if (!path.endsWith("/")) {
+ // no trailing slash
+ String targetURL = req.getContextPath() + path + "/";
+ String query = req.getQueryString();
+ if (query != null && query.length() > 0) {
+ targetURL += "?" + query;
+ }
+ resp.sendRedirect(resp.encodeRedirectURL(targetURL));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * SAX content handler class to pick welcome-file-list out of web.xml
+ */
+ private class WebXMLReader extends DefaultHandler {
+
+ StringBuffer m_buffer = new StringBuffer();
+
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException {
+ // we don't want to read the web.xml dtd
+ if (WEB_XML_22_PUBLIC_ID.equals(publicId)
+ || WEB_XML_23_PUBLIC_ID.equals(publicId)) {
+ StringReader reader = new StringReader(" ");
+ return new InputSource(reader);
+ } else {
+ try {
+ return super.resolveEntity(publicId, systemId);
+ } catch (Exception e) {
+ if (e instanceof SAXException) {
+ throw (SAXException) e;
+ } else {
+ throw new UncheckedWrapperException("Resolve Error", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int len) {
+ for (int i = 0; i < len; i++) {
+ m_buffer.append(ch[start + i]);
+ }
+ }
+
+ @Override
+ public void endElement(String uri,
+ String localName,
+ String qname) {
+ if (qname.equals("welcome-file-list")) {
+ String[] welcomeFiles = StringUtils.split(m_buffer.toString(),
+ ',');
+ for (int i = 0; i < welcomeFiles.length; i++) {
+ m_welcomeFiles.add(welcomeFiles[i].trim());
+ }
+ }
+ m_buffer = new StringBuffer();
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/ChainedDispatcher.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/ChainedDispatcher.java
new file mode 100755
index 000000000..60d1aec5d
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/ChainedDispatcher.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This interface defines
+ * a dispatcher that is intended to be chained together with other dispatchers.
+ * The functional difference between ChainedDispatcher and Dispatcher is
+ * that a ChainedDispatcher's chainedDispatch() method can return a status
+ * code to indicate that it was successful or unsuccessful in finding
+ * a resource to dispatch to.
+ *
+ * This interface is mainly used in conjunction with
+ * DispatcherChain, a general-purpose dispatcher that joins many
+ * different ChainedDispatchers together in a chain of different
+ * URL-to-resource mappings; if one cannot find a resource, the next
+ * one is tried. This is useful when an application has several
+ * different methods to find a resource for a URL and each method can
+ * be separated into a re-usable module.
+ *
+ * For example, suppose an application resolves a URL to a resource
+ * like this:
+ *
+ *
+ *
+ * If a dispatcher object for a package also implements HttpServlet,
+ * then it is also an entry point for the entire web application.
+ *
+ * @author Bill Schneider
+ * @version $Id$
+ */
+
+public interface Dispatcher {
+
+
+ /**
+ * Dispatches this request.
+ * @param request the servlet request object
+ * @param response the servlet response object
+ * @param actx the request context
+ * @exception java.io.IOException may be thrown by the dispatcher
+ * to indicate an I/O error
+ * @exception javax.servlet.ServletException may be thrown by the
+ * dispatcher to propagate a generic error to its caller
+ */
+ public void dispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException;
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherChain.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherChain.java
new file mode 100755
index 000000000..c1c6caff0
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherChain.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Generic implementation
+ * of "try this URL-resource mapping; if nothing found try ..."
+ * pattern. This is useful for packages whose URL-to-resource mapping
+ * is a composition of many separate, reusable mappings. The goal is to
+ * reduce multi-branched, hard-coded if-else blocks.
+ *
+ * This class makes it easier to break up dispatchers into a series
+ * of smaller, re-usable, but not totally self-contained classes whose
+ * dispatch method tries to find a resource according to its mapping,
+ * and serves it if it finds one. If it can't find a resource, it
+ * returns a failure
+ * status code (DISPATCHER_CONTINUE) and the DispatcherChain tries the
+ * next dispatcher in the sequence.
+ *
+ * The dispatcher chain tries each dispatcher in the dispatcher
+ * chain successively in the order in which they were added to the chain.
+ *
+ * @version $Id$
+ */
+public class DispatcherChain implements Dispatcher {
+
+ private LinkedList m_dispatcherChain = new LinkedList();
+
+ /**
+ * Dispatches to the dispatcher chain. Tries each dispatcher
+ * in the sequence, in which they were added, and breaks out of
+ * the loop when either one dispatcher returns DISPATCH_BREAK or
+ * a dispatcher throws an exception.
+ *
+ * @param req the current servlet request
+ * @param resp the current servlet response object
+ * @param ctx the current
+ * Given an application context that contains a SiteNode, and a
+ * remaining URL, tries to resolve the remaining URL into a concrete file: To make the URL mapping in JSPApplicationDispatcher work, you
+ * must map the "jsp" servlet in web.xml to whatever your entry-point
+ * dispatcher is (whatever servlet is mapped to the URL pattern "/").
+ * Otherwise, requests for *.jsp will get picked up by the JSP
+ * container before any ACS dispatchers have a shot at URL mapping.
+ *
+ *
+ * Example: if the application sample-app is mounted on the site nodes
+ * /sample1 and /sample2 then URLs will be dispatched as follows:
+ * SecurityManager for this
+ * session.
+ *
+ * @see com.arsdigita.cms.SecurityManager
+ * @return true if a security manager is available
+ */
+ public final boolean hasSecurityManager() {
+ return m_security != null;
+ }
+
+ /**
+ * Returns the current security manager.
+ *
+ * @return the current security manager
+ */
+ public final SecurityManager getSecurityManager() {
+ Assert.exists(m_security, SecurityManager.class);
+
+ return m_security;
+ }
+
+ public final void setSecurityManager(final SecurityManager security) {
+ m_security = security;
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Security manager set to " + security);
+ }
+ }
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenter.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenter.java
new file mode 100644
index 000000000..24445a50c
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.cms;
+
+import org.libreccm.web.CcmApplication;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class ContentCenter extends CcmApplication {
+
+
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java
new file mode 100644
index 000000000..3b81d23c1
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2016 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.cms;
+
+import com.arsdigita.bebop.Page;
+import com.arsdigita.cms.dispatcher.CMSPage;
+import com.arsdigita.cms.ui.CMSApplicationPage;
+
+import com.arsdigita.dispatcher.DispatcherHelper;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.kernel.security.Util;
+import com.arsdigita.templating.PresentationManager;
+import com.arsdigita.templating.Templating;
+import com.arsdigita.ui.login.LoginHelper;
+import com.arsdigita.web.ApplicationFileResolver;
+import com.arsdigita.web.BaseApplicationServlet;
+import com.arsdigita.web.LoginSignal;
+import com.arsdigita.web.WebConfig;
+import com.arsdigita.xml.Document;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.shiro.authz.AuthorizationException;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.PermissionChecker;
+import org.libreccm.security.Shiro;
+import org.libreccm.web.CcmApplication;
+import org.librecms.CmsConstants;
+import org.librecms.contentsection.ContentSection;
+import org.librecms.contentsection.ContentSectionRepository;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * CMS ContentCenter (content-center) application servlet serves all request
+ * made within the Content Center application.
+ *
+ * @author Peter Boy inUrl.
+ *
+ * @param inUrl URL, possibly including the template context.
+ *
+ * @return inUrl with the template context removed
+ */
+ public String stripTemplateFromURL(String inUrl) {
+ String sTemplateContext = getTemplateFromURL(inUrl);
+
+ if (sTemplateContext != null) {
+ //there is a template context, so strip it
+ int iTemplateLength = TEMPLATE_CONTEXT_PREFIX.length()
+ + sTemplateContext.length() + 1;
+ String sStripped = inUrl.substring(iTemplateLength, inUrl.length());
+ return sStripped;
+ } else {
+ return inUrl;
+ }
+ }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSDispatcher.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSDispatcher.java
new file mode 100755
index 000000000..e4a07c402
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSDispatcher.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.dispatcher.ChainedDispatcher;
+import com.arsdigita.dispatcher.Dispatcher;
+import com.arsdigita.dispatcher.DispatcherHelper;
+import com.arsdigita.dispatcher.JSPApplicationDispatcher;
+import com.arsdigita.dispatcher.RedirectException;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.web.LoginSignal;
+import com.arsdigita.web.URL;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.apache.shiro.authz.AuthorizationException;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.PermissionChecker;
+import org.libreccm.security.Shiro;
+import org.libreccm.security.User;
+import org.librecms.CmsConstants;
+import org.librecms.contentsection.ContentItem;
+import org.librecms.contentsection.ContentSection;
+import org.librecms.contentsection.ContentSectionRepository;
+
+/**
+ *
+ *
+ *
+ * @author Michael Pih (pihman@arsdigita.com)
+ * @author Uday Mathur (umathur@arsdigita.com)
+ * @author Jack Chung (flattop@arsdigita.com)
+ * @author Jens Pelzetter
+ */
+public class CMSDispatcher implements Dispatcher, ChainedDispatcher {
+
+ private static Logger s_log = Logger.getLogger(CMSDispatcher.class);
+
+ public static final String CONTENT_SECTION
+ = "com.arsdigita.cms.dispatcher.section";
+
+ public static final String CONTENT_ITEM
+ = "com.arsdigita.cms.dispatcher.item";
+
+ public static final String[] INDEX_FILES = {
+ "index.jsp", "index.html", "index.htm"};
+
+ private static final String DEBUG = "/debug";
+ private static final String ADMIN_SECTION = "admin";
+
+ public static final String ADMIN_URL = "admin/index";
+
+ /**
+ * The context for previewing items
+ */
+ public static final String PREVIEW = "preview";
+
+ // Content section cache
+ private static HashMap s_pageResolverCache = new HashMap();
+ private static HashMap s_itemResolverCache = new HashMap();
+ private static HashMap s_xmlGeneratorCache = new HashMap();
+
+ private boolean m_adminPagesOnly = false;
+
+ public CMSDispatcher() {
+ this(false);
+ }
+
+ public CMSDispatcher(boolean adminOnly) {
+ m_adminPagesOnly = adminOnly;
+ }
+
+ /**
+ * Handles requests made to a CMS package instance. 1) fetches the current
+ * content section 2) fetches the resource mapped to the current section/URL
+ * 3) if no resource, fetches the item associated with the current
+ * section/URL 4) if no item, passes request to the JSP dispatcher, which
+ * serves JSP's, HTML pages, and media from the cms/packages/www directory
+ *
+ * @param request The request
+ * @param response The response
+ * @param actx The request context
+ */
+ public void dispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Dispatching request for " + new URL(request)
+ .toDebugString());
+ }
+
+ // This is the path to the current site node.
+ String processedUrl = actx.getProcessedURLPart();
+ String webappURLContext = request.getContextPath();
+ if (processedUrl.startsWith(webappURLContext)) {
+ processedUrl = processedUrl.substring(webappURLContext.length());
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Determined the path to the current site node; it "
+ + "is '" + processedUrl + "' according to the "
+ + "request context");
+ }
+
+ // This is the path within the site node.
+ String remainingUrl = actx.getRemainingURLPart();
+ if (remainingUrl.endsWith("/")) {
+ remainingUrl = remainingUrl.substring(0, remainingUrl.length() - 1);
+ } else if (remainingUrl.endsWith(ItemDispatcher.FILE_SUFFIX)) {
+ remainingUrl = remainingUrl.substring(0, remainingUrl.length()
+ - ItemDispatcher.FILE_SUFFIX
+ .length());
+ } else if (remainingUrl.equals("")) {
+ remainingUrl = "index";
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Determined the path after the current site node; "
+ + "it is '" + remainingUrl + "'");
+ }
+
+ // Fetch the current content section.
+ ContentSection section = null;
+ try {
+ section = findContentSection(processedUrl);
+ } catch (Exception ex) {
+ throw new ServletException(ex);
+ }
+ request.setAttribute(CONTENT_SECTION, section);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found content section '" + section + "'");
+ }
+
+ // Check user access to this page and deny access if necessary.
+ checkUserAccess(request, response, actx);
+
+ // Look for a site-node-specific asset (if any).
+ // KG: This hack will be replaced by a ChainedDispatcher
+ try {
+ s_log.debug("Looking for a site node asset");
+
+ String siteNodeAssetURL = getSiteNodeAsset(request, actx);
+ if (siteNodeAssetURL != null) {
+ s_log.debug("Site node asset found at '" + siteNodeAssetURL
+ + "'");
+
+ DispatcherHelper.cacheDisable(response);
+ DispatcherHelper.setRequestContext(request, actx);
+ DispatcherHelper.forwardRequestByPath(siteNodeAssetURL,
+ request, response);
+ return;
+ }
+
+ s_log.debug("No site node asset found; proceeding with normal "
+ + "dispatching");
+ } catch (RedirectException e) {
+ throw new ServletException(e);
+ }
+
+ // Fetch the requested resource (if any).
+ ResourceHandler resource = getResource(section, remainingUrl);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Got a resource '" + resource + "'");
+ }
+
+ if (resource != null) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found resource '" + remainingUrl + "'; "
+ + "dispatching to it");
+ }
+
+ s_log.info("resource dispatch for " + remainingUrl);
+ // Found resource, now serve it.
+ // NB, ResouceHandler implementations should take care of caching options
+ resource.dispatch(request, response, actx);
+
+ } else {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("No resource found at '" + remainingUrl + "'; "
+ + "searching for a previewable content item at "
+ + "this path");
+ }
+
+ // If the remaining URL starts with "preview/", then try and
+ // preview this item. Otherwise look for the live item.
+ boolean preview = false;
+ if (remainingUrl.startsWith(PREVIEW)) {
+ remainingUrl = remainingUrl.substring(PREVIEW.length());
+ preview = true;
+ }
+
+ // Check for published / previewable item.
+ ContentItem item = null;
+
+ // Check if the user has access to view public pages
+ final PermissionChecker permissionChecker = CdiUtil.createCdiUtil()
+ .findBean(PermissionChecker.class);
+
+ if (permissionChecker.isPermitted(
+ CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item)) {
+ if (preview) {
+ item = getContentItem(section,
+ remainingUrl,
+ CMSDispatcher.PREVIEW);
+ } else {
+ item = getContentItem(section,
+ remainingUrl,
+ "live");
+ }
+ if (item != null) {
+ request.setAttribute(CONTENT_ITEM, item);
+ }
+ }
+
+ if (item != null) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found item " + item + "; serving it");
+ }
+
+ DispatcherHelper.cacheDisable(response);
+ preview(request, response, actx);
+ } else {
+ s_log.debug("No item to preview found; falling back to "
+ + "JSP dispatcher to look for some concrete "
+ + "resource in the file system");
+
+ // If no resource was found, look for a JSP page.
+ JSPApplicationDispatcher jsp = JSPApplicationDispatcher
+ .getInstance();
+ //DispatcherHelper.cacheDisable(response);
+ jsp.dispatch(request, response, actx);
+ }
+
+ }
+
+ }
+
+ public int chainedDispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException {
+ if (m_adminPagesOnly) {
+ String url = actx.getRemainingURLPart();
+
+ if (url.endsWith(ItemDispatcher.FILE_SUFFIX)) {
+ url = url.substring(0, url.length() - ItemDispatcher.FILE_SUFFIX
+ .length());
+ } else if (url.endsWith("/")) {
+ url = url.substring(0, url.length() - 1);
+ }
+
+ if (url.equals(ADMIN_URL)) {
+ if (s_log.isInfoEnabled()) {
+ s_log.info("Resolving admin URL '" + url + "'");
+ }
+
+ dispatch(request, response, actx);
+
+ return ChainedDispatcher.DISPATCH_BREAK;
+ } else {
+ return ChainedDispatcher.DISPATCH_CONTINUE;
+ }
+ }
+
+ dispatch(request, response, actx);
+ return ChainedDispatcher.DISPATCH_BREAK;
+ }
+
+ /**
+ * Verify that the user is logged in and is able to view the page.
+ * Subclasses can override this method if they need to, but should always be
+ * sure to call super.checkUserAccess(...)
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @param actx The request context
+ *
+ * @exception AccessDeniedException if the user does not have access.
+ *
+ */
+ protected void checkUserAccess(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws ServletException, AuthorizationException {
+
+ final Shiro shiro = CdiUtil.createCdiUtil().findBean(Shiro.class);
+ User user = shiro.getUser();
+ final PermissionChecker permissionChecker = CdiUtil.createCdiUtil()
+ .findBean(PermissionChecker.class);
+
+ ContentSection section = getContentSection(request);
+
+ if (isAdminPage(actx.getRemainingURLPart())) {
+
+ // Handle admin page requests.
+ // If the user is not logged in, redirect to the login page.
+ // Otherwise, perform the Admin Pages access check.
+ if (user == null) {
+ redirectToLoginPage(request, response);
+ return;
+ }
+ //if (!sm.canAccess(user, SecurityManager.ADMIN_PAGES)) {
+ permissionChecker.checkPermission(CmsConstants.PRIVILEGE_ITEMS_EDIT,
+ section.getRootDocumentsFolder());
+ } else {
+ // For public page requests, use the SecurityManager to check access
+ // SecurityManager.canAccess(user, SecurityManager.PUBLIC_PAGES) must
+ permissionChecker.checkPermission(
+ CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED,
+ section.getRootDocumentsFolder());
+ }
+ }
+
+ /**
+ * Fetches the content section from the request attributes.
+ *
+ * @param request The HTTP request
+ *
+ * @return The content section
+ *
+ * @pre ( state != null )
+ */
+ public static ContentSection getContentSection(HttpServletRequest request) {
+ return (ContentSection) request.getAttribute(CONTENT_SECTION);
+ }
+
+ /**
+ * Fetches the content item from the request attributes.
+ *
+ * @param request The HTTP request
+ *
+ * @return The content item
+ *
+ * @pre ( state != null )
+ */
+ public static ContentItem getContentItem(HttpServletRequest request) {
+ return (ContentItem) request.getAttribute(CONTENT_ITEM);
+ }
+
+ /**
+ * Looks up the current content section using the remaining URL stored in
+ * the request context object and the SiteNode class.
+ *
+ * @param url The section URL stub
+ *
+ * @return The current Content Section
+ */
+ protected ContentSection findContentSection(String url) {
+
+ // MP: This is a hack to get the debugging info in
+ // SiteNodePresentationManager.servePage, but since it's
+ // debugging info...
+ // Remove /debug from the start of the URL if it exists.
+ if (url.startsWith(DEBUG)) {
+ url = url.substring(DEBUG.length());
+ }
+
+ final String debugXMLString = "/xml";
+ if (url.startsWith(debugXMLString)) {
+ url = url.substring(debugXMLString.length());
+ }
+
+ final String debugXSLString = "/xsl";
+ if (url.startsWith(debugXSLString)) {
+ url = url.substring(debugXSLString.length());
+ }
+
+ // Fetch the current site node from the URL.
+ final ContentSectionRepository sectionRepo = CdiUtil.createCdiUtil()
+ .findBean(ContentSectionRepository.class);
+ ContentSection section = sectionRepo.findByLabel(url);
+ return section;
+ }
+
+ /**
+ * Fetch a resource based on the URL stub.
+ *
+ * @param section The current content section
+ * @param url The section-relative URL
+ *
+ * @return A ResourceHandler resource or null if none exists.
+ *
+ * @pre (url != null)
+ */
+ protected ResourceHandler getResource(ContentSection section, String url)
+ throws ServletException {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Searching for a resource for the URL fragment '" + url
+ + "' under " + section);
+ }
+
+ final PageResolver pageResolver = CMSDispatcher.getPageResolver(section);
+
+ final ResourceHandler handler = pageResolver.getPage(url);
+
+ return handler;
+ }
+
+ /**
+ * Lookup a content item by section and URL.
+ *
+ * @param section The content section
+ * @param url The URL relative to the content section
+ * @param context The use context
+ *
+ * @return The item associated with the URL, or null if no such item exists
+ */
+ protected ContentItem getContentItem(ContentSection section, String url,
+ String context)
+ throws ServletException {
+
+ ItemResolver itemResolver = CMSDispatcher.getItemResolver(section);
+
+ ContentItem item = null;
+ item = itemResolver.getItem(section, url, context);
+
+ return item;
+ }
+
+ /**
+ * Preview the current content item.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @param actx The request context
+ */
+ protected void preview(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException {
+
+ ContentSection section = getContentSection(request);
+ ContentItem item = getContentItem(request);
+
+ ItemResolver itemResolver = CMSDispatcher.getItemResolver(section);
+ CMSPage page = itemResolver.getMasterPage(item, request);
+ page.dispatch(request, response, actx);
+ }
+
+ /**
+ * Flushes the page resolver cache.
+ *
+ * @param section The current content section
+ * @param url The section-relative URL
+ */
+ public static void releaseResource(ContentSection section, String url) {
+ final String pageResolverClassName = section.getPageResolverClass();
+ final PageResolver pageResolver;
+ try {
+ pageResolver = (PageResolver) Class.forName(pageResolverClassName)
+ .newInstance();
+ } catch (ClassNotFoundException |
+ IllegalAccessException |
+ InstantiationException ex) {
+ throw new RuntimeException(ex);
+ }
+ pageResolver.releasePage(url);
+ }
+
+ /**
+ * Fetches the PageResolver for a content section. Checks cache first.
+ *
+ * @param section The content section
+ *
+ * @return The PageResolver associated with the content section
+ */
+ public static PageResolver getPageResolver(ContentSection section) {
+ s_log.debug("Getting the page resolver");
+
+ final String name = section.getLabel();
+ PageResolver pr = (PageResolver) s_pageResolverCache.get(name);
+
+ if (pr == null) {
+ s_log.debug("The page resolver was not cached; fetching a new "
+ + "one and placing it in the cache");
+
+ final String pageResolverClassName = section.getPageResolverClass();
+ final PageResolver pageResolver;
+ try {
+ pageResolver = (PageResolver) Class.forName(
+ pageResolverClassName)
+ .newInstance();
+ } catch (ClassNotFoundException |
+ IllegalAccessException |
+ InstantiationException ex) {
+ throw new RuntimeException(ex);
+ }
+ s_pageResolverCache.put(name, pageResolver);
+ }
+
+ return pr;
+ }
+
+ /**
+ * Fetches the ItemResolver for a content section. Checks cache first.
+ *
+ * @param section The content section
+ *
+ * @return The ItemResolver associated with the content section
+ */
+ public static ItemResolver getItemResolver(ContentSection section) {
+ String name = section.getLabel();
+ ItemResolver itemResolver = (ItemResolver) s_itemResolverCache.get(name);
+ if (itemResolver == null) {
+ final String itemResolverClassName = section.getItemResolverClass();
+ try {
+ itemResolver = (ItemResolver) Class.forName(
+ itemResolverClassName).newInstance();
+ } catch (ClassNotFoundException |
+ IllegalAccessException |
+ InstantiationException ex) {
+ throw new RuntimeException(ex);
+ }
+ s_itemResolverCache.put(name, itemResolver);
+ }
+ return itemResolver;
+ }
+
+ /**
+ * Fetches the XMLGenerator for a content section. Checks cache first.
+ *
+ * @param section The content section
+ *
+ * @return The XMLGenerator associated with the content section
+ */
+ public static XMLGenerator getXMLGenerator(ContentSection section) {
+ String name = section.getLabel();
+ XMLGenerator xmlGenerator = (XMLGenerator) s_xmlGeneratorCache.get(name);
+ if (xmlGenerator == null) {
+ final String xmlGeneratorClassName = section.getXmlGeneratorClass();
+ try {
+ xmlGenerator = (XMLGenerator) Class.forName(
+ xmlGeneratorClassName).newInstance();
+ } catch (ClassNotFoundException |
+ IllegalAccessException |
+ InstantiationException ex) {
+ throw new RuntimeException(ex);
+ }
+ s_xmlGeneratorCache.put(name, xmlGenerator);
+ }
+
+ return xmlGenerator;
+ }
+
+ /**
+ * Does this URL correspond to an admin page?
+ */
+ protected boolean isAdminPage(String url) {
+
+ // MP: This is a hack to get the debugging info in
+ // SiteNodePresentationManager.servePage, but since it's
+ // debugging info...
+ // Remove /debug from the start of the URL if it exists.
+ if (url.startsWith(DEBUG)) {
+ url = url.substring(DEBUG.length());
+ }
+
+ return (url != null && (url.startsWith(ADMIN_SECTION) || url.startsWith(
+ PREVIEW)));
+ }
+
+ /**
+ * Redirects the client to the login page, setting the return url to the
+ * current request URI.
+ *
+ * @exception ServletException If there is an exception thrown while trying
+ * to redirect, wrap that exception in a
+ * ServletException
+ *
+ */
+ protected void redirectToLoginPage(HttpServletRequest req,
+ HttpServletResponse resp)
+ throws ServletException {
+ throw new LoginSignal(req);
+ }
+
+ // modified from JSPApplicationDispatcher
+ private String getSiteNodeAsset(HttpServletRequest request,
+ RequestContext actx)
+ throws RedirectException {
+
+ String siteNodeAssetURL = null;
+
+ ServletContext sctx = actx.getServletContext();
+ String processedURL = actx.getProcessedURLPart();
+ String remainingURL = actx.getRemainingURLPart();
+ // REMOVE THIS HACK ONCE we have working publish to file code in the build
+ //String templateRoot = PublishToFile.getDefaultDestinationForType(Template.class);
+ String templateRoot = null;
+
+ /* Allow a graceful early exit if publishToFile is not initialized */
+ if (null == templateRoot) {
+ return null;
+ }
+
+ File siteNodeAssetRoot = new File(templateRoot, processedURL);
+ File assetFile = new File(siteNodeAssetRoot, remainingURL);
+
+ String contextPath = request.getContextPath();
+
+ if (assetFile.isDirectory()) {
+
+ if (remainingURL.endsWith("/")) {
+ throw new RedirectException(actx.getOriginalURL() + "/");
+ }
+
+ for (int i = 0; i < INDEX_FILES.length; i++) {
+ File indexFile = new File(assetFile, INDEX_FILES[i]);
+ if (indexFile.exists()) {
+ assetFile = indexFile;
+ }
+ }
+ }
+
+ if (assetFile.exists()) {
+ siteNodeAssetURL = contextPath + "/" + templateRoot
+ + processedURL + remainingURL;
+ }
+
+ return siteNodeAssetURL;
+ }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSPage.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSPage.java
new file mode 100755
index 000000000..46c07fe02
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSPage.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.bebop.BebopConfig;
+import com.arsdigita.bebop.Container;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.Page;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.page.PageTransformer;
+import com.arsdigita.cms.ContentSectionServlet;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.templating.PresentationManager;
+import com.arsdigita.web.Web;
+import com.arsdigita.xml.Document;
+import com.arsdigita.xml.Element;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.PermissionChecker;
+import org.libreccm.security.Shiro;
+import org.libreccm.security.User;
+import org.libreccm.web.CcmApplication;
+import org.librecms.CmsConstants;
+import org.librecms.contentsection.ContentItem;
+import org.librecms.contentsection.ContentItemRepository;
+import org.librecms.contentsection.ContentSection;
+
+
+/**
+ *
+ *
+ * @author Michael Pih (pihman@arsdigita.com)
+ * @author Uday Mathur (umathur@arsdigita.com)
+ * @version $Id$
+ */
+public class CMSPage extends Page implements ResourceHandler {
+
+ private static final Logger s_log = Logger.getLogger(CMSPage.class);
+
+ /** The global assets URL stub XML parameter name. */
+ public final static String ASSETS = "ASSETS";
+
+ /** The XML page class. */
+ public final static String PAGE_CLASS = "CMS";
+
+ /** Map of XML parameters */
+ private HashMap m_params;
+
+ /** */
+ private PageTransformer m_transformer;
+
+ public CMSPage() {
+ super();
+ buildPage();
+ }
+
+ public CMSPage(String title) {
+ super(title);
+ buildPage();
+ }
+
+ public CMSPage(String title, Container panel) {
+ super(title, panel);
+ buildPage();
+ }
+
+ public CMSPage(Label title) {
+ super(title);
+ buildPage();
+ }
+
+ public CMSPage(Label title, Container panel) {
+ super(title, panel);
+ buildPage();
+ }
+
+ /**
+ * Builds the page.
+ */
+ protected void buildPage() {
+ // Set the class attribute value. May be overwritten by child class
+ // to hold a more specific value
+ setClassAttr(PAGE_CLASS);
+
+ // Global XML params.
+ // MP: This only works with older versions of Xalan.
+ m_params = new HashMap();
+ setXMLParameter(ASSETS, Utilities.getGlobalAssetsURL());
+
+ // MP: This is a hack to so that the XML params work with the newer
+ // version of Xalan.
+ // Sets attribute in SimpleComponent, attributes of the same name will
+ // be overweritten.
+ setAttribute(ASSETS, Utilities.getGlobalAssetsURL());
+
+ // Make sure the error display gets rendered.
+ getErrorDisplay().setIdAttr("page-body");
+
+ final Class
+ * getContentSection(PageState state);
+ *
ContentPanel component fetches the
+ * {@link com.arsdigita.cms.dispatcher.XMLGenerator} for the content
+ * section.XMLGenerator registered to the current
+ * {@link com.arsdigita.cms.ContentSection}.
+ *
+ * @param state The page state
+ *
+ * @return The XMLGenerator used by this Content Panel
+ */
+ protected XMLGenerator getXMLGenerator(PageState state) {
+ ContentSection section = CMS.getContext().getContentSection();
+ Assert.exists(section);
+ try {
+ return (XMLGenerator) Class.forName(section.getXmlGeneratorClass())
+ .newInstance();
+ } catch (ClassNotFoundException |
+ InstantiationException |
+ IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Generates XML that represents a content item.
+ *
+ * @param state The page state
+ * @param parent The parent DOM element
+ *
+ * @see com.arsdigita.cms.dispatcher.XMLGenerator
+ */
+ @Override
+ public void generateXML(PageState state, Element parent) {
+ if (isVisible(state)) {
+ Element content = parent.newChildElement("cms:contentPanel",
+ CMS.CMS_XML_NS);
+ exportAttributes(content);
+
+ // Generate path information about the content item
+ generatePathInfoXML(state, content);
+
+ // Take advantage of caching in the CMS Dispatcher.
+ XMLGenerator xmlGenerator = getXMLGenerator(state);
+
+ xmlGenerator.generateXML(state, content, null);
+ }
+ }
+
+ /**
+ * Generate information about the path to this content item.
+ *
+ * @param state the page state
+ * @param parent the element that will contain the path info
+ */
+ protected void generatePathInfoXML(PageState state, Element parent) {
+ Element pathInfo = parent
+ .newChildElement("cms:pathInfo", CMS.CMS_XML_NS);
+
+ if (CMS.getContext().hasContentSection()) {
+ pathInfo.newChildElement("cms:sectionPath", CMS.CMS_XML_NS).setText(
+ CMS.getContext().getContentSection().getPrimaryUrl());
+ }
+ String url = DispatcherHelper.getRequestContext().getRemainingURLPart();
+ if (url.startsWith(CMSDispatcher.PREVIEW)) {
+ pathInfo.newChildElement("cms:previewPath", CMS.CMS_XML_NS).setText(
+ ContentSectionServlet.PREVIEW);
+ }
+ pathInfo.newChildElement("cms:templatePrefix", CMS.CMS_XML_NS).setText(
+ "/" + AbstractItemResolver.TEMPLATE_CONTEXT_PREFIX);
+
+ if (CMS.getContext().hasContentItem()) {
+ ContentItem item = CMS.getContext().getContentItem();
+ pathInfo.newChildElement("cms:itemPath", CMS.CMS_XML_NS).setText("/"
+ + item.getName());
+ }
+ }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentSectionDispatcher.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentSectionDispatcher.java
new file mode 100755
index 000000000..5be302dc2
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentSectionDispatcher.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.dispatcher.Dispatcher;
+import com.arsdigita.dispatcher.DispatcherChain;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.util.Assert;
+import com.arsdigita.web.Web;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.PermissionChecker;
+import org.libreccm.web.ApplicationManager;
+import org.librecms.CmsConstants;
+import org.librecms.contentsection.ContentItem;
+import org.librecms.contentsection.ContentSection;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Unsupported Refactored content section
+ * dispatcher (under development).
+ *
+ * @author Karl Goldstein (karlg@arsdigita.com)
+ * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $
+ * @version $Id$
+ */
+public class ContentSectionDispatcher implements Dispatcher {
+
+ public static final String CONTENT_ITEM
+ = "com.arsdigita.cms.dispatcher.item";
+
+ static final String CONTENT_SECTION = "com.arsdigita.cms.dispatcher.section";
+
+ private DispatcherChain dispatcherChain = new DispatcherChain();
+
+ public ContentSectionDispatcher() {
+
+ dispatcherChain.addChainedDispatcher(new CMSDispatcher(true));
+ dispatcherChain.addChainedDispatcher(new FileDispatcher());
+ dispatcherChain.addChainedDispatcher(new ItemDispatcher());
+ dispatcherChain.addChainedDispatcher(new CMSDispatcher());
+ }
+
+ public void dispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext context)
+ throws IOException, ServletException {
+
+ setContentSection(request, context);
+ dispatcherChain.dispatch(request, response, context);
+ }
+
+ /**
+ * Fetches the content section from the request attributes.
+ *
+ * @param request The HTTP request
+ *
+ * @return The content section
+ *
+ * @pre ( request != null )
+ */
+ public static ContentSection getContentSection(HttpServletRequest request) {
+ return (ContentSection) request.getAttribute(CONTENT_SECTION);
+ }
+
+ /**
+ * Fetches the content item from the request attributes.
+ *
+ * @param request The HTTP request
+ *
+ * @return The content item
+ *
+ * @pre ( request != null )
+ */
+ public static ContentItem getContentItem(HttpServletRequest request) {
+ return (ContentItem) request.getAttribute(CONTENT_ITEM);
+ }
+
+ /**
+ * Looks up the current content section using the remaining URL stored in
+ * the request context object and the SiteNode class.
+ *
+ * @param url The section URL stub
+ *
+ * @return The current Content Section
+ */
+ private void setContentSection(HttpServletRequest request,
+ // SiteNodeRequestContext actx)
+ RequestContext actx)
+ throws ServletException {
+
+ final ContentSection section = (ContentSection) Web.getWebContext()
+ .getApplication();
+ request.setAttribute(CONTENT_SECTION, section);
+ }
+
+ /**
+ * Checks that the current user has permission to access the admin pages.
+ *
+ * @param request
+ * @param section
+ */
+ public static boolean checkAdminAccess(HttpServletRequest request,
+ ContentSection section) {
+
+ return CdiUtil.createCdiUtil().findBean(PermissionChecker.class)
+ .isPermitted(CmsConstants.PRIVILEGE_ITEMS_EDIT, section
+ .getRootDocumentsFolder());
+ }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/DefaultTemplateResolver.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/DefaultTemplateResolver.java.off
new file mode 100755
index 000000000..16c522f5b
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/DefaultTemplateResolver.java.off
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.cms.ContentItem;
+import com.arsdigita.cms.ContentSection;
+import com.arsdigita.cms.ContentType;
+import com.arsdigita.cms.Folder;
+import com.arsdigita.cms.Template;
+import com.arsdigita.cms.TemplateManager;
+import com.arsdigita.cms.TemplateManagerFactory;
+import com.arsdigita.mimetypes.MimeType;
+import com.arsdigita.util.Assert;
+import org.apache.log4j.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * ------- May be outdated. TemplateResolver has been reworked. ----------
+ * Resolves the JSP template to use for dispatching an
+ * item. This replaces TemplateResolver since the latter
+ * has a useless API.
+ * ------------------------------------------------------------------------
+ *
+ *
+ *
+ *
+ */
+
+public class DefaultTemplateResolver extends AbstractTemplateResolver
+ implements TemplateResolver {
+
+ private static Logger s_log = Logger.getLogger(DefaultTemplateResolver.class);
+
+ /**
+ * Returns the JSP template filename relative to the webapp
+ * root.
+ *
+ * @param section The ContentSection for the request
+ * @param item The ContentItem for the request
+ * @param request The current HttpServletRequest
+ *
+ * @return The path to the jsp template.
+ */
+ public String getTemplate(ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+
+ String template = getItemTemplate(section, item, request);
+ MimeType mimeType = MimeType.loadMimeType(Template.JSP_MIME_TYPE);
+
+ if (template == null) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("No item template, looking for content type template");
+ }
+ template = getTypeTemplate(section, item, request, mimeType);
+ }
+
+ if (template == null) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("No content type template, looking for default template");
+ }
+
+ template = getDefaultTemplate(section, item, request);
+
+ Assert.exists(template, "default template");
+ }
+
+ if (s_log.isInfoEnabled()) {
+ s_log.info("Got template " + template + " for item " + item.getOID());
+ }
+
+ return ContentSection.getConfig().getTemplateRoot() + template;
+ }
+
+ /**
+ * Returns the JSP template filename relative to the webapp
+ * root for a given Template reference.
+ *
+ * @param template The Template to resolve the URL for.
+ *
+ * @return The path to the jsp template.
+ */
+ public String getTemplatePath(Template template) {
+
+ return ContentSection.getConfig().getTemplateRoot() +
+ getTemplateFilename(template, template.getContentSection());
+ }
+
+ /**
+ * Returns the XSL template filename relative to the webapp
+ * root for a given Template reference.
+ *
+ * @param template The Template to resolve the URL for.
+ *
+ * @return The path to the xsl template.
+ */
+ public String getTemplateXSLPath(Template template) {
+
+ return ContentSection.getConfig().getTemplateRoot() +
+ getTemplateXSLFilename(template, template.getContentSection());
+ }
+
+ /**
+ * Returns the template associated with the item (if any)
+ */
+ protected String getItemTemplate(ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+ TemplateManager manager = TemplateManagerFactory.getInstance();
+ String context = getTemplateContext(request);
+ Template template = manager.getTemplate(item, context);
+
+ return template == null ? null : getTemplateFilename(
+ template, section
+ );
+ }
+
+ /**
+ * Returns the template associated with the type (if any)
+ * @deprecated Use the version that specifies a mime type
+ */
+ protected String getTypeTemplate(ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+ MimeType mimeType = MimeType.loadMimeType(Template.JSP_MIME_TYPE);
+ return getTypeTemplate(section, item, request, mimeType);
+ }
+
+ /**
+ * Returns the template associated with the type (if any)
+ */
+ protected String getTypeTemplate(ContentSection section,
+ ContentItem item,
+ HttpServletRequest request,
+ MimeType mimeType) {
+ TemplateManager manager = TemplateManagerFactory.getInstance();
+ ContentType type = item.getContentType();
+
+ Template template = null;
+
+ if (type != null ) {
+ String context = getTemplateContext(request);
+ template = manager.getDefaultTemplate(section, type, context, mimeType);
+ } else {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Item has no content type, not looking for a " +
+ "content type specific template");
+ }
+ }
+
+ return template == null ? null : getTemplateFilename(
+ template, section
+ );
+ }
+
+ /**
+ * Returns the default template
+ */
+ protected String getDefaultTemplate(ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+ String path = (item instanceof Folder) ?
+ ContentSection.getConfig().getDefaultFolderTemplatePath() :
+ ContentSection.getConfig().getDefaultItemTemplatePath();
+
+ return path;
+ }
+
+ /**
+ * Returns the filename for a Template object
+ */
+ protected String getTemplateFilename(Template template,
+ ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+ return getTemplateFilename(template, section);
+ }
+
+ /**
+ * Returns the filename for a Template object
+ */
+ protected String getTemplateXSLFilename(Template template,
+ ContentSection section,
+ ContentItem item,
+ HttpServletRequest request) {
+ return getTemplateXSLFilename(template, section);
+ }
+
+ /**
+ * Returns the filename for a Template object
+ */
+ protected String getTemplateFilename(Template template,
+ ContentSection section) {
+
+ String templateName = template.getPath();
+ String sectionURL = section.getPath();
+ return sectionURL + "/" + templateName;
+ }
+
+ /**
+ * Returns the filename for a Template object
+ */
+ protected String getTemplateXSLFilename(Template template,
+ ContentSection section) {
+
+ String templateName = template.getPathNoJsp() + ".xsl";
+ String sectionURL = section.getPath();
+
+ return sectionURL + "/" + templateName;
+ }
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/FileDispatcher.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/FileDispatcher.java
new file mode 100755
index 000000000..1c738b3c5
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/FileDispatcher.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.dispatcher.ChainedDispatcher;
+import com.arsdigita.dispatcher.DispatcherHelper;
+import com.arsdigita.dispatcher.RequestContext;
+import java.io.File;
+import java.io.IOException;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
+
+/**
+ * Dispatches to a file stored under the CMS package root
+ * (/packages/cms/www). This includes both unmanaged
+ * files copied or created directly in the file system, as well as
+ * pages and assets published to the file system from CMS.
+ *
+ * @author Karl Goldstein (karlg@arsdigita.com)
+ * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $
+ * @version $Id$
+ **/
+public class FileDispatcher implements ChainedDispatcher {
+
+ private static Logger s_log =
+ Logger.getLogger(ChainedDispatcher.class);
+
+ public int chainedDispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext context)
+ throws IOException, ServletException {
+
+ File jspFile = getPackageFile(context);
+
+ if (jspFile.exists() && !jspFile.isDirectory()) {
+ String packageURL = context.getPageBase() + context.getRemainingURLPart();
+ s_log.debug ("DISPATCHING to " + packageURL);
+
+ // don't match folders, since they don't actually match a file
+ if ( !packageURL.endsWith("/")) {
+ s_log.debug ("DISPATCHING to " + packageURL);
+ // Don't set caching headers - let JSP file do it if required
+ //DispatcherHelper.maybeCacheDisable(response);
+ DispatcherHelper.setRequestContext(request, context);
+ DispatcherHelper.forwardRequestByPath(packageURL, request, response);
+ return ChainedDispatcher.DISPATCH_BREAK;
+ }
+ }
+
+ return ChainedDispatcher.DISPATCH_CONTINUE;
+ }
+
+ /**
+ * Matches the request URL to a file in the package www directory.
+ **/
+ private File getPackageFile(RequestContext appContext) {
+
+ ServletContext servletContext = appContext.getServletContext();
+
+ String filePath = appContext.getRemainingURLPart();
+
+ String packageDocRoot =
+ servletContext.getRealPath(appContext.getPageBase());
+
+ File jspFile = new File(packageDocRoot, filePath);
+
+ return jspFile;
+ }
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemDispatcher.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemDispatcher.java
new file mode 100755
index 000000000..1ac9b10e5
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemDispatcher.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.cms.ContentSectionServlet;
+import com.arsdigita.dispatcher.ChainedDispatcher;
+import com.arsdigita.dispatcher.DispatcherHelper;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.web.LoginSignal;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.PermissionChecker;
+import org.libreccm.security.Shiro;
+import org.librecms.CmsConstants;
+import org.librecms.contentsection.ContentItem;
+import org.librecms.contentsection.ContentSection;
+import org.librecms.lifecycle.Lifecycle;
+
+import java.util.logging.Level;
+
+/**
+ * Dispatches to the JSP or Servlet for rendering a content item.
+ *
+ * @author Karl Goldstein (karlg@arsdigita.com)
+ * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $
+ *
+ */
+public class ItemDispatcher implements ChainedDispatcher {
+
+ private static Logger s_log = Logger.getLogger(ItemDispatcher.class);
+
+ public static Map s_itemResolverCache = Collections.synchronizedMap(
+ new HashMap());
+ public static Map s_templateResolverCache = Collections.synchronizedMap(
+ new HashMap());
+
+ public static final String FILE_SUFFIX = ".jsp";
+ public static final String INDEX_FILE = "/index";
+// public static final String TEMPLATE_ROOT =
+// "/packages/content-section/templates";
+// public static final String DEFAULT_ITEM_TEMPLATE = "/default/item.jsp";
+// public static final String DEFAULT_FOLDER_TEMPLATE = "/default/folder.jsp";
+
+ public static final String XML_SUFFIX = ".xml";
+ public static final String XML_MODE = "xmlMode";
+
+ private static boolean m_cacheItems = true;
+
+ /**
+ * The context for previewing items
+ */
+ public static final String PREVIEW = "/preview";
+
+ protected ItemXML m_itemXML;
+
+ public ItemDispatcher() {
+ super();
+ m_itemXML = new ItemXML();
+ }
+
+ public static void setCacheItems(boolean value) {
+ m_cacheItems = value;
+ }
+
+ public int chainedDispatch(final HttpServletRequest request,
+ final HttpServletResponse response,
+ final RequestContext actx)
+ throws IOException, ServletException {
+ String queryString = request.getQueryString();
+ String url = actx.getRemainingURLPart();
+
+ s_log.info("Resolving item URL " + url);
+
+ if (url.endsWith(XML_SUFFIX)) {
+ request.setAttribute(XML_MODE, Boolean.TRUE);
+ s_log.debug("StraightXML Requested");
+ url = "/" + url.substring(0, url.length() - XML_SUFFIX.length());
+ } else {
+ request.setAttribute(XML_MODE, Boolean.FALSE);
+ // it's neither a .jsp or a .xml, thus its an error
+ if (url.endsWith(FILE_SUFFIX)) {
+ url = "/" + url
+ .substring(0, url.length() - FILE_SUFFIX.length());
+ } else if (url.endsWith("/")) {
+ url = "/" + url.substring(0, url.length() - 1);
+ } else {
+ s_log.warn("Fail: URL doesn't have right suffix.");
+ return ChainedDispatcher.DISPATCH_CONTINUE;
+ }
+ }
+
+ final ContentSection section = ContentSectionServlet.getContentSection(
+ request);
+ // ContentSectionDispatcher.getContentSection(request);
+
+ final ContentItem item = getItem(section, url);
+ if (item == null) {
+ s_log.warn("Fail: No live item found matching " + url);
+ return ChainedDispatcher.DISPATCH_CONTINUE;
+ }
+
+ request.setAttribute(ContentSectionDispatcher.CONTENT_ITEM, item);
+
+ s_log.debug("MATCHED " + item.getObjectId());
+
+ // Work out how long to cache for....
+ // We take minimum(default timeout, lifecycle expiry)
+ //ToDo
+// Lifecycle cycle = item.getLifecycle();
+ int expires = DispatcherHelper.getDefaultCacheExpiry();
+// if (cycle != null) {
+// Date endDate = cycle.getEndDate();
+//
+// if (endDate != null) {
+// int maxAge = (int) ( ( endDate.getTime() - System.currentTimeMillis() ) / 1000l );
+// if (maxAge < expires) {
+// expires = maxAge;
+// }
+// }
+// }
+//ToDo end
+ // NB, this is not the same as the security check previously
+ // We are checking if anyone can access - ie can we allow
+ // this page to be publically cached
+ if (m_cacheItems && !url.startsWith(PREVIEW)) {
+// if (sm.canAccess((User)null, SecurityManager.PUBLIC_PAGES, item)) {
+ if (CdiUtil.createCdiUtil().findBean(PermissionChecker.class)
+ .isPermitted(
+ CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item)) {
+ DispatcherHelper.cacheForWorld(response, expires);
+ } else {
+ DispatcherHelper.cacheForUser(response, expires);
+ }
+ } else {
+ DispatcherHelper.cacheDisable(response);
+ }
+
+ if (((Boolean) request.getAttribute(XML_MODE)).booleanValue()) {
+ m_itemXML.dispatch(request, response, actx);
+ return ChainedDispatcher.DISPATCH_BREAK;
+ } else {
+
+ // normal dispatching
+ // This part assumes the template is JSP.
+// final String templateURL = getTemplateURL(section, item, request,
+// actx);
+
+// s_log.debug("TEMPLATE " + templateURL);
+
+ DispatcherHelper.setRequestContext(request, actx);
+ DispatcherHelper.forwardRequestByPath(null, request,
+ response);
+ return ChainedDispatcher.DISPATCH_BREAK;
+ }
+ }
+
+ public ContentItem getItem(ContentSection section, String url) {
+
+ ItemResolver itemResolver = getItemResolver(section);
+ ContentItem item;
+ // Check if the user has access to view public or preview pages
+ boolean hasPermission = true;
+ HttpServletRequest request = DispatcherHelper.getRequest();
+
+ // If the remaining URL starts with "preview/", then try and
+ // preview this item. Otherwise look for the live item.
+ boolean preview = false;
+ if (url.startsWith(PREVIEW)) {
+ url = url.substring(PREVIEW.length());
+ preview = true;
+ }
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PermissionChecker permissionChecker = cdiUtil.findBean(
+ PermissionChecker.class);
+
+ if (preview) {
+ item = itemResolver.getItem(section, url, "draft");
+ if (item != null) {
+ hasPermission = permissionChecker.isPermitted(
+ CmsConstants.PRIVILEGE_ITEMS_PREVIEW, item);
+ }
+ } else {
+ item = itemResolver.getItem(section, url, "live");
+ if (item != null) {
+ hasPermission = permissionChecker.isPermitted(
+ CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item);
+ }
+ }
+
+ if (item == null && url.endsWith(INDEX_FILE)) {
+
+ // look up folder if it's an index
+ url = url.substring(0, url.length() - INDEX_FILE.length());
+ s_log.info("Attempting to match folder " + url);
+ item = itemResolver.getItem(section, url, "live");
+ if (item != null) {
+ hasPermission = permissionChecker.isPermitted(
+ CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item);
+ }
+ }
+ // chris.gilbert@westsussex.gov.uk - if user is not logged in, give them a chance to do that, else show them the door
+ if (!hasPermission && !cdiUtil.findBean(Shiro.class).getSubject()
+ .isAuthenticated()) {
+ throw new LoginSignal(request);
+ }
+ if (!hasPermission) {
+ throw new com.arsdigita.dispatcher.AccessDeniedException();
+ }
+
+ return item;
+ }
+
+ /**
+ * Fetches the ItemResolver for a content section. Checks cache first.
+ *
+ * @param section The content section
+ *
+ * @return The ItemResolver associated with the content section
+ */
+ public ItemResolver getItemResolver(ContentSection section) {
+
+ String name = section.getLabel();
+ ItemResolver ir = (ItemResolver) s_itemResolverCache.get(name);
+
+ if (ir == null) {
+ try {
+ ir = (ItemResolver) Class
+ .forName(section.getItemResolverClass()).newInstance();
+ s_itemResolverCache.put(name, ir);
+ } catch (ClassNotFoundException |
+ IllegalAccessException |
+ InstantiationException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ return ir;
+ }
+
+ /**
+ * Fetches the ItemResolver for a content section. Checks cache first.
+ *
+ * @param section The content section
+ *
+ * @return The ItemResolver associated with the content section
+ */
+// public TemplateResolver getTemplateResolver(ContentSection section) {
+//
+// String name = section.getName();
+// TemplateResolver ir = (TemplateResolver) s_templateResolverCache.get(
+// name);
+//
+// if (ir == null) {
+// ir = section.getTemplateResolver();
+// s_templateResolverCache.put(name, ir);
+// }
+//
+// return ir;
+// }
+
+ /**
+ * Fetches the URL of a template for an item. The returned URL is relative
+ * to the webapp context.
+ */
+// public String getTemplateURL(ContentSection section,
+// ContentItem item,
+// HttpServletRequest request,
+// RequestContext actx) {
+//
+// String templateURL = getTemplateResolver(section).getTemplate(section,
+// item,
+// request);
+//
+// return templateURL;
+// }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemResolver.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemResolver.java
new file mode 100755
index 000000000..f0a571d1c
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemResolver.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.bebop.PageState;
+
+import org.librecms.contentsection.ContentItem;
+import org.librecms.contentsection.ContentSection;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import java.math.BigDecimal;
+
+/**
+ * inUrl.
+ *
+ * @param inUrl URL, possibly including the template context.
+ * @return inUrl with the template context removed
+ */
+ public String stripTemplateFromURL(String inUrl);
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemXML.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemXML.java
new file mode 100755
index 000000000..d859ef41e
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemXML.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2002-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+
+import com.arsdigita.cms.CMS;
+import com.arsdigita.dispatcher.RequestContext;
+import com.arsdigita.xml.Document;
+import com.arsdigita.xml.Element;
+
+import org.librecms.contentsection.ContentItem;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/***
+ *
+ * XMLPage
+ *
+ * Designed to allow you to output straight XML directly from the ContentItem
+ * that implements XMLGenerator, with none of the surrounding headers, footers, etc
+ *
+ * @author slater@arsdigita.com
+ *
+ ***/
+
+public class ItemXML extends ResourceHandlerImpl {
+
+ public ItemXML() {
+ super();
+ }
+
+ public void dispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException {
+
+ ContentItem item = getContentItem(request);
+
+ Element content = new Element("cms:item", CMS.CMS_XML_NS);
+
+// ContentItemXMLRenderer renderer =
+// new ContentItemXMLRenderer(content);
+ //ToDo
+// renderer.setWrapAttributes(true);
+// renderer.setWrapRoot(false);
+// renderer.setWrapObjects(false);
+//
+// renderer.walk(item, SimpleXMLGenerator.ADAPTER_CONTEXT);
+//ToDo End
+
+ Document doc;
+ try {
+ doc = new Document(content);
+ } catch (javax.xml.parsers.ParserConfigurationException e) {
+ throw new javax.servlet.ServletException(e);
+ }
+
+ OutputStream out = response.getOutputStream();
+ try {
+ out.write(doc.toString(true).getBytes());
+ } catch (IOException e) {
+ throw new ServletException(e);
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MasterPage.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MasterPage.java
new file mode 100755
index 000000000..2278982b1
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MasterPage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+import com.arsdigita.bebop.SimpleContainer;
+import com.arsdigita.util.Assert;
+
+import org.librecms.contentsection.ContentSection;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * ContentPanel component which fetches
+ * the {@link com.arsdigita.cms.dispatcher.XMLGenerator} for the content
+ * section.ContentItem.LIVE,
+ * CMSDispatcher.PREVIEW or
+ * ContentItem.DRAFT. See {@link
+ * #getCurrentContext}.
+ * @return The content item, or null if no such item exists
+ */
+ @Override
+ public ContentItem getItem(final ContentSection section,
+ String url,
+ final String context) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolving the item in content section " + section +
+ " at URL '" + url + "' for context " + context);
+ }
+
+ Assert.exists(section, "ContentSection section");
+ Assert.exists(url, "String url");
+ Assert.exists(context, "String context");
+
+ Category rootFolder = section.getRootDocumentsFolder();
+ url = stripTemplateFromURL(url);
+
+ // nothing to do, if root folder is null
+ if (rootFolder == null) {
+ s_log.debug("The root folder is null; returning no item");
+ } else {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Using root folder " + rootFolder);
+ }
+
+ if ("live".equals(context)) {
+ s_log.debug("The use context is 'live'");
+
+ // We allow for returning null, so the root folder may
+ // not be live.
+ //Assert.isTrue(rootFolder.isLive(),
+ // "live context - root folder of secion must be live");
+
+ // If the context is 'live', we need the live item.
+
+ rootFolder = (Folder) rootFolder.getLiveVersion();
+
+ if (rootFolder == null) {
+ s_log.debug("The live version of the root folder is " +
+ "null; returning no item");
+ } else {
+ s_log.debug("The root folder has a live version; " +
+ "recursing");
+
+ final String prefix =
+ section.getURL() + rootFolder.getPath();
+
+ if (url.startsWith(prefix)) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("The URL starts with prefix '" +
+ prefix + "'; removing it");
+ }
+
+ url = url.substring(prefix.length());
+ }
+
+ final ContentItem item = getItemFromLiveURL(url, rootFolder);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolved URL '" + url + "' to item " +
+ item);
+ }
+
+ return item;
+ }
+ } else if (ContentItem.DRAFT.equals(context)) {
+ s_log.debug("The use context is 'draft'");
+
+ // For 'draft' items, 'generateUrl()' method returns
+ // URL like this
+ // '/acs/wcms/admin/item.jsp?item_id=10201&set_tab=1'
+ // Check if URL contains any match of string
+ // 'item_id', then try to instanciate item_id value
+ // and return FIXME: Please hack this if there is
+ // more graceful solution. [aavetyan]
+
+ if (Assert.isEnabled()) {
+ Assert.isTrue
+ (url.indexOf(ITEM_ID) >= 0,
+ "url must contain parameter " + ITEM_ID);
+ }
+
+ final ContentItem item = getItemFromDraftURL(url);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolved URL '" + url + "' to item " + item);
+ }
+
+ return item;
+ } else if (CMSDispatcher.PREVIEW.equals(context)) {
+ s_log.debug("The use context is 'preview'");
+
+ final String prefix = CMSDispatcher.PREVIEW + "/";
+
+ if (url.startsWith(prefix)) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("The URL starts with prefix '" +
+ prefix + "'; removing it");
+ }
+
+ url = url.substring(prefix.length());
+ }
+
+ final ContentItem item = getItemFromLiveURL(url, rootFolder);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolved URL '" + url + "' to item " + item);
+ }
+
+ return item;
+ } else {
+ throw new IllegalArgumentException
+ ("Invalid item resolver context " + context);
+ }
+ }
+
+ s_log.debug("No item resolved; returning null");
+
+ return null;
+ }
+
+ /**
+ * Fetches the current context based on the page state.
+ *
+ * @param state the current page state
+ * @return the context of the current URL, such as
+ * ContentItem.LIVE or ContentItem.DRAFT
+ * @see ContentItem#LIVE
+ * @see ContentItem#DRAFT
+ */
+ @Override
+ public String getCurrentContext(final PageState state) {
+ s_log.debug("Getting the current context");
+
+ // XXX need to use Web.getWebContext().getRequestURL() here.
+ String url = state.getRequest().getRequestURI();
+
+ final ContentSection section =
+ CMS.getContext().getContentSection();
+
+ // If this page is associated with a content section,
+ // transform the URL so that it is relative to the content
+ // section site node.
+
+ if (section != null) {
+ final String sectionURL = section.getURL();
+
+ if (url.startsWith(sectionURL)) {
+ url = url.substring(sectionURL.length());
+ }
+ }
+
+ // Remove any template-specific URL components (will only work
+ // if they're first in the URL at this point; verify). XXX but
+ // we don't actually verify?
+
+ url = stripTemplateFromURL(url);
+
+ // Determine if we are under the admin UI.
+
+ if (url.startsWith(ADMIN_PREFIX) || url.startsWith(ContentCenter.getURL())) {
+ return ContentItem.DRAFT;
+ } else {
+ return ContentItem.LIVE;
+ }
+ }
+
+ /**
+ * Generates a URL for a content item.
+ *
+ * @param itemId The item ID
+ * @param name The name of the content page
+ * @param state The page state
+ * @param section the content section to which the item belongs
+ * @param context the context of the URL, such as "live" or
+ * "admin"
+ * @return The URL of the item
+ * @see #getCurrentContext
+ */
+ @Override
+ public String generateItemURL(final PageState state,
+ final BigDecimal itemId,
+ final String name,
+ final ContentSection section,
+ final String context) {
+ return generateItemURL(state, itemId, name, section, context, null);
+ }
+
+ /**
+ * Generates a URL for a content item.
+ *
+ * @param itemId The item ID
+ * @param name The name of the content page
+ * @param state The page state
+ * @param section the content section to which the item belongs
+ * @param context the context of the URL, such as "live" or
+ * "admin"
+ * @param templateContext the context for the URL, such as
+ * "public"
+ * @return The URL of the item
+ * @see #getCurrentContext
+ */
+ @Override
+ public String generateItemURL(final PageState state,
+ final BigDecimal itemId,
+ final String name,
+ final ContentSection section,
+ final String context,
+ final String templateContext) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generating an item URL for item id " + itemId +
+ ", section " + section + ", and context '" +
+ context + "' with name '" + name + "'");
+ }
+
+ Assert.exists(itemId, "BigDecimal itemId");
+ Assert.exists(context, "String context");
+ Assert.exists(section, "ContentSection section");
+
+ if (ContentItem.DRAFT.equals(context)) {
+ // No template context here.
+ return generateDraftURL(section, itemId);
+ } else if (CMSDispatcher.PREVIEW.equals(context)) {
+ ContentItem item = new ContentItem(itemId);
+
+ return generatePreviewURL(section, item, templateContext);
+ } else if (ContentItem.LIVE.equals(context)) {
+ ContentItem item = new ContentItem(itemId);
+
+ if (Assert.isEnabled()) {
+ Assert.exists(item, "item");
+ Assert.isTrue(ContentItem.LIVE.equals(item.getVersion()),
+ "Generating " + ContentItem.LIVE + " " +
+ "URL; this item must be the live version");
+ }
+
+ return generateLiveURL(section, item, templateContext);
+ } else {
+ throw new IllegalArgumentException
+ ("Unknown context '" + context + "'");
+ }
+ }
+
+ /**
+ * Generates a URL for a content item.
+ *
+ * @param item The item
+ * @param state The page state
+ * @param section the content section to which the item belongs
+ * @param context the context of the URL, such as "live" or
+ * "admin"
+ * @return The URL of the item
+ * @see #getCurrentContext
+ */
+ @Override
+ public String generateItemURL(final PageState state,
+ final ContentItem item,
+ final ContentSection section,
+ final String context) {
+ return generateItemURL(state, item, section, context, null);
+ }
+
+ /**
+ * Generates a URL for a content item.
+ *
+ * @param item The item
+ * @param state The page state
+ * @param section the content section to which the item belongs
+ * @param context the context of the URL, such as "live" or
+ * "admin"
+ * @param templateContext the context for the URL, such as
+ * "public"
+ * @return The URL of the item
+ * @see #getCurrentContext
+ */
+ @Override
+ public String generateItemURL(final PageState state,
+ final ContentItem item,
+ ContentSection section,
+ final String context,
+ final String templateContext) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generating an item URL for item " + item +
+ ", section " + section + ", and context " +
+ context);
+ }
+
+ Assert.exists(item, "ContentItem item");
+ Assert.exists(context, "String context");
+
+ if (section == null) {
+ section = item.getContentSection();
+ }
+
+ if (ContentItem.DRAFT.equals(context)) {
+ if (Assert.isEnabled()) {
+ Assert.isTrue(ContentItem.DRAFT.equals(item.getVersion()),
+ "Generating " + ContentItem.DRAFT +
+ " url: item must be draft version");
+ }
+
+ return generateDraftURL(section, item.getID());
+ } else if (CMSDispatcher.PREVIEW.equals(context)) {
+ return generatePreviewURL(section, item, templateContext);
+ } else if (ContentItem.LIVE.equals(context)) {
+ if (Assert.isEnabled()) {
+ Assert.isTrue(ContentItem.LIVE.equals(item.getVersion()),
+ "Generating " + ContentItem.LIVE +
+ " url: item must be live version");
+ }
+
+ return generateLiveURL(section, item, templateContext);
+ } else {
+ throw new RuntimeException("Unknown context " + context);
+ }
+ }
+
+ /**
+ * Returns a master page based on page state (and content
+ * section).
+ *
+ * @param item The content item
+ * @param request The HTTP request
+ * @return The master page
+ * @throws javax.servlet.ServletException
+ */
+ @Override
+ public CMSPage getMasterPage(final ContentItem item,
+ final HttpServletRequest request)
+ throws ServletException {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Getting the master page for item " + item);
+ }
+
+ // taken from SimpleItemResolver
+ if (s_masterP == null) {
+ s_masterP = new MasterPage();
+ s_masterP.init();
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Returning master page " + s_masterP);
+ }
+
+ return s_masterP;
+ }
+
+ /**
+ * Returns content item's draft version URL
+ *
+ * @param section The content section to which the item belongs
+ * @param itemId The content item's ID
+ * @return generated URL string
+ */
+ protected String generateDraftURL(final ContentSection section,
+ final BigDecimal itemId) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generating draft URL for item ID " + itemId +
+ " and section " + section);
+ }
+
+ if (Assert.isEnabled()) {
+ Assert.isTrue(section != null && itemId != null,
+ "get draft url: neither secion nor item " +
+ "can be null");
+ }
+
+ final String url = ContentItemPage.getItemURL
+ (section.getPath() + "/", itemId, ContentItemPage.AUTHORING_TAB);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generated draft URL " + url);
+ }
+
+ return url;
+ }
+
+ /**
+ * Generate a language-independent URL to the
+ * item in the given section.ContentSection that contains this item
+ * @param item ContentItem for which a URL should be
+ * constructed.
+ * @param templateContext template context; will be ignored if null
+ *
+ * @return a language-independent URL to the
+ * item in the given section, which will
+ * be presented within the given templateContext
+ */
+ protected String generateLiveURL(final ContentSection section,
+ final ContentItem item,
+ final String templateContext) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generating live URL for item " + item + " in " +
+ "section " + section);
+ }
+
+ /*
+ * URL = URL of section + templateContext + path to the ContentBundle
+ * which contains the item
+ */
+ final StringBuffer url = new StringBuffer(400);
+ //url.append(section.getURL());
+ url.append(section.getPath()).append("/");
+
+ /*
+ * add template context, if one is given
+ */
+ // This is breaking URL's...not sure why it's here. XXX
+ // this should work with the appropriate logic. trying again.
+ if (!(templateContext == null || templateContext.length() == 0)) {
+ url.append(TEMPLATE_CONTEXT_PREFIX).append(templateContext).append("/");
+ }
+
+ // Try to retrieve the bundle.
+ final ContentItem bundle = (ContentItem) item.getParent();
+
+ /*
+ * It would be nice if we had a ContentPage here, which
+ * supports the getContentBundle() method, but unfortunately
+ * the API sucks and there is no real distinction between mere
+ * ContentItems and top-level items, so we have to use this
+ * hack. TODO: add sanity check that bundle is actually a
+ * ContentItem.
+ */
+ if (bundle != null && bundle instanceof ContentBundle) {
+ s_log.debug("Found a bundle; building its file name");
+
+ final String fname = bundle.getPath();
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Appending the bundle's file name '" +
+ fname + "'");
+ }
+
+ url.append(fname);
+
+ } else {
+ s_log.debug("No bundle found; using the item's path directly");
+
+ url.append(item.getPath());
+ }
+/*
+ * This will append the language to the url, which will in turn render
+ * the language negotiation inoperative
+ final String language = item.getLanguage();
+
+ if (language == null) {
+ s_log.debug("The item has no language; omitting the " +
+ "language encoding");
+ } else {
+ s_log.debug("Encoding the language of the item passed in, '" +
+ language + "'");
+
+ url.append("." + language);
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generated live URL " + url.toString());
+ }
+*/
+ return url.toString();
+ }
+
+ /**
+ * Generate a URL which can be used to preview the
+ * item, using the given
+ * templateContext.ContentSection which contains
+ * the item
+ * @param item The ContentItem for which a URL should
+ * be generated.
+ * @param templateContext the context that determines which
+ * template should render the item when it is previewed; ignored
+ * if the argument given here is null
+ * @return a URL which can be used to preview the given
+ * item
+ */
+ protected String generatePreviewURL(ContentSection section,
+ ContentItem item,
+ String templateContext) {
+ Assert.exists(section, "ContentSection section");
+ Assert.exists(item, "ContentItem item");
+
+ final StringBuffer url = new StringBuffer(100);
+ url.append(section.getPath());
+ url.append("/");
+ url.append(CMSDispatcher.PREVIEW);
+ url.append("/");
+ /*
+ * add template context, if one is given
+ */
+ // This is breaking URL's...not sure why it's here. XXX
+ // this should work with the appropriate logic. trying again.
+ if (!(templateContext == null || templateContext.length() == 0)) {
+ url.append(TEMPLATE_CONTEXT_PREFIX).append(templateContext).append("/");
+ }
+
+ // Try to retrieve the bundle.
+ ContentItem bundle = (ContentItem) item.getParent();
+
+ /* It would be nice if we had a ContentPage here, which
+ * supports the getContentBundle() method, but unfortunately
+ * the API sucks and there is no real distinction between mere
+ * ContentItems and top-level items, so we have to use this
+ * hack. TODO: add sanity check that bundle is actually a
+ * ContentItem.
+ */
+ if (bundle != null && bundle instanceof ContentBundle) {
+ s_log.debug("Found a bundle; using its path");
+
+ url.append(bundle.getPath());
+ } else {
+ s_log.debug("No bundle found; using the item's path directly");
+
+ url.append(item.getPath());
+ }
+
+ final String language = item.getLanguage();
+
+ if (language == null) {
+ s_log.debug("The item has no language; omitting the " +
+ "language encoding");
+ } else {
+ s_log.debug("Encoding the language of the item passed in, '" +
+ language + "'");
+
+ url.append(".").append(language);
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Generated preview URL " + url.toString());
+ }
+
+ return url.toString();
+ }
+
+ /**
+ * Retrieves ITEM_ID parameter from URL and
+ * instantiates item according to this ID.
+ *
+ * @param url URL that indicates which item should be retrieved;
+ * must contain the ITEM_ID parameter
+ * @return the ContentItem the given url
+ * points to, or null if no ID has been found in the
+ * url
+ */
+ protected ContentItem getItemFromDraftURL(final String url) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Looking up the item from draft URL " + url);
+ }
+
+ int pos = url.indexOf(ITEM_ID);
+
+ // XXX this is wrong: here we abort on not finding the
+ // parameter; below we return null.
+ if (Assert.isEnabled()) {
+ Assert.isTrue(pos >= 0,
+ "Draft URL must contain parameter " + ITEM_ID);
+ }
+
+ String item_id = url.substring(pos); // item_id == ITEM_ID=.... ?
+
+ pos = item_id.indexOf("="); // should be exactly after the ITEM_ID string
+
+ if (pos != ITEM_ID.length()) {
+ // item_id seems to be something like ITEM_IDFOO=
+
+ s_log.debug("No suitable item_id parameter found; returning null");
+
+ return null; // no ID found
+ }
+
+ pos++; // skip the "="
+
+ // ID is the string between the equal (at pos) and the next separator
+ int i = item_id.indexOf(SEPARATOR);
+ item_id = item_id.substring(pos, Math.min(i, item_id.length() -1));
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Looking up item using item ID " + item_id);
+ }
+
+ OID oid = new OID(ContentItem.BASE_DATA_OBJECT_TYPE, new BigDecimal(item_id));
+ final ContentItem item = (ContentItem) DomainObjectFactory.newInstance
+ (oid);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Returning item " + item);
+ }
+
+ return item;
+ }
+
+ /**
+ * Returns a content item based on URL relative to the root
+ * folder.
+ *
+ * @param url The content item url
+ * @param parentFolder The parent folder object, url must be relevant to it
+ * @return The Content Item instance, it can also be either Bundle
+ * or Folder objects, depending on URL and file language extension
+ */
+ protected ContentItem getItemFromLiveURL(String url,
+ Folder parentFolder) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolving the item for live URL " + url +
+ " and parent folder " + parentFolder);
+ }
+
+ if (parentFolder == null || url == null || url.equals("")) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("The url is null or parent folder was null " +
+ "or something else is wrong, so just return " +
+ "the parent folder");
+ }
+
+ return parentFolder;
+ }
+
+ int len = url.length();
+ int index = url.indexOf('/');
+
+ if (index >= 0) {
+ s_log.debug("The URL starts with a slash; paring off the first " +
+ "URL element and recursing");
+
+ // If we got first slash (index == 0), ignore it and go
+ // on, sample '/foo/bar/item.html.en', in next recursion
+ // will have deal with 'foo' folder.
+
+ String name = index > 0 ? url.substring(0, index) : "";
+ parentFolder = "".equals(name) ? parentFolder
+ : (Folder) parentFolder.getItem(URLEncoder.encode(name), true);
+ url = index + 1 < len ? url.substring(index + 1) : "";
+
+ return getItemFromLiveURL(url, parentFolder);
+ } else {
+ s_log.debug("Found a file element in the URL");
+
+ String[] nameAndLang = getNameAndLangFromURLFrag(url);
+ String name = nameAndLang[0];
+ String lang = nameAndLang[1];
+
+ ContentItem item = parentFolder.getItem(URLEncoder.encode(name), false);
+ return getItemFromLangAndBundle(lang, item);
+ }
+ }
+
+ /**
+ * Returns an array containing the the item's name and lang based
+ * on the URL fragment.
+ *
+ * @return a two-element string array, the first element
+ * containing the bundle name, and the second element containing
+ * the lang string
+ */
+ protected String[] getNameAndLangFromURLFrag(String url) {
+ String name = null;
+ String lang = null;
+
+ /*
+ * Try to find out if there's an extension with the language code
+ * 1 Get a list of all "extensions", i.e. parts of the url
+ * which are separated by colons
+ * 2 If one or more extensions have been found, compare them against
+ * the list of known language codes
+ * 2a if a match is found, this language is used to retrieve an instance
+ * from a bundle
+ * 2b if no match is found
+ */
+
+ final ArrayList exts = new ArrayList(5);
+ final StringTokenizer tok = new StringTokenizer(url, ".");
+
+ while (tok.hasMoreTokens()) {
+ exts.add(tok.nextToken());
+ }
+
+ if (exts.size() > 0) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found some file extensions to look at; they " +
+ "are " + exts);
+ }
+
+ /*
+ * We have found at least one extension, so we can
+ * proceed. Now try to find out if one of the
+ * extensions looks like a language code (we only
+ * support 2-letter language codes!).
+ * If so, use this as the language to look for.
+ */
+
+ /*
+ * First element is the NAME of the item, not an extension!
+ */
+ name = (String) exts.get(0);
+ String ext = null;
+ Collection supportedLangs =
+ LanguageUtil.getSupportedLanguages2LA();
+ Iterator supportedLangIt = null;
+
+ for (int i = 1; i < exts.size(); i++) {
+ ext = (String) exts.get(i);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Examining extension " + ext);
+ }
+
+ /*
+ * Loop through all extensions, but discard the
+ * first one, which is the name of the item.
+ */
+ if (ext != null && ext.length() == 2) {
+ /* Only check extensions consisting of 2
+ * characters.
+ *
+ * Compare current extension with known
+ * languages; if it matches, we've found the
+ * language we should use!
+ */
+ supportedLangIt = supportedLangs.iterator();
+ while (supportedLangIt.hasNext()) {
+ if (ext.equals(supportedLangIt.next())) {
+ lang = ext;
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found a match; using " +
+ "language " + lang);
+ }
+ break;
+ }
+ }
+ } else {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Discarding extension " + ext + "; " +
+ "it is too short");
+ }
+ }
+ }
+ } else {
+ s_log.debug("The file has no extensions; no language was " +
+ "encoded");
+ name = url; // no extension, so we just have a name here
+ lang = null; // no extension, so we cannot guess the language
+ }
+
+ if (Assert.isEnabled()) {
+ Assert.exists(name, "String name");
+ Assert.exists(lang == null || lang.length() == 2);
+ }
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("File name resolved to " + name);
+ s_log.debug("File language resolved to " + lang);
+ }
+ String[] returnArray = new String[2];
+ returnArray[0] = name;
+ returnArray[1] = lang;
+ return returnArray;
+ }
+
+
+ /**
+ * Finds a language instance of a content item given the bundle,
+ * name, and lang string
+ *
+ * @param lang The lang string from the URL
+ * @param item The content bundle
+ *
+ * @return The negotiated lang instance for the current request.
+ */
+ protected ContentItem getItemFromLangAndBundle(String lang, ContentItem item) {
+ if (item != null && item instanceof ContentBundle) {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Found content bundle " + item);
+ }
+ if (lang == null) {
+ s_log.debug("The URL has no language encoded in it; " +
+ "negotiating the language");
+ // There is no language, so we get the negotiated locale and call
+ // this method again with a proper language
+ return this.getItemFromLangAndBundle(GlobalizationHelper.getNegotiatedLocale().getLanguage(), item);
+ } else {
+ s_log.debug("The URL is encoded with a langauge; " +
+ "fetching the appropriate item from " +
+ "the bundle");
+ /*
+ * So the request contains a language code as an
+ * extension of the "name" ==>go ahead and try to
+ * find the item from its ContentBundle. Fail if
+ * the bundle does not contain an instance for the
+ * given language.
+ */
+
+ final ContentItem resolved =
+ ((ContentBundle) item).getInstance(lang);
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Resolved URL to item " + resolved);
+ }
+ return resolved;
+ }
+ } else {
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("I expected to get a content bundle; I got " +
+ item);
+ }
+
+ /*
+ * We expected something like a Bundle, but it seems
+ * like we got something completely different... just
+ * return this crap and let other people's code deal
+ * with it ;-).
+ *
+ * NOTE: This should never happen :-)
+ */
+
+ return item; // might be null
+ }
+ }
+
+}
diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/PageResolver.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/PageResolver.java
new file mode 100755
index 000000000..9ea2c6581
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/PageResolver.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.cms.dispatcher;
+
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+
+
+/**
+ * ContentItem that is set in the page state
+ * by the dispatcher.
+ *
+ * @param state The page state
+ * @return A content item
+ */
+ protected ContentItem getContentItem(final PageState state) {
+ if (CMS.getContext().hasContentItem()) {
+ return CMS.getContext().getContentItem();
+ } else {
+ final CMSPage page = (CMSPage) state.getPage();
+ return page.getContentItem(state);
+ }
+ }
+
+ protected void generateUDItemXML(final UserDefinedContentItem UDItem,
+ final PageState state,
+ final Element parent,
+ final String useContext) {
+
+ final Element element = startElement(useContext, parent);
+ final Element additionalAttrs = UDItemElement(useContext);
+
+ element.addAttribute("type", UDItem.getContentType().getName());
+ element.addAttribute("id", UDItem.getID().toString());
+ element.addAttribute("name", UDItem.getName());
+ element.addAttribute("title", UDItem.getTitle());
+ element.addAttribute("javaClass", UDItem.getContentType().getClassName());
+
+ final DynamicObjectType dot = new DynamicObjectType(UDItem.getSpecificObjectType());
+ final Iterator declaredProperties =
+ dot.getObjectType().getDeclaredProperties();
+ Property currentProperty;
+ Object value;
+ while (declaredProperties.hasNext()) {
+ currentProperty = (Property) declaredProperties.next();
+ value = (Object) UDItem.get(currentProperty.getName());
+ if (value != null) {
+ element.addContent(
+ UDItemAttrElement(currentProperty.getName(),
+ value.toString()));
+ } else {
+ element.addContent(
+ UDItemAttrElement(currentProperty.getName(),
+ "none specified"));
+ }
+ }
+
+ //element.addContent(additionalAttrs);
+ //parent.addContent(element);
+
+ }
+
+ private Element startElement(final String useContext, final Element parent) {
+ //Element element = new Element("cms:item", CMS.CMS_XML_NS);
+ //final Element element = new Element(itemElemName, itemElemNs);
+ final Element element = parent.newChildElement(itemElemName, itemElemNs);
+ if (useContext != null) {
+ element.addAttribute("useContext", useContext);
+ }
+
+ for (Map.EntrygenerateXML method in turn asks the
+ * containing page for the content item, the one that the
+ * {@link com.arsdigita.cms.dispatcher.ItemResolver} found before, and
+ * formats it as an XML document.
+ *
+ * @author Michael Pih (pihman@arsdigita.com)
+ * @author Uday Mathur (umathur@arsdigita.com)
+ * @author Jens Pelzetter
+ */
+public class CMSApplicationPage extends Page {
+
+ private static final Logger LOGGER = LogManager.getLogger(
+ CMSApplicationPage.class);
+
+ /**
+ * The global assets URL stub XML parameter name.
+ */
+ public final static String ASSETS = "ASSETS";
+
+ /**
+ * The XML pageElement class.
+ */
+ public final static String PAGE_CLASS = "CMS";
+
+ /**
+ * Map of XML parameters
+ */
+ private Map
+ * getContentSection(PageState state);
+ *
+ * taskAlerts = {
+ * { "Authoring",
+ * { "enable", "finish", "rollback" }
+ * },
+ * { "Approval",
+ * { "enable", "finish", "rollback" }
+ * },
+ * { "Deploy",
+ * { "enable", "finish", "rollback" }
+ * }
+ * };
+ *
+ *
+ * In the new Initializer system we use a specifically formatted String
+ * Array because we have no List parameter. Format: - A string for each task
+ * to handle, possible values: Authoring, Approval, Deploy - Each Task
+ * String: [taskName]:[alert_1]:...:[alert_n] The specially formatted string
+ * is not handled by StringArray parameter, but forwarded untouched to the
+ * initializer which has the duty to process it!
+ *
+ * Currently there is no way to persist taskAlerts section specific. So all
+ * sections have to treated equally. Default values are provided here.
+ */
+ @Setting
+ private Listcom.arsdigita.sitenode.SiteNodeDispatcher, mapped to
+ * URL "/".
+ *
+ *
+ *
+ * @author Bill Schneider
+ * @author Jens Pelzetter
+ * $
+ */
+public abstract class BaseDispatcherServlet extends HttpServlet
+ implements Dispatcher, DispatcherConstants {
+
+ private static final Logger s_log = Logger.getLogger(
+ BaseDispatcherServlet.class);
+ private final static int NOT_FOUND = 0;
+ private final static int STATIC_FILE = 1;
+ private final static int JSP_FILE = 2;
+ private final static String WEB_XML_22_PUBLIC_ID
+ = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN";
+ private final static String WEB_XML_23_PUBLIC_ID
+ = "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN";
+ /**
+ * We use a Vector here instead of another collection because Vector is
+ * synchronised.
+ */
+ private static Vector s_listenerList = new Vector();
+ /**
+ * list of active requests
+ */
+ private static Vector s_activeList = new Vector();
+
+ static {
+ s_log.debug("Static initalizer starting...");
+ // Add the basic request listeners.
+
+ BaseDispatcherServlet.addRequestListener(new RequestListener() {
+
+ public void requestStarted(RequestEvent re) {
+ DispatcherHelper.setRequest(re.getRequest());
+ }
+
+ public void requestFinished(RequestEvent re) {
+ // We could do this:
+ // DispatcherHelper.setRequest(null);
+ // but some later RequestListener might want to access
+ // the request or session. So we'll just let the
+ // DispatcherHelper hang on to one stale
+ // HttpServletRequest (per thread). The reference will
+ // be overwritten on the next request, so we keep only
+ // a small amount of garbage.
+ }
+
+ });
+
+ BaseDispatcherServlet.addRequestListener(new RequestListener() {
+
+ public void requestStarted(RequestEvent re) {
+
+ }
+
+ public void requestFinished(RequestEvent re) {
+
+ }
+
+ });
+
+
+ s_log.debug("Static initalizer finished.");
+ }
+
+ private List m_welcomeFiles = new ArrayList();
+
+ /**
+ * Reads web.xml to get the configured list of welcome files. We have to
+ * read web.xml ourselves because there is no public API to get this
+ * information from the ServletContext.
+ */
+ public synchronized void init() throws ServletException {
+ super.init();
+ try {
+ File file = new File(getServletContext().getRealPath(
+ "/WEB-INF/web.xml"));
+ // all we care about is the welcome-file-list element
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setValidating(false);
+ SAXParser parser = spf.newSAXParser();
+ parser.parse(file, new WebXMLReader());
+ } catch (SAXException se) {
+ s_log.error("error in init", se);
+ } catch (ParserConfigurationException pce) {
+ s_log.error("error in init", pce);
+ } catch (IOException ioe) {
+ s_log.error("error in init", ioe);
+ }
+ // default to index.jsp, index.html
+ if (m_welcomeFiles.isEmpty()) {
+ m_welcomeFiles.add("index.jsp");
+ m_welcomeFiles.add("index.html");
+ }
+ getServletContext().setAttribute(WELCOME_FILES, m_welcomeFiles);
+ }
+
+ /**
+ * Adds a request listener to dispatch method.
+ *
+ * this.
+ *
+ * @param rl the RequestListener to add to the listener list
+ */
+ public static void addRequestListener(RequestListener rl) {
+ s_listenerList.add(rl);
+ }
+
+ /**
+ * A placeholder method for performing user authentication during request
+ * processing. Subclasses should override this method.
+ *
+ * @param req the current servlet request object
+ * @param req the current servlet response object
+ * @param req the current request context
+ *
+ * @return the updated request context (which may be the same as the context
+ * context parameter).
+ *
+ * @throws com.arsdigita.dispatcher.RedirectException if the dispatcher
+ * should redirect the
+ * client to the page
+ * contained in the
+ * exception
+ *
+ */
+ protected abstract RequestContext authenticateUser(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext ctx)
+ throws RedirectException;
+
+ /**
+ * Called directly by the servlet container when this servlet is invoked
+ * from a URL request. First tries to dispatch the URL to a concrete file on
+ * disk, if there is a matching file. Otherwise, sets up an initial
+ * RequestContext, tries to identify the user/session, parses form
+ * variables, and wraps the request object to handle multipart forms if
+ * necessary. Calls the dispatch method as declared in
+ * implementing subclasses.
+ *
+ * @param req the servlet request
+ * @param resp the servlet response
+ *
+ * @throws javax.servlet.ServletException re-thrown when
+ * dispatch throws an
+ * exception
+ * @throws java.io.IOException re-thrown when
+ * dispatch throws an
+ * IOException
+ */
+ public void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("\n*** *** *** *** *** ***\n"
+ + "Servicing request for URL '" + req
+ .getRequestURI()
+ + "'\n" + "*** *** *** *** *** ***");
+ }
+
+ boolean reentrant = true;
+ RequestContext reqCtx = DispatcherHelper.getRequestContext(req);
+ boolean finishedNormal = false;
+
+ // there are two types of re-entrancy we need to consider:
+ // * forwarded requests specified by the application,
+ // where the forwarded request is picked up by a subclass
+ // of BDS. (e.g., SND forwards /foo/bar to
+ // /packages/foo/www/bar.jsp)
+ // * a secondary request, forwarded by the CONTAINER in response
+ // to an exception thrown from service(), after the first request
+ // completes
+ //
+ // in the FIRST case, we need to guard against running
+ // the start/end listeners again. in the SECOND case,
+ // we need to treat this like a new request so that
+ // we open a transaction, etc. for serving the error page.
+ // wrap entire rest of method in try-catch block. that way if
+ // some method call throws ServletException or IOException,
+ // implicitly exiting the service method, we'll still be able
+ // to free up the database connection in a finally.
+ // STEP #1: if no extension, treat as directory;
+ // make sure we have a trailing slash. and redirect
+ //otherwise.
+ DispatcherHelper.setRequest(req);
+
+ if (trailingSlashRedirect(req, resp)) {
+ // note, this is OUTSIDE of try/catch/finally. No
+ // listeners of any kind are run!
+ return;
+ }
+
+ // STEP #2: try to serve concrete static file, if one exists.
+ // (defer serving concrete JSPs until after listeners run)
+ int concreteFileType = concreteFileType(req);
+ if (concreteFileType == STATIC_FILE) {
+ s_log.debug("Setting world cache headers on static file");
+ DispatcherHelper.cacheForWorld(resp);
+ DispatcherHelper.forwardRequestByName("default", req, resp,
+ getServletContext());
+ return;
+ }
+
+ try {
+ if (req.getAttribute(REENTRANCE_ATTRIBUTE) == null) {
+ reentrant = false;
+
+ waitForPreviousRequestToFinish(req);
+
+ // need an identifier for this particular request
+ String requestId = Thread.currentThread().getName() + "|"
+ + System.
+ currentTimeMillis();
+ req.setAttribute(REENTRANCE_ATTRIBUTE, requestId);
+ s_activeList.add(requestId);
+
+ try {
+ // first time through:
+ // do all actions that must be done initially on hit
+ StartRequestRecord srr = startRequest(req, resp);
+ reqCtx = srr.m_reqCtx;
+ req = srr.m_req;
+ s_log.debug("After startRequest the request is now " + req);
+ } catch (RedirectException re) {
+ final String url = re.getRedirectURL();
+
+ resp.sendRedirect(resp.encodeRedirectURL(url));
+
+ return;
+ }
+ } else {
+ req = DispatcherHelper.maybeWrapRequest(req);
+
+ // if we're handling a secondary request for an
+ // error, but we haven't run the finally ... block
+ // on the primary request yet (this happens when
+ // sendError is called explicitly, as opposed to when
+ // the container calls sendError(500...) in response
+ // to an exception rethrown here) we DON'T run the
+ // request listeners. BUT we need to clear
+ // the request context.
+ if (req.getAttribute(ERROR_REQUEST_ATTRIBUTE) != null
+ || req.getAttribute(JSP_EXCEPTION_ATTRIBUTE) != null) {
+ // reset URL boookeeping but don't wipe out
+ // whole object since it might actually be a
+ // KernelRequestContext with user / session info
+ if (reqCtx instanceof InitialRequestContext) {
+ ((InitialRequestContext) reqCtx).
+ initializeURLFromRequest(req, true);
+ }
+ }
+ }
+ // finally, call dispatch
+ finishedNormal = false;
+
+ if (concreteFileType == JSP_FILE) {
+ // STEP #3: dispatch to a concrete JSP if we have a matching
+ // one
+ DispatcherHelper.forwardRequestByName("jsp", req, resp);
+ } else {
+ // STEP #4: if no concrete file exists, dispatch to
+ // implementing class
+ dispatch(req, resp, reqCtx);
+ }
+
+ // if JSP already dispatched to error page, no exception
+ // will be thrown. have to check for attribute manually.
+ if (req.getAttribute(JSP_EXCEPTION_ATTRIBUTE) == null) {
+ finishedNormal = true;
+ }
+ } catch (AbortRequestSignal ars) {
+ // treat this as a normal end of request and
+ // try to commit
+ finishedNormal = true;
+ } catch (IOException ioe) {
+ s_log.error("error in BaseDispatcherServlet", ioe);
+ throw ioe;
+ } catch (ServletException se) {
+ // SDM #140226, improved handling of
+ // ServletException.getRootCause()
+ Throwable t = se;
+ Throwable rootError;
+ do {
+ rootError = t;
+ t = ((ServletException) t).getRootCause();
+ } while (t instanceof ServletException);
+ if (t != null) {
+ rootError = t;
+ }
+ // handle this in case AbortRequestSignal got wrapped
+ // accidentally--e.g., inside a JSP.
+ if (rootError != null
+ && (rootError instanceof AbortRequestSignal)) {
+ finishedNormal = true;
+ } else if (rootError != null
+ && (rootError instanceof RedirectSignal)) {
+ s_log.debug("rethrowing RedirectSignal", rootError);
+ throw (RedirectSignal) rootError;
+ } else {
+ s_log.error("error in BaseDispatcherServlet", rootError);
+ throw new ServletException(rootError);
+ }
+ } catch (RuntimeException re) {
+ s_log.error("error in BaseDispatcherServlet", re);
+ throw re;
+ } catch (Error error) {
+ s_log.error("error in BaseDispatcherServlet", error);
+ throw error;
+ } finally {
+ if (!reentrant) {
+ // run the request listener events
+ fireFinishedListener(
+ new RequestEvent(req, resp, reqCtx, false,
+ finishedNormal));
+ // at this point, clear the attribute so
+ // a secondary request will work
+ // and remove the request from the list of currently-active
+ // requests
+ Object requestId = req.getAttribute(REENTRANCE_ATTRIBUTE);
+ synchronized (s_activeList) {
+ s_activeList.remove(requestId);
+ s_activeList.notifyAll();
+ }
+ req.removeAttribute(REENTRANCE_ATTRIBUTE);
+ }
+ }
+ }
+
+ /**
+ * Processes a request when it is first handled by the servlet. This method
+ * runs exactly once for each request, even if the request is reentrant.
+ *
+ * @return a tuple containing the updated request context and the request
+ *
+ * @throws com.arsdigita.dispatcher.RedirectException if the dispatcher
+ * should redirect the
+ * client to the page
+ * contained in the
+ * exception
+ *
+ */
+ private StartRequestRecord startRequest(HttpServletRequest req,
+ HttpServletResponse resp)
+ throws RedirectException, IOException, ServletException {
+
+ // turn multipart request into wrapped request
+ // to make up for servlet 2.2 brokenness
+ req = DispatcherHelper.maybeWrapRequest(req);
+
+ RequestContext reqCtx = new InitialRequestContext(req,
+ getServletContext());
+
+ // run the request listener events
+ fireStartListener(new RequestEvent(req, resp, reqCtx, true));
+
+ // Authenticate user AFTER request listeners because authentication
+ // may need to use the database connection (opened by a listener).
+ // Allow subclass to update request context with user info.
+ reqCtx = authenticateUser(req, resp, reqCtx);
+
+ // save the request context in the request
+ DispatcherHelper.setRequestContext(req, reqCtx);
+
+ return new StartRequestRecord(reqCtx, req);
+ }
+
+ /**
+ * Fires all finished listeners. Collects and logs errors to ensure that all
+ * finished listeners run.
+ *
+ * @param evt the current RequestEvent to broadcast to all event listeners
+ */
+ protected void fireFinishedListener(RequestEvent evt) {
+ for (int i = 0; i < s_listenerList.size(); i++) {
+ try {
+ ((RequestListener) s_listenerList.get(i)).requestFinished(evt);
+ } catch (Exception e) {
+ s_log.error("Error running request finished listener "
+ + s_listenerList.
+ get(i) + " (#" + i + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Fires all start listeners. Does not collect and log errors.
+ * Instead, a runtime failure in a start listener will inhibit further
+ * servicing of the request.
+ *
+ * @param evt the current RequestEvent to broadcast to all event listeners
+ */
+ protected void fireStartListener(RequestEvent evt) {
+ for (int i = 0; i < s_listenerList.size(); i++) {
+ ((RequestListener) s_listenerList.get(i)).requestStarted(evt);
+ }
+ }
+
+ /**
+ * Kludge for returning a typed 2-tuple.
+ */
+ private class StartRequestRecord {
+
+ RequestContext m_reqCtx;
+ HttpServletRequest m_req;
+
+ public StartRequestRecord(RequestContext rc, HttpServletRequest req) {
+ m_reqCtx = rc;
+ m_req = req;
+ }
+
+ }
+
+ private void waitForPreviousRequestToFinish(HttpServletRequest req) {
+ // handle concurrence -- serialize requests from the same
+ // user agent, so that you can't follow a link/redirect from
+ // a request until the request's transaction has committed
+
+ // get identifier from previous request, if there is any
+ HttpSession sess = req.getSession(false);
+ if (sess != null) {
+ Object sema = sess.getAttribute(REDIRECT_SEMAPHORE);
+ if (sema != null) {
+ while (s_activeList.indexOf(sema) != -1) {
+ try {
+ synchronized (s_activeList) {
+ s_activeList.wait();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ sess.removeAttribute(REDIRECT_SEMAPHORE);
+ }
+ }
+ }
+
+ /**
+ * helper method: if the current request URL points to a concrete file under
+ * the webapp root, returns STATIC_FILE or JSP_FILE indicating the type of
+ * file. returns NOT_FOUND if no corresponding concrete file exists.
+ *
+ *
+ *
+ *
+ * If we implement each stage as a separate ChainedDispatcher, then we
+ * can mix and match these dispatch stages in any number of
+ * applications.
+ *
+ * @author Bill Schneider
+ * @version $Id$
+ */
+
+public interface ChainedDispatcher {
+
+ public final static int DISPATCH_BREAK = 0;
+ public final static int DISPATCH_CONTINUE = 1;
+
+ /**
+ * Dispatch this request and return a status code if
+ * successful.
+ *
+ * @param request The servlet request object
+ * @param response the servlet response object
+ * @param actx The request context
+ * @return DISPATCH_BREAK if dispatch successful, DISPATCH_CONTINUE
+ * if no resource found (try next dispatcher in chain)
+ */
+ public int chainedDispatch(HttpServletRequest request,
+ HttpServletResponse response,
+ RequestContext actx)
+ throws IOException, ServletException;
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/Dispatcher.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/Dispatcher.java
new file mode 100755
index 000000000..d8758bb89
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/Dispatcher.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Defines a single dispatch
+ * method that allows programmers to pass extra context information
+ * around in a RequestContext object. Thus, when dispatcher A chains
+ * to dispatcher B, dispatcher B can determine what portion of the
+ * original request URL it needs to work with and what portion just
+ * made dispatcher A chain to dispatcher B. This context information
+ * allows a dispatcher to dynamically hand off a request to another
+ * dispatcher.
+ *
+ * A dispatcher is an entry point for a package. Each package
+ * defines a dispatcher, which brokers out requests within the package.
+ * The dispatcher for a package can dispatch requests to other packages.
+ * Multiple packages can share the same dispatcher type but
+ * not instance.RequestContext object
+ * @throws java.io.IOException re-thrown when a dispatcher in the
+ * chain throws an IOException.
+ * @throws javax.servlet.ServletException re-thrown when a dispatcher
+ * in the chain throws a ServletException.
+ */
+ public void dispatch(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext ctx)
+ throws ServletException, IOException {
+
+ Iterator iter = null;
+ synchronized(this) {
+ iter = m_dispatcherChain.iterator();
+ }
+ // already have a new iterator instance, so don't need
+ // to synchronize rest of proc.
+ while (iter.hasNext()) {
+ ChainedDispatcher disp = (ChainedDispatcher)iter.next();
+ int status = disp.chainedDispatch(req, resp, ctx);
+ if (status == ChainedDispatcher.DISPATCH_BREAK) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Adds a dispatcher to the dispatcher chain. Dispatchers are
+ * executed in the order they are added to the chain, so this
+ * dispatcher will be executed after all the dispatchers that
+ * were previously added to the chain and before all the
+ * dispatchers that haven't yet been added to the chain.
+ * @param cd the dispatcher to add
+ */
+ public synchronized void addChainedDispatcher(ChainedDispatcher cd) {
+ m_dispatcherChain.addLast(cd);
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherHelper.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherHelper.java
index d8d1449a3..202876dac 100644
--- a/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherHelper.java
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherHelper.java
@@ -27,6 +27,7 @@ import com.arsdigita.util.URLRewriter;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.web.RedirectSignal;
import com.arsdigita.web.URL;
+import com.arsdigita.web.Web;
import java.io.File;
import java.io.FileNotFoundException;
@@ -50,6 +51,8 @@ import javax.servlet.jsp.PageContext;
import org.apache.log4j.Logger;
+import java.net.URLEncoder;
+
/**
* Class static helper methods for request dispatching.
* Contains various generally useful procedural abstractions.
@@ -1155,4 +1158,42 @@ public final class DispatcherHelper implements DispatcherConstants {
}
}
+ /**
+ * Encodes the given request into a return URL parameter. Returns
+ * URLencode(returnURL) where returnURL is
+ * returnURI?key=URLencode(val)&.... The original parameter values are
+ * doubly-encoded so that they are decoded appropriately.
+ *
+ *
+ * @param req the request to encode
+ *
+ * @return the URL-encoded parameter
+ *
+ */
+ public static String encodeReturnURL(HttpServletRequest req) {
+ StringBuilder returnURL = new StringBuilder(100);
+ returnURL.append(Web.getWebContext().getRequestURL().getRequestURI());
+ returnURL.append('?');
+
+ // convert posted parameters to URL parameters
+ Enumeration params = req.getParameterNames();
+ boolean first = true;
+ while (params.hasMoreElements()) {
+ String key = (String) params.nextElement();
+ String[] vals = req.getParameterValues(key);
+ for (int i = 0; i < vals.length; i++) {
+ if (first) {
+ first = false;
+ } else {
+ returnURL.append('&');
+ }
+ returnURL.append(key);
+ returnURL.append('=');
+ returnURL.append(URLEncoder.encode(vals[i]));
+ }
+ }
+
+ return URLEncoder.encode(returnURL.toString());
+ }
+
}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/JSPApplicationDispatcher.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/JSPApplicationDispatcher.java
new file mode 100755
index 000000000..57522fad2
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/JSPApplicationDispatcher.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import java.io.File;
+import java.io.IOException;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
+
+/**
+ * Basic dispatcher class for dispatching URLs to JSP or
+ * other file-based assets (images, CSS, HTML, etc.) in the file
+ * system in the context of a single package's URL space.
+ *
+ * $root/packages/$key/$remaining-url
+ *
+ * where
+ * $root is the ACS webapp root
+ * $key is siteNode.getPackageKey()
+ * $remaining-url is given by appContext.getRemainingURLPart()
+ *
+ *
+ *
+ * @author Bill Schneider
+ * @author Jens Pelzetter
+ */
+public class JSPApplicationDispatcher extends BaseDispatcherServlet
+ implements Dispatcher {
+
+ private static final Logger s_log = Logger.getLogger
+ (JSPApplicationDispatcher.class);
+
+ private static JSPApplicationDispatcher s_instance = newInstance();
+
+ /**
+ * Returns a new instance of a JSPApplicationDispatcher.
+ * @return a new JSPApplicationDispatcher.
+ */
+ public static JSPApplicationDispatcher newInstance() {
+ return new JSPApplicationDispatcher();
+ }
+
+ /**
+ * Returns a new instance of JSPApplicationDispatcher.
+ * @return a JSPApplicationDispatcher object.
+ * @deprecated No longer returns a singleton instance. Remains
+ * in place for API stability.
+ */
+ public static JSPApplicationDispatcher getInstance() {
+ return s_instance;
+ }
+
+ // No Authentication is performed here.
+ protected RequestContext authenticateUser(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext ctx)
+ throws RedirectException {
+ return ctx;
+ }
+
+ public void dispatch(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext actx)
+ throws IOException, ServletException {
+
+ // Set the request context as a request attribute because a
+ // JSP page might need it, which doesn't get passed the
+ // request context as a parameter.
+ DispatcherHelper.setRequestContext(req, actx);
+
+ ServletContext sctx = actx.getServletContext();
+ String remainingURL = actx.getRemainingURLPart();
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("I think the remaining URL is '" + remainingURL + "'");
+ }
+
+ // This is where we forward a request from /foo1/bar.ext or
+ // /foo2/bar.ext to /packages/foo/www/bar.ext the concrete
+ // file should then get picked up by BaseDispatcherServlet.
+
+ String concreteURL =
+ actx.getPageBase() +
+ actx.getRemainingURLPart();
+
+ if (s_log.isDebugEnabled()) {
+ s_log.debug("Looking for a concrete resource under the web app " +
+ "context at '" + concreteURL + "'");
+ }
+
+ File concreteFile = new File(sctx.getRealPath(concreteURL));
+
+ if (concreteFile.exists()) {
+ s_log.debug("Resource was found; forwarding");
+ DispatcherHelper.setRequestContext(req, actx);
+ DispatcherHelper.forwardRequestByPath(concreteURL, req, resp);
+ } else {
+ s_log.debug("Resource not found");
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestEvent.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestEvent.java
new file mode 100755
index 000000000..7c7e8fc7a
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestEvent.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Request event class. Passed as a
+ * parameter to the methods of RequestListener.
+ *
+ * @see RequestListener
+ * @author Bill Schneider
+ * @version $Id$
+ * @since 4.5 */
+public class RequestEvent {
+
+
+ private HttpServletRequest m_req;
+ private HttpServletResponse m_resp;
+ private RequestContext m_ctx;
+ private boolean m_start;
+ private boolean m_finished_normal;
+
+ /**
+ * Creates a new
+ * Request URL File served
+ * /sample1/page.jsp /packages/sample-app/www/page.jsp
+ *
+ * /sample2/image.gif /packages/sample-app/www/image.gif
+ *
+ * /sample2/script.jsp /packages/sample-app/www/script.jsp
+ * RequestEvent with a success/failure status
+ * code.
+ * @param req the current request
+ * @param resp the current response
+ * @param ctx the current request context
+ * @param start if true, indicates that this is a start-request event;
+ * if false, this is an end-request event.
+ * @param finishedNormal if true, indicates the request finished
+ * without error.
+ */
+ public RequestEvent(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext ctx,
+ boolean start,
+ boolean finishedNormal) {
+ m_req = req;
+ m_resp = resp;
+ m_ctx = ctx;
+ m_start = start;
+ m_finished_normal = finishedNormal;
+ }
+
+
+ /**
+ * Creates a new RequestEvent with no status code.
+ * @param req the current request
+ * @param resp the current response
+ * @param ctx the current request context
+ * @param start if true, indicates that this is a start-request event;
+ * if false, this is an end-request event.
+ */
+ public RequestEvent(HttpServletRequest req,
+ HttpServletResponse resp,
+ RequestContext ctx,
+ boolean start) {
+ this(req, resp, ctx, start, false);
+ }
+
+
+ /**
+ * Returns the current request for the request event.
+ * @return the current request
+ */
+ public HttpServletRequest getRequest() {
+ return m_req;
+ }
+
+ /**
+ * Returns the current response for the request event.
+ * @return the current response
+ */
+ public HttpServletResponse getResponse() {
+ return m_resp;
+ }
+
+ /**
+ * Returns the current request context for the request event.
+ * @return the current request context
+ */
+ public RequestContext getRequestContext() {
+ return m_ctx;
+ }
+
+ /**
+ * Returns true if the event is a start-request event;
+ * false for an end-request event.
+ *
+ * @return true if we're starting a request, false at end.
+ */
+ public boolean isStart() {
+ return m_start;
+ }
+
+ /**
+ * Returns a status code to indicate whether the request
+ * finished without error.
+ *
+ * @return true if the request finished without exception.
+ * false if the request finished with an error, or if the request
+ * event is a start-request.
+ */
+ public boolean finishedNormal() {
+ return m_finished_normal;
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestListener.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestListener.java
new file mode 100755
index 000000000..4a46ed758
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/RequestListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+package com.arsdigita.dispatcher;
+
+import java.util.EventListener;
+
+/**
+ * Used to register callbacks for the code to run
+ * when a request starts or ends.
+ *
+ * @author Bill Schneider
+ * @version $Id$
+ * @since 4.5 */
+public interface RequestListener extends EventListener {
+
+ /**
+ * Called when ACSServlet starts processing an incoming request.
+ * @param e the event
+ */
+ public void requestStarted(RequestEvent e);
+
+ /**
+ * Called when ACSServlet finishes processing an incoming request.
+ * @param e the event
+ */
+ public void requestFinished(RequestEvent e);
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java b/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java
index 71de14941..46b5750ba 100644
--- a/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java
+++ b/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java
@@ -234,7 +234,9 @@ public final class WebConfig {
= (Class