From 23f682b16d6b4323f3904265a70a84f7a8b872c3 Mon Sep 17 00:00:00 2001 From: jensp Date: Thu, 4 Aug 2016 16:45:18 +0000 Subject: [PATCH] CCM NG: - Content Center Application now works again. - JavaDoc for ContentItemRepository and ContentItemManager - A prototype/test for using JSF/PrimeFaces instead of Bebop for the Backend UI (will may be removed later) git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4211 8810af33-2d31-482b-a856-94f89814c4df --- .../src/main/resources/log4j2.xml | 3 + .../src/main/webapp/WEB-INF/faces-config.xml | 7 + .../src/main/webapp/WEB-INF/web.xml | 16 + .../src/main/webapp/test-index.xhtml | 17 + .../cms/ContentCenterAppCreator.java | 52 ++ .../arsdigita/cms/ContentCenterServlet.java | 9 +- .../com/arsdigita/cms/ContentCenterSetup.java | 48 ++ .../com/arsdigita/cms/ui/ItemSearch.java.off | 86 +++ .../cms/ui/authoring/NewItemForm.java.off | 247 +++++++ .../ContentSectionContainer.java | 649 ++++++++++++++++++ .../cms/ui/contentcenter/TasksPanel.java | 67 +- ccm-cms/src/main/java/org/librecms/Cms.java | 18 + .../main/java/org/librecms/CmsConstants.java | 1 + .../contentsection/ContentItemManager.java | 132 +++- .../contentsection/ContentItemRepository.java | 85 ++- .../contentsection/ContentSectionSetup.java | 22 +- .../librecms/contentsection/ContentType.java | 16 +- ...7_0_0_1__fix_content_types_constraints.sql | 15 + ...7_0_0_1__fix_content_types_constraints.sql | 16 + .../ContentCenterResources.properties | 3 + .../ContentCenterResources_de.properties | 3 + .../contentsection/EqualsAndHashCodeTest.java | 26 +- ccm-core/pom.xml | 5 + .../java/com/arsdigita/bebop/Embedded.java | 146 ++++ .../arsdigita/web/CCMDispatcherServlet.java | 2 +- .../admin/ui/AdminJsfApplicationCreator.java | 53 ++ .../admin/ui/AdminJsfApplicationSetup.java | 51 ++ .../AbstractAuditedEntityRepository.java | 3 +- .../main/java/org/libreccm/core/CcmCore.java | 14 +- .../ui/admin/AuthorizationListener.java | 129 ++++ .../org/libreccm/ui/admin/ConfProperty.java | 30 +- .../ui/admin/ConfigurationController.java | 131 ++++ .../ui/admin/UserContextController.java | 66 ++ .../META-INF/resources/admin-jsf/admin.xhtml | 124 ++++ .../META-INF/resources/admin-jsf/header.css | 33 + .../META-INF/resources/admin-jsf/libreccm.png | Bin 0 -> 22448 bytes .../resources/META-INF/resources/test.xhtml | 16 + .../src/main/resources/META-INF/test.xhtml | 16 + pom.xml | 6 + 39 files changed, 2265 insertions(+), 98 deletions(-) create mode 100644 ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/faces-config.xml create mode 100644 ccm-bundle-devel-wildfly-web/src/main/webapp/test-index.xhtml create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterAppCreator.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterSetup.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/NewItemForm.java.off create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/ContentSectionContainer.java create mode 100644 ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/h2/V7_0_0_1__fix_content_types_constraints.sql create mode 100644 ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/pgsql/V7_0_0_1__fix_content_types_constraints.sql create mode 100644 ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources.properties create mode 100644 ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources_de.properties create mode 100644 ccm-core/src/main/java/com/arsdigita/bebop/Embedded.java create mode 100644 ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationCreator.java create mode 100644 ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationSetup.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/AuthorizationListener.java rename ccm-cms/src/main/java/org/librecms/ContentType.java => ccm-core/src/main/java/org/libreccm/ui/admin/ConfProperty.java (70%) create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/ConfigurationController.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/UserContextController.java create mode 100644 ccm-core/src/main/resources/META-INF/resources/admin-jsf/admin.xhtml create mode 100644 ccm-core/src/main/resources/META-INF/resources/admin-jsf/header.css create mode 100644 ccm-core/src/main/resources/META-INF/resources/admin-jsf/libreccm.png create mode 100644 ccm-core/src/main/resources/META-INF/resources/test.xhtml create mode 100644 ccm-core/src/main/resources/META-INF/test.xhtml diff --git a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml index c7937b77e..37684c867 100644 --- a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml +++ b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml @@ -45,5 +45,8 @@ + + \ No newline at end of file diff --git a/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/faces-config.xml b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/faces-config.xml new file mode 100644 index 000000000..59d6547c7 --- /dev/null +++ b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/faces-config.xml @@ -0,0 +1,7 @@ + + + diff --git a/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml index fcb05ca5c..524e50256 100644 --- a/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml +++ b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml @@ -26,4 +26,20 @@ org.apache.shiro.web.env.EnvironmentLoaderListener + + + Faces Servlet + javax.faces.webapp.FacesServlet + 1 + + + + Faces Servlet + *.xhtml + + + + javax.faces.PROJECT_STAGE + Development + diff --git a/ccm-bundle-devel-wildfly-web/src/main/webapp/test-index.xhtml b/ccm-bundle-devel-wildfly-web/src/main/webapp/test-index.xhtml new file mode 100644 index 000000000..32b0d058d --- /dev/null +++ b/ccm-bundle-devel-wildfly-web/src/main/webapp/test-index.xhtml @@ -0,0 +1,17 @@ + + + + + JSF Test INDEX + + + + + + + + + + + diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterAppCreator.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterAppCreator.java new file mode 100644 index 000000000..557a4c0c4 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterAppCreator.java @@ -0,0 +1,52 @@ +/* + * 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.ApplicationCreator; +import org.libreccm.web.ApplicationRepository; +import org.libreccm.web.ApplicationType; +import org.libreccm.web.CcmApplication; +import org.librecms.CmsConstants; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class ContentCenterAppCreator implements ApplicationCreator { + + @Inject + private ApplicationRepository appRepository; + + @Override + public CcmApplication createInstance(final String primaryUrl, + final ApplicationType type) { + if (!CmsConstants.CONTENT_CENTER_URL.equals(primaryUrl)) { + throw new IllegalArgumentException( + "ContentCenter is a singleton application which is mounted at " + + "/content-center/"); + } + + return appRepository.retrieveApplicationForPath(primaryUrl); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java index b5eecd808..587e3819d 100644 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterServlet.java @@ -48,7 +48,6 @@ 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; @@ -136,8 +135,8 @@ public class ContentCenterServlet extends BaseApplicationServlet { /* 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 + if (!shiro.getSubject().isAuthenticated()) { + throw new LoginSignal(sreq); // send to login page } final PermissionChecker permissionChecker = cdiUtil.findBean( PermissionChecker.class); @@ -178,7 +177,7 @@ public class ContentCenterServlet extends BaseApplicationServlet { // DispatcherHelper.sendRedirect(sresp, originalUrl + "/"); // return; // } - final Page page = (Page) pages.get(pathInfo); + final Page page = pages.get(pathInfo); if (page != null) { // Check user access. @@ -274,7 +273,7 @@ public class ContentCenterServlet extends BaseApplicationServlet { ) throws ServletException { - if (CdiUtil.createCdiUtil().findBean(Shiro.class).getSubject() + if (!CdiUtil.createCdiUtil().findBean(Shiro.class).getSubject() .isAuthenticated()) { throw new LoginSignal(request); } diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterSetup.java b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterSetup.java new file mode 100644 index 000000000..f03aad40e --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ContentCenterSetup.java @@ -0,0 +1,48 @@ +/* + * 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.modules.InstallEvent; +import org.libreccm.web.AbstractCcmApplicationSetup; +import org.libreccm.web.CcmApplication; +import org.librecms.CmsConstants; + +import java.util.UUID; + +/** + * + * @author Jens Pelzetter + */ +public class ContentCenterSetup extends AbstractCcmApplicationSetup { + + public ContentCenterSetup(final InstallEvent event) { + super(event); + } + + @Override + public void setup() { + final CcmApplication contentCenter = new CcmApplication(); + contentCenter.setUuid(UUID.randomUUID().toString()); + contentCenter.setApplicationType(CmsConstants.CONTENT_CENTER_APP_TYPE); + contentCenter.setPrimaryUrl(CmsConstants.CONTENT_CENTER_URL); + + getEntityManager().persist(contentCenter); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.off new file mode 100755 index 000000000..7f563c90d --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.off @@ -0,0 +1,86 @@ +/* + * 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.ui; + +import com.arsdigita.bebop.Form; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.Resettable; +import com.arsdigita.bebop.SimpleContainer; + +import com.arsdigita.search.QuerySpecification; +import com.arsdigita.search.ui.QueryGenerator; + +/** + * A wrapper around the {@link ItemSearchSection} which embedds + * the form section in a form. + * + * @author Stanislav Freidin (sfreidin@arsdigita.com) + * @version $Id: ItemSearch.java 1940 2009-05-29 07:15:05Z terry $ + */ +public class ItemSearch extends Form implements Resettable, QueryGenerator { + + private static final org.apache.log4j.Logger s_log = + org.apache.log4j.Logger.getLogger(ItemSearch.class); + public static final String SINGLE_TYPE_PARAM = ItemSearchSection.SINGLE_TYPE_PARAM; + private ItemSearchSection m_section; + + /** + * Construct a new ItemSearch component + * Default to limit the search to current content section + * + * @param context the context for the retrieved items. Should be + * {@link ContentItem#DRAFT} or {@link ContentItem#LIVE} + */ + public ItemSearch(String context) { + this(context, true); + } + + /** + * Construct a new ItemSearch component + * + * @param context the context for the retrieved items. Should be + * {@link ContentItem#DRAFT} or {@link ContentItem#LIVE} + * @param limitToContentSection limit the search to the current content section + */ + public ItemSearch(String context, boolean limitToContentSection) { + super("itemSearch", new SimpleContainer()); + //setMethod("GET"); + m_section = createSearchSection(context, limitToContentSection); + add(m_section); + } + + protected ItemSearchSection createSearchSection(String context, boolean limitToContentSection) { + return new ItemSearchSection(context, limitToContentSection); + } + + @Override + public boolean hasQuery(PageState state) { + return m_section.hasQuery(state); + } + + @Override + public QuerySpecification getQuerySpecification(PageState state) { + return m_section.getQuerySpecification(state); + } + + @Override + public void reset(PageState state) { + m_section.reset(state); + } +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/NewItemForm.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/NewItemForm.java.off new file mode 100755 index 000000000..17f626f2b --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/NewItemForm.java.off @@ -0,0 +1,247 @@ +/* + * 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.ui.authoring; + +import com.arsdigita.bebop.BoxPanel; +import com.arsdigita.bebop.Form; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.event.PrintEvent; +import com.arsdigita.bebop.event.PrintListener; +import com.arsdigita.bebop.form.Option; +import com.arsdigita.bebop.form.OptionGroup; +import com.arsdigita.bebop.form.SingleSelect; +import com.arsdigita.bebop.form.Submit; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.cms.ui.ItemSearch; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.ui.admin.GlobalizationUtil; +import com.arsdigita.util.UncheckedWrapperException; +import com.arsdigita.xml.Element; + +import java.math.BigDecimal; + +import org.apache.log4j.Logger; +import org.libreccm.security.Party; +import org.librecms.CmsConstants; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentType; + +import java.awt.image.Kernel; +import java.util.List; + +/** + * A form element which displays a select box of all content types available + * under the given content section, and forwards to the item creation UI when + * the user selects a content type to instantiate. + * + * @author Stanislav Freidin (sfreidin@arsdigtia.com) + * @version $Revision: #12 $ $DateTime: 2004/08/17 23:15:09 $ + * @version $Id: NewItemForm.java 2161 2011-02-02 00:16:13Z pboy $ + */ +public abstract class NewItemForm extends Form { + + /** + * 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.ui.authoring.NewItemForm=DEBUG by uncommenting or + * adding the line. + */ + private static final Logger s_log = Logger.getLogger(NewItemForm.class); + + private final SingleSelect m_typeWidget; + private final Submit m_submit; + private final Label m_emptyLabel; + private final Label m_createLabel; + public static final String TYPE_ID = "tid"; + + public NewItemForm(String name) { + this(name, BoxPanel.HORIZONTAL); + } + + /** + * Construct a new NewItemForm. It sets a vertical BoxPanel as the component + * container. + * + * @param name the name attribute of the form. + */ + public NewItemForm(String name, int orientation) { + + super(name, new BoxPanel(BoxPanel.VERTICAL)); + setIdAttr("new_item_form"); + + //BoxPanel panel = new BoxPanel(BoxPanel.HORIZONTAL); + BoxPanel panel = new BoxPanel(orientation); + panel.setWidth("2%"); + panel.setBorder(0); + + // create and add an "empty" component + m_emptyLabel = new Label(new GlobalizedMessage( + "cms.ui.authoring.no_types_registered", CmsConstants.CMS_BUNDLE), + false); + m_emptyLabel.setIdAttr("empty_label"); + panel.add(m_emptyLabel); + + m_createLabel = new Label(new GlobalizedMessage( + "cms.ui.authoring.create_new", CmsConstants.CMS_BUNDLE), + false); + m_createLabel.setIdAttr("create_label"); + panel.add(m_createLabel); + + m_typeWidget = new SingleSelect(new BigDecimalParameter(TYPE_ID), + OptionGroup.SortMode.ALPHABETICAL_ASCENDING); + try { + m_typeWidget.addPrintListener(new PrintListener() { + + // Read the content section's content types and add them as options + @Override + public void prepare(PrintEvent e) { + OptionGroup o = (OptionGroup) e.getTarget(); + o.clearOptions(); + PageState state = e.getPageState(); + + // gather the content types of this section into a list + ContentSection section = getContentSection(state); + ContentType parentType = null; + List typesCollection = null; + BigDecimal singleTypeID = (BigDecimal) state.getValue( + new BigDecimalParameter( + ItemSearch.SINGLE_TYPE_PARAM)); + + if (singleTypeID != null) { + try { + parentType = new ContentType(singleTypeID); + } catch (DataObjectNotFoundException ex) { + parentType = null; + } + } + + if (parentType == null) { + typesCollection = section.getCreatableContentTypes(); + } else { + typesCollection = section.getDescendantsOfContentType( + parentType); + } + + typesCollection.addOrder(ContentType.LABEL); + + if (!typesCollection.isEmpty()) { + // Add content types + while (typesCollection.next()) { + boolean list = true; + ContentType type = typesCollection.getContentType(); + if (PermissionService + .getDirectGrantedPermissions(type.getOID()) + .size() > 0) { + // chris gilbert - allow restriction of some types + // to certain users/groups. No interface to do + // this, but group could be created and permission + // granted in a content type loader + // + // can't permission filter the collection because + // most types will have no permissions granted. + // This approach involves a small overhead getting + // the count of granted permissions for each type + // (mitigated by only checking DIRECT permissions) + + Party party = Kernel.getContext().getParty(); + if (party == null) { + party = Kernel.getPublicUser(); + } + PermissionDescriptor create + = new PermissionDescriptor( + PrivilegeDescriptor + .get(SecurityManager.CMS_NEW_ITEM), + type, + party); + list = PermissionService.checkPermission(create); + + } + if (list) { + // o.addOption(new Option(type.getID().toString(), type.getName())); + o.addOption(new Option(type.getID().toString(), + new Label(type.getLabel()))); + } + + } + typesCollection.reset(); + } + } + + }); + } catch (java.util.TooManyListenersException e) { + throw new UncheckedWrapperException("Too many listeners: " + e + .getMessage(), e); + } + + panel.add(m_typeWidget); + + m_submit = new Submit("new", GlobalizationUtil.globalize( + "cms.ui.authoring.go")); + panel.add(m_submit); + + add(panel); + } + + public abstract ContentSection getContentSection(PageState state); + + /** + * + * @param state + * + * @return + */ + public BigDecimal getTypeID(PageState state) { + return (BigDecimal) m_typeWidget.getValue(state); + } + + /** + * + * @return + */ + public final SingleSelect getTypeSelect() { + return m_typeWidget; + } + + /** + * Generate XML - show/hide labels/widgets + * + * @param state + * @param parent + */ + @Override + public void generateXML(PageState state, Element parent) { + + if (isVisible(state)) { + ContentSection section = getContentSection(state); + + ContentTypeCollection c = section.getCreatableContentTypes(); + boolean isEmpty = c.isEmpty(); + c.close(); + + m_createLabel.setVisible(state, !isEmpty); + m_typeWidget.setVisible(state, !isEmpty); + m_submit.setVisible(state, !isEmpty); + m_emptyLabel.setVisible(state, isEmpty); + + super.generateXML(state, parent); + } + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/ContentSectionContainer.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/ContentSectionContainer.java new file mode 100755 index 000000000..7fffe49c2 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/ContentSectionContainer.java @@ -0,0 +1,649 @@ +/* + * 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.ui.contentcenter; + +import com.arsdigita.bebop.BoxPanel; + +import java.math.BigDecimal; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.Embedded; +import com.arsdigita.bebop.FormProcessException; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.Link; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.SingleSelectionModel; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.event.FormProcessListener; +import com.arsdigita.bebop.event.FormSectionEvent; +import com.arsdigita.bebop.event.FormSubmissionListener; +import com.arsdigita.bebop.form.Hidden; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.bebop.table.TableCellRenderer; +import com.arsdigita.bebop.table.TableColumn; +import com.arsdigita.bebop.table.TableColumnModel; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.table.TableModelBuilder; +import com.arsdigita.cms.ui.CMSContainer; +import com.arsdigita.ui.admin.GlobalizationUtil; +import com.arsdigita.util.Assert; +import com.arsdigita.util.LockableImpl; +import com.arsdigita.web.Web; + +import org.libreccm.categorization.Category; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.security.PermissionChecker; +import org.libreccm.security.User; +import org.librecms.CmsConstants; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionConfig; +import org.librecms.contentsection.ContentSectionRepository; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javax.mail.Folder; + +/** + * Displays all the content sections in table, with links to the admin (and in + * legacy mode to legacy public pages as well). Also displays a form for each + * content section to create an object of a given type (configurable). The list + * of available types retrieved for each content section. + * + *

+ * This class is a container for two other components: a form and a table. The + * form represents the drop down list of the content types available in a + * particular content section. It is an extension of the + * {@link com.arsdigita.cms.ui.authoring.NewItemForm}. The table displays each + * content section in one row, along with the specified form. The same form is + * reused in every row of the table. + * + * @author Michael Bryzek + * @version $Id: ContentSectionContainer.java 287 2005-02-22 00:29:02Z sskracic$ + */ +public class ContentSectionContainer extends CMSContainer { + + private static final String CONTENT_SECTION_CLASS = "contentSections"; + + private final ContentSectionTable m_table; + private final FormContainer m_formContainer; + private final SingleSelectionModel m_typeSel; + private final SingleSelectionModel m_sectionSel; + + /** + * Constructs a new ContentSectionContainer which containts: + * + *

    + *
  • SimpleContainer (to contain the form) + *
      + *
    • Form (for creating a new content item in each section) + *
    + *
  • Table (Displays all content sections) + *
+ * + * @param typeSel passthrough to {@link NewItemForm} + * @param sectionSel passthrough to {@link NewItemForm} + */ + public ContentSectionContainer(SingleSelectionModel typeSel, + SingleSelectionModel sectionSel) { + super(); + setClassAttr(CONTENT_SECTION_CLASS); + + m_typeSel = typeSel; + m_sectionSel = sectionSel; + + m_formContainer = new FormContainer(); + add(m_formContainer); + m_table = new ContentSectionTable(); + add(m_table); + } + + /** + * + * @param p + */ + @Override + public void register(Page p) { + super.register(p); + p.setVisibleDefault(m_formContainer, false); + } + + /** + * + */ + private class FormContainer extends CMSContainer { + +// private final StaticNewItemForm m_form; + private final BigDecimalParameter m_sectionIdParam; + + /** + * Constructor + */ + private FormContainer() { + super(); + m_sectionIdParam = new BigDecimalParameter("sectionId"); +// m_form = new StaticNewItemForm(m_sectionIdParam); + +// m_form.addSubmissionListener(new FormSubmissionListener() { +// +// /** +// * Cancels the form if the user lacks the "create new items" +// * privilege. +// */ +// @Override +// public void submitted(FormSectionEvent event) +// throws FormProcessException { +// PageState state = event.getPageState(); +// StaticNewItemForm form = (StaticNewItemForm) event +// .getSource(); +// +// ContentSection section = form.getContentSection(state); +// final PermissionChecker permissionChecker = CdiUtil +// .createCdiUtil().findBean(PermissionChecker.class); +// Category folder = null; +// //ToDo +//// User user = Web.getWebContext().getUser(); +//// if (user != null) { +//// folder = Folder.getUserHomeFolder(user, section); +//// } +//// if (folder == null) { +//// folder = section.getRootFolder(); +//// } +////ToDo End +// folder = section.getRootDocumentsFolder(); +// +// if (!permissionChecker.isPermitted( +// CmsConstants.PRIVILEGE_ITEMS_CREATE_NEW, folder)) { +// throw new FormProcessException( +// (GlobalizationUtil.globalize( +// "cms.ui.insufficient_privileges"))); +// } +// } +// +// }); +// +// m_form.addProcessListener(new FormProcessListener() { +// +// /** +// * Process listener: redirects to the authoring kit to create a +// * new item. +// */ +// @Override +// public void process(FormSectionEvent e) throws +// FormProcessException { +// StaticNewItemForm form = (StaticNewItemForm) e.getSource(); +// PageState state = e.getPageState(); +// +// BigDecimal typeId = form.getTypeID(state); +// if (typeId != null) { +// Long sectionId = form.getContentSectionID(state); +// m_sectionSel.setSelectedKey(state, sectionId); +// m_typeSel.setSelectedKey(state, typeId); +// } +// } +// +// }); +// +// add(m_form); + } + + @Override + public void register(Page p) { + super.register(p); + p.addComponentStateParam(this, m_sectionIdParam); + } + +// public StaticNewItemForm getNewItemForm() { +//// return m_form; +// } + + } + +// private static class StaticNewItemForm extends NewItemForm { +// +// private final Hidden m_sectionIDParamWidget; +// +// public StaticNewItemForm(BigDecimalParameter sectionParam) { +// super("StaticNewItemForm", BoxPanel.VERTICAL); +// setClassAttr("static-new-item-form"); +// m_sectionIDParamWidget = new Hidden(sectionParam); +// add(m_sectionIDParamWidget); +// setProcessInvisible(true); +// } +// +// /** +// * Sets the id of the content section in this form. This ID is used to +// * generate a list of available content types in the section. +// * +// * @param state The current page state. +// * @param id The id of the ContentSection for which this form should +// * display a list of content types +// * +// * @pre ( state != null && id != null ) +// */ +// public void setSectionId(PageState state, BigDecimal id) { +// Assert.exists(id); +// m_sectionIDParamWidget.setValue(state, id); +// } +// +// /** +// * Retrieves the content section for this form given the specified page +// * state. This method will return null if there is no content section. +// * +// * @param state The current page state. +// * +// * @return The current content section or null if the section does not +// * exist +// * +// * @pre ( state != null ) +// */ +// @Override +// public ContentSection getContentSection(PageState state) { +// Long id = getContentSectionID(state); +// Assert.exists(id); +// ContentSection section; +// section = CdiUtil.createCdiUtil().findBean( +// ContentSectionRepository.class).findById(id); +// return section; +// } +// +// /** +// * Retrieves the ID of the content section for this form given the +// * specified page state. This method will return null if no content +// * section id has been set. +// * +// * @param state The current page state. +// * +// * @return The id of the content section or null if it has not been set. +// * +// * @pre ( state != null ) +// */ +// private Long getContentSectionID(PageState state) { +// return (Long) Long.parseLong((String) m_sectionIDParamWidget +// .getValue(state)); +// } +// +// } + + /** + * A table that displays all content sections, with links to their locations + * and admin pages and a {@link NewItemForm} next to each section. + * + * @author Michael Bryzek + * @version $Revision$ $DateTime: 2004/08/17 23:15:09 $ + * + */ + private class ContentSectionTable extends Table { + + // We will use a (symboloc) headerKey to match columns. Because the + // number of columns depends on configuration for the llocation column, + // the index varies and con not be used. + private static final String COLUMN_SECTION = "Section"; + private static final String COLUMN_LOCATION = "Public Site"; + private static final String COLUMN_ACTION = "Action"; + + /** + * Constructs a new ContentSectionTable, using a default table model + * builder. + */ + private ContentSectionTable() { + super(); + + // we must use symbolic keys (instead of symbolic column index as + // usual) to identify a column because their number is dynamic + // depending on configuration of the location column! + Integer colNo = 0; + + Label emptyView = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.section")); + emptyView.setFontWeight(Label.ITALIC); + setEmptyView(emptyView); + + setClassAttr("dataTable"); + + // add columns to the table + TableColumnModel columnModel = getColumnModel(); + + // prepare column headers + Label sectionHead = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.section")); + sectionHead.setHint(GlobalizationUtil + .globalize("cms.ui.contentcenter.section_hint")); + Label locationHead = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.location")); + locationHead.setHint(GlobalizationUtil + .globalize("cms.ui.contentcenter.location_hint")); + Label actionHead = new Label(GlobalizationUtil + .globalize("cms.ui.contentcenter.action")); + actionHead.setHint(GlobalizationUtil + .globalize("cms.ui.contentcenter.action_hint")); + + //TableColumn contentSectionColumn = new TableColumn(colNo, COLUMN_SECTION); + TableColumn contentSectionColumn = new TableColumn( + colNo, + sectionHead, + COLUMN_SECTION); + contentSectionColumn + .setCellRenderer(new AdminURLTableCellRenderer()); + columnModel.add(contentSectionColumn); + + TableColumn actionColumn = new TableColumn( + colNo++, + actionHead, + COLUMN_ACTION); + actionColumn.setCellRenderer(new ActionTableCellRenderer()); + columnModel.add(actionColumn); + + setModelBuilder(new ContentSectionTableModelBuilder()); + } + + /** + * An ContentSections table model builder + * + * @author Michael Bryzek + * + */ + private class ContentSectionTableModelBuilder extends LockableImpl + implements TableModelBuilder { + + @Override + public TableModel makeModel(Table table, PageState state) { + table.getRowSelectionModel().clearSelection(state); + return new ContentSectionTableModel((ContentSectionTable) table, + state); + } + + } + + /** + * An ContentSections table model + * + * @author Michael Bryzek + * + */ + private class ContentSectionTableModel implements TableModel { + + private final ContentSectionTable m_table; + private final TableColumnModel m_columnModel; + private final PageState m_state; + private final List m_contentSections; + private ContentSection m_section; + private int index = -1; + + private ContentSectionTableModel(ContentSectionTable table, + PageState state) { + m_table = table; + m_columnModel = table.getColumnModel(); + m_state = state; + + // retrieve all Content Sections + m_contentSections = getContentSectionCollection(); + } + + /** + * Returns a collection of ContentSections to display in this table. + * This implementation orders the content sections by + * lower(label). They are also already filtered for the + * sections to which the current user has no access. + * + */ + private List getContentSectionCollection() { + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final PermissionChecker permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + final List allSections = cdiUtil.findBean( + ContentSectionRepository.class).findAll(); + return allSections + .stream() + .filter(section -> permissionChecker + .isPermitted(CmsConstants.PRIVILEGE_ITEMS_VIEW_PUBLISHED, + section)) + .collect(Collectors.toList()); + } + + @Override + public int getColumnCount() { + return m_columnModel.size(); + } + + @Override + public boolean nextRow() { + index++; + if (index < m_contentSections.size()) { + m_section = m_contentSections.get(index); + return true; + } else { + return false; + } + } + + /** + * By default, we return null. For the section, location, and action + * columns, we return the current Content Section if there is one. + * + * @param columnIndex The index of the current column + */ + @Override + public Object getElementAt(int columnIndex) { + if (m_columnModel == null || m_section == null) { + return null; + } + + TableColumn tc = m_columnModel.get(columnIndex); + String columnKey = (String) tc.getHeaderKey(); + + Object result = m_section; + if (columnKey.equals(COLUMN_SECTION) + || columnKey.equals(COLUMN_LOCATION) + || columnKey.equals( + COLUMN_ACTION)) { + result = m_section; + } + return result; + } + + @Override + public Object getKeyAt(int columnIndex) { + return m_section.getObjectId(); + } + + /** + * Returns the table associated with this table model. + * + */ + protected Table getTable() { + return m_table; + } + + /** + * Returns the current page state + * + */ + protected PageState getPageState() { + return m_state; + } + + } + + /** + * Sets the hidden parameter in the form containers form to the id of + * the current section. Then returns the form for display, but only if + * the user has permission to create new items in the current section. + * + * @author Michael Bryzek + * + */ + private class ActionTableCellRenderer implements TableCellRenderer { + + @Override + public Component getComponent(Table table, PageState state, + Object value, + boolean isSelected, Object key, + int row, int column) { + ContentSection section = (ContentSection) value; + Category folder = null; + //ToDo +// User user = Web.getWebContext().getUser(); +// if (user != null) { +// folder = Folder.getUserHomeFolder(user, section); +// } +// if (folder == null) { +// folder = section.getRootFolder(); +// } + + folder = section.getRootDocumentsFolder(); + // If the user has no access, return an empty Label + +// +// SecurityManager sm = new SecurityManager(section); +// +// if (!sm.canAccess(state.getRequest(), SecurityManager.NEW_ITEM, +// folder) +// || !ContentSection.getConfig() +// .getAllowContentCreateInSectionListing()) { +// // return null; // produces NPE here but works somewhere else. +// // It's a kind of a hack. Label is supposed not to accept +// // not-gloabalized data. Usually aou will return null here +// // and xmlgenerator takes care of it. Doesn't work here. +// return new Embedded( +// "   - -   "); +// } else { +// // set the value of the sectionIdParameter in the form +// // to this section +// m_formContainer.getNewItemForm().setSectionId(state, section +// .getID()); +// return m_formContainer.getNewItemForm(); +// } + //ToDo End + return new Embedded( + "   - -   "); + + } + + } + + } + + /** + * Generates the correct URL to the public pages for a content section. + * + * @author Michael Bryzek + * + */ + public static class URLTableCellRenderer implements TableCellRenderer { + + /** + * The object passed in is the current content section. This returns a + * Link whose name and target are the url to the public pages. + * + * @return Link whose name and target are the url to the public pages of + * the current (passed in) content section or a Label if current + * use does not habe acces priviledge for the content section + */ + @Override + public Component getComponent(Table table, + PageState state, + Object value, + boolean isSelected, + Object key, + int row, + int column) { + + /* cast to ContentSection for further processing */ + ContentSection section = (ContentSection) value; + String name = section.getLabel(); + String path = section.getPrimaryUrl(); // from Application + + // If the user has no access, return a Label instead of a Link + // Kind of a hack because Label is supposed not to accept + // "un-globalized" display data. Label had been abused here to + // to display a DataValue + return new Embedded("/" + name + "/", false); + // return null; // produces NPE here + + } + + } + + /** + * Generates the correct URL to the admin pages for a content section. + * + * @author Michael Bryzek + * + */ + public static class AdminURLTableCellRenderer extends URLTableCellRenderer { + + /** + * The object passed in is the current content section + * + * @param table + * @param state + * @param row + * @param value + * @param column + * @param isSelected + * @param key + * + * @return + * + */ + @Override + public Component getComponent(Table table, PageState state, Object value, + boolean isSelected, Object key, + int row, int column) { + ContentSection section = (ContentSection) value; + + final PermissionChecker permissionChecker = CdiUtil.createCdiUtil() + .findBean(PermissionChecker.class); + + // If the user has no access, return a Label instead of a Link + if (permissionChecker.isPermitted( + CmsConstants.PRIVILEGE_ITEMS_EDIT, + section.getRootDocumentsFolder())) { + + return new Link(section.getLabel(), + generateURL(section.getPrimaryUrl() + "/")); + } else { + //return new Label(section.getName(), false); + // return null; // Produces a NPE although it shouldn't and + // indeed doesn't elsewhere + // Kind of a hack because Label is supposed not to accept + // "un-globalized" display data. Label had been abused here to + // to display a DataValue + return new Embedded(section.getLabel(), false); + } + } + + /** + * Generates the admin url for the specified prefix. Always returns + * something that does not start with a forward slash. + * + * @param prefix The prefix of the URL + * + * @return + */ + protected String generateURL(String prefix) { + return prefix;// + PageLocations.SECTION_PAGE; + } + + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/TasksPanel.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/TasksPanel.java index 12d0a81d6..77d93c43e 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/TasksPanel.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/contentcenter/TasksPanel.java @@ -75,8 +75,12 @@ public class TasksPanel extends CMSContainer { // private ActionLink m_viewAllLink; // private ActionLink m_viewShortLink; private Paginator m_paginator; - private ActionLink m_viewLockLink, m_viewUnlockLink, m_viewAllLockLink; - private Label m_viewLockLabel, m_viewUnlockLabel, m_viewAllLockLabel; + private ActionLink m_viewLockLink; + private ActionLink m_viewUnlockLink; + private ActionLink m_viewAllLockLink; + private Label m_viewLockLabel; + private Label m_viewUnlockLabel; + private Label m_viewAllLockLabel; private StringParameter m_sortDirectionParam; private StringParameter m_sortTypeParam; private StringParameter m_lockFilterParam; @@ -103,7 +107,7 @@ public class TasksPanel extends CMSContainer { private Label m_selectorLabel; //ToDo // private CreationSelector m_selector; -// private ContentSectionContainer m_sections; + private ContentSectionContainer m_sections; // ToDo End private CcmObjectSelectionModel m_sectionSel; private CcmObjectSelectionModel m_typeSel; @@ -134,7 +138,8 @@ public class TasksPanel extends CMSContainer { * @pre maxRows != null * */ - public TasksPanel(int maxRows, CcmObjectSelectionModel typeModel, + public TasksPanel(int maxRows, + CcmObjectSelectionModel typeModel, CcmObjectSelectionModel sectionModel) { super(); @@ -192,14 +197,13 @@ public class TasksPanel extends CMSContainer { // m_selector = new CreationSelector(m_typeSel, m_folderSel); // m_creationPane.add(m_selector); //ToDo End - m_creationPane.setClassAttr("itemCreationPane"); add(m_creationPane); // The section list UIx //ToDo -// m_sections = new ContentSectionContainer(m_typeSel, m_sectionSel); -// add(m_sections); + m_sections = new ContentSectionContainer(m_typeSel, m_sectionSel); + add(m_sections); //ToDo End // When a new type is selected, show the creation UI. // When the selection is cleared, return to section list @@ -444,8 +448,6 @@ public class TasksPanel extends CMSContainer { // query.addEqualsFilter("isLocked", "f"); // } // else show all // } - - // private static class RootFolderSelectionModel // extends FolderSelectionModel { // @@ -474,7 +476,6 @@ public class TasksPanel extends CMSContainer { // // } //ToDo End - /** * */ @@ -512,9 +513,8 @@ public class TasksPanel extends CMSContainer { // // return query; // } - public int size(PageState ps) { - return ((Long) m_taskCount.get(ps)).intValue(); + return ((Integer)m_taskCount.get(ps)).intValue(); } private RequestLocal m_taskCount = new RequestLocal() { @@ -523,7 +523,7 @@ public class TasksPanel extends CMSContainer { public Object initialValue(PageState state) { // DataQuery query = makeQuery(state); // return new Long(query.size()); -return null; + return 0; } }; @@ -536,7 +536,6 @@ return null; exportAttributes(content); // DataQuery query = makeQuery(state); - String lockFilterType = getLockFilterType(state); content.addAttribute("lockFilterType", lockFilterType); @@ -664,18 +663,18 @@ return null; // String.valueOf( // ContentItemPage.AUTHORING_TAB)); // } - } + } - // m_actionLabel.generateXML(state, content); - String[][] sortableHeaders = {{SORT_TITLE, - "cms.ui.workflow.task.item_title"}, - {SORT_ACTION, "cms.ui.action"}, - {SORT_DATE, "cms.ui.tasks_due_date"}, - {SORT_STATUS, - "cms.ui.tasks_status_no_colon"}, - {SORT_USER, - "cms.ui.workflow.task.locking_user"}, - {SORT_WORKFLOW, "cms.ui.workflow"}}; + // m_actionLabel.generateXML(state, content); + String[][] sortableHeaders = {{SORT_TITLE, + "cms.ui.workflow.task.item_title"}, + {SORT_ACTION, "cms.ui.action"}, + {SORT_DATE, "cms.ui.tasks_due_date"}, + {SORT_STATUS, + "cms.ui.tasks_status_no_colon"}, + {SORT_USER, + "cms.ui.workflow.task.locking_user"}, + {SORT_WORKFLOW, "cms.ui.workflow"}}; // for (int i = 0; i < sortableHeaders.length; i++) { // String header = sortableHeaders[i][0]; // String labelKey = sortableHeaders[i][1]; @@ -705,14 +704,15 @@ return null; // link.generateXML(state, content); // state.clearControlEvent(); // } - } - @Override - public void respond(PageState state) throws ServletException { - String key = state.getControlEventName(); - String value = state.getControlEventValue(); - if (TASK_ACTION.equals(key)) { - BigDecimal itemID = new BigDecimal(value); + } + + @Override + public void respond(PageState state) throws ServletException { + String key = state.getControlEventName(); + String value = state.getControlEventValue(); + if (TASK_ACTION.equals(key)) { + BigDecimal itemID = new BigDecimal(value); // // try { // ContentItem item = new ContentItem(itemID); @@ -751,7 +751,7 @@ return null; // throw new ServletException("Unknown content ID" + itemID); // } // } else - if (SORT_UP.equals(key) || SORT_DOWN.equals(key)) { + if (SORT_UP.equals(key) || SORT_DOWN.equals(key)) { state.setValue(m_sortTypeParam, value); if (SORT_DOWN.equals(key)) { state.setValue(m_sortDirectionParam, SORT_DOWN); @@ -776,5 +776,4 @@ return null; // return PermissionService.getFilterQuery(factory, "itemID", privilege, // partyOID); // } - } diff --git a/ccm-cms/src/main/java/org/librecms/Cms.java b/ccm-cms/src/main/java/org/librecms/Cms.java index 9d5e0212c..c58cc31cf 100644 --- a/ccm-cms/src/main/java/org/librecms/Cms.java +++ b/ccm-cms/src/main/java/org/librecms/Cms.java @@ -3,6 +3,10 @@ */ package org.librecms; +import com.arsdigita.cms.ContentCenterAppCreator; +import com.arsdigita.cms.ContentCenterServlet; +import com.arsdigita.cms.ContentCenterSetup; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.libreccm.core.CoreConstants; @@ -14,6 +18,7 @@ import org.libreccm.modules.RequiredModule; import org.libreccm.modules.ShutdownEvent; import org.libreccm.modules.UnInstallEvent; import org.libreccm.web.ApplicationType; +import org.libreccm.web.CcmApplication; import org.librecms.contentsection.ContentSection; import org.librecms.contentsection.ContentSectionCreator; import org.librecms.contentsection.ContentSectionSetup; @@ -29,6 +34,13 @@ import java.util.Properties; @RequiredModule(module = org.libreccm.core.CcmCore.class) }, applicationTypes = { + @ApplicationType( + name = CmsConstants.CONTENT_CENTER_APP_TYPE, + applicationClass = CcmApplication.class, + descBundle = CmsConstants.CONTENT_CENTER_DESC_BUNDLE, + creator = ContentCenterAppCreator.class, + servlet = ContentCenterServlet.class + ), @ApplicationType( name = CmsConstants.CONTENT_SECTION_APP_TYPE, applicationClass = ContentSection.class, @@ -66,6 +78,12 @@ public class Cms implements CcmModule { ex); } + LOGGER.info("Setting content center..."); + final ContentCenterSetup contentCenterSetup = new ContentCenterSetup( + event); + contentCenterSetup.setup(); + + LOGGER.info("Setting up content sections..."); final ContentSectionSetup contentSectionSetup = new ContentSectionSetup( event); contentSectionSetup.setup(); diff --git a/ccm-cms/src/main/java/org/librecms/CmsConstants.java b/ccm-cms/src/main/java/org/librecms/CmsConstants.java index e174cbd44..cfa4bd3de 100644 --- a/ccm-cms/src/main/java/org/librecms/CmsConstants.java +++ b/ccm-cms/src/main/java/org/librecms/CmsConstants.java @@ -32,6 +32,7 @@ public class CmsConstants { public static final String CONTENT_CENTER_APP_TYPE = "com.arsdigita.cms.ContentCenter"; public static final String CONTENT_CENTER_URL = "/content-center/"; + public static final String CONTENT_CENTER_DESC_BUNDLE = "org.librecms.contentcenter.ContentCenterResources"; public static final String CONTENT_SECTION_APP_TYPE = "org.librecms.contentsection.ContentSection"; diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java index 9d9d6a26d..9e82d0f09 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemManager.java @@ -19,11 +19,14 @@ package org.librecms.contentsection; import org.libreccm.categorization.Category; +import org.libreccm.workflow.WorkflowTemplate; +import org.librecms.lifecycle.LifecycleDefinition; import java.util.List; import java.util.Optional; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; /** * @@ -32,10 +35,88 @@ import javax.enterprise.context.RequestScoped; @RequestScoped public class ContentItemManager { + @Inject + private ContentItemRepository contentItemRepo; + + /** + * Creates a new content item in the provided content section and folder + * with the default lifecycle and workflow. + * + * The folder must be a subfolder of the + * {@link ContentSection#rootDocumentsFolder} of the provided content + * section. Otherwise an {@link IllegalArgumentException} is thrown. + * + * @param The type of the content item. + * @param name The name (URL stub) of the new content item. + * @param section The content section in which the item is generated. + * @param folder The folder in which in the item is stored. + * @param type The type of the new content item. + * + * @return The new content item. + */ + public T createContentItem( + final String name, + final ContentSection section, + final Category folder, + final Class type) { + throw new UnsupportedOperationException(); + } + + /** + * Creates a new content item in the provided content section and folder + * with the provided lifecycle and workflow. + * + * The folder must be a subfolder of the + * {@link ContentSection#rootDocumentsFolder} of the provided content + * section. Otherwise an {@link IllegalArgumentException} is thrown. + * + * Likewise the provided {@link LifecycleDefinition} and + * {@link WorkflowTemplate} must be defined in the provided content section. + * Otherwise an {@link IllegalArgumentException} is thrown. + * + * @param The type of the content item. + * @param name The name (URL stub) of the new content item. + * @param section The content section in which the item is + * generated. + * @param folder The folder in which in the item is stored. + * @param workflowTemplate + * @param lifecycleDefinition + * @param type The type of the new content item. + * + * @return The new content item. + */ + public T createContentItem( + final String name, + final ContentSection section, + final Category folder, + final WorkflowTemplate workflowTemplate, + final LifecycleDefinition lifecycleDefinition, + final Class type) { + throw new UnsupportedOperationException(); + } + + /** + * Moves a content item to another folder in the same content section. This + * only moves the draft version of the item. The live version is moved after + * a the item is republished. + * + * @param item The item to move. + * @param targetFolder The folder to which the item is moved. + */ public void move(final ContentItem item, final Category targetFolder) { throw new UnsupportedOperationException(); } + /** + * Creates an copy of the draft version of the item in the provided + * {@code targetFolder}. + * + * @param item The item to copy. + * @param targetFolder The folder in which the copy is created. If the + * target folder is the same folder as the folder of the + * original item an index is appended to the name of the + * item. + */ public void copy(final ContentItem item, final Category targetFolder) { throw new UnsupportedOperationException(); } @@ -64,21 +145,26 @@ public class ContentItemManager { /** * Determines if a content item has a live version. * - * @param item The item + * @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 + * + * @param Type of the content item. + * @param item The item of which the live version should be retrieved. + * @param type Type of the content item. + * + * @return The live version of an item. If the item provided is already the + * live version the provided item is returned, otherwise the live + * version is returned. If there is no live version an empty + * {@link Optional} is returned. */ public Optional getLiveVersion( final ContentItem item, @@ -86,11 +172,37 @@ public class ContentItemManager { throw new UnsupportedOperationException(); } - public List getPendingVersions() { + /** + * Retrieves the pending versions of an item if there are any. + * + * @param Type of the content item to retrieve. + * @param item The item of which the pending versions are retrieved. + * @param type Type of the content item to retrieve. + * + * @return A list of the pending versions of the item. + */ + public List getPendingVersions( + final ContentItem item, + final Class type) { throw new UnsupportedOperationException(); } - - public T getDraftVersion(final ContentItem item) { + + /** + * Retrieves the draft version + * + * @param Type of the item. + * @param item The item of which the draft version is retrieved. + * @param type Type of the item. + * + * @return The draft version of the provided content item. If the provided + * item is the draft version the provided item is simply returned. + * Otherwise the draft version is retrieved from the database and is + * returned. Each content item has a draft version (otherwise + * something is seriously wrong with the database) this method will + * never return {@code null}. + */ + public T getDraftVersion(final ContentItem item, + final Class type) { 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 c6e357b52..1095afd96 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemRepository.java @@ -24,21 +24,23 @@ import org.libreccm.core.CcmObject; import org.libreccm.core.CcmObjectRepository; import java.util.List; +import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; /** + * Repository for content items. * * @author Jens Pelzetter */ @RequestScoped -public class ContentItemRepository - extends AbstractAuditedEntityRepository{ +public class ContentItemRepository + extends AbstractAuditedEntityRepository { @Inject - private CcmObjectRepository ccmObjectRepo; - + private CcmObjectRepository ccmObjectRepo; + @Override public Long getEntityId(final ContentItem item) { return item.getObjectId(); @@ -53,21 +55,48 @@ public class ContentItemRepository public boolean isNew(final ContentItem item) { return ccmObjectRepo.isNew(item); } - - public ContentItem findById(final long itemId) { + + /** + * Finds a content item by is id. + * + * @param itemId The id of item to retrieve. + * + * @return The content item identified by the provided {@code itemId} or + * nothing if there is such content item. + */ + public Optional findById(final long itemId) { final CcmObject result = ccmObjectRepo.findObjectById(itemId); if (result instanceof ContentItem) { - return (ContentItem) result; + return Optional.of((ContentItem) result); } else { - return null; + return Optional.empty(); } } - - public T findById(final long itemId, - final Class type) { + + /** + * Finds a content item by its ID and ensures that is a the requested type. + * + * @param The type of the content item. + * @param itemId The id of item to retrieve. + * @param type The type of the content item. + * + * @return The content item identified by the provided id or an empty + * {@link Optional} if there is no such item or if it is not of the + * requested type. + */ + public Optional findById(final long itemId, + final Class type) { throw new UnsupportedOperationException(); } - + + /** + * Finds a content item by is UUID. + * + * @param uuid The id of item to retrieve. + * + * @return The content item identified by the provided {@code uuid} or + * nothing if there is such content item. + */ public ContentItem findByUuid(final String uuid) { final CcmObject result = ccmObjectRepo.findObjectByUuid(uuid); if (result instanceof ContentItem) { @@ -76,18 +105,42 @@ public class ContentItemRepository return null; } } - + + /** + * Finds a content item by its UUID and ensures that is a the requested type. + * + * @param The type of the content item. + * @param uuid The UUID of item to retrieve. + * @param type The type of the content item. + * + * @return The content item identified by the provided UUID or an empty + * {@link Optional} if there is no such item or if it is not of the + * requested type. + */ public T findByUuid(final String uuid, final Class type) { throw new UnsupportedOperationException(); } - + + /** + * Finds all content items of a specific type. + * + * @param The type of the items. + * @param type The type of the items. + * @return A list of all content items of the requested type. + */ public List findByType(final Class type) { throw new UnsupportedOperationException(); } - + + /** + * Retrieves all content items in the provided folder. + * + * @param folder The folder. + * @return A list of all items in the provided folder. + */ public List findByFolder(final Category folder) { throw new UnsupportedOperationException(); } - + } diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionSetup.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionSetup.java index f197f6d9e..863be218d 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionSetup.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionSetup.java @@ -26,7 +26,6 @@ import org.libreccm.security.Role; import org.libreccm.web.AbstractCcmApplicationSetup; import org.librecms.CmsConstants; -import java.util.Locale; import java.util.UUID; import static org.librecms.CmsConstants.*; @@ -54,8 +53,13 @@ public class ContentSectionSetup extends AbstractCcmApplicationSetup { if (getIntegrationProps().containsKey(INITIAL_CONTENT_SECTIONS)) { sectionNames = getIntegrationProps().getProperty( INITIAL_CONTENT_SECTIONS); + LOGGER.info( + "Found names for initial content sections in integration " + + "properties: {}", sectionNames); } else { sectionNames = "info"; + LOGGER.info("No initial content sections definied integration " + + "properties, using default: {}", sectionNames); } for (final String contentSectionName : sectionNames.split(",")) { @@ -64,13 +68,27 @@ public class ContentSectionSetup extends AbstractCcmApplicationSetup { } private void createContentSection(final String sectionName) { + LOGGER.debug("Creating content section with section name \"{}\"...", + sectionName); final ContentSection section = new ContentSection(); section.setUuid(UUID.randomUUID().toString()); section.setApplicationType(CmsConstants.CONTENT_SECTION_APP_TYPE); - section.setPrimaryUrl(sectionName); + section.setPrimaryUrl(String.format("/%s/", sectionName)); section.setDisplayName(sectionName); section.setLabel(sectionName); + LOGGER.debug("New content section properties: " + + "uuid = {}; " + + "applicationType = \"{}\"; " + + "primaryUrl = \"{}\"; " + + "displayName = \"{}\"; " + + "label = \"{}\"", + section.getUuid(), + section.getApplicationType(), + section.getPrimaryUrl(), + section.getDisplayName(), + section.getLabel()); + final Category rootFolder = new Category(); rootFolder.setUuid(UUID.randomUUID().toString()); rootFolder.setUniqueId(rootFolder.getUuid()); diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentType.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentType.java index 7681d0ff5..d472c2669 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentType.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentType.java @@ -25,8 +25,8 @@ import static org.librecms.CmsConstants.*; import org.libreccm.core.CcmObject; import org.libreccm.l10n.LocalizedString; -import org.libreccm.workflow.Workflow; -import org.librecms.lifecycle.Lifecycle; +import org.libreccm.workflow.WorkflowTemplate; +import org.librecms.lifecycle.LifecycleDefinition; import java.io.Serializable; import java.util.Objects; @@ -98,11 +98,11 @@ public class ContentType extends CcmObject implements Serializable { @ManyToOne @JoinColumn(name = "DEFAULT_LIFECYCLE_ID") - private Lifecycle defaultLifecycle; + private LifecycleDefinition defaultLifecycle; @ManyToOne @JoinColumn(name = "DEFAULT_WORKFLOW") - private Workflow defaultWorkflow; + private WorkflowTemplate defaultWorkflow; public String getContentItemClass() { return contentItemClass; @@ -160,19 +160,19 @@ public class ContentType extends CcmObject implements Serializable { this.mode = mode; } - public Lifecycle getDefaultLifecycle() { + public LifecycleDefinition getDefaultLifecycle() { return defaultLifecycle; } - protected void setDefaultLifecycle(final Lifecycle defaultLifecycle) { + protected void setDefaultLifecycle(final LifecycleDefinition defaultLifecycle) { this.defaultLifecycle = defaultLifecycle; } - public Workflow getDefaultWorkflow() { + public WorkflowTemplate getDefaultWorkflow() { return defaultWorkflow; } - protected void setDefaultWorkflow(final Workflow defaultWorkflow) { + protected void setDefaultWorkflow(final WorkflowTemplate defaultWorkflow) { this.defaultWorkflow = defaultWorkflow; } diff --git a/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/h2/V7_0_0_1__fix_content_types_constraints.sql b/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/h2/V7_0_0_1__fix_content_types_constraints.sql new file mode 100644 index 000000000..1f9798ba6 --- /dev/null +++ b/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/h2/V7_0_0_1__fix_content_types_constraints.sql @@ -0,0 +1,15 @@ +alter table CCM_CMS.CONTENT_TYPES + drop constraint FKoqvcvktnvt4ncx5k6daqat4u8; + +alter table CCM_CMS.CONTENT_TYPES + drop constraint FKpgeccqsr50xwb268ypmfx0r66; + +alter table CCM_CMS.CONTENT_TYPES + add constraint FK8s83we1tuh9r3j57dyos69wfa + foreign key (DEFAULT_LIFECYCLE_ID) + references CCM_CMS.LIFECYLE_DEFINITIONS; + +alter table CCM_CMS.CONTENT_TYPES + add constraint FKhnu9oikw8rpf22lt5fmk41t7k + foreign key (DEFAULT_WORKFLOW) + references CCM_CORE.WORKFLOW_TEMPLATES; diff --git a/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/pgsql/V7_0_0_1__fix_content_types_constraints.sql b/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/pgsql/V7_0_0_1__fix_content_types_constraints.sql new file mode 100644 index 000000000..de4e04d5e --- /dev/null +++ b/ccm-cms/src/main/resources/db/migrations/org/librecms/ccm_cms/pgsql/V7_0_0_1__fix_content_types_constraints.sql @@ -0,0 +1,16 @@ +alter table CCM_CMS.CONTENT_TYPES + drop constraint FKoqvcvktnvt4ncx5k6daqat4u8; + +alter table CCM_CMS.CONTENT_TYPES + drop constraint FKpgeccqsr50xwb268ypmfx0r66; + +alter table CCM_CMS.CONTENT_TYPES + add constraint FK8s83we1tuh9r3j57dyos69wfa + foreign key (DEFAULT_LIFECYCLE_ID) + references CCM_CMS.LIFECYLE_DEFINITIONS; + +alter table CCM_CMS.CONTENT_TYPES + add constraint FKhnu9oikw8rpf22lt5fmk41t7k + foreign key (DEFAULT_WORKFLOW) + references CCM_CORE.WORKFLOW_TEMPLATES; + diff --git a/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources.properties b/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources.properties new file mode 100644 index 000000000..940caea92 --- /dev/null +++ b/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources.properties @@ -0,0 +1,3 @@ + +application_title=Content Center +application_desc=Content Center application diff --git a/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources_de.properties b/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources_de.properties new file mode 100644 index 000000000..940caea92 --- /dev/null +++ b/ccm-cms/src/main/resources/org/librecms/contentcenter/ContentCenterResources_de.properties @@ -0,0 +1,3 @@ + +application_title=Content Center +application_desc=Content Center application diff --git a/ccm-cms/src/test/java/org/librecms/contentsection/EqualsAndHashCodeTest.java b/ccm-cms/src/test/java/org/librecms/contentsection/EqualsAndHashCodeTest.java index a03eb9a5e..e63457677 100644 --- a/ccm-cms/src/test/java/org/librecms/contentsection/EqualsAndHashCodeTest.java +++ b/ccm-cms/src/test/java/org/librecms/contentsection/EqualsAndHashCodeTest.java @@ -31,7 +31,9 @@ import org.libreccm.tests.categories.UnitTest; import org.libreccm.testutils.EqualsVerifier; import org.libreccm.web.CcmApplication; import org.libreccm.workflow.Workflow; +import org.libreccm.workflow.WorkflowTemplate; import org.librecms.lifecycle.Lifecycle; +import org.librecms.lifecycle.LifecycleDefinition; import java.util.Arrays; import java.util.Collection; @@ -137,17 +139,17 @@ public class EqualsAndHashCodeTest extends EqualsVerifier { final Resource resource2 = new Resource(); resource2.setDisplayName("Resource 2"); - final Lifecycle lifecycle1 = new Lifecycle(); - lifecycle1.setFinished(true); + final LifecycleDefinition lifecycleDef1 = new LifecycleDefinition(); + lifecycleDef1.setDefinitionId(-100); - final Lifecycle lifecycle2 = new Lifecycle(); - lifecycle2.setFinished(false); + final LifecycleDefinition lifecycleDef2 = new LifecycleDefinition(); + lifecycleDef2.setDefinitionId(-110); - final Workflow workflow1 = new Workflow(); - workflow1.setWorkflowId(-100); + final WorkflowTemplate workflowTemplate1 = new WorkflowTemplate(); + workflowTemplate1.setWorkflowId(-200); - final Workflow workflow2 = new Workflow(); - workflow2.setWorkflowId(-200); + final WorkflowTemplate workflowTemplate2 = new WorkflowTemplate(); + workflowTemplate2.setWorkflowId(-210); verifier .withPrefabValues(ContentItem.class, item1, item2) @@ -161,8 +163,12 @@ public class EqualsAndHashCodeTest extends EqualsVerifier { .withPrefabValues(CcmApplication.class, application1, application2) .withPrefabValues(Domain.class, domain1, domain2) .withPrefabValues(Resource.class, resource1, resource2) - .withPrefabValues(Lifecycle.class, lifecycle1, lifecycle2) - .withPrefabValues(Workflow.class, workflow1, workflow2); + .withPrefabValues(LifecycleDefinition.class, + lifecycleDef1, + lifecycleDef2) + .withPrefabValues(WorkflowTemplate.class, + workflowTemplate1, + workflowTemplate2); } /** diff --git a/ccm-core/pom.xml b/ccm-core/pom.xml index 23c889459..1dde82744 100644 --- a/ccm-core/pom.xml +++ b/ccm-core/pom.xml @@ -186,6 +186,11 @@ h2 test + + + org.primefaces + primefaces + diff --git a/ccm-core/src/main/java/com/arsdigita/bebop/Embedded.java b/ccm-core/src/main/java/com/arsdigita/bebop/Embedded.java new file mode 100644 index 000000000..83b6eaee6 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/bebop/Embedded.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2014 Peter Boy, University of Bremen. 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.bebop; + +import static com.arsdigita.bebop.Component.BEBOP_XML_NS; +import com.arsdigita.bebop.event.PrintEvent; +import com.arsdigita.bebop.event.PrintListener; +import com.arsdigita.xml.Element; + +/** + * Injects arbitrary content as a String into the xml output. It is not for + * any semantic type of data und it is not localizable. Specifically it is + * meant for data as Javascript and alike. + * + * It generates some fixed string to be included in the XML output. + * + * It resembles the Label methods for String parameters and currently + * generates the same XML attributes in order to avoid any need to modify the + * themes. + * + * @author pb + */ +public class Embedded extends SimpleComponent { + + private final String m_content; + /** The setting for output escaping affects how markup in the + * content is handled. + *
  • If output escaping is in effect (true), <b>example</b> + * will appear literally.
  • + *
  • If output escaping is disabled, <b>example</b> appears as the + * String "example" in bold (i.e. retaining the markup.
+ * Default is false. */ + private boolean m_escaping = false; // default for a primitive + private PrintListener m_printListener; + + /** + * Default constructor creates a new Embedded with the empty + * content. + * + * @param content + */ + public Embedded() { + m_content = ""; + } + + /** + * Constructor creates a new Embedded with the specified + * (fixed) content. + * + * @param content + */ + public Embedded(String content) { + m_content = content; + } + + /** + * Constructor creates a new Embedded with the specified + * content and output escaping turned on if escaping is + * true. + * + * The setting for output escaping affects how markup in the + * content is handled. For example:
  • If output escaping + * is in effect, <b>content</b> will appear literally.
  • If + * output escaping is disabled, <b>content</b> appears as the String + * "context" in bold.
+ * + * @param content the content to inject into the output. + * @param escaping true if output escaping will be in effect; + * false if output escaping will be disabled + */ + public Embedded(String content, boolean escaping) { + m_content = content; + m_escaping = escaping; + } + + /** + * Generates the (J)DOM fragment for a embedded. + *

+     * <bebop:link href="..." type="..." %bebopAttr;/>
+     * 
+ * + * @param state The current {@link PageState}. + * @param parent The XML element to attach the XML to. + */ + @Override + public void generateXML(PageState state, Element parent) { + + if (!isVisible(state)) { + return; + } + + Embedded target = firePrintEvent(state); + + Element content = parent.newChildElement("bebop:label", BEBOP_XML_NS); + target.exportAttributes(content); + + if (!target.m_escaping) { + content.addAttribute("escape", "yes"); + } else { + content.addAttribute("escape", "no"); + } + + content.setText(m_content); + } + + /** + * + * @param state + * @return + */ + protected Embedded firePrintEvent(PageState state) { + Embedded e = this; + + if (m_printListener != null) { + try { + e = (Embedded) this.clone(); + m_printListener.prepare(new PrintEvent(this, state, e)); + } catch (CloneNotSupportedException nse) { + throw new RuntimeException( + "Couldn't clone Embedded for PrintListener. " + + "This probably indicates a serious programming error: " + + nse.getMessage()); + } + } + + return e; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/web/CCMDispatcherServlet.java b/ccm-core/src/main/java/com/arsdigita/web/CCMDispatcherServlet.java index 6a9f78610..0a30f6bcc 100644 --- a/ccm-core/src/main/java/com/arsdigita/web/CCMDispatcherServlet.java +++ b/ccm-core/src/main/java/com/arsdigita/web/CCMDispatcherServlet.java @@ -437,7 +437,7 @@ public class CCMDispatcherServlet extends BaseServlet { m_typeURI = servletAnnotation.urlPatterns()[0]; } } else { - m_typeURI = ""; + m_typeURI = appType.servletPath(); } } diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationCreator.java b/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationCreator.java new file mode 100644 index 000000000..398bcd20a --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationCreator.java @@ -0,0 +1,53 @@ +/* + * 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.libreccm.admin.ui; + +import org.libreccm.web.ApplicationCreator; +import org.libreccm.web.ApplicationRepository; +import org.libreccm.web.ApplicationType; +import org.libreccm.web.CcmApplication; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class AdminJsfApplicationCreator implements ApplicationCreator{ + + @Inject + private ApplicationRepository appRepo; + + @Override + public CcmApplication createInstance(final String primaryUrl, + final ApplicationType type) { + if ("org.libreccm.ui.admin.AdminFaces".equals(primaryUrl)) { + throw new IllegalArgumentException( + "CCM Admin Faces is a singleton application" + + "which is mounted at /admin-jsf"); + } + + return appRepo.retrieveApplicationForPath(primaryUrl); + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationSetup.java b/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationSetup.java new file mode 100644 index 000000000..1b47adb49 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/admin/ui/AdminJsfApplicationSetup.java @@ -0,0 +1,51 @@ +/* + * 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.libreccm.admin.ui; + +import org.libreccm.modules.InstallEvent; +import org.libreccm.web.AbstractCcmApplicationSetup; +import org.libreccm.web.CcmApplication; + +import java.util.UUID; + +/** + * + * @author Jens Pelzetter + */ +public class AdminJsfApplicationSetup extends AbstractCcmApplicationSetup { + + public static final String ADMIN_APP_NAME = "CcmAdminJsf"; + + public AdminJsfApplicationSetup(final InstallEvent event) { + super(event); + } + + @Override + public void setup() { + final CcmApplication admin = new CcmApplication(); + admin.setUuid(UUID.randomUUID().toString()); + admin.setApplicationType("org.libreccm.ui.admin.AdminFaces"); + admin.setPrimaryUrl("/admin-jsf/"); + + getEntityManager().persist(admin); + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/auditing/AbstractAuditedEntityRepository.java b/ccm-core/src/main/java/org/libreccm/auditing/AbstractAuditedEntityRepository.java index 0846703f0..80ffeb184 100644 --- a/ccm-core/src/main/java/org/libreccm/auditing/AbstractAuditedEntityRepository.java +++ b/ccm-core/src/main/java/org/libreccm/auditing/AbstractAuditedEntityRepository.java @@ -30,7 +30,7 @@ import java.util.List; /** * * @author Jens Pelzetter - * @author Tobias Osmers * @param Primary key of the entity. * @param Type of the entity */ @@ -42,6 +42,7 @@ public abstract class AbstractAuditedEntityRepository public abstract K getEntityId(final T entity); + @SuppressWarnings("unchecked") public T retrieveRevisionOfEntity(final T entity, final Number revision) { final AuditQuery query = auditReader.createQuery() .forEntitiesAtRevision(getEntityClass(), revision); diff --git a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java index 41c8d0006..071e183df 100644 --- a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java +++ b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java @@ -35,6 +35,8 @@ import javax.persistence.EntityManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.libreccm.admin.ui.AdminJsfApplicationCreator; +import org.libreccm.admin.ui.AdminJsfApplicationSetup; import org.libreccm.modules.CcmModule; import org.libreccm.modules.InitEvent; @@ -60,7 +62,12 @@ import org.libreccm.web.ApplicationType; descBundle = "com.arsdigita.ui.admin.AdminResources", singleton = true, creator = AdminApplicationCreator.class, - servlet = AdminServlet.class)}, + servlet = AdminServlet.class), + @ApplicationType(name = "org.libreccm.ui.admin.AdminFaces", + descBundle = "com.arsdigita.ui.admin.AdminResources", + singleton = true, + creator = AdminJsfApplicationCreator.class, + servletPath = "/admin-jsf/admin.xhtml")}, configurations = { com.arsdigita.bebop.BebopConfig.class, com.arsdigita.dispatcher.DispatcherConfig.class, @@ -94,6 +101,11 @@ public class CcmCore implements CcmModule { = new AdminApplicationSetup(event); adminSetup.setup(); + LOGGER.info("Setting up admin-jsf application (/ccm/admin-jsf/)..."); + final AdminJsfApplicationSetup adminJsfSetup + = new AdminJsfApplicationSetup(event); + adminJsfSetup.setup(); + LOGGER.info("Setting up login application..."); final LoginApplicationSetup loginSetup = new LoginApplicationSetup(event); diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/AuthorizationListener.java b/ccm-core/src/main/java/org/libreccm/ui/admin/AuthorizationListener.java new file mode 100644 index 000000000..b603db11b --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/AuthorizationListener.java @@ -0,0 +1,129 @@ +/* + * 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.libreccm.ui.admin; + +import com.arsdigita.web.BaseServlet; +import com.arsdigita.web.CCMDispatcherServlet; +import com.arsdigita.web.WebConfig; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.net.URLCodec; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.enterprise.context.RequestScoped; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.event.ComponentSystemEvent; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.shiro.subject.Subject; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.security.PermissionChecker; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named +public class AuthorizationListener { + + private static final Logger LOGGER = LogManager.getLogger( + AuthorizationListener.class); + + @Inject + private Subject subject; + + @Inject + private HttpServletRequest request; + +// @Inject +// private HttpServletResponse response; + @Inject + private PermissionChecker permissionChecker; + + @Inject + private ConfigurationManager confManager; + + public void isPermitted(final ComponentSystemEvent event) { + if (!subject.isAuthenticated()) { + redirectToLogin(); + return; + } + + final String requiredPrivilege = (String) event.getComponent(). + getAttributes().get("requiredPrivilege"); + + if (!permissionChecker.isPermitted(requiredPrivilege)) { + try { + final FacesContext facesContext = FacesContext. + getCurrentInstance(); + final ExternalContext externalContext = facesContext. + getExternalContext(); + final HttpServletResponse response + = (HttpServletResponse) externalContext + .getResponse(); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } catch (IOException ex) { + LOGGER.error("Failed to send FORBIDDEN error to client.", ex); + throw new RuntimeException( + "Failed to send FORBIDDEN error to client", ex); + } + } + + } + + private void redirectToLogin() { + try { + final FacesContext facesContext = FacesContext.getCurrentInstance(); + final ExternalContext externalContext = facesContext. + getExternalContext(); + final HttpServletResponse response + = (HttpServletResponse) externalContext + .getResponse(); + final WebConfig webConfig = confManager.findConfiguration( + WebConfig.class); + final URLCodec urlCodec = new URLCodec("utf-8"); + response.sendRedirect(new URI(String.format( + "%s://%s:%d%s%s/register/?return_url=%s", + request.getScheme(), + request.getServerName(), + request.getLocalPort(), + CCMDispatcherServlet.getContextPath(), + webConfig.getDispatcherServletPath(), + urlCodec.encode(request.getAttribute( + BaseServlet.REQUEST_URL_ATTRIBUTE).toString()))) + .toString()); + } catch (IOException | + URISyntaxException | + EncoderException ex) { + LOGGER.error("Failed to redirect to login.", ex); + throw new RuntimeException("Failed to redirect to login.", ex); + } + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ContentType.java b/ccm-core/src/main/java/org/libreccm/ui/admin/ConfProperty.java similarity index 70% rename from ccm-cms/src/main/java/org/librecms/ContentType.java rename to ccm-core/src/main/java/org/libreccm/ui/admin/ConfProperty.java index 265b88f7d..745e77962 100644 --- a/ccm-cms/src/main/java/org/librecms/ContentType.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/ConfProperty.java @@ -16,18 +16,28 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ -package org.librecms; - -import org.librecms.contentsection.ContentItem; +package org.libreccm.ui.admin; /** - * 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 - +public class ConfProperty { + + private final String name; + private final String value; + + public ConfProperty(final String name, final String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/ConfigurationController.java b/ccm-core/src/main/java/org/libreccm/ui/admin/ConfigurationController.java new file mode 100644 index 000000000..0d88ac81b --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/ConfigurationController.java @@ -0,0 +1,131 @@ +/* + * 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.libreccm.ui.admin; + +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Named; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named +public class ConfigurationController { + + public List getSystemInformation() { + final Properties properties = new Properties(); + + try (final InputStream stream = getClass().getResourceAsStream( + "systeminformation.properties")) { + if (stream == null) { + properties.put("version", ""); + properties.put("appname", "LibreCCM"); + properties.put("apphomepage", "http://www.libreccm.org"); + } else { + properties.load(stream); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + final List sysInfo = new ArrayList<>(); + properties.stringPropertyNames().forEach(propName -> sysInfo.add( + new ConfProperty(propName, + properties.getProperty(propName)))); + return sysInfo; + } + + public List getJavaSystemProperties() { + final Properties systemProperties = System.getProperties(); + final List javaSysProps = new ArrayList<>(); + systemProperties.stringPropertyNames().forEach(propName -> javaSysProps + .add(new ConfProperty(propName, systemProperties.getProperty( + propName)))); + return javaSysProps; + } + + public List getXmlConfig() { + final List xmlProps = new ArrayList<>(); + + final ResourceBundle texts = ResourceBundle.getBundle( + "com.arsdigita.ui.admin.AdminResources"); + + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_transformer_factory"), + TransformerFactory.newInstance().getClass().getName())); + try { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_transformer"), + TransformerFactory.newInstance().newTransformer().getClass() + .getName())); + } catch (TransformerConfigurationException ex) { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_transformer"), "???")); + } + + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_document_builder_factory"), + DocumentBuilderFactory.newInstance().getClass().getName())); + + try { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_document_builder"), + DocumentBuilderFactory.newInstance().newDocumentBuilder() + .getClass().getName())); + } catch (ParserConfigurationException ex) { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.xml_document_builder"), + "???")); + } + + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.sax_parser_factory"), + SAXParserFactory.newInstance().getClass().getName())); + + try { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.sax_parser"), + SAXParserFactory.newInstance().newSAXParser().getClass() + .getName())); + } catch (ParserConfigurationException | SAXException ex) { + xmlProps.add(new ConfProperty( + texts.getString("ui.admin.sysinfo.sax_parser"), "???")); + } + + return xmlProps; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/UserContextController.java b/ccm-core/src/main/java/org/libreccm/ui/admin/UserContextController.java new file mode 100644 index 000000000..8627e84ee --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/UserContextController.java @@ -0,0 +1,66 @@ +/* + * 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.libreccm.ui.admin; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.inject.Named; +import org.apache.shiro.subject.Subject; +import org.libreccm.security.Shiro; +import org.libreccm.security.User; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named +public class UserContextController { + + @Inject + private Shiro shiro; + + @Inject + private Subject subject; + + public boolean isLoggedIn() { + return subject.isAuthenticated(); + } + + public String getCurrentUserName() { + final User user = shiro.getUser(); + + if (user == null) { + return ""; + } else { + return String.format("%s %s", + user.getGivenName(), + user.getFamilyName()); + } + } + + public void changePassword() { + + } + + public void logout() { + subject.logout(); + } + +} diff --git a/ccm-core/src/main/resources/META-INF/resources/admin-jsf/admin.xhtml b/ccm-core/src/main/resources/META-INF/resources/admin-jsf/admin.xhtml new file mode 100644 index 000000000..7d3112e73 --- /dev/null +++ b/ccm-core/src/main/resources/META-INF/resources/admin-jsf/admin.xhtml @@ -0,0 +1,124 @@ + + + + + + + LibreCCM Admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ccm-core/src/main/resources/META-INF/resources/admin-jsf/header.css b/ccm-core/src/main/resources/META-INF/resources/admin-jsf/header.css new file mode 100644 index 000000000..bf7075fe4 --- /dev/null +++ b/ccm-core/src/main/resources/META-INF/resources/admin-jsf/header.css @@ -0,0 +1,33 @@ +body { + margin: 0; +} + +div#header { + background-color: #56a1bd; + + background-image: -moz-linear-gradient(top, #56a1bd 5%, #024C68 95%); + background-image: -webkit-linear-gradient(top, #56a1bd 5%, #024C68 95%); + background-image: linear-gradient(top, #56a1bd 5%, #024C68 95%); + + display: flex; + + height: 70px; + + padding: 0 10px; + + position: relative; +} + +#logo, #user-widget { + flex: 1; +} + +#user-widget { + position: absolute; + top: 15px; + right: 10px; +} + +.ui-widget, .ui-widget .ui-widget { + font-size: 90% !important; +} \ No newline at end of file diff --git a/ccm-core/src/main/resources/META-INF/resources/admin-jsf/libreccm.png b/ccm-core/src/main/resources/META-INF/resources/admin-jsf/libreccm.png new file mode 100644 index 0000000000000000000000000000000000000000..93fd7a317e34e7d5e63b326c1c51321317c43781 GIT binary patch literal 22448 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 literal 0 HcmV?d00001 diff --git a/ccm-core/src/main/resources/META-INF/resources/test.xhtml b/ccm-core/src/main/resources/META-INF/resources/test.xhtml new file mode 100644 index 000000000..b64fcda97 --- /dev/null +++ b/ccm-core/src/main/resources/META-INF/resources/test.xhtml @@ -0,0 +1,16 @@ + + + + + JSF Test + + + + + + + + + + diff --git a/ccm-core/src/main/resources/META-INF/test.xhtml b/ccm-core/src/main/resources/META-INF/test.xhtml new file mode 100644 index 000000000..cdfa0c7ba --- /dev/null +++ b/ccm-core/src/main/resources/META-INF/test.xhtml @@ -0,0 +1,16 @@ + + + + + JSF Test + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 3dacd7b2a..788bc74c9 100644 --- a/pom.xml +++ b/pom.xml @@ -291,6 +291,12 @@ 1.2.5 + + + org.primefaces + primefaces + 6.0 +