From 61bea04c13f9ebe3ce9add92a45ec405d89587cc Mon Sep 17 00:00:00 2001 From: jensp Date: Thu, 28 Jul 2016 17:36:52 +0000 Subject: [PATCH] CCM NG: Current status of porting the CMS UI to CCM NG git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4209 8810af33-2d31-482b-a856-94f89814c4df --- .../src/main/java/com/arsdigita/cms/CMS.java | 85 ++ .../java/com/arsdigita/cms/CMSContext.java | 173 ++++ .../java/com/arsdigita/cms/ContentCenter.java | 31 + .../arsdigita/cms/ContentCenterServlet.java | 307 ++++++ .../cms/ContentItemXMLRenderer.java.off | 155 +++ .../arsdigita/cms/ContentSectionServlet.java | 757 ++++++++++++++ .../cms/dispatcher/AbstractItemResolver.java | 172 ++++ .../cms/dispatcher/CMSDispatcher.java | 707 ++++++++++++++ .../com/arsdigita/cms/dispatcher/CMSPage.java | 317 ++++++ .../cms/dispatcher/ContentItemDispatcher.java | 327 +++++++ .../cms/dispatcher/ContentPanel.java | 128 +++ .../dispatcher/ContentSectionDispatcher.java | 132 +++ .../DefaultTemplateResolver.java.off | 246 +++++ .../cms/dispatcher/FileDispatcher.java | 88 ++ .../cms/dispatcher/ItemDispatcher.java | 305 ++++++ .../cms/dispatcher/ItemResolver.java | 173 ++++ .../com/arsdigita/cms/dispatcher/ItemXML.java | 89 ++ .../arsdigita/cms/dispatcher/MasterPage.java | 64 ++ .../MultilingualItemResolver.java.off | 916 +++++++++++++++++ .../cms/dispatcher/PageResolver.java | 105 ++ .../cms/dispatcher/ResourceHandler.java | 63 ++ .../cms/dispatcher/ResourceHandlerImpl.java | 104 ++ .../dispatcher/SimpleXMLGenerator.java.off | 445 +++++++++ .../cms/dispatcher/TemplateResolver.java.off | 97 ++ .../arsdigita/cms/dispatcher/Utilities.java | 250 +++++ .../cms/dispatcher/XMLGenerator.java | 52 + .../arsdigita/cms/ui/CMSApplicationPage.java | 229 +++++ .../arsdigita/cms/ui/ItemSearchPage.java.off | 346 +++++++ .../cms/ui/contentcenter/MainPage.java.off | 234 +++++ .../java/org/arsdigita/cms/CMSConfig.java | 922 ++++++++++++++++++ .../main/java/org/librecms/CmsConstants.java | 41 +- .../main/java/org/librecms/ContentType.java | 33 + .../main/java/org/librecms/ContentTypes.java | 35 + .../contentsection/ContentItemManager.java | 92 ++ .../contentsection/ContentItemRepository.java | 23 +- .../contentsection/ContentSectionConfig.java | 143 ++- .../dispatcher/BaseDispatcherServlet.java | 639 ++++++++++++ .../dispatcher/ChainedDispatcher.java | 79 ++ .../com/arsdigita/dispatcher/Dispatcher.java | 66 ++ .../arsdigita/dispatcher/DispatcherChain.java | 97 ++ .../dispatcher/DispatcherHelper.java | 41 + .../dispatcher/JSPApplicationDispatcher.java | 139 +++ .../arsdigita/dispatcher/RequestEvent.java | 126 +++ .../arsdigita/dispatcher/RequestListener.java | 43 + .../java/com/arsdigita/web/WebConfig.java | 4 +- 45 files changed, 9601 insertions(+), 19 deletions(-) create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/CMS.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/CMSContext.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ContentCenter.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ContentItemXMLRenderer.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ContentSectionServlet.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/AbstractItemResolver.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSDispatcher.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/CMSPage.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentItemDispatcher.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentPanel.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentSectionDispatcher.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/DefaultTemplateResolver.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/FileDispatcher.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemDispatcher.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemResolver.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ItemXML.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MasterPage.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MultilingualItemResolver.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/PageResolver.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandler.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandlerImpl.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleXMLGenerator.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/TemplateResolver.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/Utilities.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/XMLGenerator.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ui/CMSApplicationPage.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/MainPage.java.off create mode 100644 ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java create mode 100644 ccm-cms/src/main/java/org/librecms/ContentType.java create mode 100644 ccm-cms/src/main/java/org/librecms/ContentTypes.java create mode 100644 ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/BaseDispatcherServlet.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/ChainedDispatcher.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/Dispatcher.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/DispatcherChain.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/JSPApplicationDispatcher.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/RequestEvent.java create mode 100755 ccm-core/src/main/java/com/arsdigita/dispatcher/RequestListener.java 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 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 + * @author Jens Pelzetter + */ +@WebServlet(urlPatterns = "/content-center/*") +public class ContentCenterServlet extends BaseApplicationServlet { + + private static final long serialVersionUID = 16543266935651171L; + + /** + * URL (pathinfo) -> Page object mapping. Based on it (and the http request + * url) the doService method to selects a page to display + */ + private final Map pages = new HashMap<>(); + + /** + * Path to directory containg ccm-cms template files + */ + private String m_templatePath; + /** + * Resolvers to find templates (JSP) and other stuff stored in file system. + */ + private ApplicationFileResolver m_resolver; + + private static final Logger LOGGER = LogManager.getLogger( + ContentCenterServlet.class); + + /** + * Use parent's class initialisation extension point to perform additional + * initialisation tasks. + */ + @Override + protected void doInit() { + LOGGER.info("starting doInit method"); + + // NEW STUFF here used to process the pages in this servlet + // Addresses previously noted in WEB-INF/resources/content-center-map.xml + // Obviously not required. + +//ToDo +// addPage("/", new MainPage()); // index page at address ~/cc +// addPage("/index", new MainPage()); +//ToDo End + +// addPage("/item-search", new CMSItemSearchPage()); + // Old style + //addPage("/item-search", new ItemSearchPage()); + //addPage("/searchredirect", new CMSSearchResultRedirector()); + + // STUFF to use for JSP extension, i.e. jsp's to try for URLs which are not + // handled by the this servlet directly. + /** + * Set Template base path for JSP's + */ + // ToDo: Make it configurable by an appropriate config registry entry! + // m_templatePath = CMS.getConfig().getTemplateRoot(); + m_templatePath = "/templates/ccm-cms/content-center"; + /** + * Set TemplateResolver class + */ + m_resolver = WebConfig.getConfig().getResolver(); + } + + @Override + protected void doService(final HttpServletRequest sreq, + final HttpServletResponse sresp, + final CcmApplication app) throws ServletException, + IOException { + LOGGER.info("starting doService method"); + + // ContentCenter workspace = (ContentCenter) app; + + /* Check user and privilegies */ + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final Shiro shiro = cdiUtil.findBean(Shiro.class); + if (shiro.getSubject().isAuthenticated()) { + throw new LoginSignal(sreq); // send to login page + } + final PermissionChecker permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + final ContentSectionRepository sectionRepo = cdiUtil.findBean( + ContentSectionRepository.class); + final List sections = sectionRepo.findAll(); + boolean hasAccess = false; + for (final ContentSection section : sections) { + if (permissionChecker.isPermitted(CmsConstants.PRIVILEGE_ITEMS_EDIT, + section.getRootDocumentsFolder())) { + hasAccess = true; + break; + } + } + + if (!hasAccess) { // user has no access privilege + throw new AuthorizationException( + "User is not entitled to access any content section"); + // throw new LoginSignal(sreq); // send to login page + } + + // New way to fetch the page + String pathInfo = sreq.getPathInfo(); + if (pathInfo.length() > 1 && pathInfo.endsWith("/")) { + /* NOTE: ServletAPI specifies, pathInfo may be empty or will + * start with a '/' character. It currently carries a + * trailing '/' if a "virtual" page, i.e. not a real jsp, but + * result of a servlet mapping. But Application requires url + * NOT to end with a trailing '/' for legacy free applications. */ + pathInfo = pathInfo.substring(0, pathInfo.length() - 1); + } + + // An empty remaining URL or a URL which doesn't end in trailing slash: + // probably want to redirect. + // Probably DEPRECATED with new access method or only relevant for jsp + // extension + // if (m_trailingSlashList.contains(url) && !originalUrl.endsWith("/")) { + // DispatcherHelper.sendRedirect(sresp, originalUrl + "/"); + // return; + // } + final Page page = (Page) pages.get(pathInfo); + if (page != null) { + + // Check user access. + checkUserAccess(sreq, sresp); + + if (page instanceof CMSPage) { + // backwards compatibility fix until migration completed + final CMSPage cmsPage = (CMSPage) page; + final RequestContext ctx = DispatcherHelper.getRequestContext(); + cmsPage.init(); + cmsPage.dispatch(sreq, sresp, ctx); + } else { + final CMSApplicationPage cmsAppPage = (CMSApplicationPage) page; + cmsAppPage.init(sreq, sresp, app); + // Serve the page. + final Document doc = cmsAppPage.buildDocument(sreq, sresp); + + PresentationManager pm = Templating.getPresentationManager(); + pm.servePage(doc, sreq, sresp); + } + + } else { + // Fall back on the JSP application dispatcher. + // NOTE: The JSP must ensure the proper authentication and + // authorisation if required! + LOGGER.info("NO page registered to serve the requst url."); + + RequestDispatcher rd = m_resolver.resolve(m_templatePath, + sreq, sresp, app); + if (rd != null) { + LOGGER.debug("Got dispatcher " + rd); + + final HttpServletRequest origreq = DispatcherHelper + .restoreOriginalRequest(sreq); + rd.forward(origreq, sresp); + } else { + + sresp.sendError(404, sreq.getRequestURI() + + " not found on this server."); + } + + } + + LOGGER.info("doService method completed"); + + } // END doService() + + /** + * Internal service mechod, adds one pair of Url - Page to the internal hash + * map, used as a cache. + * + * @param pathInfo url stub for a page to display + * @param page Page object to display + */ + private void addPage(final String pathInfo, final Page page) { + + // Current Implementation requires pathInfo to start with a leading '/' + // SUN Servlet API specifies: "PathInfo *may be empty* or will start + // with a '/' character." + pages.put(pathInfo, page); + + } + +// /** +// * Service Method returns the URL stub for the class name, can return null +// * if not mapped +// */ +// // Currently still in use by c.ad.cms.ui.ItemSearchWidget +// public static String getURLStubForClass(String classname) { +// LOGGER.debug("Getting URL Stub for : " + classname); +// Iterator itr = s_pageURLs.keySet().iterator(); +// while (itr.hasNext()) { +// String classname2 = (String) itr.next(); +// s_log.debug("key: " + classname + " value: " +// + (String) s_pageURLs.get(classname2)); +// } +// String url = (String) s_pageURLs.get(classname); +// return url; +// } + /** + * 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 + * + */ + protected void checkUserAccess(final HttpServletRequest request, + final HttpServletResponse response //, + /// final RequestContext actx + ) + throws ServletException { + + if (CdiUtil.createCdiUtil().findBean(Shiro.class).getSubject() + .isAuthenticated()) { + throw new LoginSignal(request); + } + } + + /** + * 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 { + String url = Util.getSecurityHelper() + .getLoginURL(req) + + "?" + LoginHelper.RETURN_URL_PARAM_NAME + + "=" + DispatcherHelper.encodeReturnURL(req); + try { + LoginHelper.sendRedirect(req, resp, url); + } catch (IOException e) { + LOGGER.error("IO Exception", e); + throw new ServletException(e.getMessage(), e); + } + } + + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentItemXMLRenderer.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ContentItemXMLRenderer.java.off new file mode 100644 index 000000000..0ce8896c3 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentItemXMLRenderer.java.off @@ -0,0 +1,155 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.arsdigita.cms; + +import com.arsdigita.cms.contenttypes.GenericAddress; +import com.arsdigita.dispatcher.DispatcherHelper; +import com.arsdigita.domain.DomainObject; +import com.arsdigita.domain.DomainObjectTraversalAdapter; +import com.arsdigita.domain.DomainObjectXMLRenderer; +import com.arsdigita.globalization.GlobalizationHelper; +import com.arsdigita.persistence.metadata.Property; +import com.arsdigita.xml.Element; +import org.apache.log4j.Logger; + +/** + * This is a special ContentItemXMLRenderer for CMS to get a more transparent + * way to handle ContentBundles during XML output. + * + * The problem was to change RelatedLinks and therefore Link to always link to + * the corresponding ContentBundle instead of the content item. To get the + * corresponding content item during XML generation, I have to test for + * ContentBundle and negotiate the language version. + * This is not possible in com.arsdigita.ccm + * + * @author quasi + */ +public class ContentItemXMLRenderer extends DomainObjectXMLRenderer { + + private static final Logger logger = + Logger.getLogger(ContentItemXMLRenderer.class); + private String m_propertyName = ""; + private String m_keyName = ""; + private String m_relationAttribute = ""; + + public ContentItemXMLRenderer(final Element root) { + super(root); + } + + // This method will be called by DomainObjectTraversal.walk() + // It's purpose is to test for ContentBundle objects and if found, replace + // that object with the negotiated version of the content item. + // Otherwise this methd will do nothing. + @Override + protected void walk(final DomainObjectTraversalAdapter adapter, + final DomainObject obj, + final String path, + final String context, + final DomainObject linkObject) { + //final long start = System.nanoTime(); + + DomainObject nObj = obj; + + if (nObj instanceof ContentBundle) { + + nObj = ((ContentBundle) obj).getInstance(GlobalizationHelper.getNegotiatedLocale(), true); + } + + super.walk(adapter, nObj, path, context, linkObject); + + //System.out.printf("Walked object in %d ms\n", (System.nanoTime() - start) / 1000000); + } + + @Override + protected void handleAttribute(final DomainObject obj, final String path, final Property property) { + final String propertyName = property.getName(); + + // Special handling for the isoCountryCode field in GenericAddress + if (obj instanceof GenericAddress && "isoCountryCode".equals(propertyName)) { + //if (propertyName.equals("isoCountryCode")) { + super.handleAttribute(obj, path, property); + + final Element element = newElement(m_element, "country"); + element.setText(GenericAddress.getCountryNameFromIsoCode(((GenericAddress) obj).getIsoCountryCode())); + return; + + } + + // Special handling for the relation attribute keys + if (!m_relationAttribute.isEmpty()) { + String key = ""; + + // The RelationAttribute is part of this domain object as field + if (obj instanceof RelationAttributeInterface + && ((RelationAttributeInterface) obj). + hasRelationAttributeProperty(propertyName)) { + + final RelationAttributeInterface relationAttributeObject = (RelationAttributeInterface) obj; + key = relationAttributeObject.getRelationAttributeKey( + propertyName); + + } + + // This RelationAttribute is part of an n:m association as link attribute + if (obj instanceof LinkDomainObject + && propertyName.equals(m_keyName)) { + key = (String) ((LinkDomainObject) obj).get(m_keyName); + } + + // Replace value of the property defined in RELATION_ATTRIBUTES string + // of the primary domain object with the localized String. + if (!key.isEmpty()) { +// logger.debug(String.format( +// "Getting relation attribute value for key '%s' of relation attribute '%s'", +// key, m_relationAttribute)); + final RelationAttributeCollection relationAttributeCollection = new RelationAttributeCollection( + m_relationAttribute, key); + relationAttributeCollection.addLanguageFilter(GlobalizationHelper. + getNegotiatedLocale().getLanguage()); + if (!relationAttributeCollection.isEmpty()) { + relationAttributeCollection.next(); + final Element element = newElement(m_element, m_keyName); + element.setText(relationAttributeCollection.getName()); + final Element elementId = newElement(m_element, m_keyName + "Id"); + elementId.setText(relationAttributeCollection.getKey()); + relationAttributeCollection.close(); + } + return; + } + } + + super.handleAttribute(obj, path, property); + } + + @Override + protected void beginAssociation(final DomainObject obj, final String path, final Property property) { + super.beginAssociation(obj, path, property); + + final String propertyName = property.getName(); + + if (obj instanceof RelationAttributeInterface + && ((RelationAttributeInterface) obj).hasRelationAttributeProperty( + propertyName)) { + + final RelationAttributeInterface relationAttributeObject = (RelationAttributeInterface) obj; + + m_propertyName = propertyName; + m_keyName = relationAttributeObject.getRelationAttributeKeyName(propertyName); + m_relationAttribute = relationAttributeObject.getRelationAttributeName(propertyName); + + } + } + + @Override + protected void endAssociation(final DomainObject obj, final String path, final Property property) { + + m_propertyName = ""; + m_keyName = ""; + m_relationAttribute = ""; + + super.endAssociation(obj, path, property); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentSectionServlet.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentSectionServlet.java new file mode 100755 index 000000000..0442fea9f --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentSectionServlet.java @@ -0,0 +1,757 @@ +/* + * Copyright (C) 2003-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.bebop.Page; +import com.arsdigita.cms.dispatcher.CMSDispatcher; +import com.arsdigita.cms.dispatcher.CMSPage; +import com.arsdigita.cms.dispatcher.ContentItemDispatcher; +import com.arsdigita.cms.dispatcher.ItemResolver; +import com.arsdigita.cms.ui.CMSApplicationPage; +import com.arsdigita.dispatcher.AccessDeniedException; +import com.arsdigita.dispatcher.DispatcherHelper; +import com.arsdigita.dispatcher.RequestContext; +import com.arsdigita.templating.PresentationManager; +import com.arsdigita.templating.Templating; +import com.arsdigita.util.Assert; +import com.arsdigita.util.Classes; +import com.arsdigita.web.ApplicationFileResolver; +import com.arsdigita.web.BaseApplicationServlet; +import com.arsdigita.web.LoginSignal; +import com.arsdigita.web.Web; +import com.arsdigita.web.WebConfig; +import com.arsdigita.xml.Document; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.arsdigita.cms.CMSConfig; +import org.hibernate.secure.spi.PermissibleAction; +import org.hibernate.secure.spi.PermissionCheckEntityInformation; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.l10n.GlobalizationHelper; +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.ContentItemManager; +import org.librecms.contentsection.ContentItemRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionConfig; +import org.librecms.lifecycle.Lifecycle; + +import javax.enterprise.inject.spi.CDI; + +/* + * NOTE: + * Repaired ItemURLCache to save multilingual items with automatic + * language negotiation. The cache now uses the remaining url part + * and the language concatinated as a hash table key. The delimiter + * is CACHE_KEY_DELIMITER. + */ + + /* + * NOTE 2: + * In a process of refactoring from legacy compatible to legacy free applications. + * TODO: + * - replace url check using RequestContext which resolves to SiteNodeRequest + * implementation (due to SiteNodeRequest used in BaseApplicationServlet). + * - Refactor content item UI bebop ApplicationPage or PageFactory instead of + * legacy infected sitenode / package dispatchers. + */ +/** + * Content Section's Application Servlet according CCM core web application + * structure { + * + * @see com.arsdigita.web.Application} implements the content section UI. + * + * It handles the UI for content items and delegates the UI for sections and + * folders to jsp templates. + * + * @author unknown + * @author Sören Bernstein + * @author Peter Boy + */ +public class ContentSectionServlet extends BaseApplicationServlet { + + /** + * Internal logger instance to faciliate debugging. Enable logging output by + * editing /WEB-INF/conf/log4j.properties int hte runtime environment and + * set com.arsdigita.cms.ContentSectionServlet=DEBUG by uncommenting or + * adding the line. + */ + private static final Logger s_log = Logger.getLogger( + ContentSectionServlet.class); + /** + * Stringarray of file name patterns for index files. + */ +// private static final String[] WELCOME_FILES = new String[]{ +// "index.jsp", "index.html" +// }; + + // Some literals + /** + * Literal for the prefix (in url) for previewing items + */ + public static final String PREVIEW = "/preview"; + /** + * Literal Template files suffix + */ + public static final String FILE_SUFFIX = ".jsp"; + /** + * Literal of URL Stub for index file name (includes leading slash) + */ + public static final String INDEX_FILE = "/index"; + public static final String XML_SUFFIX = ".xml"; + public static final String XML_MODE = "xmlMode"; + public static final String MEDIA_TYPE = "templateContext"; + private static final String CACHE_KEY_DELIMITER = "%"; + + public static final String CONTENT_ITEM + = "com.arsdigita.cms.dispatcher.item"; + public static final String CONTENT_SECTION + = "com.arsdigita.cms.dispatcher.section"; + + private final ContentItemDispatcher m_disp = new ContentItemDispatcher(); + public static Map s_itemResolverCache = Collections + .synchronizedMap(new HashMap()); +// private static Map s_itemURLCacheMap = null; + /** + * Whether to cache the content items + */ + private static final boolean s_cacheItems = true; + // NEW STUFF here used to process the pages in this servlet + /** + * URL (pathinfo) -> Page object mapping. Based on it (and the http request + * url) the doService method selects a page to display + */ + private final Map m_pages = new HashMap(); + /** + * Path to directory containg ccm-cms template (jsp) files + */ + private String m_templatePath; + + /** + * Resolver to actually use to find templates (JSP). JSP may be stored in + * file system or otherwise, depends on resolver. Resolver is retrieved from + * configuration. (probably used for other stuff as JSP's as well) + */ + private ApplicationFileResolver m_resolver; + + /** + * Init method overwrites parents init to pass in optional parameters + * {@link com.arsdigita.web.BaseServlet}. If not specified system wide + * defaults are used. + * + * @param config + * + * @throws javax.servlet.ServletException + */ + @Override + public void init(ServletConfig config) throws ServletException { + + super.init(config); + + // optional init-param named template-path from ~/WEB-INF/web.xml + // may overwrite configuration parameters + String templatePath = config.getInitParameter("template-path"); + if (templatePath == null) { + m_templatePath = CMSConfig.getConfig().getTemplateRootPath(); + } else { + m_templatePath = config.getInitParameter("template-path"); + } + + Assert.exists(m_templatePath, String.class); + Assert.isTrue(m_templatePath.startsWith("/"), + "template-path must start with '/'"); + Assert.isTrue(!m_templatePath.endsWith("/"), + "template-path must not end with '/'"); + + // optional init-param named file-resolver from ~/WEB-INF/web.xml + String resolverName = config.getInitParameter("file-resolver"); + if (resolverName == null) { + m_resolver = WebConfig.getConfig().getResolver(); + } else { + m_resolver = (ApplicationFileResolver) Classes.newInstance( + resolverName); + } + if (s_log.isDebugEnabled()) { + s_log.debug("Template path is " + m_templatePath + " with resolver " + + m_resolver. + getClass().getName()); + } + + // NEW STUFF here will be used to process the pages in this servlet + // Currently NOT working + // addPage("/admin", new MainPage()); // index page at address ~/cs + // addPage("/admin/index.jsp", new MainPage()); + // addPage("/admin/item.jsp", new MainPage()); + } + + /** + * Internal service method, adds one pair of Url - Page to the internal hash + * map, used as a cache. + * + * @param pathInfo url stub for a page to display + * @param page Page object to display + */ + private void addPage(final String pathInfo, final Page page) { + + Assert.exists(pathInfo, String.class); + Assert.exists(page, Page.class); + // Current Implementation requires pathInfo to start with a leading '/' + // SUN Servlet API specifies: "PathInfo *may be empty* or will start + // with a '/' character." + Assert.isTrue(pathInfo.startsWith("/"), "path starts not with '/'"); + + m_pages.put(pathInfo, page); + } + + /** + * Implementation of parent's (abstract) doService method checks HTTP + * request to determine whether to handle a content item or other stuff + * which is delegated to jsp templates. { + * + * @see com.arsdigita.web.BaseApplicationServlet#doService + * (HttpServletRequest, HttpServletResponse, Application)} + * + * @param sreq + * @param sresp + * @param app + * + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + @Override + protected void doService(HttpServletRequest sreq, + HttpServletResponse sresp, + CcmApplication app) + throws ServletException, IOException { + + ContentSection section = (ContentSection) app; + + // //////////////////////////////////////////////////////////////////// + // Prepare OLD style dispatcher based page service + // //////////////////////////////////////////////////////////////////// + /* + * NOTE: + * Used to resolve to SiteNodeRequestContext (old style applications) + * which has been removed. + * Resolves currently to + * KernelRequestContext which will be removed as well. + */ + RequestContext ctx = DispatcherHelper.getRequestContext(); + String url = ctx.getRemainingURLPart(); // here KernelRequestContext now + if (s_log.isInfoEnabled()) { + s_log.info("Resolving URL " + url + " and trying as item first."); + } + final ItemResolver itemResolver = getItemResolver(section); + + // //////////////////////////////////////////////////////////////////// + // Prepare NEW style servlet based bebob page service + // //////////////////////////////////////////////////////////////////// + String pathInfo = sreq.getPathInfo(); + + final ContentItem item = getItem(section, pathInfo, itemResolver); + + Assert.exists(pathInfo, "String pathInfo"); + if (pathInfo.length() > 1 && pathInfo.endsWith("/")) { + /* NOTE: ServletAPI specifies, pathInfo may be empty or will + * start with a '/' character. It currently carries a + * trailing '/' if a "virtual" page, i.e. not a real jsp, but + * result of a servlet mapping. But Application requires url + * NOT to end with a trailing '/' for legacy free applications. */ + pathInfo = pathInfo.substring(0, pathInfo.length() - 1); + } + final Page page = (Page) m_pages.get(pathInfo); + + // //////////////////////////////////////////////////////////////////// + // Serve the page + // //////////////////////////////////////////////////////////////////// + /* FIRST try new style servlet based service */ + if (page != null) { + + // Check user access. + // checkUserAccess(sreq, sresp); // done in individual pages ?? + if (page instanceof CMSPage) { + // backwards compatibility fix until migration completed + final CMSPage cmsPage = (CMSPage) page; + // final RequestContext ctx = DispatcherHelper.getRequestContext(); + cmsPage.init(); + cmsPage.dispatch(sreq, sresp, ctx); + } else { + final CMSApplicationPage cmsAppPage = (CMSApplicationPage) page; + cmsAppPage.init(sreq, sresp, app); + // Serve the page. + final Document doc = cmsAppPage.buildDocument(sreq, sresp); + + PresentationManager pm = Templating.getPresentationManager(); + pm.servePage(doc, sreq, sresp); + } + + /* SECONDLY try if we have to serve an item (old style dispatcher based */ + } else if (item != null) { + + serveItem(sreq, sresp, section, item); + + /* OTHERWISE delegate to a JSP in file system */ + } else { + + /* We have to deal with a content-section, folder or another bit */ + if (s_log.isInfoEnabled()) { + s_log.info("NOT serving content item"); + } + + /* Store content section in http request to make it available + * for admin/index.jsp */ + sreq.setAttribute(CONTENT_SECTION, section); + + RequestDispatcher rd = m_resolver.resolve(m_templatePath, + sreq, sresp, app); + if (rd != null) { + if (s_log.isDebugEnabled()) { + s_log.debug("Got dispatcher " + rd); + } + sreq = DispatcherHelper.restoreOriginalRequest(sreq); + rd.forward(sreq, sresp); + } else { + if (s_log.isDebugEnabled()) { + s_log.debug("No dispatcher found for" + rd); + } + String requestUri = sreq.getRequestURI(); // same as ctx.getRemainingURLPart() + sresp.sendError(404, requestUri + " not found on this server."); + } + } + } // END doService + + /** + * + * @param sreq + * @param sresp + * @param section + * @param item + * + * @throws ServletException + * @throws IOException + */ + private void serveItem(HttpServletRequest sreq, + HttpServletResponse sresp, + ContentSection section, + ContentItem item) + throws ServletException, IOException { + + if (s_log.isInfoEnabled()) { + s_log.info("serving content item"); + } + + RequestContext ctx = DispatcherHelper.getRequestContext(); + String url = ctx.getRemainingURLPart(); + + final ItemResolver itemResolver = getItemResolver(section); + + //set the content item in the request + sreq.setAttribute(CONTENT_ITEM, item); + + //set the template context + //TemplateResolver templateResolver = m_disp.getTemplateResolver(section); + String templateURL = url; + if (!templateURL.startsWith("/")) { + templateURL = "/" + templateURL; + } + if (templateURL.startsWith(PREVIEW)) { + templateURL = templateURL.substring(PREVIEW.length()); + } + + String sTemplateContext = itemResolver.getTemplateFromURL(templateURL); + if (s_log.isDebugEnabled()) { + s_log.debug("setting template context to " + sTemplateContext); + } + //templateResolver.setTemplateContext(sTemplateContext, sreq); + + // 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 + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final ContentItemManager contentItemManager = cdiUtil.findBean( + ContentItemManager.class); + final PermissionChecker permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + if (s_cacheItems && contentItemManager.isLive(item)) { + if (permissionChecker.isPermitted( + CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item)) { + DispatcherHelper.cacheForWorld(sresp, expires); + } else { + DispatcherHelper.cacheForUser(sresp, expires); + } + } else { + DispatcherHelper.cacheDisable(sresp); + } + + //use ContentItemDispatcher + m_disp.dispatch(sreq, sresp, ctx); + } + + /** + * 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 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 path = section.getPrimaryUrl(); + ItemResolver ir = (ItemResolver) s_itemResolverCache.get(path); + + if (ir == null) { + final String itemResolverClassName = section.getItemResolverClass(); + try { + ir = (ItemResolver) Class + .forName(section.getItemResolverClass()).newInstance(); + } catch (ClassNotFoundException | + IllegalAccessException | + InstantiationException ex) { + throw new RuntimeException(ex); + } + s_itemResolverCache.put(path, ir); + } + + if (s_log.isDebugEnabled()) { + s_log.debug("using ItemResolver " + ir.getClass().getName()); + } + + return ir; + } + + /** + * + * @param section + * @param url + * @param itemResolver + * + * @return + */ + public ContentItem getItem(ContentSection section, String url, + ItemResolver itemResolver) { + + if (s_log.isDebugEnabled()) { + s_log.debug("getting item at url " + url); + } + HttpServletRequest request = Web.getRequest(); + + //first sanitize the 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); + 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); + } + } + + if (!url.startsWith("/")) { + url = "/" + url; + } + + ContentItem item; + // Check if the user has access to view public or preview pages + final PermissionChecker permissionChecker = CdiUtil.createCdiUtil() + .findBean(PermissionChecker.class); + boolean hasPermission = true; + + // 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; + } + + if (preview) { + if (s_log.isInfoEnabled()) { + s_log.info("Trying to get item for PREVIEW"); + } + + item = itemResolver.getItem(section, url, CMSDispatcher.PREVIEW); + if (item != null) { + hasPermission = permissionChecker.isPermitted( + CmsConstants.PRIVILEGE_ITEMS_PREVIEW, item); + } + } else { + if (s_log.isInfoEnabled()) { + s_log.info("Trying to get LIVE item"); + } + + //check if this item is in the cache + //we only cache live items + if (s_log.isDebugEnabled()) { + s_log.debug("Trying to get content item for URL " + url + + " from cache"); + } + + // Get the negotiated locale + String lang = CdiUtil.createCdiUtil().findBean( + GlobalizationHelper.class).getNegotiatedLocale() + .getLanguage(); + + // XXX why assign a value and afterwards null?? + // Effectively it just ignores the cache and forces a fallback to + // itemResover in any case. Maybe otherwise language selection / + // negotiation doesn't work correctly? +// item = itemURLCacheGet(section, url, lang); + item = null; + + if (item == null) { + if (s_log.isDebugEnabled()) { + s_log.debug("Did not find content item in cache, so trying " + + "to retrieve and cache..."); + } + //item not cached, so retreive it and cache it + item = itemResolver.getItem(section, url, "live"); +// itemURLCachePut(section, url, lang, item); + } else if (s_log.isDebugEnabled()) { + s_log.debug("Found content item in cache"); + } + + //ToDo +// if (s_log.isDebugEnabled() && item != null) { +// s_log.debug("Sanity check: item.getPath() is " + item.getPath()); +// } + if (item != null) { + if (s_log.isDebugEnabled()) { + s_log.debug("Content Item is not null"); + } + + hasPermission = permissionChecker.isPermitted( + CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item); + + if (hasPermission) { + } + } + } + + if (item == null && url.endsWith(INDEX_FILE)) { + + if (item == null) { + if (s_log.isInfoEnabled()) { + s_log.info("no item found"); + } + } + + // look up folder if it's an index + url = url.substring(0, url.length() - INDEX_FILE.length()); + if (s_log.isInfoEnabled()) { + 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); + } + } + + if (!hasPermission) { + + // first, check if the user is logged-in + // if he isn't, give him a chance to do so... + if (!CdiUtil.createCdiUtil().findBean(Shiro.class).getSubject() + .isAuthenticated()) { + throw new LoginSignal(request); + } + + throw new AccessDeniedException(); + } + + return item; + } + + public ContentItem getItem(ContentSection section, String url) { + ItemResolver itemResolver = getItemResolver(section); + + return getItem(section, url, itemResolver); + } + + // synchronize access to the item-url cache +// private static synchronized void itemURLCachePut(ContentSection section, +// String sURL, +// String lang, +// Long itemID) { +// +// getItemURLCache(section).put(sURL + CACHE_KEY_DELIMITER + lang, itemID); +// } + /** + * Maps the content item to the URL in a cache + * + * @param section the content section in which the content item is published + * @param sURL the URL at which the content item s published + * @param lang + * @param item the content item at the URL + */ +// public static synchronized void itemURLCachePut(ContentSection section, +// String sURL, +// String lang, +// ContentItem item) { +// if (sURL == null || item == null) { +// return; +// } +// if (s_log.isDebugEnabled()) { +// s_log.debug("adding cached entry for url " + sURL + " and language " +// + lang); +// } +// +// itemURLCachePut(section, sURL, lang, item.getObjectId()); +// } + /** + * Removes the cache entry for the URL, sURL + * + * @param section the content section in which to remove the key + * @param sURL the cache entry key to remove + * @param lang + */ +// public static synchronized void itemURLCacheRemove(ContentSection section, +// String sURL, +// String lang) { +// if (s_log.isDebugEnabled()) { +// s_log.debug("removing cached entry for url " + sURL +// + "and language " + lang); +// } +// getItemURLCache(section).remove(sURL + CACHE_KEY_DELIMITER + lang); +// } + /** + * Fetches the ContentItem published at that URL from the cache. + * + * @param section the content section in which the content item is published + * @param sURL the URL for the item to fetch + * @param lang + * + * @return the ContentItem in the cache, or null + */ +// public static ContentItem itemURLCacheGet(ContentSection section, +// final String sURL, +// final String lang) { +// final Long itemID = (Long) getItemURLCache(section).get( +// sURL + CACHE_KEY_DELIMITER + lang); +// +// if (itemID == null) { +// return null; +// } else { +// +// final ContentItemRepository itemRepo = CdiUtil.createCdiUtil().findBean(ContentItemRepository.class); +// return itemRepo.findById(itemID); +// +// } +// } +// private static synchronized CacheTable getItemURLCache( +// ContentSection section) { +// Assert.exists(section, ContentSection.class); +// if (s_itemURLCacheMap == null) { +// initializeItemURLCache(); +// } +// +// if (s_itemURLCacheMap.get(section.getPath()) == null) { +// final CacheTable cache = new CacheTable( +// "ContentSectionServletItemURLCache" + section.getID().toString()); +// s_itemURLCacheMap.put(section.getPath(), cache); +// } +// +// return (CacheTable) s_itemURLCacheMap.get(section.getPath()); +// } +// +// private static synchronized void initializeItemURLCache() { +// ContentSectionCollection sections = ContentSection.getAllSections(); +// s_itemURLCacheMap = new HashMap(); +// while (sections.next()) { +// ContentSection section = sections.getContentSection(); +// String idStr = section.getID().toString(); +// String path = section.getPath(); +// CacheTable itemURLCache = new CacheTable( +// "ContentSectionServletItemURLCache" + idStr); +// s_itemURLCacheMap.put(path, itemURLCache); +// +// } +// } + /** + * Checks that the current user has permission to access the admin pages. + * + * @param request + * @param section + * + * @return + * + */ + 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/AbstractItemResolver.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/AbstractItemResolver.java new file mode 100755 index 000000000..9285aaeb3 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/AbstractItemResolver.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2003-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; +import java.util.StringTokenizer; + +/** + * @author bche + */ +public abstract class AbstractItemResolver implements ItemResolver { + + protected static final String TEMPLATE_CONTEXT_PREFIX = "tem_"; + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#getItem( + * com.arsdigita.cms.ContentSection, java.lang.String, java.lang.String) + */ + public abstract ContentItem getItem( + ContentSection section, + String url, + String context); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#getCurrentContext( + * com.arsdigita.bebop.PageState) + */ + public abstract String getCurrentContext(PageState state); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#generateItemURL( + * com.arsdigita.bebop.PageState, java.math.BigDecimal, + * java.lang.String, com.arsdigita.cms.ContentSection, + * java.lang.String) + */ + public abstract String generateItemURL( + PageState state, + BigDecimal itemId, + String name, + ContentSection section, + String context); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#generateItemURL( + * com.arsdigita.bebop.PageState, + * java.math.BigDecimal, + * java.lang.String, + * com.arsdigita.cms.ContentSection, + * java.lang.String, java.lang.String) + */ + public abstract String generateItemURL( + PageState state, + BigDecimal itemId, + String name, + ContentSection section, + String context, + String templateContext); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#generateItemURL( + * com.arsdigita.bebop.PageState, + * com.arsdigita.cms.ContentItem, + * com.arsdigita.cms.ContentSection, + * java.lang.String) + */ + public abstract String generateItemURL( + PageState state, + ContentItem item, + ContentSection section, + String context); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#generateItemURL( + * com.arsdigita.bebop.PageState, + * com.arsdigita.cms.ContentItem, + * com.arsdigita.cms.ContentSection, + * java.lang.String, java.lang.String) + */ + public abstract String generateItemURL( + PageState state, + ContentItem item, + ContentSection section, + String context, + String templateContext); + + /* (non-Javadoc) + * @see com.arsdigita.cms.dispatcher.ItemResolver#getMasterPage( + * com.arsdigita.cms.ContentItem, + * javax.servlet.http.HttpServletRequest) + */ + public abstract CMSPage getMasterPage(ContentItem item, + HttpServletRequest request) + throws ServletException; + + /** + * Finds the template context from the URL and returns it, if it is there. + * Otherwise, returns null. + * + * @param inUrl the URL from which to get the template context + * + * @return the template context, or null if there is no template context + */ + public String getTemplateFromURL(String inUrl) { + String tempUrl; + String url; + if (inUrl.startsWith("/")) { + tempUrl = inUrl.substring(1); + } else { + tempUrl = inUrl; + } + + String templateContext = null; + StringTokenizer tokenizer = new StringTokenizer(tempUrl, "/"); + + if (tokenizer.hasMoreTokens()) { + templateContext = tokenizer.nextToken(); + } + + if (templateContext != null && templateContext.startsWith( + TEMPLATE_CONTEXT_PREFIX)) { + return templateContext.substring(TEMPLATE_CONTEXT_PREFIX.length()); + } else { + return null; + } + } + + /** + * Removes the template context from the 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; + +/** + *

+ * 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:

+ * + *
    + *
  1. + * A client sends a request to the web server, which passes it on to the global + * ACS dispatcher.

  2. + * + *
  3. + * 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.

  4. + * + *
  5. + * 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}.

  6. + * + *
  7. + * 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.

  8. + * + *
  9. + * 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).

  10. + * + *
  11. + * The CMS dispatcher hands the master Page object to the + * {@link com.arsdigita.sitenode.SiteNodePresentationManager} to serve the + * page.

  12. + * + *
  13. + * The presentation manager asks the master Page object for an XML + * document representing the data for the page.

  14. + * + *
  15. + * 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.

  16. + * + *
  17. + * 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.

  18. + *
