From 31884ea9e37029e5a76f2ad695543a38beaf26c6 Mon Sep 17 00:00:00 2001 From: jensp Date: Tue, 20 Dec 2016 18:37:35 +0000 Subject: [PATCH] CCM NG: Current progress of porting the ItemSearch forms to NG git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4495 8810af33-2d31-482b-a856-94f89814c4df --- ccm-cms-types-agenda/pom.xml | 8 +- ccm-cms-types-bookmark/pom.xml | 6 + ccm-cms-types-decisiontree/pom.xml | 6 + ccm-cms-types-externallink/pom.xml | 6 + ccm-cms-types-faqitem/pom.xml | 6 + ccm-cms-types-glossaryitem/pom.xml | 6 + ccm-cms-types-minutes/pom.xml | 6 + .../com/arsdigita/cms/ui/ItemSearch.java.todo | 43 +- .../cms/ui/ItemSearchBrowsePane.java.todo | 395 ++++++++++++++ .../cms/ui/ItemSearchFolderBrowser.java.todo | 494 ++++++++++++++++++ .../arsdigita/cms/ui/ItemSearchPage.java.todo | 5 +- .../arsdigita/cms/ui/ItemSearchSection.java | 202 +++++++ .../cms/ui/search/ItemQueryComponent.java | 145 +++++ .../com/arsdigita/search/SearchConstants.java | 40 ++ .../search/ui/BaseQueryComponent.java | 148 ++++++ .../arsdigita/search/ui/QueryComponent.java | 85 +++ .../arsdigita/search/ui/QueryGenerator.java | 51 ++ .../com/arsdigita/search/ui/ResultsPane.java | 381 ++++++++++++++ 18 files changed, 2007 insertions(+), 26 deletions(-) create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchBrowsePane.java.todo create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchFolderBrowser.java.todo create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchSection.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/cms/ui/search/ItemQueryComponent.java create mode 100644 ccm-core/src/main/java/com/arsdigita/search/SearchConstants.java create mode 100755 ccm-core/src/main/java/com/arsdigita/search/ui/BaseQueryComponent.java create mode 100755 ccm-core/src/main/java/com/arsdigita/search/ui/QueryComponent.java create mode 100755 ccm-core/src/main/java/com/arsdigita/search/ui/QueryGenerator.java create mode 100755 ccm-core/src/main/java/com/arsdigita/search/ui/ResultsPane.java diff --git a/ccm-cms-types-agenda/pom.xml b/ccm-cms-types-agenda/pom.xml index ecd835899..45cf503d1 100644 --- a/ccm-cms-types-agenda/pom.xml +++ b/ccm-cms-types-agenda/pom.xml @@ -55,7 +55,13 @@ hibernate-envers provided - + + + org.hibernate + hibernate-search-orm + provided + + org.hibernate hibernate-validator diff --git a/ccm-cms-types-bookmark/pom.xml b/ccm-cms-types-bookmark/pom.xml index 6f15bec6e..f98e74c17 100644 --- a/ccm-cms-types-bookmark/pom.xml +++ b/ccm-cms-types-bookmark/pom.xml @@ -55,6 +55,12 @@ hibernate-envers provided + + + org.hibernate + hibernate-search-orm + provided + org.hibernate diff --git a/ccm-cms-types-decisiontree/pom.xml b/ccm-cms-types-decisiontree/pom.xml index c12b09ea2..b13caecf0 100644 --- a/ccm-cms-types-decisiontree/pom.xml +++ b/ccm-cms-types-decisiontree/pom.xml @@ -55,6 +55,12 @@ hibernate-envers provided + + + org.hibernate + hibernate-search-orm + provided + org.hibernate diff --git a/ccm-cms-types-externallink/pom.xml b/ccm-cms-types-externallink/pom.xml index aa66d0440..b1e050eb1 100644 --- a/ccm-cms-types-externallink/pom.xml +++ b/ccm-cms-types-externallink/pom.xml @@ -55,6 +55,12 @@ hibernate-envers provided + + + org.hibernate + hibernate-search-orm + provided + org.hibernate diff --git a/ccm-cms-types-faqitem/pom.xml b/ccm-cms-types-faqitem/pom.xml index c6a76be3e..ad4cbbc23 100644 --- a/ccm-cms-types-faqitem/pom.xml +++ b/ccm-cms-types-faqitem/pom.xml @@ -55,6 +55,12 @@ hibernate-envers provided + + + org.hibernate + hibernate-search-orm + provided + org.hibernate diff --git a/ccm-cms-types-glossaryitem/pom.xml b/ccm-cms-types-glossaryitem/pom.xml index af9871778..a75b4aee9 100644 --- a/ccm-cms-types-glossaryitem/pom.xml +++ b/ccm-cms-types-glossaryitem/pom.xml @@ -56,6 +56,12 @@ provided + + org.hibernate + hibernate-search-orm + provided + + org.hibernate hibernate-validator diff --git a/ccm-cms-types-minutes/pom.xml b/ccm-cms-types-minutes/pom.xml index 361ccfb38..65ae35bbf 100644 --- a/ccm-cms-types-minutes/pom.xml +++ b/ccm-cms-types-minutes/pom.xml @@ -55,6 +55,12 @@ hibernate-envers provided + + + org.hibernate + hibernate-search-orm + provided + org.hibernate diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.todo index 7f563c90d..633629e17 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.todo +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearch.java.todo @@ -22,30 +22,29 @@ 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; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** - * A wrapper around the {@link ItemSearchSection} which embedds - * the form section in a form. + * A wrapper around the {@link ItemSearchSection} which embeds the form section + * in a form. * * @author Stanislav Freidin (sfreidin@arsdigita.com) - * @version $Id: ItemSearch.java 1940 2009-05-29 07:15:05Z terry $ + * @author Jens Pelzetter */ -public class ItemSearch extends Form implements Resettable, QueryGenerator { +public class ItemSearch extends Form implements Resettable { - private static final org.apache.log4j.Logger s_log = - org.apache.log4j.Logger.getLogger(ItemSearch.class); + private static final Logger LOGGER = LogManager.getLogger(ItemSearch.class); public static final String SINGLE_TYPE_PARAM = ItemSearchSection.SINGLE_TYPE_PARAM; - private ItemSearchSection m_section; + + private ItemSearchSection itemSearchSection; /** - * Construct a new ItemSearch component - * Default to limit the search to current content 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} + * {@link ContentItem#DRAFT} or {@link ContentItem#LIVE} */ public ItemSearch(String context) { this(context, true); @@ -55,32 +54,34 @@ public class ItemSearch extends Form implements Resettable, QueryGenerator { * 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 + * {@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); + itemSearchSection = createSearchSection(context, limitToContentSection); + add(itemSearchSection); } - protected ItemSearchSection createSearchSection(String context, boolean limitToContentSection) { + protected ItemSearchSection createSearchSection(String context, + boolean limitToContentSection) { return new ItemSearchSection(context, limitToContentSection); } @Override public boolean hasQuery(PageState state) { - return m_section.hasQuery(state); + return itemSearchSection.hasQuery(state); } @Override public QuerySpecification getQuerySpecification(PageState state) { - return m_section.getQuerySpecification(state); + return itemSearchSection.getQuerySpecification(state); } @Override public void reset(PageState state) { - m_section.reset(state); + itemSearchSection.reset(state); } } diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchBrowsePane.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchBrowsePane.java.todo new file mode 100755 index 000000000..bfff7fdfb --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchBrowsePane.java.todo @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms.ui; + +import com.arsdigita.bebop.BoxPanel; +import com.arsdigita.bebop.Form; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.Resettable; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.bebop.SingleSelectionModel; +import com.arsdigita.bebop.Tree; +import com.arsdigita.bebop.event.ActionEvent; +import com.arsdigita.bebop.event.ActionListener; +import com.arsdigita.bebop.event.ChangeEvent; +import com.arsdigita.bebop.event.ChangeListener; +import com.arsdigita.bebop.event.FormInitListener; +import com.arsdigita.bebop.event.FormProcessListener; +import com.arsdigita.bebop.event.FormSectionEvent; +import com.arsdigita.bebop.event.FormSubmissionListener; +import com.arsdigita.bebop.event.TreeExpansionEvent; +import com.arsdigita.bebop.event.TreeExpansionListener; +import com.arsdigita.bebop.form.Option; +import com.arsdigita.bebop.form.SingleSelect; +import com.arsdigita.bebop.form.Submit; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.cms.dispatcher.Utilities; + +import com.arsdigita.cms.CMS; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.Folder; +import com.arsdigita.cms.ui.authoring.NewItemForm; +import com.arsdigita.cms.ui.folder.FolderRequestLocal; +import com.arsdigita.cms.ui.folder.FolderSelectionModel; +import com.arsdigita.cms.ui.folder.FolderTreeModelBuilder; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.toolbox.ui.LayoutPanel; +import com.arsdigita.util.Assert; + +import java.math.BigDecimal; +import org.apache.logging.log4j.LogManager; + +import org.apache.logging.log4j.Logger; +import org.arsdigita.cms.CMSConfig; +import org.librecms.CmsConstants; +import org.librecms.contentsection.ContentSectionConfig; + +/** + * A pane that contains a folder tree on the left and a folder manipulator on + * the right. + * + * @author David LutterKort <dlutter@redhat.com> + * @author Jens Pelzetter + */ +public class ItemSearchBrowsePane extends SimpleContainer implements Resettable, + TreeExpansionListener, + ChangeListener, + FormProcessListener, + FormSubmissionListener { + + private static final String CONTENT_TYPE_ID = "ct"; + private static final Logger LOGGER = LogManager.getLogger( + ItemSearchBrowsePane.class); + private final FolderSelectionModel folderSelectionModel; + private final FolderRequestLocal folderRequestLocal; + private final Tree tree; + private ItemSearchFolderBrowser folderBrowser; + private SingleSelect sectionSelect; + private SingleSelectionModel typeSelectionModel; + + public ItemSearchBrowsePane() { + + final LayoutPanel mainPanel = new LayoutPanel(); + + setClassAttr("sidebarNavPanel"); + setAttribute("navbar-title", + new GlobalizedMessage("cms.ui.folder_browser", + CmsConstants.CMS_BUNDLE) + .localize().toString()); + + final BoxPanel left = new BoxPanel(BoxPanel.VERTICAL); + + final Label label = new Label(new GlobalizedMessage( + "cms.ui.folder_browser", CmsConstants.CMS_BUNDLE)); + label.setClassAttr("heading"); + left.add(label); + + // As described in ticket 20540, some clients do not want the option to pick items from other + // subsites through the ItemSearchBrowsePane. A new parameter has been added to allow the + // administrator to pick between the old and new versions. + boolean linksOnlyInSameSubsite = CMSConfig.getConfig() + .isLinksOnlyInSameSubsite(); + LOGGER.debug("linksOnlyInSameSubsite value is {}", + linksOnlyInSameSubsite); + + tree = new Tree(new FolderTreeModelBuilder() { + @Override + protected Folder getRoot(PageState ps) { + Folder root = getRootFolder(ps); + + if (null == root) { + return super.getRoot(ps); + } + return root; + } + + }); + folderSelectionModel = createFolderSelectionModel(); + folderSelectionModel.addChangeListener(this); + folderRequestLocal = new FolderRequestLocal(folderSelectionModel); + + if (!linksOnlyInSameSubsite) { + // The client should be able to pick between the subsites + Form sectionForm = getSectionForm(); + add(sectionForm); + } + + tree.setSelectionModel(folderSelectionModel); + + tree.setClassAttr("navbar"); + tree.addTreeExpansionListener(this); + left.add(tree); + +// CMSContainer container = new CMSContainer(); + left.setClassAttr("main"); + + final BoxPanel body = new BoxPanel(BoxPanel.VERTICAL); + folderBrowser = new ItemSearchFolderBrowser(folderSelectionModel); + body.add(folderBrowser); + body.add(folderBrowser.getPaginator()); + +// m_newItem = new SectionNewItemForm("newItem"); +// m_typeSel = new ParameterSingleSelectionModel(new BigDecimalParameter(CONTENT_TYPE_ID)); +// m_newItem.addProcessListener(this); +// +// container.add(m_newItem); + //add(container); + mainPanel.setLeft(left); + mainPanel.setBody(body); + add(mainPanel); + } + + @Override + public boolean isVisible(PageState s) { + // Always expand root node + if (tree.isCollapsed(getRootFolder(s).getID().toString(), s)) { + tree.expand(getRootFolder(s).getID().toString(), s); + } + + return super.isVisible(s); + } + + private Form getSectionForm() { + Form sectionForm = new Form("isfbSectionForm", + new BoxPanel(BoxPanel.HORIZONTAL)); + sectionForm.setClassAttr("navbar"); + + sectionSelect = new SingleSelect(new OIDParameter("isfbSection")); + ContentSectionCollection sections = ContentSection.getAllSections(); + while (sections.next()) { + ContentSection section = sections.getContentSection(); + sectionSelect.addOption(new Option(section.getOID().toString(), + section.getDisplayName())); + } + + sectionForm.addInitListener(new FormInitListener() { + @Override + public void init(FormSectionEvent ev) { + PageState ps = ev.getPageState(); + + if (null == sectionSelect.getValue(ps)) { + ContentSection section = CMS.getContext(). + getContentSection(); + sectionSelect.setValue(ps, section.getOID()); + } + } + + }); + + sectionForm.add(sectionSelect); + sectionForm.add(new Submit("Change Section")); + + return sectionForm; + } + + private Folder getRootFolder(PageState ps) { + LOGGER.debug("Getting the root folder."); + if (sectionSelect != null) { + // We have more than one subsite to choose between + OID sectionOID = (OID) sectionSelect.getValue(ps); + if (LOGGER.isDebugEnabled()) { + if (null != sectionOID) { + LOGGER.debug("Using section " + sectionOID.toString()); + } else { + LOGGER.debug("Using default section"); + } + } + + if (null == sectionOID) { + return null; + } + + ContentSection section = (ContentSection) DomainObjectFactory. + newInstance(sectionOID); + + return section.getRootFolder(); + } else { + return null; + } + } + + @Override + public void register(Page p) { + super.register(p); + p.addComponentStateParam(this, folderSelectionModel.getStateParameter()); + + // Only add the SingleSelect item if it exists + if (sectionSelect != null) { + p.addComponentStateParam(this, sectionSelect.getParameterModel()); + } + + // Save the state of the new item component +// p.addComponentStateParam(this, m_typeSel.getStateParameter()); + p.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final PageState state = e.getPageState(); + + if (state.isVisibleOnPage(ItemSearchBrowsePane.this)) { + showHideSegments(state); + } + } + + }); + } + + /** + * Show/hide segments based on access checks. + * + * @param state The page state + * @pre ( state != null ) + */ + private void showHideSegments(PageState state) { + SecurityManager sm = Utilities.getSecurityManager(state); + Folder folder = folderRequestLocal.getFolder(state); + Assert.exists(folder); + + // MP: This should be checked on the current folder instead of just + // the content section. +// boolean newItem = +// sm.canAccess(state.getRequest(), SecurityManager.NEW_ITEM, folder); +// +// if (!newItem) { +// browseMode(state); +// } +// m_newItem.setVisible(state, newItem); + } + + @Override + public void reset(PageState s) { + //m_browser.reset(s); + } + + public ItemSearchFolderBrowser getFolderBrowser() { + return folderBrowser; + } + + public final FolderSelectionModel getFolderSelectionModel() { + return folderSelectionModel; + } + + /** + * sets the current level of expansion of the folder tree and in the folder + * browser table + */ + protected void setSelectedFolder(PageState s, String key) { + + //set the selected folder of the folder browser + folderBrowser.getFolderSelectionModel().setSelectedKey(s, key); + + //set the selected folder of the folder tree + folderSelectionModel.setSelectedKey(s, key); + Folder current = (Folder) folderSelectionModel.getSelectedObject(s); + Folder parent = (Folder) current.getParent(); + if (parent != null) { + BigDecimal id = parent.getID(); + tree.expand(id.toString(), s); + } + } + + // Implement TreeExpansionListener + @Override + public void treeCollapsed(TreeExpansionEvent e) { + PageState s = e.getPageState(); + folderSelectionModel.setSelectedKey(s, e.getNodeKey()); + } + + @Override + public void treeExpanded(TreeExpansionEvent e) { + return; + } + + @Override + public void stateChanged(ChangeEvent e) { + PageState s = e.getPageState(); + Folder current = (Folder) folderSelectionModel.getSelectedObject(s); + Folder parent = (Folder) current.getParent(); + folderBrowser.getPaginator().reset(s); + if (parent != null) { + BigDecimal id = parent.getID(); + tree.expand(id.toString(), s); + } + //m_browser.getPermissionsPane().reset(s); + //m_browser.setPermissionLinkVis(s); + } + + @Override + public void process(FormSectionEvent e) { + PageState s = e.getPageState(); + final Object source = e.getSource(); +// if (source == m_newItem) { +// BigDecimal typeID = m_newItem.getTypeID(s); +// m_typeSel.setSelectedKey(s, typeID); +// newItemMode(s); +// } else { + browseMode(s); +// } + } + + @Override + public void submitted(FormSectionEvent e) { + PageState s = e.getPageState(); + final Object source = e.getSource(); +// if (source == m_newItem) { +// BigDecimal typeID = m_newItem.getTypeID(s); +// m_typeSel.setSelectedKey(s, typeID); +// //newItemMode(s); +// } + } + + private void browseMode(PageState s) { +// m_browseSeg.setVisible(s, true); + typeSelectionModel.clearSelection(s); + } + + private void newItemMode(PageState s) { +// m_newItemSeg.setVisible(s, true); + } + + private FolderSelectionModel createFolderSelectionModel() { + return new FolderSelectionModel("folder") { + @Override + protected BigDecimal getRootFolderID(PageState ps) { + Folder root = getRootFolder(ps); + + if (null == root) { + return super.getRootFolderID(ps); + } + return root.getID(); + } + + }; + } + + private static class SectionNewItemForm extends NewItemForm { + + public SectionNewItemForm(String name) { + super(name); + } + + @Override + public ContentSection getContentSection(PageState s) { + return CMS.getContext().getContentSection(); + } + + } +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchFolderBrowser.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchFolderBrowser.java.todo new file mode 100755 index 000000000..948483f55 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchFolderBrowser.java.todo @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms.ui; + +import com.arsdigita.bebop.Bebop; +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.Link; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.RequestLocal; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.PaginationModelBuilder; +import com.arsdigita.bebop.Paginator; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.event.ActionEvent; +import com.arsdigita.bebop.event.ActionListener; +import com.arsdigita.bebop.event.TableActionAdapter; +import com.arsdigita.bebop.event.TableActionEvent; +import com.arsdigita.bebop.event.TableActionListener; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.bebop.table.AbstractTableModelBuilder; +import com.arsdigita.bebop.table.DefaultTableCellRenderer; +import com.arsdigita.bebop.table.TableColumn; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.util.BebopConstants; +import com.arsdigita.cms.CMS; +import com.arsdigita.cms.ContentItem; +import com.arsdigita.cms.ContentPage; +import com.arsdigita.cms.ContentSection; +import com.arsdigita.cms.ContentType; +import com.arsdigita.cms.Folder; +import com.arsdigita.cms.SecurityManager; +import com.arsdigita.cms.dispatcher.Utilities; +import com.arsdigita.cms.ui.folder.FolderSelectionModel; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.persistence.CompoundFilter; +import com.arsdigita.persistence.FilterFactory; +import com.arsdigita.toolbox.GlobalisationUtil; +import com.arsdigita.util.Assert; + +import java.math.BigDecimal; + +/** + * Browse folders and items. If the user clicks on a folder, the folder selection model is updated. + * If the user clicks on any other item, an separate item selection model is updated. + * + * @author David Lutterkort + * @version $Revision: #9 $ $DateTime: 2004/08/17 23:15:09 $ + */ +public class ItemSearchFolderBrowser extends Table { + + private static final org.apache.log4j.Logger s_log = org.apache.log4j.Logger.getLogger( + ItemSearchFolderBrowser.class); + private static GlobalizedMessage[] s_headers = { + globalize("cms.ui.folder.name"), + globalize("cms.ui.folder.title"), + globalize("cms.ui.folder.type")}; + private FolderSelectionModel m_currentFolder; + private TableActionListener m_folderChanger; + private TableActionListener m_deleter; + private TableActionListener m_indexChanger; + private TableColumn m_nameColumn; + private Paginator m_paginator; + + public ItemSearchFolderBrowser(FolderSelectionModel currentFolder) { + super((FolderTableModelBuilder) null, s_headers); + + FolderTableModelBuilder builder = new FolderTableModelBuilder(); + setModelBuilder(builder); + + m_paginator = new Paginator(builder, ContentSection.getConfig(). + getFolderBrowseListSize()); + + m_currentFolder = currentFolder; + + setClassAttr("dataTable"); + + getHeader().setDefaultRenderer( + new com.arsdigita.cms.ui.util.DefaultTableCellRenderer()); + m_nameColumn = getColumn(0); + m_nameColumn.setCellRenderer(new NameCellRenderer()); + + m_folderChanger = new FolderChanger(); + addTableActionListener(m_folderChanger); + + setEmptyView(new Label(globalize("cms.ui.folder.no_items"))); + + Assert.exists(m_currentFolder.getStateParameter()); + } + + public Paginator getPaginator() { + return m_paginator; + } + + @Override + public void register(Page p) { + super.register(p); + p.addComponentStateParam(this, m_currentFolder.getStateParameter()); + + p.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent event) { + // MP: This action listener should only be called when the + // folder browser is visible. + showHideFolderActions(event.getPageState()); + } + + }); + } + + private Folder getCurrentFolder(PageState state) { + return (Folder) m_currentFolder.getSelectedObject(state); + } + + private void showHideFolderActions(PageState state) { + SecurityManager sm = Utilities.getSecurityManager(state); + Folder folder = getCurrentFolder(state); + Assert.exists(folder); + } + + public FolderSelectionModel getFolderSelectionModel() { + return m_currentFolder; + } + + private class FolderTableModelBuilder + extends AbstractTableModelBuilder implements PaginationModelBuilder { + + private RequestLocal m_size = new RequestLocal() { + + @Override + protected Object initialValue(PageState state) { + Folder.ItemCollection itemColl = getItemCollection(state); + + if (null == itemColl) { + return new Integer(0); + } + return new Integer((int) itemColl.size()); + } + + }; + private RequestLocal m_itemColl = new RequestLocal() { + + @Override + protected Object initialValue(PageState state) { + Folder.ItemCollection itemColl = getItemCollection(state); + + itemColl.addOrder("item.name"); + itemColl.setRange(new Integer(m_paginator.getFirst(state)), + new Integer(m_paginator.getLast(state) + 1)); + + return itemColl; + } + + }; + + public TableModel makeModel(Table t, PageState s) { + FolderSelectionModel sel = ((ItemSearchFolderBrowser) t). + getFolderSelectionModel(); + Folder f = getCurrentFolder(s); + + if (s_log.isDebugEnabled()) { + if (null == f) { + s_log.debug("Selected folder is null"); + } else { + s_log.debug("Selected folder: " + f.getLabel() + " " + f. + getOID().toString()); + } + } + + if (f == null) { + return Table.EMPTY_MODEL; + } else { + t.getRowSelectionModel().clearSelection(s); + return new FolderTableModel((Folder.ItemCollection) m_itemColl. + get(s)); + } + } + + private Folder.ItemCollection getItemCollection(PageState state) { + Folder f = getCurrentFolder(state); + Folder.ItemCollection itemColl = f.getPrimaryInstances(); + + if (null == itemColl) { + return null; + } + + BigDecimal singleTypeID = (BigDecimal) state.getValue(new BigDecimalParameter( + ItemSearch.SINGLE_TYPE_PARAM)); + + if (singleTypeID != null) { + + // The Filter Factory + FilterFactory ff = itemColl.getFilterFactory(); + + // Create an or-filter + CompoundFilter or = ff.or(); + + // The content type must be either of the requested type + or.addFilter(ff.equals(ContentItem.CONTENT_TYPE + "." + + ContentType.ID, singleTypeID)); + + // Or must be a sibling of the requested type + /* + * jensp 2011-11-14: The orginal code here was only traversing + * one level, but ContentType hierarchies may have several + * levels. Therefore, this code was replaced by method which is + * called recursivly until the type with no descendents is + * reached. + */ + createSiblingFilter(or, ff, singleTypeID); + /*try { + ContentType ct = new ContentType(singleTypeID); + + StringTokenizer strTok = new StringTokenizer(ct. + getDescendants(), "/"); + while (strTok.hasMoreElements()) { + or.addFilter(ff.equals(ContentItem.CONTENT_TYPE + "." + + ContentType.ID, + (String) strTok.nextElement())); + } + } catch (Exception ex) { + // WTF? The selected content type does not exist in the table??? + s_log.error(String.format( + "Something is very wrong here, the ContentType '%s' " + + "seems not to exist. Ignoring for now, but please " + + "make your checks.", + singleTypeID.toString()), + ex); + }*/ + + itemColl.addFilter(or); + + } + + itemColl.addOrder("isFolder desc"); + itemColl.addOrder("lower(item." + ContentItem.NAME + ") "); + return itemColl; + } + + private void createSiblingFilter(final CompoundFilter filter, + final FilterFactory filterFactory, + final BigDecimal typeId) { + final ContentType type = new ContentType(typeId); + if ((type.getDescendants() == null) + || type.getDescendants().trim().isEmpty()) { + return; + } else { + final String[] descendantsIds = type.getDescendants().split("/"); + + for (String descendantId : descendantsIds) { + filter.addFilter(filterFactory.equals(String.format( + ContentItem.CONTENT_TYPE + "." + ContentType.ID), + descendantId)); + createSiblingFilter(filter, filterFactory, descendantId); + } + } + } + + private void createSiblingFilter(final CompoundFilter filter, + final FilterFactory filterFactory, + final String typeId) { + try { + final BigDecimal _typeId = new BigDecimal(typeId); + createSiblingFilter(filter, filterFactory, _typeId); + } catch (NumberFormatException ex) { + s_log.error(String.format("Failed to parse typeId '%s'.", + typeId), + ex); + } + } + + public int getTotalSize(Paginator paginator, PageState state) { + + Integer size = (Integer) m_size.get(state); + return size.intValue(); + } + + /** + * Indicates whether the paginator should be visible, based on the visibility of the folder + * browser itself and how many items are displayed + * + * @return true if folder browser is visible and there is more than 1 page of items, false + * otherwise + */ + public boolean isVisible(PageState state) { + int size = ((Integer) m_size.get(state)).intValue(); + + return ItemSearchFolderBrowser.this.isVisible(state) + && (size + > ContentSection.getConfig().getFolderBrowseListSize()); + } + + } + + /** + * Produce links to view an item or control links for folders to change into the folder. + */ + private class NameCellRenderer extends DefaultTableCellRenderer { + + public NameCellRenderer() { + super(true); + } + + @Override + public Component getComponent(Table table, PageState state, + Object value, boolean isSelected, + Object key, int row, int column) { + Folder.ItemCollection coll = (Folder.ItemCollection) value; + String name = coll.getName(); + if (coll.isFolder()) { + return super.getComponent(table, state, name, isSelected, key, + row, column); + } else { + ContentSection section = CMS.getContext().getContentSection(); + BigDecimal id = (BigDecimal) key; + + if (section == null) { + return new Label(name); + } else { + //ItemResolver resolver = section.getItemResolver(); + + //String url = + //resolver.generateItemURL + //(state, id, name, section, coll.getVersion())); + SimpleContainer container = new SimpleContainer(); + + String widget = (String) state.getValue(new StringParameter( + ItemSearchPopup.WIDGET_PARAM)); + String searchWidget = (String) state.getValue( + new StringParameter("searchWidget")); + boolean useURL = "true".equals(state.getValue(new StringParameter( + ItemSearchPopup.URL_PARAM))); + + String fillString; + if (useURL) { + fillString = ItemSearchPopup.getItemURL(state.getRequest(), + coll.getDomainObject().getOID()); + } else { + fillString = id.toString();// + " (" + name + ")"; + } + + String title = ((ContentPage) coll.getDomainObject()).getTitle(); + + Label js = new Label( + generateJSLabel(id, widget, searchWidget, fillString, title), + false); + container.add(js); + + String url = "#"; + + Link link = new Link(name, url); + link.setClassAttr("title"); + link.setOnClick("return fillItem" + id + "()"); + + container.add(link); + + return container; + } + } + } + + private String generateJSLabel(BigDecimal id, String widget, String searchWidget, + String fill, String title) { + StringBuilder buffer = new StringBuilder(); + buffer.append(" "); + + return buffer.toString(); + } + + } + + /** + * Table model around ItemCollection + */ + private static class FolderTableModel implements TableModel { + + private static final int NAME = 0; + private static final int TITLE = 1; + private static final int TYPE = 2; + private Folder.ItemCollection m_itemColl; + + public FolderTableModel(Folder.ItemCollection itemColl) { + m_itemColl = itemColl; + } + + public int getColumnCount() { + return 3; + } + + public boolean nextRow() { + return m_itemColl != null ? m_itemColl.next() : false; + } + + public Object getElementAt(int columnIndex) { + switch (columnIndex) { + case NAME: + return m_itemColl; + case TITLE: + return m_itemColl.getDisplayName(); + case TYPE: + return m_itemColl.getTypeLabel(); + default: + throw new IndexOutOfBoundsException("Column index " + + columnIndex + + " not in table model."); + } + } + + public Object getKeyAt(int columnIndex) { + // Mark folders by using their negative ID (dirty, dirty) + return (m_itemColl.isFolder()) ? m_itemColl.getID().negate() + : m_itemColl.getID(); + } + + } + + private class FolderChanger extends TableActionAdapter { + + @Override + public void cellSelected(TableActionEvent e) { + PageState s = e.getPageState(); + int col = e.getColumn().intValue(); + + if (m_nameColumn != getColumn(col)) { + return; + } + String key = (String) e.getRowKey(); + if (key.startsWith("-")) { + clearSelection(s); + getFolderSelectionModel().setSelectedKey(s, key.substring(1)); + m_paginator.reset(s); + } + } + + } + + /** + * Getting the GlobalizedMessage using a CMS Class targetBundle. + * + * @param key The resource key + * + * @pre ( key != null ) + */ + private static GlobalizedMessage globalize(String key) { + //return FolderManipulator.globalize(key); + final GlobalisationUtil util = new GlobalisationUtil( + "com.arsdigita.cms." + + "ui.folder.CMSFolderResources"); + return util.globalize(key); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.todo index 255ac53cb..16469cebb 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.todo +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchPage.java.todo @@ -28,15 +28,12 @@ import com.arsdigita.bebop.parameters.BigDecimalParameter; import com.arsdigita.bebop.parameters.BooleanParameter; import com.arsdigita.bebop.parameters.IntegerParameter; import com.arsdigita.bebop.parameters.StringParameter; -import com.arsdigita.cms.*; import com.arsdigita.cms.dispatcher.CMSPage; -import com.arsdigita.cms.util.GlobalizationUtil; import com.arsdigita.dispatcher.RequestContext; -import com.arsdigita.domain.DataObjectNotFoundException; import com.arsdigita.templating.PresentationManager; import com.arsdigita.templating.Templating; import com.arsdigita.util.UncheckedWrapperException; -import com.arsdigita.web.Application; +import org.libreccm.web.CcmApplication; import com.arsdigita.web.Web; import com.arsdigita.xml.Document; diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchSection.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchSection.java new file mode 100755 index 000000000..317cd85bb --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ItemSearchSection.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms.ui; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.bebop.Container; +import com.arsdigita.bebop.Resettable; +import com.arsdigita.bebop.FormProcessException; +import com.arsdigita.bebop.FormSection; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.event.FormProcessListener; +import com.arsdigita.bebop.event.FormSectionEvent; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentType; +import com.arsdigita.cms.ui.search.ItemQueryComponent; +import com.arsdigita.globalization.GlobalizedMessage; + +import com.arsdigita.search.ui.ResultsPane; +import com.arsdigita.search.ui.QueryGenerator; +import com.arsdigita.toolbox.ui.LayoutPanel; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.lucene.search.Query; +import org.librecms.CmsConstants; + +/** + * Contains a form for specifying search parameters, as well as a + * {@link com.arsdigita.search.ui.ResultsPane} which will perform the search and + * display the results + * + * @author Stanislav Freidin (sfreidin@arsdigita.com) + * @version $Id: ItemSearchSection.java 1940 2009-05-29 07:15:05Z terry $ + */ +public class ItemSearchSection extends FormSection implements Resettable, + QueryGenerator { + + private static final Logger LOGGER = LogManager.getLogger( + ItemSearchSection.class); + public static final String SINGLE_TYPE_PARAM = "single_type"; + + private ItemQueryComponent itemQueryComponent; + private Component resultsComponent; + + /** + * Construct a new ItemSearchSection 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 ItemSearchSection(final String context, + final boolean limitToContentSection) { + this(null, context, limitToContentSection); + } + + /** + * Construct a new ItemSearchSection component + * + * @param context the context for the retrieved items. Should be + * {@link ContentItem#DRAFT} or {@link ContentItem#LIVE} + * @param name The name of the search parameter for the particular + * FormSection + * @param limitToContentSection limit the search to the current content + * section + */ + public ItemSearchSection(final String name, + final String context, + final boolean limitToContentSection) { + this(name, context, limitToContentSection, null); + } + + public ItemSearchSection(final String name, + final String context, + final boolean limitToContentSection, + final ContentType type) { + super(new SimpleContainer()); + final String thisName; + if (name == null) { + thisName = "itemSearch"; + } else { + thisName = name; + } + + if (type == null) { + itemQueryComponent = createQueryGenerator(context, + limitToContentSection); + } else { + itemQueryComponent = createQueryGenerator(context, + limitToContentSection, + type); + } + resultsComponent = createResultsPane(itemQueryComponent); + + LayoutPanel searchPanel = new LayoutPanel(); + searchPanel.setLeft(itemQueryComponent); + searchPanel.setBody(resultsComponent); + this.add(searchPanel); + +// addQueryGenerator(this); +// addResultsPane(this); + addFormListener(); + + setClassAttr("itemSearch"); + } + + @Override + public boolean hasQuery(final PageState state) { + return itemQueryComponent.hasQuery(state); + } + + @Override + public Query getQuerySpecification(final PageState state) { + return itemQueryComponent.getQuerySpecification(state); + } + + @Override + public void reset(final PageState state) { + resultsComponent.setVisible(state, false); + } + + protected ItemQueryComponent createQueryGenerator( + final String context, final boolean limitToContentSection) { + return new ItemQueryComponent(context, limitToContentSection); + } + + protected ItemQueryComponent createQueryGenerator( + final String context, + final boolean limitToContentSection, + final ContentType type) { + + return new ItemQueryComponent(context, limitToContentSection, type); + } + + protected Component createResultsPane(QueryGenerator generator) { + ResultsPane pane = new ResultsPane(generator); + pane.setRelativeURLs(true); + pane.setSearchHelpMsg(new GlobalizedMessage("cms.ui.search.help", + CmsConstants.CMS_BUNDLE)); + pane.setNoResultsMsg(new GlobalizedMessage("cms.ui.search.no_results", + CmsConstants.CMS_BUNDLE)); + return pane; + } + + protected void addResultsPane(final Container container) { + container.add(resultsComponent); + } + + protected void addQueryGenerator(final Container container) { + container.add(itemQueryComponent); + } + + protected void processQuery(final PageState state) { + resultsComponent.setVisible(state, itemQueryComponent.hasQuery(state)); + } + + protected void addFormListener() { + addProcessListener(new SearchFormProcessListener()); + } + + // Hide results by default + @Override + public void register(final Page page) { + super.register(page); + page.setVisibleDefault(resultsComponent, false); + page.addGlobalStateParam(new BigDecimalParameter(SINGLE_TYPE_PARAM)); + } + + /** + * Displays the "keywords" and "content types" widgets + */ + private class SearchFormProcessListener implements FormProcessListener { + + @Override + public void process(final FormSectionEvent event) + throws FormProcessException { + + PageState s = event.getPageState(); + processQuery(s); + } + } +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/search/ItemQueryComponent.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/search/ItemQueryComponent.java new file mode 100755 index 000000000..cb01af551 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/search/ItemQueryComponent.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.cms.ui.search; + +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.form.Submit; +import org.libreccm.categorization.Category; +import com.arsdigita.cms.CMS; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentType; +import com.arsdigita.cms.ui.ContentSectionPage; +import com.arsdigita.search.ui.BaseQueryComponent; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides a basic query form for CMS admin pages that automatically + * adds components for the maximal set of filters supported by the current + * search query engine. + * + * @author unknown + * @author Sören Bernstein + * @author Jens Pelzetter (jens@jp-digital.de) + */ +public class ItemQueryComponent extends BaseQueryComponent { + + private String context; + + public ItemQueryComponent(final String context, + final boolean limitToContentSection) { + this(context, limitToContentSection, null); + } + + public ItemQueryComponent(final String context, + final boolean limitToContentSection, + final ContentType type) { + this.context = context; + +// add(new PermissionFilterComponent( +// SecurityManager.CMS_PREVIEW_ITEM)); +// +// add(new SimpleCategoryFilterWidget() { +// +// @Override +// protected Category[] getRoots(PageState state) { +// Category[] roots; +// if (limitToContentSection == true && CMS.getContext(). +// hasContentSection()) { +// ContentSection section = CMS.getContext(). +// getContentSection(); +// roots = new Category[]{section.getRootCategory()}; +// } else { +// ContentSectionCollection sections = +// ContentSection.getAllSections(); +// List cats = new ArrayList(); +// while (sections.next()) { +// ContentSection section = +// sections.getContentSection(); +// cats.add(section.getRootCategory()); +// } +// roots = +// (Category[]) cats.toArray(new Category[cats.size()]); +// } +// return roots; +// } +// }); +// +// if (type == null) { +// add(new ContentTypeFilterWidget() { +// +// @Override +// protected ContentSection getContentSection() { +// if (limitToContentSection == true && CMS.getContext(). +// hasContentSection()) { +// return CMS.getContext().getContentSection(); +// } else { +// return super.getContentSection(); +// } +// } +// }); +// } else { +// add(new ContentTypeFilterWidget(type) { +// +// @Override +// protected ContentSection getContentSection() { +// if (limitToContentSection == true && CMS.getContext(). +// hasContentSection()) { +// return CMS.getContext().getContentSection(); +// } else { +// return super.getContentSection(); +// } +// } +// }); +// } +// +// add(new VersionFilterComponent(context)); +// if (limitToContentSection == true) { +// add(new ContentSectionFilterComponent()); +// } +// add(new DateRangeFilterWidget(new LastModifiedDateFilterType(), +// LastModifiedDateFilterType.KEY)); +// add(new DateRangeFilterWidget(new CreationDateFilterType(), +// CreationDateFilterType.KEY)); +// add(new PartyFilterWidget(new CreationUserFilterType(), +// CreationUserFilterType.KEY)); +// add(new PartyFilterWidget(new LastModifiedUserFilterType(), +// LastModifiedUserFilterType.KEY)); + + + Submit submit = + new Submit(context + "_search", + ContentSectionPage.globalize("cms.ui.search")); + add(submit); + } + +// private class LaunchDateFilterWidget extends DateRangeFilterWidget { +// +// public LaunchDateFilterWidget(FilterType type, String name) { +// super(type, name); +// +// } +// +// @Override +// public boolean isVisible(PageState state) { +// return !ContentSection.getConfig().getHideLaunchDate() +// && super.isVisible(state); +// } +// } +} diff --git a/ccm-core/src/main/java/com/arsdigita/search/SearchConstants.java b/ccm-core/src/main/java/com/arsdigita/search/SearchConstants.java new file mode 100644 index 000000000..34d2fe283 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/search/SearchConstants.java @@ -0,0 +1,40 @@ +/* + * 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.search; + +/** + * + * @author Jens Pelzetter + */ +public final class SearchConstants { + + private SearchConstants() { + //Nothing + } + + /** + * Constant for search XML namespace prefix + */ + public static final String XML_PREFIX = "search:"; + /** + * Constant for search XML namespace URL + */ + public static final String XML_NS = "http://rhea.redhat.com/search/1.0"; + +} diff --git a/ccm-core/src/main/java/com/arsdigita/search/ui/BaseQueryComponent.java b/ccm-core/src/main/java/com/arsdigita/search/ui/BaseQueryComponent.java new file mode 100755 index 000000000..4538578d6 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/search/ui/BaseQueryComponent.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.search.ui; + +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.Form; +import com.arsdigita.bebop.FormModel; +import com.arsdigita.bebop.FormData; +import com.arsdigita.bebop.parameters.ParameterData; +import com.arsdigita.globalization.Globalization; +import com.arsdigita.xml.Element; + +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.search.SearchConstants; + +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import org.apache.logging.log4j.LogManager; + +import org.apache.logging.log4j.Logger; + +/** + * This is a simple extension of the QueryComponent that provides management of + * the 'terms' parameter and uses FilterGenerators to populate a query + * specification + *

+ * Typical use would be as follows: + *

+ * Form f = new Form("search");
+ * BaseQueryComponent q = new BaseQueryComponent();
+ * q.add(new ObjectTypeFilterComponent("com.arsdigita.kernel.User");
+ * q.add(new PermissionGenerator(PrivilegeDescriptor.READ));
+ * q.add(new Submit("Go"));
+ * f.add(q);
+ * 
+ */ +public class BaseQueryComponent extends QueryComponent { + + private static final Logger LOGGER = LogManager.getLogger( + BaseQueryComponent.class); + + private Set filters; + private Form form; + private StringParameter termsParameter = new StringParameter("terms"); + + /** + * Creates a new query component + */ + public BaseQueryComponent() { + super("query"); + filters = new HashSet(); + } + + @Override + public void register(final Page page) { + super.register(page); + } + + @Override + public void register(final Form form, final FormModel formModel) { + LOGGER.debug("Adding {} to form model...", termsParameter.getName()); + + termsParameter.setPassIn(true); + formModel.addFormParam(termsParameter); + this.form = form; + } + + /** + * Gets the current search terms + * + * @return + */ + @Override + protected String getTerms(final PageState state) { + final FormData formData = form.getFormData(state); + + if (formData != null) { + final ParameterData data = formData.getParameter(termsParameter. + getName()); + LOGGER.debug("Search terms were: {}", (String) data.getValue()); + + return (String) data.getValue(); + } else { + return null; + } + } + + /** + * + * @param state + * @param parent + */ + @Override + public void generateXML(final PageState state, final Element parent) { + final Element content = generateParent(parent); + + final Element terms = new Element(SearchConstants.XML_PREFIX + "terms", + SearchConstants.XML_NS); + terms.addAttribute("param", termsParameter.getName()); + terms.addAttribute("value", + Globalization.decodeParameter( + state.getRequest(), + termsParameter.getName())); + generateErrorXML(state, terms); + content.addContent(terms); + + generateChildrenXML(state, content); + } + + protected void generateErrorXML(final PageState state, + final Element parent) { + final FormData formData = form.getFormData(state); + if (formData == null) { + return; + } + + final Iterator iterator = formData.getErrors(termsParameter.getName()); + while (iterator.hasNext()) { + final Element error = new Element( + SearchConstants.XML_PREFIX + "error", + SearchConstants.XML_NS); + error.setText((String) ((GlobalizedMessage) iterator.next()). + localize(state.getRequest()) + ); + parent.addContent(error); + } + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/search/ui/QueryComponent.java b/ccm-core/src/main/java/com/arsdigita/search/ui/QueryComponent.java new file mode 100755 index 000000000..18e2ad25a --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/search/ui/QueryComponent.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.search.ui; + +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.SimpleContainer; +import com.arsdigita.search.SearchConstants; + +import com.arsdigita.util.Assert; +import org.apache.lucene.search.Query; +import org.hibernate.search.query.dsl.QueryBuilder; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.core.CcmObject; +import org.libreccm.search.SearchManager; + +/** + * A base class for generating a query specification from the state. Subclasses + * must implement two methods, one for getting the query terms, the other for + * getting a set of filter specs. + */ +public abstract class QueryComponent extends SimpleContainer + implements QueryGenerator { + + public QueryComponent(final String name) { + setTag(SearchConstants.XML_PREFIX + name); + setNamespace(SearchConstants.XML_NS); + } + + /** + * Determine if a query specification is available + * + * @return true if the user has entered some search terms + */ + @Override + public boolean hasQuery(final PageState state) { + String terms = getTerms(state); + + return (terms != null && !"".equals(terms)); + } + + /** + * Returns the current query specification + */ + @Override + public Query getQuerySpecification(final PageState state) { + final String terms = getTerms(state); + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final SearchManager searchManager = cdiUtil. + findBean(SearchManager.class); + + final QueryBuilder queryBuilder = searchManager.createQueryBuilder(CcmObject.class); + + return queryBuilder + .keyword().onFields("displayName", "summary", "description", "title") + .matching(terms) + .createQuery(); + + } + + /** + * Returns the current query terms + * + * @param state + * @return the query terms, or null + */ + protected abstract String getTerms(PageState state); + +} diff --git a/ccm-core/src/main/java/com/arsdigita/search/ui/QueryGenerator.java b/ccm-core/src/main/java/com/arsdigita/search/ui/QueryGenerator.java new file mode 100755 index 000000000..cf4be3acd --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/search/ui/QueryGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.search.ui; + +import com.arsdigita.bebop.PageState; +import org.apache.lucene.search.Query; + +/** + * This interface provides the API for retrieving a query specification based on + * the current state. The ResultsPane component uses an instance of this class + * to retrieve the query spec and display a list of results + * + * @see com.arsdigita.search.ui.QueryComponent + * @see com.arsdigita.search.ui.ResultsPane + */ +public interface QueryGenerator { + + /** + * Determines whether a query spec currently exists. + * + * @param state The current page state. + * @return true if a query spec is available. + */ + boolean hasQuery(PageState state); + + /** + * Retrieves the current query spec. This method can only be called if + * hasQuery(state) returns true. + * + * @param state The current page. + * @return The query + */ + Query getQuerySpecification(PageState state); + +} diff --git a/ccm-core/src/main/java/com/arsdigita/search/ui/ResultsPane.java b/ccm-core/src/main/java/com/arsdigita/search/ui/ResultsPane.java new file mode 100755 index 000000000..797172ee7 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/search/ui/ResultsPane.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.search.ui; + +import com.arsdigita.bebop.SimpleComponent; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.parameters.IntegerParameter; +import com.arsdigita.globalization.Globalization; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.search.SearchConstants; +import com.arsdigita.util.UncheckedWrapperException; + +import org.libreccm.security.Party; +import com.arsdigita.xml.Element; +import com.arsdigita.xml.XML; + +import com.arsdigita.web.URL; +import com.arsdigita.web.ParameterMap; +import com.arsdigita.web.Web; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import org.apache.logging.log4j.LogManager; + +import org.apache.logging.log4j.Logger; +import org.apache.lucene.search.Query; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.core.CcmObject; +import org.libreccm.search.SearchManager; + +public class ResultsPane extends SimpleComponent { + + private static final Logger LOGGER = LogManager.getLogger(ResultsPane.class); + public static final int PAGE_SIZE = 10; + + private final int pageSize = PAGE_SIZE; + + private final QueryGenerator queryGenerator; + private IntegerParameter pageNumber; + private boolean relative; + //jensp 2014-03-04 Allow using classes to set a suitable info messages. + private GlobalizedMessage searchHelpMsg; + private GlobalizedMessage noResultsMsg; + + public ResultsPane(final QueryGenerator query) { + pageNumber = new IntegerParameter("page"); + relative = false; + this.queryGenerator = query; + } + + /** + * Determines whether the links to the search results will be relative or + * absolute. The default is absolute. + * + * @param relative + */ + public void setRelativeURLs(final boolean relative) { + this.relative = relative; + } + + public void setSearchHelpMsg(final GlobalizedMessage msg) { + searchHelpMsg = msg; + } + + public void setNoResultsMsg(final GlobalizedMessage msg) { + noResultsMsg = msg; + } + + @Override + public void generateXML(final PageState state, final Element parent) { + if (!queryGenerator.hasQuery(state)) { + + LOGGER.debug("No query available, skipping XML generation"); + + final Element content = new Element( + SearchConstants.XML_PREFIX + "results", + SearchConstants.XML_NS); + final Element info = content.newChildElement("info"); + if (searchHelpMsg == null) { + info.setText( + "To search for content items, please enter at least 3 letters into the search field. You can narrow the result by using additional parameters."); + } else { + //info.setText(GlobalizationUtil.globalize("cms.ui.search_help").localize().toString()); + info.setText(searchHelpMsg.localize().toString()); + } + + parent.addContent(content); + return; + } + + final Query spec = queryGenerator.getQuerySpecification(state); + final SearchManager searchManager = CdiUtil.createCdiUtil().findBean( + SearchManager.class); + final List results = (List) searchManager. + executeQuery(spec); + LOGGER.debug("Got result list with {} items.", results.size()); + if (results.isEmpty()) { + final long objectCount = results.size(); + final int pageCount = (int) Math.ceil((double) objectCount + / (double) pageSize); + + final Integer page = (Integer) pageNumber.transformValue(state. + getRequest()); + int pageNum; + if (page == null) { + pageNum = 1; + } else if (page < 1) { + pageNum = 1; + } else if (page > pageCount) { + if (pageCount == 0) { + pageNum = 1; + } else { + pageNum = page; + } + } else { + pageNum = page; + } + + final long begin = ((pageNum - 1) * pageSize); + final long count = Math.min(pageSize, (objectCount - begin)); + final long end = begin + count; + + final Iterator iterator = results + .subList((int) begin, (int) begin + (int) count) + .iterator(); + + final Element content = new Element( + SearchConstants.XML_PREFIX + "results", + SearchConstants.XML_NS); + exportAttributes(content); + + LOGGER.debug("Paginator stats\n" + + " page number: {}\n" + + " page count.: {}\n" + + " page size..: {}\n" + + " begin......: {}\n" + + " end........: {}\n" + + " count : {}", + pageNum, + pageCount, + pageSize, + begin, + end, + objectCount); + + content.addContent(generatePaginatorXML(state, + pageNumber.getName(), + pageNum, + pageCount, + pageSize, + begin, + end, + objectCount)); + content.addContent(generateDocumentsXML(state, iterator)); + + parent.addContent(content); + } else { + // No search result, so we don't need a paginator, but we want + // to inform the user, that there are no results for this search + final Element content = new Element( + SearchConstants.XML_PREFIX + "results", + SearchConstants.XML_NS); + final Element info = content.newChildElement("info"); +// info.setText(GlobalizationUtil.globalize("cms.ui.search_no_results").localize().toString()); + if (noResultsMsg == null) { + info.setText("Sorry. Your search returned 0 results."); + } else { + info.setText(noResultsMsg.localize().toString()); + } + parent.addContent(content); + } + } + + protected Element generatePaginatorXML(final PageState state, + final String pageParam, + final int pageNumber, + final int pageCount, + final int pageSize, + final long begin, + final long end, + final long objectCount) { + final Element paginator = new Element( + SearchConstants.XML_PREFIX + "paginator", + SearchConstants.XML_NS); + final URL url = Web.getWebContext().getRequestURL(); + + final ParameterMap parameterMap = new ParameterMap(); + final Iterator current = url.getParameterMap().keySet().iterator(); + while (current.hasNext()) { + final String key = (String) current.next(); + if (key.equals(pageParam)) { + continue; + } + parameterMap.setParameterValues( + key, decodeParameters(url.getParameterValues(key), state)); + } + + paginator.addAttribute("pageParam", this.pageNumber.getName()); + paginator.addAttribute("baseURL", URL.there(url.getPathInfo(), + parameterMap).toString()); + paginator.addAttribute("pageNumber", XML.format(pageNumber)); + paginator.addAttribute("pageCount", XML.format(pageCount)); + paginator.addAttribute("pageSize", XML.format(pageSize)); + paginator.addAttribute("objectBegin", XML.format(begin + 1)); + paginator.addAttribute("objectEnd", XML.format(end)); + paginator.addAttribute("objectCount", XML.format(objectCount)); + + return paginator; + } + + private String[] decodeParameters(final String[] parameters, + final PageState state) { + + final String[] decoded = new String[parameters.length]; + + for (int i = 0; i < parameters.length; i++) { + decoded[i] = decodeParameter(parameters[i], state); + } + + return decoded; + } + + private String decodeParameter(final String parameter, + final PageState state) { + String re = state.getRequest().getParameter( + Globalization.ENCODING_PARAM_NAME); + + if ((re == null) || (re.isEmpty())) { + re = Globalization.getDefaultCharset(); + } + + if ((parameter == null) || (parameter.isEmpty())) { + return parameter; + } else if (Globalization.getDefaultCharset(state.getRequest()). + equals(re)) { + return parameter; + } else { + try { + return new String(parameter.getBytes(Globalization. + getDefaultCharset( + state.getRequest())), re); + } catch (UnsupportedEncodingException ex) { + LOGGER.warn("Unsupported encoding.", ex); + return parameter; + } + } + } + + protected Element generateDocumentsXML(final PageState state, + final Iterator results) { + + final Element documents = new Element( + SearchConstants.XML_PREFIX + "documents", + SearchConstants.XML_NS); + + LOGGER.debug("Outputting documents"); + + while (results.hasNext()) { + final CcmObject doc = results.next(); + LOGGER.debug("Current document {} {}", + doc.getObjectId(), + doc.getDisplayName()); + + documents.addContent(generateDocumentXML(state, doc)); + } + + return documents; + } + + private Optional getSummary(final CcmObject doc) { + final BeanInfo beanInfo; + try { + beanInfo = Introspector.getBeanInfo(doc.getClass()); + } catch (IntrospectionException ex) { + throw new UncheckedWrapperException(ex); + } + + final Optional propertyDesc + = Arrays.stream(beanInfo. + getPropertyDescriptors()) + .filter(descriptor -> { + return "description".equals(descriptor.getName()) + || "summary".equals(descriptor. + getName()); + }) + .findFirst(); + + if (propertyDesc.isPresent()) { + final Method readMethod = propertyDesc.get().getReadMethod(); + final String summary; + try { + summary = (String) readMethod.invoke(doc); + } catch (IllegalAccessException + | IllegalArgumentException + | InvocationTargetException ex) { + throw new UncheckedWrapperException(ex); + } + return Optional.of(summary); + } else { + return Optional.empty(); + } + } + + protected Element generateDocumentXML(final PageState state, + final CcmObject doc) { + final Element entry = new Element(SearchConstants.XML_PREFIX + "object", + SearchConstants.XML_NS); + + final Optional summary = getSummary(doc); + + entry.addAttribute("id", XML.format(doc.getObjectId())); + entry.addAttribute("uuid", XML.format(doc.getUuid())); +// entry.addAttribute("url", XML.format(relative ? url.getPath() + "?" +// + url.getQuery() +// : url.toString())); + entry.addAttribute("title", XML.format(doc.getDisplayName())); + if (summary.isPresent()) { + entry.addAttribute("summary", XML.format(summary)); + } + +// entry.addAttribute("locale", XML.format(doc.getLocale())); + +// Date creationDate = doc.getCreationDate(); +// if (creationDate != null) { +// entry.addAttribute("creationDate", XML.format( +// creationDate.toString())); +// } +// Party creationParty = doc.getCreationParty(); +// if (creationParty != null) { +// entry.addAttribute("creationParty", +// XML.format(creationParty.getDisplayName())); +// } +// +// Date lastModifiedDate = doc.getLastModifiedDate(); +// if (lastModifiedDate != null) { +// entry.addAttribute("lastModifiedDate", +// XML.format(lastModifiedDate)); +// } +// Party lastModifiedParty = doc.getLastModifiedParty(); +// if (lastModifiedParty != null) { +// entry.addAttribute("lastModifiedParty", +// XML.format(lastModifiedParty.getDisplayName())); +// } + +// LOGGER.debug( +// "about to add the contentSectionName from search index Doc to search result xml"); +// entry.addAttribute("contentSectionName", XML.format(doc. +// getContentSection())); + + return entry; + } + +}