+ * + * @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; + + +/** + *

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:

+ *
+ *     getContentSection(PageState state);
+ *     
+ * + * @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 presenterClass = BebopConfig.getConfig().getPresenterClass(); + final PresentationManager pm; + try { + pm = presenterClass.newInstance(); + } catch (InstantiationException | IllegalAccessException ex) { + throw new RuntimeException(ex); + } + + if (pm instanceof PageTransformer) { + m_transformer = (PageTransformer) pm; + } + else { + m_transformer = new PageTransformer(); + } + } + + /** + * Finishes and locks the page. If the page is already locked, does nothing. + * + * This method is called by the {@link com.arsdigita.dispatcher.Dispatcher} + * that initializes this page. + */ + @Override + public synchronized void init() { + s_log.debug("Initializing the page"); + + if (!isLocked()) { + s_log.debug("The page hasn't been locked; locking it now"); + + lock(); + } + } + + /** + * Fetches the value of the XML parameter. + * + * @param name The parameter name + * @return The parameter value + * @pre (name != null) + */ + public String getXMLParameter(String name) { + return (String) m_params.get(name); + } + + /** + * Set an XML parameter. + * + * @param name The parameter name + * @param value The parameter value + * @pre (name != null) + */ + public void setXMLParameter(String name, String value) { + m_params.put(name, value); + } + + /** + * Fetch the request-local content section. + * + * @param request The HTTP request + * @return The current content section + * + * @deprecated use com.arsdigita.cms.CMS.getContext().getContentSection() + * instead + * Despite of being deprecated it can not be removed because it + * is required by the interface Resourcehandler which is + * implemented by this class. + * On the other hand, if deprecated, implementing ResourceHandler + * may not be required + */ + @Override + public ContentSection getContentSection(HttpServletRequest request) { + // Resets all content sections associations. + // return ContentSectionDispatcher.getContentSection(request); + return ContentSectionServlet.getContentSection(request); + } + + /** + * Fetch the request-local content section. + * + * @param state The page state + * @return The current content section + * + * @deprecated use com.arsdigita.cms.CMS.getContext().getContentSection() + * instead + * Despite of being deprecated it can not be removed because it + * is required by ContentItemPage which extends CMSPage and + * uses this method. + */ + public ContentSection getContentSection(PageState state) { + return getContentSection(state.getRequest()); + } + + /** + * Fetch the request-local content item. + * + * @param request The HTTP request + * @return The current content item + * + * @deprecated use com.arsdigita.cms.CMS.getContext().getContentItem() + * instead + * Despite of being deprecated it can not be removed because it + * is required by the interface Resourcehandler which is + * implemented by this class. + * On the other hand, if deprecated, implementing ResourceHandler + * may not be required + */ + public ContentItem getContentItem(HttpServletRequest request) { + // resets all content item associations + return ContentSectionDispatcher.getContentItem(request); + } + + /** + * Fetch the request-local content item. + * + * @param state The page state + * @return The current content item + * @deprecated use com.arsdigita.cms.CMS.getContext().getContentItem() + * instead. + * Despite of being deprecated it can not be removed because it + * is required by ContentItemPage which extends CMSPage and + * uses this method. + */ + public ContentItem getContentItem(PageState state) { + return getContentItem(state.getRequest()); + } + + /** + * Services the Bebop page. + * + * @param request The servlet request object + * @param response the servlet response object + * @param actx The request context + * + * @pre m_transformer != null + */ + @Override + public void dispatch(final HttpServletRequest request, + final HttpServletResponse response , + final RequestContext actx) + throws IOException, ServletException { + + final CcmApplication app = Web.getWebContext().getApplication(); + ContentSection section = null; + + if (app == null) { + //Nothing to do + } else if(app instanceof ContentSection) { + section = (ContentSection) app; + } + + final String itemId = request.getParameter("item_id"); + + if (itemId != null) { + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final ContentItemRepository itemRepo = cdiUtil.findBean(ContentItemRepository.class); + final ContentItem item = itemRepo.findById(Long.parseLong("item_id")); + final PermissionChecker permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + permissionChecker.checkPermission(CmsConstants.PRIVILEGE_ITEMS_PREVIEW, + item); + } + + final Document document = buildDocument(request, response); + + m_transformer.servePage(document, request, response); + } + + /** + * Overwrites bebop.Page#generateXMLHelper to add the name of the user + * logged in to the page (displayed as part of the header). + * @param ps + * @param parent + * @return + */ + @Override + protected Element generateXMLHelper(PageState ps, Document parent) { + Element page = super.generateXMLHelper(ps,parent); + final User user = CdiUtil.createCdiUtil().findBean(Shiro.class).getUser(); + if ( user != null ) { + page.addAttribute("name",user.getName()); + } + + return page; + } +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentItemDispatcher.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentItemDispatcher.java new file mode 100755 index 000000000..60f884113 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentItemDispatcher.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2003-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.Dispatcher; +import com.arsdigita.dispatcher.DispatcherHelper; +import com.arsdigita.dispatcher.RequestContext; +import com.arsdigita.util.Assert; +import com.arsdigita.web.Web; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +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.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionConfig; + +/** + * This is the dispatcher for content-sections. It maintains a + * ContentItem-to-Template cache Code that modifies a published ContentItem's + * template must update the cache in this class by calling the appropriate cache + * methods. + * + * @author bche@redhat.com + */ +public class ContentItemDispatcher implements Dispatcher { + + /** cache for the template resolver */ + public static Map s_templateResolverCache = Collections + .synchronizedMap(new HashMap()); + + /** */ + protected ItemXML m_itemXML; + + + /** + * Private Logger instance for debugging purpose. + */ + private static final Logger s_log = Logger.getLogger( + ContentItemDispatcher.class.getName()); + + /** + * */ + public ContentItemDispatcher() { + m_itemXML = new ItemXML(); + } + + /** + * @see com.arsdigita.dispatcher.Dispatcher#dispatch (HttpServletRequest, + * HttpServletResponse, RequestContext) + */ + public void dispatch(final HttpServletRequest request, + final HttpServletResponse response, + final RequestContext actx) + throws IOException, ServletException { + + Boolean bXMLMode = (Boolean) request + .getAttribute(ContentSectionServlet.XML_MODE); + if (bXMLMode != null && bXMLMode.booleanValue()) { + //if this is XML mode, then use itemXML + m_itemXML.dispatch(request, response, actx); + } else { + //this is normal dispatching + + //get the Content Item + //final ContentItem item = (ContentItem) request.getAttribute + // (ContentSectionServlet.CONTENT_ITEM); + final ContentItem item = getContentItem(request); + //get the Content Section + final ContentSection section = (ContentSection) Web.getWebContext() + .getApplication(); + + Assert.exists(item); + + //get the item's template +// final String sTemplateURL = getTemplatePath(item, request, actx); + + //dispatch to the template + DispatcherHelper.setRequestContext(request, actx); + DispatcherHelper.forwardRequestByPath(null, request, + response); + + } + } + + /** + * 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( + ContentSectionServlet.CONTENT_ITEM); + } + +// //synchronize access to the cache +// private static synchronized void cachePut(BigDecimal contentItemID, +// String sTemplatePath) { +// s_cache.put(contentItemID, sTemplatePath); +// } +// +// private static synchronized void cacheRemove(BigDecimal contentItemID) { +// s_cache.remove(contentItemID); +// } +// +// /** +// * Method cacheRemove. Removes the cached template path for the contentItem +// * item +// * +// * @param item +// */ +// public static void cacheRemove(ContentItem item) { +// if (item == null) { +// return; +// } +// if (s_log.isDebugEnabled()) { +// s_log.debug("removing cached entry for item " + item.getName() +// + " with ID " + item.getID()); +// } +// s_cache.remove(item.getID()); +// } + + /** + * Method cachePut. Maps the ContentItem item to the template t in the cache + * + * @param item + * @param t + */ +// public static void cachePut(ContentItem item, Template t) { +// ContentSection section = item.getContentSection(); +// String sPath = getTemplatePath(section, t); +// +// //only cache live items +// if (item == null || item.getVersion().compareTo(ContentItem.LIVE) != 0) { +// return; +// } +// +// if (s_log.isDebugEnabled()) { +// s_log.debug("updating mapping for item " + item.getName() +// + " with ID " + item.getID() + " in section " + section +// .getName() + " of type " + item.getContentType().getName() +// + " to template " + sPath); +// } +// +// cachePut(item.getID(), sPath); +// } + + /** + * Method cachePut. Maps all the content items of ContentType type and in + * ContentSection section that don't have their own templates to the + * template t in the cache + * + * @param section + * @param type + * @param t + */ +// public static void cachePut(ContentSection section, +// ContentType type, +// Template t) { +// s_log.debug("updating cache for section " + section.getName() +// + " and type " + type.getName()); +// +// //get all the items in the section +// ItemCollection items = section.getItems(); +// +// //filter items by content type +// BigDecimal typeID = type.getID(); +// Filter filter = items.addFilter("type.id = :typeID"); +// filter.set("typeID", typeID); +// +// //get only live items +// Filter liveFilter = items.addFilter("version = '" + ContentItem.LIVE +// + "'"); +// +// //filter out content items in ContentSection section of +// //ContentType type with a template for the "public" context +// Filter itemsFilter = items.addNotInSubqueryFilter("id", +// "com.arsdigita.cms.ItemsWithTemplateMapping"); +// itemsFilter.set("sectionId", section.getID()); +// itemsFilter.set("typeId", type.getID()); +// +// //TODO: FILTER OUT CONTENT ITEMS IN THIS SECTION OF THIS TYPE +// //WITH A TEMPLATE FOR THE "PUBLIC" CONTEXT +// /* +// * select items.item_id +// * from cms_items items, cms_item_template_map map +// * where items.item_id = map.item_id +// * and use_context = 'public' +// * and items.version = 'live' +// * and items.section_id = :section_id +// * and items.type_id = :type_id +// */ +// synchronized (s_cache) { +// //update the cache for all items +// while (items.next()) { +// cachePut(items.getContentItem(), t); +// } +// } +// } + +// private static String getTemplatePath(ContentSection section, +// Template template) { +// //the template path is +// // TEMPLATE_ROOT/[content-section-name]/[template-path] +// final String sep = java.io.File.separator; +// String sPath = ContentSectionConfig.getConfig().getTemplateRoot() + sep +// + section.getName() + sep + template.getPath(); +// return sPath; +// } +// +// private static void updateTemplateCache(ContentSection section, +// ContentItem item, +// String sTemplatePath) { +// //use the live version of the item for the cache +// item = item.getLiveVersion(); +// s_log.debug("updating mapping for item " + item.getName() + " with ID " +// + item.getID() + " in section " + item.getContentSection() +// .getName() + " of type " + item.getContentType().getName() +// + " to template " + sTemplatePath); +// cachePut(item.getID(), sTemplatePath); +// } +// +// private String cacheGet(BigDecimal key) { +// return (String) s_cache.get(key); +// } + +// private String getTemplatePath(ContentItem item, +// HttpServletRequest req, +// RequestContext ctx) { +// +// //check if the template path is cached +// //BigDecimal id = item.getID(); +// //String sPath = cacheGet(id); +// //return from cache +// // current cache scheme doesn't work when there are +// //multiple templates per item, as would happen with +// // multiple template contexts or in the case of +// //category item resolution, more than one category for +// //the item. +// //if (sPath != null) { +// //s_log.debug("returning template path from cache"); +// // return sPath; +// //} +// //s_log.debug("template path not in cache, so fecthing"); +// //template is not in the cache, so retrieve it and place it in +// //the cache +// String sPath = fetchTemplateURL(item, req, ctx); +// //cachePut(id, sPath); +// +// return sPath; +// } + + /** + * Fetches the URL of a template for an item. The returned URL is relative + * to the webapp context. + */ +// public String fetchTemplateURL(ContentItem item, +// HttpServletRequest request, +// RequestContext actx) { +// if (s_log.isDebugEnabled()) { +// s_log.debug("fetching URL for item " + item.getName() + " with ID " +// + item.getID()); +// } +// +// ContentSection section = item.getContentSection(); +// String templateURL = getTemplateResolver(section).getTemplate(section, +// item, +// request); +// +// if (s_log.isDebugEnabled()) { +// s_log.debug("templateURL is " + templateURL); +// } +// return templateURL; +// +// } +// +// /** +// * Fetches the TemplateResolver for a content section. Checks cache first. +// * +// * @param section The content section +// * +// * @return The TemplateResolver 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; +// } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentPanel.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentPanel.java new file mode 100755 index 000000000..7970f765a --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ContentPanel.java @@ -0,0 +1,128 @@ +/* + * 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 com.arsdigita.bebop.SimpleComponent; +import com.arsdigita.cms.CMS; +import com.arsdigita.cms.ContentSectionServlet; +import com.arsdigita.dispatcher.DispatcherHelper; +import com.arsdigita.util.Assert; +import com.arsdigita.xml.Element; + +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

+ * This ContentPanel component fetches the + * {@link com.arsdigita.cms.dispatcher.XMLGenerator} for the content + * section.

+ * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Revision$ $Date$ + * @version $Id$ + */ +public class ContentPanel extends SimpleComponent { + + public ContentPanel() { + super(); + } + + /** + * Fetches an XML Generator. This method can be overridden to fetch any + * {@link com.arsdigita.cms.dispatcher.XMLGenerator}, but by default, it + * fetches the 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. + * ------------------------------------------------------------------------ + * + *

In general, the process for resolving a template involves two + * steps:

+ * + *
    + * + *
  1. The template resolver examines specific properties of the + * item, the content section, and/or the request itself and selects + * an appropriate context. A context is simply a token + * such as "plain" or "fancy". + * + *
  2. Based on the selected context, the template resolver + * identifies an appropriate template for the item. This is a + * three-step process: (1) the resolver queries for an association + * between the item and a specific template for the selected + * context; (2) if no such association exists, the resolver queries + * the item's content type for a default template to use in the + * selected context; (3) if a default template is not found, return + * null (at which point the dispatcher should probably give up and + * return a 404 error). + * + *
+ */ + +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; + +/** + *

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).

+ * + * @author Michael Pih (pihman@arsdigita.com) + * @author Stanislav Freidin (sfreidin@arsdigita.com) + * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id$ + */ +public interface ItemResolver { + + /** + * Return a content item based on section, url, and use context. + * + * @param section The current content section + * @param url The section-relative URL + * @param context The use context + * @return The content item, or null if no such item exists + */ + public ContentItem getItem(ContentSection section, String url, + String context); + + /** + * Fetches the current context based on the page state. + * + * @param state the current page state + * @return the context of the current URL, such as "live" or "admin" + */ + public String getCurrentContext(PageState state); + + /** + * 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 + */ + public String generateItemURL ( + PageState state, BigDecimal itemId, String name, + ContentSection section, String context + ); + + /** + * 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 + */ + public String generateItemURL ( + PageState state, BigDecimal itemId, String name, + ContentSection section, String context, String templateContext + ); + + /** + * 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 + */ + public String generateItemURL ( + PageState state, ContentItem item, ContentSection section, String 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" + * @param templateContext the context for the URL, such as "public" + * @return The URL of the item + * @see #getCurrentContext + */ + public String generateItemURL ( + PageState state, ContentItem item, ContentSection section, String context, + String templateContext + ); + + /** + * Return a master page based on page state (and content section). + * + * @param item The content item + * @param request The HTTP request + * @return The master page + */ + public CMSPage getMasterPage(ContentItem item, HttpServletRequest request) + throws ServletException; + + + /* + * Having to stick the following methods, getTemplateFromURL, and + * stripTemplateFromURL in the ItemResolver interface is somewhat ugly. + * But, the relationship between ItemResolver and TemplateResolver needs + * to be cleaned up to fix this. As it is, ItemResolver parses URL's for + * template contexts, and TemplateResolver sets the actual template contexts + * in the request. + */ + + /** + * Finds the template context from the URL and returns it, if it is there. + * Otherwise, returns null. + * @param inUrl the URL from which to get the template context + * @return the template context, or null if there is no template context + */ + public String getTemplateFromURL(String inUrl); + + /** + * Removes the template context from the 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; + + +/** + *

A {@link com.arsdigita.cms.dispatcher.CMSPage} used for serving + * content items.

+ * + *

This page contains a ContentPanel component which fetches + * the {@link com.arsdigita.cms.dispatcher.XMLGenerator} for the content + * section.

+ * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id$ + */ +public class MasterPage extends CMSPage { + + public MasterPage() { + super("Master", new SimpleContainer()); + setIdAttr("master_page"); + + add(new ContentPanel()); + } + + /** + * Fetch the request-local content section. + * + * @param request The HTTP request + * @return The current content section + */ + public ContentSection getContentSection(HttpServletRequest request) { + // Resets all content sections associations. + ContentSection section = super.getContentSection(request); + Assert.exists(section); + return section; + } + + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MultilingualItemResolver.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MultilingualItemResolver.java.off new file mode 100755 index 000000000..a632c181a --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/MultilingualItemResolver.java.off @@ -0,0 +1,916 @@ +/* + * Copyright (C) 2003-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 com.arsdigita.cms.CMS; +import com.arsdigita.cms.ContentCenter; +import com.arsdigita.util.Assert; + +import org.apache.log4j.Logger; +import org.libreccm.categorization.Category; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.StringTokenizer; + +/** + * Resolves items to URLs and URLs to items for multiple language + * variants. + * + * Created Mon Jan 20 14:30:03 2003. + * + * @author Michael Hanisch + * @version $Id: MultilingualItemResolver.java 2090 2010-04-17 08:04:14Z pboy $ + */ +public class MultilingualItemResolver + extends AbstractItemResolver implements ItemResolver { + + private static final Logger s_log = Logger.getLogger + (MultilingualItemResolver.class); + + private static MasterPage s_masterP = null; + private static final String ADMIN_PREFIX = "admin"; + + /** + * The string identifying an item's ID in the query string of a + * URL. + */ + protected static final String ITEM_ID = "item_id"; + + /** + * The separator used in URL query strings; should be either "&" + * or ";". + */ + protected static final String SEPARATOR = "&"; + + public MultilingualItemResolver() { + s_log.debug("Undergoing creation"); + } + + /** + * Returns a content item based on section, url, and use context. + * + * @param section The current content section + * @param url The section-relative URL + * @param context The use context, + * e.g. 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.

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 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.

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 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; + + +/** + *

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.

+ * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Revision$ $Date$ + * @version $Id$ + */ +public abstract class PageResolver { + + private BigDecimal m_sectionID; + + // Used for caching pages + private HashMap m_pages; + + + public PageResolver() { + m_pages = new HashMap(); + } + + public void setContentSectionID(BigDecimal id) { + m_sectionID = id; + } + + protected BigDecimal getContentSectionID() { + return m_sectionID; + } + + + /** + * Fetch the page associated with the request URL. + * + * @param url The content section-relative URL stub + * @return The resource + */ + public ResourceHandler getPage(String url) { + return (ResourceHandler) m_pages.get(url); + } + + /** + * Register a page to the content section. + * + * @param page The master page + * @param url The desired URL of the page + */ + public abstract void registerPage(ResourceHandler page, String url); + + + /** + * Register a page to the content section. + * + * @param page The master page + * @param url The desired URL of the page + */ + public abstract void unregisterPage(ResourceHandler page, String url); + + + /** + * Loads a page into the page resolver cache. + * + * @param url The URL of the resource to load into the cache + * @param page The resource + */ + public void loadPage(String url, ResourceHandler page) { + m_pages.put(url, page); + } + + /** + * Flushes a page from the page resolver cache. + * + * @param url The URL of the resource to remove from the cache + */ + public void releasePage(String url) { + m_pages.remove(url); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandler.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandler.java new file mode 100755 index 000000000..72efec6dd --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandler.java @@ -0,0 +1,63 @@ +/* + * 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 org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + + +/** + * An interface for resources that can be served. + * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id$ + **/ +public interface ResourceHandler extends Dispatcher { + + /** + * This method is called by the {@link com.arsdigita.dispatcher.Dispatcher} + * that initializes this page. + */ + public void init() throws ServletException; + + /** + * Fetches the content section context for this resource. + * + * @param request The HTTP request + * @return A content section or null if there is none + * @pre ( request != null ) + */ + public ContentSection getContentSection(HttpServletRequest request); + + /** + * Fetches the content item context for this resource. + * + * @param request The HTTP request + * @return A content item or null if there is none + * @pre ( request != null ) + */ + public ContentItem getContentItem(HttpServletRequest request); + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandlerImpl.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandlerImpl.java new file mode 100755 index 000000000..5cba54ab2 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/ResourceHandlerImpl.java @@ -0,0 +1,104 @@ +/* + * 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.RequestContext; +import com.arsdigita.util.Assert; + +import org.apache.shiro.authz.AuthorizationException; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.security.PermissionChecker; +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; + +/** + * An interface for resources that can be served. + * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id$ + * + */ +public abstract class ResourceHandlerImpl implements ResourceHandler { + + /** + * This method is called by the {@link com.arsdigita.dispatcher.Dispatcher} + * that initializes this page. + */ + public void init() throws ServletException { + // Do nothing. + } + + /** + * Fetch the request-local content section. + * + * @param request The HTTP request + * + * @return The current content section + */ + public ContentSection getContentSection(HttpServletRequest request) { + // resets all content sections associations + ContentSection section = CMSDispatcher.getContentSection(request); + Assert.exists(section); + return section; + } + + /** + * Fetch the request-local content item. + * + * @param request The HTTP request + * + * @return The current content item + */ + public ContentItem getContentItem(HttpServletRequest request) { + // resets all content item associations + return CMSDispatcher.getContentItem(request); + } + + public void checkUserAccess(HttpServletRequest request, + HttpServletResponse response, + RequestContext actx, + ContentItem item) { + if (!CdiUtil.createCdiUtil().findBean(PermissionChecker.class) + .isPermitted(CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, item)) { + throw new AuthorizationException( + "cms.dispatcher.no_permission_to_access_resource"); + } + } + + /** + * Services this resource. + * + * @param request The servlet request object + * @param response the servlet response object + * @param actx The request context + */ + public abstract void dispatch(HttpServletRequest request, + HttpServletResponse response, + RequestContext actx) + throws IOException, ServletException; + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleXMLGenerator.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleXMLGenerator.java.off new file mode 100755 index 000000000..29e87838c --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleXMLGenerator.java.off @@ -0,0 +1,445 @@ +/* + * 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 com.arsdigita.cms.CMS; +import com.arsdigita.cms.CMSConfig; +import com.arsdigita.cms.ContentItem; +import com.arsdigita.cms.ContentItemXMLRenderer; +import com.arsdigita.cms.ExtraXMLGenerator; +import com.arsdigita.cms.SecurityManager; +import com.arsdigita.cms.UserDefinedContentItem; +import com.arsdigita.cms.XMLDeliveryCache; +import com.arsdigita.cms.util.GlobalizationUtil; +import com.arsdigita.domain.DataObjectNotFoundException; +import com.arsdigita.domain.DomainObjectFactory; +import com.arsdigita.domain.DomainObjectTraversal; +import com.arsdigita.domain.SimpleDomainObjectTraversalAdapter; +import com.arsdigita.kernel.Kernel; +import com.arsdigita.kernel.Party; +import com.arsdigita.kernel.permissions.PermissionDescriptor; +import com.arsdigita.kernel.permissions.PermissionService; +import com.arsdigita.kernel.permissions.PrivilegeDescriptor; +import com.arsdigita.metadata.DynamicObjectType; +import com.arsdigita.persistence.OID; +import com.arsdigita.persistence.metadata.Property; +import com.arsdigita.util.UncheckedWrapperException; +import com.arsdigita.xml.Element; +import org.apache.log4j.Logger; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + *

The default XMLGenerator implementation.

+ * + * @author Michael Pih + * @version $Revision: #20 $ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id: SimpleXMLGenerator.java 2167 2011-06-19 21:12:12Z pboy $ + */ +public class SimpleXMLGenerator implements XMLGenerator { + + private static final Logger s_log = Logger.getLogger(SimpleXMLGenerator.class); + public static final String ADAPTER_CONTEXT = SimpleXMLGenerator.class.getName(); + /** + * jensp 2011-10-23: Sometimes the extra XML is not needed, for example + * when embedding the XML of a content item into the XML output of another + * content item. The default value {@code true}. To change the value + * call {@link #setUseExtraXml(booelan)} after creating an instance of + * your generator. + */ + private boolean useExtraXml = true; + /** + * jensp 2012-04-18: This value is forwarded to this ExtraXMLGenerators + * by calling {@link ExtraXMLGenerator#setListMode(boolean)}. The behavior + * triggered by this value depends on the specific implementation of + * the {@code ExtraXMLGenerator} + */ + private boolean listMode = false; + /** + * Extra attributes for the cms:item element. + */ + private final Map itemAttributes = new LinkedHashMap(); + /** + * Allows to overwrite the name and the namespace of the XML element + * used to wrap the rendered item. + */ + private String itemElemName = "cms:item"; + private String itemElemNs = CMS.CMS_XML_NS; + + // Register general purpose adaptor for all content items + static { + s_log.debug("Static initializer starting..."); + final SimpleDomainObjectTraversalAdapter adapter = new SimpleDomainObjectTraversalAdapter(); + adapter.addAssociationProperty("/object/type"); + adapter.addAssociationProperty("/object/categories"); + + DomainObjectTraversal.registerAdapter( + ContentItem.BASE_DATA_OBJECT_TYPE, + adapter, + ADAPTER_CONTEXT); + s_log.debug("Static initializer finished"); + } + + public SimpleXMLGenerator() { + super(); + } + + public void setUseExtraXml(final boolean useExtraXml) { + this.useExtraXml = useExtraXml; + } + + public void setListMode(final boolean listMode) { + this.listMode = listMode; + } + + public void addItemAttribute(final String name, final String value) { + itemAttributes.put(name, value); + } + + public void setItemElemName(final String itemElemName, final String itemElemNs) { + this.itemElemName = itemElemName; + this.itemElemNs = itemElemNs; + } + + /** + * Generates the XML to render the content panel. + * + * @param state The page state + * @param parent The parent DOM element + * @param useContext The use context + */ + @Override + public void generateXML(final PageState state, final Element parent, final String useContext) { + //final long start = System.nanoTime(); + + //ContentSection section = CMS.getContext().getContentSection(); + ContentItem item = getContentItem(state); + + s_log.info("Generate XML for item " + item.getOID()); + + Party currentParty = Kernel.getContext().getParty(); + if (currentParty == null) { + currentParty = Kernel.getPublicUser(); + } + // check if current user can edit the current item (nb privilege is granted on draft item, but live item + // has draft as its permission context + // + // Note that the xml that is generated is only of use if you DO NOT CACHE content pages. + // cg. + final PermissionDescriptor edit = new PermissionDescriptor( + PrivilegeDescriptor.get(SecurityManager.CMS_EDIT_ITEM), item, currentParty); + if (PermissionService.checkPermission(edit)) { + parent.addAttribute("canEdit", "true"); + final Element canEditElem = parent.newChildElement("canEdit"); + canEditElem.setText("true"); + + } + final PermissionDescriptor publish = new PermissionDescriptor( + PrivilegeDescriptor.get(SecurityManager.CMS_PUBLISH), item, currentParty); + if (PermissionService.checkPermission(publish)) { + parent.addAttribute("canPublish", "true"); + } + final String className = item.getDefaultDomainClass(); + + // Ensure correct subtype of ContentItem is instantiated + if (!item.getClass().getName().equals(className)) { + s_log.info("Specializing item"); + try { + item = (ContentItem) DomainObjectFactory.newInstance( + new OID(item.getObjectType().getQualifiedName(), item.getID())); + } catch (DataObjectNotFoundException ex) { + throw new UncheckedWrapperException( + (String) GlobalizationUtil.globalize( + "cms.dispatcher.cannot_find_domain_object").localize(), + ex); + } + } + + // Implementing XMLGenerator directly is now deprecated + if (item instanceof XMLGenerator) { + s_log.info("Item implements XMLGenerator interface"); + final XMLGenerator xitem = (XMLGenerator) item; + xitem.generateXML(state, parent, useContext); + + } else if ("com.arsdigita.cms.UserDefinedContentItem".equals(className)) { + s_log.info("Item is a user defined content item"); + final UserDefinedContentItem UDItem = (UserDefinedContentItem) item; + generateUDItemXML(UDItem, state, parent, useContext); + + } else { + s_log.info("Item is using DomainObjectXMLRenderer"); + + // This is the preferred method + //final Element content = startElement(useContext, parent); + final Element content = startElement(useContext); + s_log.debug("Item is not in cache, generating item."); + + final XMLDeliveryCache xmlCache = XMLDeliveryCache.getInstance(); + + if (CMSConfig.getInstanceOf().getEnableXmlCache() && xmlCache.isCached(item.getOID(), useContext, listMode)) { + xmlCache.retrieveFromCache(content, item.getOID(), useContext, listMode); + } else { + final ContentItemXMLRenderer renderer = new ContentItemXMLRenderer(content); + + renderer.setWrapAttributes(true); + renderer.setWrapRoot(false); + renderer.setWrapObjects(false); + renderer.setRevisitFullObject(true); + + //System.out.printf("Prepared renderer in %d ms\n", (System.nanoTime() - start) + // / 1000000); + + renderer.walk(item, ADAPTER_CONTEXT); + + //System.out.printf("Rendered standard item xml in %d ms\n", (System.nanoTime() - start) + // / 1000000); + + //parent.addContent(content); + + //Only item XML Cache End + +// s_log.debug("Content elem content: "); +// logElementTree(content); +// s_log.debug("Content elem content end -- "); + + + /* + * 2011-08-27 jensp: Introduced to remove the annoying special templates + * for MultiPartArticle, SiteProxy and others. The method called + * here was already definied but not used. + * + * 2011-10-23 jensp: It is now possible to disable the use of + * extra XML. + */ + //final long extraXMLStart = System.nanoTime(); + if (useExtraXml) { + for (ExtraXMLGenerator generator : item.getExtraXMLGenerators()) { + generator.setListMode(listMode); + generator.generateXML(item, content, state); + } + } + + //Only published items + //Only the XML of the item itself, no extra XML + if (CMSConfig.getInstanceOf().getEnableXmlCache() && item.isLiveVersion()) { + xmlCache.cache(item.getOID(), item, content, useContext, listMode); + } + } + + if (PermissionService.checkPermission(edit)) { + final ItemResolver resolver = item.getContentSection().getItemResolver(); + final Element editLinkElem = content.newChildElement("editLink"); + final ContentItem draftItem = item.getDraftVersion(); + editLinkElem.setText(resolver.generateItemURL(state, + draftItem, + item.getContentSection(), + draftItem.getVersion())); + } + + parent.addContent(content); + + //System.out.printf("Rendered item in %d ms\n\n", (System.nanoTime() - start) / 1000000); + } + } + + /** + * Fetches the current content item. This method can be overridden to + * fetch any {@link com.arsdigita.cms.ContentItem}, but by default, + * it fetches the 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.Entry attr : itemAttributes.entrySet()) { + element.addAttribute(attr.getKey(), attr.getValue()); + } + + return element; + } + + private Element startElement(final String useContext) { + final Element element = new Element(itemElemName, itemElemNs); + + if (useContext != null) { + element.addAttribute("useContext", useContext); + } + + for (Map.Entry attr : itemAttributes.entrySet()) { + element.addAttribute(attr.getKey(), attr.getValue()); + } + + return element; + } + + private Element startCachedElement(final String useContext) { + final Element element = new Element(itemElemName, itemElemNs) { + @Override + public Element newChildElement(Element copyFrom) { + s_log.debug("Copy of element added to cached elem."); + return super.newChildElement(copyFrom); + } + + @Override + public Element newChildElement(String name, Element copyFrom) { + s_log.debug("Copy of element added to cached elem."); + return super.newChildElement(name, copyFrom); + } + + @Override + public Element addContent(final Element newContent) { + s_log.debug("Content added to cached element"); + return super.addContent(newContent); + } + + }; + + if (useContext != null) { + element.addAttribute("useContext", useContext); + } + + for (Map.Entry attr : itemAttributes.entrySet()) { + element.addAttribute(attr.getKey(), attr.getValue()); + } + + return element; + } + + private void copyElement(final Element parent, final Element element) { + final Element copy = parent.newChildElement(element.getName()); + final Iterator attrs = element.getAttributes().entrySet().iterator(); + Map.Entry attr; + while (attrs.hasNext()) { + attr = (Map.Entry) attrs.next(); + copy.addAttribute((String) attr.getKey(), (String) attr.getValue()); + } + + final Iterator childs = element.getChildren().iterator(); + while (childs.hasNext()) { + copyElement(copy, (Element) childs.next()); + } + + if (element.getText() != null) { + copy.setText(element.getText()); + } + + if (element.getCDATASection() != null) { + copy.setCDATASection(element.getCDATASection()); + } + + } + + private Element UDItemElement(final String useContext) { + final Element element = new Element("cms:UDItemAttributes", CMS.CMS_XML_NS); + /* + if ( useContext != null ) { + element.addAttribute("useContext", useContext); + } + */ + return element; + } + + private Element UDItemAttrElement(final String name, final String value) { + final Element element = new Element("cms:UDItemAttribute", CMS.CMS_XML_NS); + element.addAttribute("UDItemAttrName", name); + element.addAttribute("UDItemAttrValue", value); + return element; + } + + private void logElementTree(final Element element) { + s_log.debug("Tree of element" + element.getName() + ":\n"); + s_log.debug("\n" + logElementTree(element, new StringBuilder(), 0)); + } + + private String logElementTree(final Element element, final StringBuilder builder, final int depth) { + for (int i = 0; i < depth; i++) { + builder.append('\t'); + } + builder.append('<').append(element.getName()).append(">\n"); + + for (Object childObj : element.getChildren()) { + final Element child = (Element) childObj; + logElementTree(child, builder, depth + 1); + } + + for (int i = 0; i < depth; i++) { + builder.append('\t'); + } + builder.append("\n"); + return builder.toString(); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/TemplateResolver.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/TemplateResolver.java.off new file mode 100755 index 000000000..cd38be0bc --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/TemplateResolver.java.off @@ -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.cms.dispatcher; + +import javax.servlet.http.HttpServletRequest; + +/** + * Reimplementation, based on ItemTemplateResolver + * + *

+ * 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.

+ * + * + * @author Karl Goldstein (karlg@arsdigita.com) + * @version $Id: TemplateResolver.java 1967 2009-08-29 21:05:51Z pboy $ + * + */ +public interface TemplateResolver { + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Sets the TemplateContext parameter in the request + * + * @param sTemplateContext the template context to set + * @param request the request in which to set the template context + */ + public void setTemplateContext(String sTemplateContext, + HttpServletRequest request); + + /** + * Gets the template context from the request. + * + * @param request the request from which to get the template context + * + * @return the template context + */ + public String getTemplateContext(HttpServletRequest request); + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/Utilities.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/Utilities.java new file mode 100755 index 000000000..12b689433 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/Utilities.java @@ -0,0 +1,250 @@ +/* + * 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 com.arsdigita.cms.CMS; +import com.arsdigita.cms.ContentCenter; +import com.arsdigita.cms.ContentCenterServlet; +import com.arsdigita.dispatcher.DispatcherHelper; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.librecms.CmsConstants; +import org.librecms.assets.BinaryAsset; +import org.librecms.assets.Image; +import org.librecms.contentsection.ContentSection; + +/** + *

This class provides many utility functions for the Content Management + * System.

+ * Specifically used by various JSP templates. + * + * @author Michael Pih (pihman@arsdigita.com) + * @version $Id$ + */ +public class Utilities { + + // Used for caching util lookups + private static HashMap m_cache = new HashMap(); + + private static Date s_lastSectionRefresh = null; + private static Map s_sectionRefreshTimes = + Collections.synchronizedMap(new HashMap()); + + public static final Logger LOG = Logger.getLogger(Utilities.class); + + /** + * Fetch the location of the CMS ContentCenter package. + * @return The URL of the CMS ContentCenter package + * @deprecated use ContentCenter.getURL() instead + */ + public static String getWorkspaceURL() { + + return CmsConstants.CONTENT_CENTER_URL; + + } + + /** + * Fetch the location (URL) of the CMS Services package. Caches the result. + * @return The URL of the CMS Services package + * @deprecated Use Service.getURL( instead + */ + public static String getServiceURL() { + String url = (String) m_cache.get(CmsConstants.SERVICE_PACKAGE_KEY); + if ( url == null ) { + // chris.gilbert@westsussex.gov.uk + // We don't want application context in this url, especially when + // it gets cached in a static variable - if I have a + // file that is maintained by a non cms application eg + // forum, then I can end up with a url that doesn't work + // and so breaks file links everywhere + // url = getSingletonPackageURLSansContext(CMS.SERVICE_PACKAGE_KEY); + url = CmsConstants.SERVICE_URL; + m_cache.put(CmsConstants.SERVICE_PACKAGE_KEY, url); + } + + return url; + } + + /** + * The URL to log out. + * @return The logout URL + */ + public static String getLogoutURL() { + //StringBuffer buf = new StringBuffer(getServiceURL()); + StringBuilder buf = new StringBuilder(CmsConstants.SERVICE_URL ); + buf.append("logout"); + return buf.toString(); + } + + /** + * Construct a URL which serves a binary asset. + * + * @param asset The binary asset + * @return the URL which will serve the specified binary asset + * @deprecated Use Service.getAssetURL(BinaryAsset asset) instead + */ + public static String getAssetURL(BinaryAsset asset) { + return getAssetURL(asset.getAssetId()); + } + + /** + * Constuct a URL which serves a binary asset. + * + * @param assetId The asset ID + * @return the URL which will serve the specified binary asset + * @deprecated Use Service.getAssetURL(BigDecimal assetId) instead + */ + public static String getAssetURL(long assetId) { + // StringBuffer buf = new StringBuffer(getServiceURL()); + StringBuilder buf = new StringBuilder(CmsConstants.SERVICE_URL ); + buf.append("stream/asset?"); + buf.append(CmsConstants.ASSET_ID).append("=").append(assetId); + return buf.toString(); + } + + + + /** + * Constuct a URL which serves an image. + * + * @param asset The image asset whose image is to be served + * @return the URL which will serve the specified image asset + * @deprecated Use Service.getImageURL(ImageAsset) instead! + */ + public static String getImageURL(Image asset) { + // StringBuffer buf = new StringBuffer(getServiceURL()); + StringBuilder buf = new StringBuilder(CmsConstants.SERVICE_URL ); + buf.append("stream/image/?"); + buf.append(CmsConstants.IMAGE_ID).append("=").append(asset.getAssetId()); + return buf.toString(); + } + + public static String getGlobalAssetsURL() { + return getWebappContext(); + } + /** + * Fetch the context path of the request. This is typically "/". + * + * @return The webapp context path + */ + public static String getWebappContext() { + return DispatcherHelper.getWebappContext(); + } + + + /** + * Check for the last refresh on authoring kits or content types in + * a section. + **/ + public static synchronized Date + getLastSectionRefresh(ContentSection section) { + + // cache by URL string instead of by section object to avoid + // holding the reference. + + String sectionURL = section.getPrimaryUrl(); + + Date lastModified = (Date) s_sectionRefreshTimes.get(sectionURL); + if (lastModified == null) { + lastModified = new Date(); + s_lastSectionRefresh = lastModified; + s_sectionRefreshTimes.put(sectionURL, lastModified); + } + + return lastModified; + } + + /** + * Check for the last refresh on authoring kits or content types in + * any section. + **/ + public static Date getLastSectionRefresh() { + + // instantiate last refresh lazily to ensure that first result is + // predictable. + + if (s_lastSectionRefresh == null) { + s_lastSectionRefresh = new Date(); + } + return s_lastSectionRefresh; + } + + /** + * Force the authoring UI to reload. This should be done every time an + * authoring kit or a content type are updated. + */ + public static void refreshItemUI(PageState state) { + // Drop the authoring kit UI to force it to refresh + // THE URL SHOULD NOT BE HARDCODED ! + + ContentSection section = CMS.getContext().getContentSection(); + + // OLD APPROACH: used in conjunction with CMSDispatcher. This + // shouldn't do any harm even if CMSDispatcher is not being used. + CMSDispatcher.releaseResource(section, "admin/item"); + refreshAdminUI(state); + + // NEW APPROACH: used in conjunction with + // ContentSectionDispatcher. cache by URL string instead of by + // section object to avoid holding the reference. This shouldn't + // do any harm even if ContentSectionDispatcher is not being used. + s_lastSectionRefresh = new Date(); + s_sectionRefreshTimes.put(section.getPrimaryUrl(), + s_lastSectionRefresh); + } + + /** + * Force the authoring UI to reload. This should be done every time an + * authoring kit or a content type are updated. + */ + public static void refreshAdminUI(PageState state) { + // Drop the admin UI to force it to refresh + // THE URL SHOULD NOT BE HARDCODED ! + + ContentSection section = CMS.getContext().getContentSection(); + + CMSDispatcher.releaseResource(section, "admin"); + CMSDispatcher.releaseResource(section, "admin/index"); + CMSDispatcher.releaseResource(section, ""); + } + + /** + * Add the "pragma: no-cache" header to the HTTP response to make sure + * the browser does not cache tha page + * + * @param response The HTTP response + * @deprecated use + * com.arsdigita.dispatcher.DispatcherHelper.cacheDisable(HttpServletResponse) + */ + public static void disableBrowserCache(HttpServletResponse response) { + response.addHeader("pragma", "no-cache"); + } + + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/XMLGenerator.java b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/XMLGenerator.java new file mode 100755 index 000000000..dabc5e599 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/XMLGenerator.java @@ -0,0 +1,52 @@ +/* + * 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 com.arsdigita.xml.Element; + + +/** + *

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 generateXML 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) + * @version $Id$ + */ +public interface XMLGenerator { + + /** + * Generates the XML to render the content panel. + * + * @param state The page state + * @param parent The parent DOM element + * @param useContext The use context + */ + public void generateXML(PageState state, Element parent, String useContext); + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/CMSApplicationPage.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/CMSApplicationPage.java new file mode 100644 index 000000000..372e63f12 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/CMSApplicationPage.java @@ -0,0 +1,229 @@ +/* + * 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.ui; + +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.dispatcher.Utilities; +import com.arsdigita.templating.PresentationManager; +import com.arsdigita.xml.Document; +import com.arsdigita.xml.Element; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.security.Shiro; +import org.libreccm.security.User; +import org.libreccm.web.CcmApplication; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A CMSApplicationPage is a Bebop {@link com.arsdigita.bebop.Page} + * implementation serving as a base for any CMS pageElement served by a servlet. + * + * It stores the current {@link com.arsdigita.cms.ContentSection} and, if + * applicable, the {@link com.arsdigita.cms.ContentItem} in the pageElement + * state as request local objects. Components that are part of the + * CMSPage may access these objects by calling: + *
+ *     getContentSection(PageState state);
+ * 
+ * + * @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 parameters; + + /** + * + */ + private PageTransformer pageTransformer; + + public CMSApplicationPage() { + super(); + buildPage(); + } + + public CMSApplicationPage(final String title) { + super(title); + buildPage(); + } + + public CMSApplicationPage(final String title, final Container panel) { + super(title, panel); + buildPage(); + } + + public CMSApplicationPage(final Label title) { + super(title); + buildPage(); + } + + public CMSApplicationPage(final Label title, final Container panel) { + super(title, panel); + buildPage(); + } + + /** + * Builds the pageElement. + */ + private void buildPage() { + + // Set the class attribute value (down in SimpleComponent). + setClassAttr(PAGE_CLASS); + + // Global XML params. + // MP: This only works with older versions of Xalan. + parameters = new HashMap<>(); + setXMLParameter(ASSETS, Utilities.getGlobalAssetsURL()); + + // MP: This is a hack to so that the XML params work with the newer + // version of Xalan. + setAttribute(ASSETS, Utilities.getGlobalAssetsURL()); + + // Make sure the error display gets rendered. + getErrorDisplay().setIdAttr("page-body"); + + final Class presenterClass = BebopConfig + .getConfig().getPresenterClass(); + final PresentationManager presenter; + try { + presenter = presenterClass.newInstance(); + } catch (InstantiationException | + IllegalAccessException ex) { + throw new RuntimeException("Failed to create PresentationManager", + ex); + } + + if (presenter instanceof PageTransformer) { + pageTransformer = (PageTransformer) presenter; + } else { + pageTransformer = new PageTransformer(); + } + } + + /** + * Finishes and locks the pageElement. If the pageElement is already locked, + * does nothing. + * + * Client classes may overwrite this method to add context specific bits to + * the page before it is locked. + * + * This method is called by the various servlets serving the various pages + * of the CMS package, before serving and displaying the page. + * + * @param request + * @param response + * @param app + */ + public synchronized void init(final HttpServletRequest request, + final HttpServletResponse response, + final CcmApplication app) { + LOGGER.debug("Initializing the page"); + + if (!isLocked()) { + LOGGER.debug("The page hasn't been locked; locking it now"); + + lock(); + } + } + + /** + * Fetches the value of the XML parameter. + * + * @param name The parameter name + * + * @return The parameter value + * + * @pre (name != null) + */ + public String getXMLParameter(final String name) { + return parameters.get(name); + } + + /** + * Set an XML parameter. + * + * @param name The parameter name + * @param value The parameter value + * + * @pre (name != null) + */ + public void setXMLParameter(String name, String value) { + parameters.put(name, value); + } + +/** + * Overwrites bebop.Page#generateXMLHelper to add the name of the user + * logged in to the pageElement (displayed as part of the header). + * @param state + * @param parent + * @return pageElement for use in generateXML + */ + @Override + protected Element generateXMLHelper(final PageState state, + final Document parent) { + + /* Retain elements already included. */ + Element pageElement = super.generateXMLHelper(state,parent); + + /* Add name of user logged in. */ + // Note: There are at least 2 ways in the API to determin the user + // TODO: Check for differences, determin the best / recommended way and + // document it in the classes. Probably remove one ore the other + // way from the API if possible. + final Shiro shiro = CdiUtil.createCdiUtil().findBean(Shiro.class); + final User user = shiro.getUser(); + // User user = Web.getWebContext().getUser(); + if ( user != null ) { + pageElement.addAttribute("name",user.getName()); + } + + return pageElement; + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.off new file mode 100755 index 000000000..255ac53cb --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.off @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2003-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.ui; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.bebop.TabbedPane; +import com.arsdigita.bebop.event.RequestEvent; +import com.arsdigita.bebop.event.RequestListener; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.bebop.parameters.BooleanParameter; +import com.arsdigita.bebop.parameters.IntegerParameter; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.cms.*; +import com.arsdigita.cms.dispatcher.CMSPage; +import com.arsdigita.cms.util.GlobalizationUtil; +import com.arsdigita.dispatcher.RequestContext; +import com.arsdigita.domain.DataObjectNotFoundException; +import com.arsdigita.templating.PresentationManager; +import com.arsdigita.templating.Templating; +import com.arsdigita.util.UncheckedWrapperException; +import com.arsdigita.web.Application; +import com.arsdigita.web.Web; +import com.arsdigita.xml.Document; + +import java.io.IOException; +import java.math.BigDecimal; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + *

The Item Search page.

+ * + * @author Scott Seago (scott@arsdigita.com) + * @author Sören Bernstein + * @author Jens Pelzetter (jens@jp-digital.de) + */ +public class ItemSearchPage extends CMSPage { + + private final static String XSL_CLASS = "CMS Admin"; + private TabbedPane m_tabbedPane; + private ItemSearchFlatBrowsePane m_flatBrowse; + private ItemSearchBrowsePane m_browse; + private ItemSearchPopup m_search; + private ItemSearchCreateItemPane m_create; + private BigDecimalParameter m_sectionId; + private int m_lastTab; + private static final CMSConfig s_conf = CMSConfig.getInstanceOf(); + private static final boolean LIMIT_TO_CONTENT_SECTION = false; + public static final String CONTENT_SECTION = "section_id"; + + /** + * Construct a new ItemSearchPage + */ + public ItemSearchPage() { + super(GlobalizationUtil.globalize("cms.ui.item_search.page_title").localize().toString(), new SimpleContainer()); + + setClassAttr("cms-admin"); + + addGlobalStateParam(new BigDecimalParameter(ItemSearch.SINGLE_TYPE_PARAM)); + addGlobalStateParam(new StringParameter(ItemSearchPopup.WIDGET_PARAM)); + addGlobalStateParam(new StringParameter("searchWidget")); + addGlobalStateParam(new StringParameter("publishWidget")); + addGlobalStateParam(new StringParameter("defaultCreationFolder")); + addGlobalStateParam(new IntegerParameter("lastTab")); + addGlobalStateParam(new BooleanParameter("disableCreatePane")); + addGlobalStateParam(new BooleanParameter("editAfterCreate")); + addGlobalStateParam(new StringParameter("queryField")); + m_sectionId = new BigDecimalParameter(CONTENT_SECTION); + addGlobalStateParam(m_sectionId); + + m_flatBrowse = getFlatBrowsePane(); + m_browse = getBrowsePane(); + m_search = getSearchPane(); + m_create = getCreatePane(); + + m_tabbedPane = createTabbedPane(); + m_tabbedPane.setIdAttr("page-body"); + add(m_tabbedPane); + + addRequestListener(new RequestListener() { + + public void pageRequested(final RequestEvent event) { + final PageState state = event.getPageState(); + + final String query = (String) state.getValue(new StringParameter(ItemSearchPopup.QUERY)); + final Boolean disableCreatePane = (Boolean) state.getValue(new BooleanParameter("disableCreatePane")); + + BigDecimal typeParam = + (BigDecimal) state.getValue(new BigDecimalParameter(ItemSearch.SINGLE_TYPE_PARAM)); + if ((typeParam == null) || disableCreatePane) { + m_tabbedPane.setTabVisible(state, m_create, false); + m_create.setVisible(state, false); + } else { + m_tabbedPane.setTabVisible(state, m_create, true); + m_create.setVisible(state, true); + } + + if (state.getValue(new IntegerParameter("lastTab")) == null) { + if ((query == null) || query.isEmpty()) { + m_tabbedPane.setSelectedIndex(state, 1); + } else { + m_tabbedPane.setSelectedIndex(state, 0); + } + +// m_tabbedPane.setTabVisible(state, m_create, false); +// m_create.setVisible(state, false); + + } + + state.setValue(new IntegerParameter("lastTab"), m_tabbedPane.getSelectedIndex(state)); + + if (state.getValue(new StringParameter("defaultCreationFolder")) != null) { + m_create.setDefaultFolder((String) state.getValue(new StringParameter("defaultCreationFolder"))); + } + + if (state.getValue(new BooleanParameter("editAfterCreate")) != null) { + m_create.setEditAfterCreate((Boolean) state.getValue(new BooleanParameter("editAfterCreate"))); + } + + if (state.getValue(new StringParameter("queryField")) == null) { + //Because of Bebops silly stateful behaviour we have to do this... + m_flatBrowse.resetQueryFields(); + }else { + m_flatBrowse.addQueryField((String) state.getValue(new StringParameter("queryField"))); + } + +// if (m_lastTab != m_tabbedPane.getSelectedIndex(state)) { +// m_lastTab = m_tabbedPane.getSelectedIndex(state); +// return; +// } +// +// //If create pane is selected do nothing (else we don't stay in the create pane) +// if (m_tabbedPane.getCurrentPane(state) == m_create) { +// return; +// } +// +// if ((query == null) || query.isEmpty()) { +// m_tabbedPane.setSelectedIndex(state, 1); +// } else { +// m_tabbedPane.setSelectedIndex(state, 1); +// } + +// if (m_tabbedPane.getCurrentPane(state) == m_create) { +// m_tabbedPane.setTabVisible(state, m_create, false); +// m_create.setVisible(state, false); +// } +// +// m_lastTab = m_tabbedPane.getSelectedIndex(state); + } + + }); + +// m_tabbedPane.addActionListener(new ActionListener() { +// +// public void actionPerformed(final ActionEvent event) { +// final PageState state = event.getPageState(); +// +// } +// +// }); + +// m_flatBrowse.addProcessListener(new FormProcessListener() { +// +// public void process(final FormSectionEvent fse) throws FormProcessException { +// if (m_flatBrowse.getSubmit().isSelected(fse.getPageState())) { +// enableCreatePane(fse.getPageState()); +// } +// } +// +// }); + } // END constructor + + + /** + * Creates, and then caches, the Browse pane. + * + * Overriding this method to return null will prevent this tab from + * appearing. Note: not implemented yet. + */ + protected ItemSearchBrowsePane getBrowsePane() { + if (m_browse == null) { + m_browse = new ItemSearchBrowsePane(); + } + + return m_browse; + } + + protected ItemSearchFlatBrowsePane getFlatBrowsePane() { + if (m_flatBrowse == null) { + //m_flatBrowse = new ItemSearchFlatBrowsePane("flatBrowse"); + m_flatBrowse = new ItemSearchFlatBrowsePane(); + } + + return m_flatBrowse; + } + + /** + * Creates, and then caches, the Creation pane. + * Overriding this method to return null will prevent this tab from + * appearing. + */ + protected ItemSearchPopup getSearchPane() { + if (m_search == null) { + // Always search in every content section +// m_search = new ItemSearchPopup(ContentItem.DRAFT, CMS.getConfig().limitToContentSection()); + m_search = new ItemSearchPopup(ContentItem.DRAFT, LIMIT_TO_CONTENT_SECTION); + } + + return m_search; + } + + protected ItemSearchCreateItemPane getCreatePane() { + if (m_create == null) { + m_create = new ItemSearchCreateItemPane(this); + } + + return m_create; + } + + /** + * Created the TabbedPane to use for this page. + * + * Sets the class attribute for this tabbed pane. The default implementation + * uses a {@link com.arsdigita.bebop.TabbedPane} and sets the class + * attribute to "CMS Admin." This implementation also adds tasks, + * content sections, and search panes. + * + * Developers can override this method to add only the tabs they want, + * or to add additional tabs after the default CMS tabs are added. + */ + protected TabbedPane createTabbedPane() { + TabbedPane pane = new TabbedPane(); + pane.setClassAttr(XSL_CLASS); + + + addToPane(pane, "flatBrowse", getFlatBrowsePane()); + addToPane(pane, "browse", getBrowsePane()); + addToPane(pane, "search", getSearchPane()); + addToPane(pane, "create", getCreatePane()); + + if ("browse".equals(s_conf.getItemSearchDefaultTab())) { + pane.setDefaultPane(m_browse); + } + if ("search".equals(s_conf.getItemSearchDefaultTab())) { + pane.setDefaultPane(m_search); + } + + //pane.setDefaultPane(m_flatBrowse); + pane.setDefaultPane(m_browse); + + return pane; + } + + /** + * Adds the specified component, with the specified tab name, to the + * tabbed pane only if it is not null. + * + * @param pane The pane to which to add the tab + * @param tabName The name of the tab if it's added + * @param comp The component to add to the pane + */ + protected void addToPane(TabbedPane pane, String tabName, Component comp) { + if (comp != null) { + + pane.addTab(GlobalizationUtil + .globalize("cms.ui.item_search." + tabName) + .localize().toString() + ,comp); + + } + } + + + /** + * This strange voodoo from Dan. No idea what it does. + */ + @Override + public void dispatch(final HttpServletRequest request, + final HttpServletResponse response, + RequestContext actx) + throws IOException, ServletException { + new CMSExcursion() { + + @Override + public void excurse() + throws IOException, ServletException { + ContentSection section = null; + Application app = Web.getWebContext().getApplication(); + if (app instanceof ContentSection) { + section = (ContentSection) app; + } else { + try { + section = new ContentSection((BigDecimal) m_sectionId.transformValue(request)); + } catch (DataObjectNotFoundException ex) { + throw new UncheckedWrapperException(ex); + } + } + setContentSection(section); + + final Document doc = buildDocument(request, response); + final PresentationManager pm = + Templating.getPresentationManager(); + + pm.servePage(doc, request, response); + } + + }.run(); + } + + protected void setTabActive(final PageState state, final Component component, final boolean value) { + m_tabbedPane.setTabVisible(state, component, value); + } + + protected void setTabActive(final PageState state, final int index, final boolean value) { + m_tabbedPane.setTabVisible(state, index, value); + } + + protected void setDefaultCreationFolder(final Folder folder) { + m_create.setDefaultFolder(folder.getOID().toString()); + } + + protected void setEditAfterCreate(final boolean editAfterCreate) { + m_create.setEditAfterCreate(editAfterCreate); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/MainPage.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/MainPage.java.off new file mode 100755 index 000000000..0f3a03a20 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/MainPage.java.off @@ -0,0 +1,234 @@ +/* + * + * 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.ui.contentcenter; + +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.bebop.TabbedPane; +import com.arsdigita.bebop.event.ActionEvent; +import com.arsdigita.bebop.event.ActionListener; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.cms.ui.CMSApplicationPage; +import com.arsdigita.globalization.GlobalizedMessage; + +import org.apache.log4j.Logger; +import org.librecms.CmsConstants; + +// //////////////////////////////////////////////////////////////////////////// +// +// Developer's Note: +// Replaces the (old) Dispatcher based Code cms.ui.CMSPageWorkspacePage +// Note should be removed as soon as the migration process is competed (in- +// cluding content section pages). +// +// //////////////////////////////////////////////////////////////////////////// +/** + *

+ * The Content Center main page.

+ * + * The page contains the general header and footer, the breadcrumb, and the + * complete content page including the tab bar, the sections/tasks page, the + * search page, and the listener to switch between the tabs. + * + * @author Jack Chung (flattop@arsdigita.com) + * @author Michael Pih (pihman@arsdigita.com) + * @author Peter Boy (pboy@barkhof.uni-bremen.de) + * @version $Id: MainPage.java pboy $ + */ +public class MainPage extends CMSApplicationPage implements ActionListener { + + private static final Logger s_log = Logger.getLogger(MainPage.class); + + private final static String XSL_CLASS = "CMS Admin"; + + private TabbedPane m_tabbedPane; + + private TasksPanel m_tasks; + private ItemSearch m_search; + private IdSearchTab m_IdSearch; + private ACSObjectSelectionModel m_typeSel; + private ACSObjectSelectionModel m_sectionSel; + + public static final String CONTENT_TYPE = "type_id"; + public static final String CONTENT_SECTION = "section_id"; + + /** + * Construct a new MainPage. + * + * Creates the complete page ready to be included in the page cache of + * ContentCenterServlet. + */ + public MainPage() { + + super(new Label(new GlobalizedMessage("cms.ui.content_center", + CmsConstants.CMS_BUNDLE)), + new SimpleContainer()); + + + /* Set the class attribute value (down in SimpleComponent). */ + setClassAttr("cms-admin"); + + BigDecimalParameter typeId = new BigDecimalParameter(CONTENT_TYPE); + addGlobalStateParam(typeId); + m_typeSel = new ACSObjectSelectionModel( + ContentType.class.getName(), + ContentType.BASE_DATA_OBJECT_TYPE, + typeId + ); + + BigDecimalParameter sectionId = new BigDecimalParameter(CONTENT_SECTION); + addGlobalStateParam(sectionId); + m_sectionSel = new ACSObjectSelectionModel( + ContentSection.class.getName(), + ContentSection.BASE_DATA_OBJECT_TYPE, + sectionId + ); + + add(new WorkspaceContextBar()); + add(new GlobalNavigation()); + + m_tasks = getTasksPane(m_typeSel, m_sectionSel); + m_search = getSearchPane(); + m_IdSearch = getIdSearchPane(); + + m_tabbedPane = createTabbedPane(); + m_tabbedPane.setIdAttr("page-body"); + add(m_tabbedPane); + + add(new DebugPanel()); + + } + + /** + * Creates, and then caches, the Tasks pane. Overriding this method to + * return null will prevent this tab from appearing. + */ + protected TasksPanel getTasksPane(ACSObjectSelectionModel typeModel, + ACSObjectSelectionModel sectionModel) { + if (m_tasks == null) { + m_tasks = new TasksPanel(typeModel, sectionModel); + } + return m_tasks; + } + + /** + * Creates, and then caches, the Search pane. Overriding this method to + * return null will prevent this tab from appearing. + * + */ + protected ItemSearch getSearchPane() { + if (m_search == null) { + m_search = new ItemSearch(ContentItem.DRAFT); + } + + return m_search; + } + + protected IdSearchTab getIdSearchPane() { + if (m_IdSearch == null) { + m_IdSearch = new IdSearchTab("idsearch"); + } + + return m_IdSearch; + } + + /** + * Created the TabbedPane to use for this page. Sets the class attribute for + * this tabbed pane. The default implementation uses a + * {@link com.arsdigita.bebop.TabbedPane} and sets the class attribute to + * "CMS Admin." This implementation also adds tasks, content sections, and + * search panes. + * + * Developers can override this method to add only the tabs they want, or to + * add additional tabs after the default CMS tabs are added. + * + */ + protected TabbedPane createTabbedPane() { + TabbedPane tabbedPane = new TabbedPane(); + tabbedPane.setClassAttr(XSL_CLASS); + Label taskLabel = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.mainpage.taskssections")); + Label searchLabel = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.mainpage.search")); + Label IdsearchLabel = new Label("ID Search"); + + addToPane(tabbedPane, + taskLabel, + getTasksPane(m_typeSel, m_sectionSel)); + addToPane(tabbedPane, + // searchLabel, + new Label(GlobalizationUtil.globalize( + "cms.ui.contentcenter.mainpage.search")), + getSearchPane()); + addToPane(tabbedPane, + IdsearchLabel, + getIdSearchPane()); + + tabbedPane.addActionListener(this); + return tabbedPane; + } + +// /** +// * Adds the specified component, with the specified tab name, to the +// * tabbed pane only if it is not null. +// * +// * @param pane The pane to which to add the tab +// * @param tabName The name of the tab if it's added +// * @param comp The component to add to the pane +// * @deprecated refactor to use addToPane(Label, Component) instead to +// * enable localized tab strips. +// */ +// protected void addToPane(TabbedPane pane, String tabName, Component comp) { +// if (comp != null) { +// pane.addTab(tabName, comp); +// } +// } + /** + * Adds the specified component, with the specified Label as tab name, to + * the tabbed pane only if it is not null. + * + * @param pane The pane to which to add the tab + * @param tabName The name of the tab if it's added + * @param comp The component to add to the pane + */ + protected void addToPane(TabbedPane pane, Label tabName, Component comp) { + if (comp != null) { + pane.addTab(tabName, comp); + } + } + + /** + * When a new tab is selected, reset the state of the formerly-selected + * pane. + * + * @param event The event fired by selecting a tab + */ + public void actionPerformed(ActionEvent event) { + PageState state = event.getPageState(); + Component pane = m_tabbedPane.getCurrentPane(state); + + if (pane == m_tasks) { + m_tasks.reset(state); + } else if (pane == m_search) { + m_search.reset(state); + } else if (pane == m_IdSearch) { + m_IdSearch.reset(state); + } + } + +} diff --git a/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java b/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java new file mode 100644 index 000000000..e4d28b078 --- /dev/null +++ b/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java @@ -0,0 +1,922 @@ +/* + * 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 org.arsdigita.cms; + +import com.arsdigita.cms.dispatcher.ItemResolver; +import com.arsdigita.util.UncheckedWrapperException; + +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.configuration.Configuration; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.configuration.Setting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Jens Pelzetter + */ +@Configuration +public class CMSConfig { + + /** + * Path for the default item template. Path is relative to the Template Root + * path. + */ + @Setting + private String defaultItemTemplatePath = "/default/item.jsp"; + + /** + * Path for the default folder template. Path is relative to the Template + * Root path. + */ + @Setting + private String defaultFolderTemplatePath = "/default/folder.jsp"; + + /** + * Path or the root folder for template folders. Path is relative to webapp + * root. Modify with care! Usually modified by developers only! + */ + @Setting + private String templateRootPath = "/templates/ccm-cms/content-section/"; + + /** + * Item Adapters File, path to an XML resource containing adapter + * specifications. Path is relative to webapp root. + */ + @Setting + private String itemAdapters = "/WEB-INF/resources/cms-item-adapters.xml"; + + /** + * Use streamlined content creation: upon item creation, automatically open + * authoring steps and forward to the next step + */ + @Setting + private boolean useStreamlinedCreation = true; + + /** + * DHTML Editor Configuration for use in CMS module, lists the config object + * name and Javascript source location for its definition. + */ + @Setting + private List dhtmlEditorConfig = Arrays.asList(new String[]{ + "Xinha.Config", "/assets/xinha/CCMcmsCinhaConfig.js"}); + + /** + * Defines which plugins to use, e.g.TableOperations,CSS Format: + * [string,string,string] + */ + @Setting + private List dhtmlEditorPlugins = Collections.emptyList(); + + /** + * Prevent undesirable functions from being made available, eg images should + * only be added through the cms methods. + */ + @Setting + private List dhtmlEditorHiddenButtons = Collections.emptyList(); + + /** + * Hide section admin tabs from users without administrative rights. + */ + @Setting + private boolean hideAdminTabs = true; + + /** + * Hide Folder Index Checkbox from folder view + */ + @Setting + private boolean hideFolderIndexCheckbox = true; + + /** + * Hide launch date parameter on all forms and displays where it's used. + */ + @Setting + private boolean hideLaunchDate = true; + + /** + * Require the launch date parameter to be set by the content author. + */ + @Setting + private boolean requireLaunchDate = true; + + /** + * Hide the templates tab on the item admin page. + */ + @Setting + private boolean hideTemplatesTab = false; + + /** + * Hide the upload file link in the editing of a text asset. + */ + @Setting + private boolean hideTextAssetUploadFile = false; + + /** + * Hide timezone labels (if, for example, all users will be in the same + * timezone and such information would be unnecessary) + */ + @Setting + private boolean hideTimezone = false; + + /** + * Whether the Wysiwyg editor should clear the text of MSWord tags, + * everytime the user clicks on 'Save' + */ + @Setting + private boolean saveTextCleansWordTags = true; + + /** + * Get the search indexing not to process FileAssets, eg to avoid PDF + * slowdowns + */ + @Setting + private boolean disableFileAssetExtraction = false; + + /** + * Whether an item's workflow should be deleted, once the item has been + * (re)published. + * + * jensp 2014-11-07: Default changed from true to false. Deleting the + * assigned workflow means that the authors have to reattach a workflow + * using the Workflow tab, which is complicated (for some users too + * complicated). Also deleting the workflow means that the new convenient + * link to restart a workflow will not work. + * + */ + @Setting + private boolean deleteWorkflowAfterPublication = false; + + /** + * Defines the number of days ahead that are covered in the 'Soon Expired' + * tab + */ + @Setting + private int soonExpiredTimespanDays = 14; + + /** + * Defines the number of months ahead that are covered in the 'Soon Expired' + * tab + */ + @Setting + private int soonExpiredTimespanMonths = 1; + + /** + * Does a redirect to the unpublished item generate not found error? + */ + @Setting + private boolean unpublishedNotFound = true; + + /** + * Links created through browse interfaces should only be within the same + * subsite + */ + @Setting + private boolean linksOnlyInSameSubsite = false; + + /** + * Link available to reset lifecycle on republish. If false don't display + * the link otherwise display. + */ + @Setting + private boolean hideResetLifecycleLink = true; + + /** + * Whether to include INPATH operators to contains clause in intermedia + * search + */ + @Setting + private boolean scoreTitleAndKeywords = false; + + /** + * Title Weight, the relative weight given to title element within cms:item + * when ranking search results (only used by interMedia) + */ + @Setting + private int titleWeight = 1; + + /** + * Keyword Weight, the relative weight given to the dcKeywords element + * within dublinCore element within cms:item element when ranking search + * results (only used by interMedia) + */ + @Setting + private int keywordWeight = 1; + + /** + * Limit the item search to current content section + */ + @Setting + private boolean limitItemSearchToContentSection = true; + + /** + * Asset steps to skip, specify asset steps that are not relevant for + * specific content types. Each entry in the list is a : separated pair. The + * first string is the className for the type (refer to classname column in + * contenttypes table eg com.arsdigita.cms.contenttypes.MultiPartArticle + * Second string is the name of the bebop step component eg + * com.arsdigita.cms.contenttypes.ui.ImageStep + */ + @Setting + private List skipAssetSteps = Collections.emptyList(); + + /** + * Mandatory Descriptions Content types may refer to this to decide whether + * to validate against empty descriptions + */ + @Setting + private boolean mandatoryDescriptions = false; + + /** + * Delete Finished Lifecycles. Decide whether lifecycles and their phases + * should be deleted from the system when finished. + */ + @Setting + private boolean deleteLifecycleWhenComplete = false; + + /** + * Delete Sent Workflow Notifications. Decide whether successfully sent + * notifications and messages should be deleted from the system + */ + @Setting + private boolean deleteWorkflowNotificationWhenSend = false; + + /** + * Decide whether successfully sent notifications and messages should be + * deleted from the system + */ + @Setting + private boolean deleteExpiryNotificationsWhenSent = false; + + /** + * Amount of time (in hours) before the expiration of a content item that + * users in the Alert Recipient role are alerted via email + */ + @Setting + private int defaultNotificationTime = 0; + + /** + * Whether a content item's author should be notified by the item's + * LifecycleListener; defaults to true + */ + @Setting + private boolean notifyAuthorOnLifecycle = false; + + /** + * XML Mapping of the content center tabs to URLs, see + * {@link ContentCenterDispatcher} + */ + @Setting + private String contentCenterMap + = "/WEB-INF/resources/content-center-map.xml"; + + @Setting + private List defaultItemResolverClassNames = Arrays.asList( + new String[]{ + ItemResolver.class.getName() + }); + +// @Setting +// private List defaultTemplateResolverClassNames = Arrays.asList( +// new String[]{ +// DefaultTemplateResolver.class.getName(), +// TemplateResolver.class.getName() +// }); + + @Setting + private String itemSearchDefaultTab = "flatBrowse"; + + @Setting + private int itemSearchFlatBrowsePanePageSize = 20; + + @Setting + private int folderBrowseListSize = 20; + + @Setting + private int folderAtoZShowLimit = 100; + + @Setting + private boolean useOldStyleItemLifecycleItemPane = false; + + @Setting + private boolean threadPublishing = true; + + @Setting + private String publishingFailureSender = ""; + + @Setting + private String publishingFailureReceiver = ""; + + @Setting + private int imageBrowserThumbnailMaxWidth = 50; + + @Setting + private int imageBrowserThumbnailMaxHeight = 50; + + @Setting + private int imageBrowserCaptionSize = 50; + + @Setting + private int imageBrowserDescriptionSize = 400; + + @Setting + private int imageBrowserTitleSize = 200; + + @Setting + private boolean imageCacheEnabled = true; + + @Setting + private boolean imageCachePrefetchEnabled = false; + + @Setting + private int imageCacheMaxSize = 100; + + @Setting + private int imageCacheMaxAge = 300; + + @Setting + private boolean attachPersonOrgaUnitsStep = true; + + @Setting + private int personOrgaUnitsStepSortKey = 20; + + @Setting + private boolean enableXmlCache = false; + + @Setting + private int xmlCacheSize = 2500; + + @Setting + private int xmlCacheAge = 60 * 60 * 24; + + /** + * Max length of the description of a link (in database max length are 4000 + * characters) + */ + @Setting + private int linkDescMaxLength = 400; + + public static CMSConfig getConfig() { + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); + return confManager.findConfiguration(CMSConfig.class); + } + + public CMSConfig() { + super(); + } + + public String getDefaultItemTemplatePath() { + return defaultItemTemplatePath; + } + + public void setDefaultItemTemplatePath(final String defaultItemTemplatePath) { + this.defaultItemTemplatePath = defaultItemTemplatePath; + } + + public String getDefaultFolderTemplatePath() { + return defaultFolderTemplatePath; + } + + public void setDefaultFolderTemplatePath( + final String defaultFolderTemplatePath) { + this.defaultFolderTemplatePath = defaultFolderTemplatePath; + } + + public String getTemplateRootPath() { + return templateRootPath; + } + + public void setTemplateRootPath(final String templateRootPath) { + this.templateRootPath = templateRootPath; + } + + public String getItemAdapters() { + return itemAdapters; + } + + public void setItemAdapters(final String itemAdapters) { + this.itemAdapters = itemAdapters; + } + + public boolean isUseStreamlinedCreation() { + return useStreamlinedCreation; + } + + public void setUseStreamlinedCreation(final boolean useStreamlinedCreation) { + this.useStreamlinedCreation = useStreamlinedCreation; + } + + public List getDhtmlEditorConfig() { + return new ArrayList<>(dhtmlEditorConfig); + } + + public void setDhtmlEditorConfig(final List dhtmlEditorConfig) { + this.dhtmlEditorConfig = new ArrayList<>(dhtmlEditorConfig); + } + + public List getDhtmlEditorPlugins() { + return new ArrayList<>(dhtmlEditorPlugins); + } + + public void setDhtmlEditorPlugins(final List dhtmlEditorPlugins) { + this.dhtmlEditorPlugins = new ArrayList<>(dhtmlEditorPlugins); + } + + public List getDhtmlEditorHiddenButtons() { + return new ArrayList<>(dhtmlEditorHiddenButtons); + } + + public void setDhtmlEditorHiddenButtons( + final List dhtmlEditorHiddenButtons) { + this.dhtmlEditorHiddenButtons + = new ArrayList<>(dhtmlEditorHiddenButtons); + } + + public boolean isHideAdminTabs() { + return hideAdminTabs; + } + + public void setHideAdminTabs(final boolean hideAdminTabs) { + this.hideAdminTabs = hideAdminTabs; + } + + public boolean isHideFolderIndexCheckbox() { + return hideFolderIndexCheckbox; + } + + public void setHideFolderIndexCheckbox(final boolean hideFolderIndexCheckbox) { + this.hideFolderIndexCheckbox = hideFolderIndexCheckbox; + } + + public boolean isHideLaunchDate() { + return hideLaunchDate; + } + + public void setHideLaunchDate(final boolean hideLaunchDate) { + this.hideLaunchDate = hideLaunchDate; + } + + public boolean isRequireLaunchDate() { + return requireLaunchDate; + } + + public void setRequireLaunchDate(final boolean requireLaunchDate) { + this.requireLaunchDate = requireLaunchDate; + } + + public boolean isHideTemplatesTab() { + return hideTemplatesTab; + } + + public void setHideTemplatesTab(final boolean hideTemplatesTab) { + this.hideTemplatesTab = hideTemplatesTab; + } + + public boolean isHideTextAssetUploadFile() { + return hideTextAssetUploadFile; + } + + public void setHideTextAssetUploadFile(final boolean hideTextAssetUploadFile) { + this.hideTextAssetUploadFile = hideTextAssetUploadFile; + } + + public boolean isHideTimezone() { + return hideTimezone; + } + + public void setHideTimezone(final boolean hideTimezone) { + this.hideTimezone = hideTimezone; + } + + public boolean isSaveTextCleansWordTags() { + return saveTextCleansWordTags; + } + + public void setSaveTextCleansWordTags(final boolean saveTextCleansWordTags) { + this.saveTextCleansWordTags = saveTextCleansWordTags; + } + + public boolean isDisableFileAssetExtraction() { + return disableFileAssetExtraction; + } + + public void setDisableFileAssetExtraction( + final boolean disableFileAssetExtraction) { + this.disableFileAssetExtraction = disableFileAssetExtraction; + } + + public boolean isDeleteWorkflowAfterPublication() { + return deleteWorkflowAfterPublication; + } + + public void setDeleteWorkflowAfterPublication( + boolean deleteWorkflowAfterPublication) { + this.deleteWorkflowAfterPublication = deleteWorkflowAfterPublication; + } + + public int getSoonExpiredTimespanDays() { + return soonExpiredTimespanDays; + } + + public void setSoonExpiredTimespanDays(final int soonExpiredTimespanDays) { + this.soonExpiredTimespanDays = soonExpiredTimespanDays; + } + + public int getSoonExpiredTimespanMonths() { + return soonExpiredTimespanMonths; + } + + public void setSoonExpiredTimespanMonths(final int soonExpiredTimespanMonths) { + this.soonExpiredTimespanMonths = soonExpiredTimespanMonths; + } + + public boolean isUnpublishedNotFound() { + return unpublishedNotFound; + } + + public void setUnpublishedNotFound(final boolean unpublishedNotFound) { + this.unpublishedNotFound = unpublishedNotFound; + } + + public boolean isLinksOnlyInSameSubsite() { + return linksOnlyInSameSubsite; + } + + public void setLinksOnlyInSameSubsite(final boolean linksOnlyInSameSubsite) { + this.linksOnlyInSameSubsite = linksOnlyInSameSubsite; + } + + public boolean isHideResetLifecycleLink() { + return hideResetLifecycleLink; + } + + public void setHideResetLifecycleLink(final boolean hideResetLifecycleLink) { + this.hideResetLifecycleLink = hideResetLifecycleLink; + } + + public boolean isScoreTitleAndKeywords() { + return scoreTitleAndKeywords; + } + + public void setScoreTitleAndKeywords(final boolean scoreTitleAndKeywords) { + this.scoreTitleAndKeywords = scoreTitleAndKeywords; + } + + public int getTitleWeight() { + return titleWeight; + } + + public void setTitleWeight(final int titleWeight) { + this.titleWeight = titleWeight; + } + + public int getKeywordWeight() { + return keywordWeight; + } + + public void setKeywordWeight(final int keywordWeight) { + this.keywordWeight = keywordWeight; + } + + public boolean isLimitItemSearchToContentSection() { + return limitItemSearchToContentSection; + } + + public void setLimitItemSearchToContentSection( + boolean limitItemSearchToContentSection) { + this.limitItemSearchToContentSection = limitItemSearchToContentSection; + } + + public List getSkipAssetSteps() { + return new ArrayList<>(skipAssetSteps); + } + + public void setSkipAssetSteps(final List skipAssetSteps) { + this.skipAssetSteps = new ArrayList<>(skipAssetSteps); + } + + public boolean isMandatoryDescriptions() { + return mandatoryDescriptions; + } + + public void setMandatoryDescriptions(final boolean mandatoryDescriptions) { + this.mandatoryDescriptions = mandatoryDescriptions; + } + + public boolean isDeleteLifecycleWhenComplete() { + return deleteLifecycleWhenComplete; + } + + public void setDeleteLifecycleWhenComplete( + boolean deleteLifecycleWhenComplete) { + this.deleteLifecycleWhenComplete = deleteLifecycleWhenComplete; + } + + public boolean isDeleteWorkflowNotificationWhenSend() { + return deleteWorkflowNotificationWhenSend; + } + + public void setDeleteWorkflowNotificationWhenSend( + boolean deleteWorkflowNotificationWhenSend) { + this.deleteWorkflowNotificationWhenSend + = deleteWorkflowNotificationWhenSend; + } + + public boolean isDeleteExpiryNotificationsWhenSent() { + return deleteExpiryNotificationsWhenSent; + } + + public void setDeleteExpiryNotificationsWhenSent( + boolean deleteExpiryNotificationsWhenSent) { + this.deleteExpiryNotificationsWhenSent + = deleteExpiryNotificationsWhenSent; + } + + public int getDefaultNotificationTime() { + return defaultNotificationTime; + } + + public void setDefaultNotificationTime(final int defaultNotificationTime) { + this.defaultNotificationTime = defaultNotificationTime; + } + + public boolean isNotifyAuthorOnLifecycle() { + return notifyAuthorOnLifecycle; + } + + public void setNotifyAuthorOnLifecycle(final boolean notifyAuthorOnLifecycle) { + this.notifyAuthorOnLifecycle = notifyAuthorOnLifecycle; + } + + public String getContentCenterMap() { + return contentCenterMap; + } + + public void setContentCenterMap(final String contentCenterMap) { + this.contentCenterMap = contentCenterMap; + } + + public List getDefaultItemResolverClassNames() { + return new ArrayList<>(defaultItemResolverClassNames); + } + + public void setDefaultItemResolverClassNames( + final List defaultItemResolverClassNames) { + this.defaultItemResolverClassNames + = new ArrayList<>(defaultItemResolverClassNames); + } + + @SuppressWarnings("unchecked") + public List> getDefaultItemResolverClasses() { + final List> resolverClasses = new ArrayList<>(); + for (final String className : getDefaultItemResolverClassNames()) { + try { + resolverClasses.add((Class) Class.forName( + className)); + } catch (ClassNotFoundException ex) { + throw new UncheckedWrapperException(String.format( + "ItemResolver class \"%s\" not found.", className), ex); + } + } + return resolverClasses; + } + +// public List getDefaultTemplateResolverClassNames() { +// return new ArrayList<>(defaultTemplateResolverClassNames); +// } +// +// public void setDefaultTemplateResolverClassNames( +// List defaultTemplateResolverClassNames) { +// this.defaultTemplateResolverClassNames = new ArrayList<>( +// defaultTemplateResolverClassNames); +// } +// +// @SuppressWarnings("unchecked") +// public List> getDefaultTemplateResolverClasses() { +// final List> resolverClasses = new ArrayList<>(); +// for (final String className : getDefaultTemplateResolverClassNames()) { +// try { +// resolverClasses.add((Class) Class.forName( +// className)); +// } catch (ClassNotFoundException ex) { +// throw new UncheckedWrapperException(String.format( +// "ItemResolver class \"%s\" not found.", className), ex); +// } +// } +// return resolverClasses; +// } + + public String getItemSearchDefaultTab() { + return itemSearchDefaultTab; + } + + public void setItemSearchDefaultTab(final String itemSearchDefaultTab) { + this.itemSearchDefaultTab = itemSearchDefaultTab; + } + + public int getItemSearchFlatBrowsePanePageSize() { + return itemSearchFlatBrowsePanePageSize; + } + + public void setItemSearchFlatBrowsePanePageSize( + int itemSearchFlatBrowsePanePageSize) { + this.itemSearchFlatBrowsePanePageSize = itemSearchFlatBrowsePanePageSize; + } + + public int getFolderBrowseListSize() { + return folderBrowseListSize; + } + + public void setFolderBrowseListSize(final int folderBrowseListSize) { + this.folderBrowseListSize = folderBrowseListSize; + } + + public int getFolderAtoZShowLimit() { + return folderAtoZShowLimit; + } + + public void setFolderAtoZShowLimit(final int folderAtoZShowLimit) { + this.folderAtoZShowLimit = folderAtoZShowLimit; + } + + public boolean isUseOldStyleItemLifecycleItemPane() { + return useOldStyleItemLifecycleItemPane; + } + + public void setUseOldStyleItemLifecycleItemPane( + boolean useOldStyleItemLifecycleItemPane) { + this.useOldStyleItemLifecycleItemPane = useOldStyleItemLifecycleItemPane; + } + + public boolean isThreadPublishing() { + return threadPublishing; + } + + public void setThreadPublishing(final boolean threadPublishing) { + this.threadPublishing = threadPublishing; + } + + public String getPublishingFailureSender() { + return publishingFailureSender; + } + + public void setPublishingFailureSender(final String publishingFailureSender) { + this.publishingFailureSender = publishingFailureSender; + } + + public String getPublishingFailureReceiver() { + return publishingFailureReceiver; + } + + public void setPublishingFailureReceiver( + final String publishingFailureReceiver) { + this.publishingFailureReceiver = publishingFailureReceiver; + } + + public int getImageBrowserThumbnailMaxWidth() { + return imageBrowserThumbnailMaxWidth; + } + + public void setImageBrowserThumbnailMaxWidth( + int imageBrowserThumbnailMaxWidth) { + this.imageBrowserThumbnailMaxWidth = imageBrowserThumbnailMaxWidth; + } + + public int getImageBrowserThumbnailMaxHeight() { + return imageBrowserThumbnailMaxHeight; + } + + public void setImageBrowserThumbnailMaxHeight( + int imageBrowserThumbnailMaxHeight) { + this.imageBrowserThumbnailMaxHeight = imageBrowserThumbnailMaxHeight; + } + + public int getImageBrowserCaptionSize() { + return imageBrowserCaptionSize; + } + + public void setImageBrowserCaptionSize(final int imageBrowserCaptionSize) { + this.imageBrowserCaptionSize = imageBrowserCaptionSize; + } + + public int getImageBrowserDescriptionSize() { + return imageBrowserDescriptionSize; + } + + public void setImageBrowserDescriptionSize( + final int imageBrowserDescriptionSize) { + this.imageBrowserDescriptionSize = imageBrowserDescriptionSize; + } + + public int getImageBrowserTitleSize() { + return imageBrowserTitleSize; + } + + public void setImageBrowserTitleSize(final int imageBrowserTitleSize) { + this.imageBrowserTitleSize = imageBrowserTitleSize; + } + + public boolean isImageCacheEnabled() { + return imageCacheEnabled; + } + + public void setImageCacheEnabled(final boolean imageCacheEnabled) { + this.imageCacheEnabled = imageCacheEnabled; + } + + public boolean isImageCachePrefetchEnabled() { + return imageCachePrefetchEnabled; + } + + public void setImageCachePrefetchEnabled( + final boolean imageCachePrefetchEnabled) { + this.imageCachePrefetchEnabled = imageCachePrefetchEnabled; + } + + public int getImageCacheMaxSize() { + return imageCacheMaxSize; + } + + public void setImageCacheMaxSize(final int imageCacheMaxSize) { + this.imageCacheMaxSize = imageCacheMaxSize; + } + + public int getImageCacheMaxAge() { + return imageCacheMaxAge; + } + + public void setImageCacheMaxAge(final int imageCacheMaxAge) { + this.imageCacheMaxAge = imageCacheMaxAge; + } + + public boolean isAttachPersonOrgaUnitsStep() { + return attachPersonOrgaUnitsStep; + } + + public void setAttachPersonOrgaUnitsStep( + final boolean attachPersonOrgaUnitsStep) { + this.attachPersonOrgaUnitsStep = attachPersonOrgaUnitsStep; + } + + public int getPersonOrgaUnitsStepSortKey() { + return personOrgaUnitsStepSortKey; + } + + public void setPersonOrgaUnitsStepSortKey( + final int personOrgaUnitsStepSortKey) { + this.personOrgaUnitsStepSortKey = personOrgaUnitsStepSortKey; + } + + public boolean isEnableXmlCache() { + return enableXmlCache; + } + + public void setEnableXmlCache(final boolean enableXmlCache) { + this.enableXmlCache = enableXmlCache; + } + + public int getXmlCacheSize() { + return xmlCacheSize; + } + + public void setXmlCacheSize(final int xmlCacheSize) { + this.xmlCacheSize = xmlCacheSize; + } + + public int getXmlCacheAge() { + return xmlCacheAge; + } + + public void setXmlCacheAge(final int xmlCacheAge) { + this.xmlCacheAge = xmlCacheAge; + } + + public int getLinkDescMaxLength() { + return linkDescMaxLength; + } + + public void setLinkDescMaxLength(final int linkDescMaxLength) { + this.linkDescMaxLength = linkDescMaxLength; + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/CmsConstants.java b/ccm-cms/src/main/java/org/librecms/CmsConstants.java index 5ce53400d..0c7cd7632 100644 --- a/ccm-cms/src/main/java/org/librecms/CmsConstants.java +++ b/ccm-cms/src/main/java/org/librecms/CmsConstants.java @@ -29,16 +29,25 @@ public class CmsConstants { public static final String DB_SCHEMA = "CCM_CMS"; public static final String CMS_BUNDLE = "org.librecms.CmsResources"; - - public static final String CONTENT_SECTION_APP_TYPE = "org.librecms.contentsection.ContentSection"; - public static final String CONTENT_SECTION_SERVLET_PATH = "/templates/servlet/content-section/*"; - public static final String CONTENT_SECTION_DESC_BUNDLE = "org.librecms.contentsection.ContentSectionResources"; - public static final String PRIVILEGE_ADMINISTER_CATEGORIES = "administer_categories"; - public static final String PRIVILEGE_ADMINISTER_CONTENT_TYPES = "administer_content_types"; - public static final String PRIVILEGE_ADMINISTER_LIFECYLES = "administer_lifecyles"; + public static final String CONTENT_CENTER_URL = "/content-center/"; + + public static final String CONTENT_SECTION_APP_TYPE + = "org.librecms.contentsection.ContentSection"; + public static final String CONTENT_SECTION_SERVLET_PATH + = "/templates/servlet/content-section/*"; + public static final String CONTENT_SECTION_DESC_BUNDLE + = "org.librecms.contentsection.ContentSectionResources"; + + public static final String PRIVILEGE_ADMINISTER_CATEGORIES + = "administer_categories"; + public static final String PRIVILEGE_ADMINISTER_CONTENT_TYPES + = "administer_content_types"; + public static final String PRIVILEGE_ADMINISTER_LIFECYLES + = "administer_lifecyles"; public static final String PRIVILEGE_ADMINISTER_ROLES = "administer_roles"; - public static final String PRIVILEGE_ADMINISTER_WORKFLOW = "administer_workflow"; + public static final String PRIVILEGE_ADMINISTER_WORKFLOW + = "administer_workflow"; public static final String PRIVILEGE_ITEMS_APPROVE = "approve_items"; public static final String PRIVILEGE_ITEMS_PUBLISH = "publish_items"; public static final String PRIVILEGE_ITEMS_CATEGORIZE = "categorize_items"; @@ -46,9 +55,21 @@ public class CmsConstants { public static final String PRIVILEGE_ITEMS_DELETE = "delete_items"; public static final String PRIVILEGE_ITEMS_EDIT = "edit_items"; public static final String PRIVILEGE_ITEMS_PREVIEW = "preview_items"; - public static final String PRIVILEGE_ITEMS_VIEW_PUBLISHED = "view_published_items"; - public static final String PRIVILEGE_APPLY_ALTERNATE_WORKFLOW = "apply_alternate_workflow"; + public static final String PRIVILEGE_ITEMS_VIEW_PUBLISHED + = "view_published_items"; + public static final String PRIVILEGE_APPLY_ALTERNATE_WORKFLOW + = "apply_alternate_workflow"; + /** + * Constant string used as key for creating service package as a legacy + * application. + */ + public static final String SERVICE_PACKAGE_KEY = "cms-service"; + public static final String SERVICE_URL = "/cms-service/"; + + public static final String ASSET_ID = "asset_id"; + public static final String IMAGE_ID = "image_id"; + private CmsConstants() { //Nothing } diff --git a/ccm-cms/src/main/java/org/librecms/ContentType.java b/ccm-cms/src/main/java/org/librecms/ContentType.java new file mode 100644 index 000000000..265b88f7d --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ContentType.java @@ -0,0 +1,33 @@ +/* + * 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 org.librecms; + +import org.librecms.contentsection.ContentItem; + +/** + * Annotation providing several informations about a content type. A content + * type is provided by a class extending the {@link ContentItem} class. + * + * @author Jens Pelzetter + */ +public @interface ContentType { + + //ToDo + +} diff --git a/ccm-cms/src/main/java/org/librecms/ContentTypes.java b/ccm-cms/src/main/java/org/librecms/ContentTypes.java new file mode 100644 index 000000000..98d8491b5 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ContentTypes.java @@ -0,0 +1,35 @@ +/* + * 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 org.librecms; + +import org.librecms.contentsection.ContentItem; + +/** + * Annotation for modules to describe the content types provided by a module. + * The content types - classes which extend the {@link ContentItem} class - must + * be annotated with the {@link ContentType} annotation. + * + * + * @author Jens Pelzetter + */ +public @interface ContentTypes { + + Class[] value(); + +} diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java new file mode 100644 index 000000000..b8ee10b8c --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java @@ -0,0 +1,92 @@ +/* + * 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 org.librecms.contentsection; + +import org.libreccm.categorization.Category; + +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class ContentItemManager { + + public void move(final ContentItem item, final Category targetFolder) { + throw new UnsupportedOperationException(); + } + + public void copy(final ContentItem item, final Category targetFolder) { + throw new UnsupportedOperationException(); + } + + /** + * Creates a live version of content item or updates the live version of a + * content item if there already a live version. + * + * @param item The content item to publish. + * + * @return The published content item. + */ + public ContentItem publish(final ContentItem item) { + throw new UnsupportedOperationException(); + } + + /** + * Unpublishes a content item by deleting its live version if any. + * + * @param item + */ + public void unpublish(final ContentItem item) { + throw new UnsupportedOperationException(); + } + + /** + * Determines if a content item has a live version. + * + * @param item The item + * @return {@code true} if the content item has a live version, + * {@code false} if not. + */ + public boolean isLive(final ContentItem item) { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the live version of the provided content item if any. + * + * @param + * @param item + * @param type + * @return + */ + public Optional getLiveVersion( + final ContentItem item, + final Class type) { + throw new UnsupportedOperationException(); + } + + public T getDraftVersion(final ContentItem item) { + throw new UnsupportedOperationException(); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java index a82e405e6..c6e357b52 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java @@ -19,13 +19,14 @@ package org.librecms.contentsection; import org.libreccm.auditing.AbstractAuditedEntityRepository; +import org.libreccm.categorization.Category; import org.libreccm.core.CcmObject; import org.libreccm.core.CcmObjectRepository; +import java.util.List; + import javax.enterprise.context.RequestScoped; import javax.inject.Inject; -import javax.persistence.NoResultException; -import javax.persistence.TypedQuery; /** * @@ -62,6 +63,11 @@ public class ContentItemRepository } } + public T findById(final long itemId, + final Class type) { + throw new UnsupportedOperationException(); + } + public ContentItem findByUuid(final String uuid) { final CcmObject result = ccmObjectRepo.findObjectByUuid(uuid); if (result instanceof ContentItem) { @@ -71,6 +77,17 @@ public class ContentItemRepository } } - //ToDo: Methods for finding items by name, path, content type etc. + public T findByUuid(final String uuid, + final Class type) { + throw new UnsupportedOperationException(); + } + + public List findByType(final Class type) { + throw new UnsupportedOperationException(); + } + + public List findByFolder(final Category folder) { + throw new UnsupportedOperationException(); + } } diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionConfig.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionConfig.java index bc9d9b806..7c3d6aeee 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionConfig.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionConfig.java @@ -18,17 +18,150 @@ */ package org.librecms.contentsection; +import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.configuration.Configuration; +import org.libreccm.configuration.ConfigurationManager; import org.libreccm.configuration.Setting; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** - * Global settings for content sections. Some of these settings control - * the initial values for new content sections. - * + * Global settings for content sections. Some of these settings control the + * initial values for new content sections. + * * @author Jens Pelzetter */ @Configuration public class ContentSectionConfig { - - + + /** + * A list of workflow tasks, and the associated events for which alerts have + * to be sent. Parameter name TASK_ALERTS in the old initializer system / + * enterprise.init Specifies when to generate email alerts: by default, + * generate email alerts on enable, finish, and rollback (happens on + * rejection) changes. There are four action types for each task type: + * enable, disable, finish, and rollback. Example: (Note that the values + * below are based on the task labels, and as such are not globalized.) + *
+     * 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 List taskAlerts = Arrays.asList(new String[]{ + "Authoring:enable:finish:rollback", + "Approval:enable:finish:rollback", + "Deploy:enable:finish:rollback" + }); + + /** + * Should we send alerts about overdue tasks at all? Send alerts when a task + * is overdue (has remained in the \"enabled\" state for a long time) + * Parameter SEND_OVERDUE_ALERTS in the old initializer system, default + * false + */ + @Setting + private boolean sendOverdueAlerts = false; + + /** + * The time between when a task is enabled (i.e. it is made available for + * completion) and when it is considered overdue (in HOURS). + */ + // XXX Once the Duration of a Task can actually be maintained (in the UI, + // or initialization parameters), we should use the value in the DB, and + // get rid of this + // Parameter name TASK_DURATION in the old initializer system. + // Description: How long a task can remain \"enabled\" before it is + // considered overdue (in hours) + @Setting + private int taskDuration = 96; + + /** + * The time to wait between sending successive alerts on the same overdue + * task (in HOURS). Parameter name OVERDUE_ALERT_INTERVAL in old initializer + * system Description: Time to wait between sending overdue notifications on + * the same task (in hours) + */ + @Setting + private int alertInterval = 24; + + /** + * The maximum number of alerts to send about any one overdue task. + * Parameter name MAX_ALERTS in old initializer system. Description: The + * maximum number of alerts to send that a single task is overdue + */ + @Setting + private int maxAlerts = 5; + + public static ContentSectionConfig getConfig() { + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); + return confManager.findConfiguration(ContentSectionConfig.class); + } + + public ContentSectionConfig() { + super(); + } + + public List getTaskAlerts() { + return new ArrayList<>(taskAlerts); + } + + public void setTaskAlerts(final List taskAlerts) { + this.taskAlerts = new ArrayList<>(taskAlerts); + } + + public boolean isSendOverdueAlerts() { + return sendOverdueAlerts; + } + + public void setSendOverdueAlerts(final boolean sendOverdueAlerts) { + this.sendOverdueAlerts = sendOverdueAlerts; + } + + public int getTaskDuration() { + return taskDuration; + } + + public void setTaskDuration(final int taskDuration) { + this.taskDuration = taskDuration; + } + + public int getAlertInterval() { + return alertInterval; + } + + public void setAlertInterval(final int alertInterval) { + this.alertInterval = alertInterval; + } + + public int getMaxAlerts() { + return maxAlerts; + } + + public void setMaxAlerts(final int maxAlerts) { + this.maxAlerts = maxAlerts; + } + } diff --git a/ccm-core/src/main/java/com/arsdigita/dispatcher/BaseDispatcherServlet.java b/ccm-core/src/main/java/com/arsdigita/dispatcher/BaseDispatcherServlet.java new file mode 100755 index 000000000..7ad17531a --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/dispatcher/BaseDispatcherServlet.java @@ -0,0 +1,639 @@ +/* + * 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 com.arsdigita.util.StringUtils; +import com.arsdigita.util.UncheckedWrapperException; +import com.arsdigita.web.RedirectSignal; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.log4j.Logger; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Contains functions common to all entry-point dispatcher servlets in the core. + * + * Any dispatcher that is the first in its chain to handle an HTTP request must + * also be a servlet and should extend this class. + * + *

+ * 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 com.arsdigita.sitenode.SiteNodeDispatcher, mapped to + * URL "/". + * + *

+ * When a request comes in: + * + *

  • first we try to serve a concrete file that matches the URL (for + * example, if the URL is /dir/image.gif, try to serve $docroot/dir/image.gif) + * + *
  • if the URL has no extension we assume it is a virtual directory. if there + * is no trailing slash, redirect to URL + "/". + * + *
  • if the URL has no extension and a trailing slash, it is treated as a + * directory. If the directory exists as a concrete directory on disk, and has a + * welcome file (index.*) then serve as a directory by forwarding to the + * "default" servlet. + * + *
  • if there is no concrete match for the URL on disk, we set up a + * RequestContext object that acts as a request wrapper storing metadata about + * the request; and call the dispatch method. + * + *
+ * + * @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 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 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: + * + *

    + *
  • try to serve a file /templates/$site-node/$page.jsp + *
  • if not found, try to serve a Bebop Page mapped to $page + *
  • if not found, try to serve a file /packages/$key/www/$page.jsp + *
  • if not found, serve "not found" page + *
+ * + * 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.

+ * + * 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 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. + *

+ * Given an application context that contains a SiteNode, and a + * remaining URL, tries to resolve the remaining URL into a concrete file:
+ * $root/packages/$key/$remaining-url
+ * + * where
+ * $root is the ACS webapp root
+ * $key is siteNode.getPackageKey()
+ * $remaining-url is given by appContext.getRemainingURLPart() + * + *

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: + * + * + * + * + * + * + * + * + *
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
+ * @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 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) Class .forName(resolverClass); return clazz.newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { + } catch (ClassNotFoundException | + InstantiationException | + IllegalAccessException ex) { throw new UncheckedWrapperException( "Unable to retrieve ApplicationFileResolver", ex); }