From 31cdeeeb7ce2c95b849ddbbf65606f920a3fe87d Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 13 Sep 2017 15:36:54 +0000 Subject: [PATCH] CCM NG: Next part of migration of ItemCategoryStep git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4997 8810af33-2d31-482b-a856-94f89814c4df --- .../ui/authoring/ItemCategoryExtension.java | 39 ++ .../cms/ui/authoring/ItemCategoryStep.java | 123 +++--- .../terms/ui/ACSObjectCategoryPicker.java | 198 ++++++++++ .../terms/ui/CategoryPickerController.java | 217 ++++++++++ .../london/terms/ui/ItemCategoryPicker.java | 79 ++++ .../arsdigita/london/terms/ui/TermWidget.java | 372 ++++++++++++++++++ .../java/org/arsdigita/cms/CMSConfig.java | 40 ++ .../org/libreccm/categorization/Category.java | 18 +- .../org/libreccm/categorization/Domain.java | 27 +- 9 files changed, 1062 insertions(+), 51 deletions(-) create mode 100644 ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryExtension.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/london/terms/ui/ACSObjectCategoryPicker.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/london/terms/ui/CategoryPickerController.java create mode 100755 ccm-cms/src/main/java/com/arsdigita/london/terms/ui/ItemCategoryPicker.java create mode 100644 ccm-cms/src/main/java/com/arsdigita/london/terms/ui/TermWidget.java diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryExtension.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryExtension.java new file mode 100644 index 000000000..619903445 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.cms.ui.authoring; + +import com.arsdigita.bebop.SimpleComponent; + +/** + * NOOP base implementation of category authoring step extension. + * Summary component returned by #getSummary() is show on category summary page, + * usually an ActionLink which activates the Form component returned by #getForm(). + * + * @author Alan Pevec + */public class ItemCategoryExtension { + + public SimpleComponent[] getSummary() { + return new SimpleComponent[0]; + } + + public SimpleComponent[] getForm() { + return new SimpleComponent[0]; + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryStep.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryStep.java index 790606549..b8fe6b3eb 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryStep.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/authoring/ItemCategoryStep.java @@ -29,43 +29,41 @@ import com.arsdigita.bebop.Resettable; import com.arsdigita.bebop.parameters.LongParameter; import com.arsdigita.web.RedirectSignal; - import com.arsdigita.cms.CMS; - import com.arsdigita.cms.ItemSelectionModel; +import com.arsdigita.util.Classes; +import org.arsdigita.cms.CMSConfig; +import org.libreccm.core.UnexpectedErrorException; import org.librecms.CmsConstants; import org.librecms.ui.authoring.ContentItemAuthoringStep; import java.math.BigDecimal; - @ContentItemAuthoringStep( labelBundle = CmsConstants.CMS_BUNDLE, labelKey = "item_category_step.label", descriptionBundle = CmsConstants.CMS_BUNDLE, descriptionKey = "item_category_step.description" ) -public class ItemCategoryStep extends SimpleContainer implements Resettable{ - +public class ItemCategoryStep extends SimpleContainer implements Resettable { private final LongParameter rootParameter; private final StringParameter modeParameter; - - private final ItemCategorySummary itemCategorySummary; - - private final SimpleComponent addComponent; - -// private SimpleComponent[] extensionSummaries; -// private SimpleComponent[] extensionForms; -// private int extensionsCount; - - public ItemCategoryStep(final ItemSelectionModel itemSelectionModel, + private final ItemCategorySummary itemCategorySummary; + + private final SimpleComponent addComponent; + + private final SimpleComponent[] extensionSummaries; + private final SimpleComponent[] extensionForms; + private int extensionsCount; + + public ItemCategoryStep(final ItemSelectionModel itemSelectionModel, final AuthoringKitWizard authoringKitWizard, final StringParameter selectedLanguage) { - + super("cms:categoryStep", CMS.CMS_XML_NS); rootParameter = new LongParameter("root"); @@ -73,43 +71,63 @@ public class ItemCategoryStep extends SimpleContainer implements Resettable{ itemCategorySummary = new ItemCategorySummary(); itemCategorySummary.registerAction(ItemCategorySummary.ACTION_ADD, - new AddActionListener("plain")); + new AddActionListener("plain")); itemCategorySummary.registerAction(ItemCategorySummary.ACTION_ADD_JS, - new AddActionListener("javascript")); + new AddActionListener("javascript")); -// Class addForm = CMSConfig.getConfig().getCategoryAuthoringAddForm(); -// addComponent = (SimpleComponent) -// Classes.newInstance(addForm, -// new Class[] { BigDecimalParameter.class, -// StringParameter.class }, -// new Object[] { rootParameter, modeParameter }); - addComponent = new ItemCategoryForm(rootParameter, modeParameter); + final String addFormClassName = CMSConfig + .getConfig() + .getCategoryAuthoringAddForm(); + final Class addFormClass; + try { + addFormClass = Class.forName(addFormClassName); + } catch (ClassNotFoundException ex) { + throw new UnexpectedErrorException(ex); + } + addComponent = (SimpleComponent) Classes + .newInstance(addFormClass, + new Class[]{LongParameter.class, + StringParameter.class}, + new Object[]{rootParameter, modeParameter}); addComponent.addCompletionListener(new ResetListener()); -// Class extensionClass = ContentSection.getConfig().getCategoryAuthoringExtension(); -// ItemCategoryExtension extension = (ItemCategoryExtension) -// Classes.newInstance(extensionClass); -// -// extensionSummaries = extension.getSummary(); -// extensionForms = extension.getForm(); -// int nSummaries = extensionSummaries.length; -// int nForms= extensionForms.length; -// Assert.isTrue(nSummaries==nForms, "invalid CategoryStep extension"); -// extensionsCount = nForms; -// for (int i=0;i extensionClass; + try { + extensionClass = Class.forName(extensionClassName); + } catch (ClassNotFoundException ex) { + throw new UnexpectedErrorException(ex); + } + final ItemCategoryExtension extension = (ItemCategoryExtension) Classes + .newInstance(extensionClass); + + extensionSummaries = extension.getSummary(); + extensionForms = extension.getForm(); + int nSummaries = extensionSummaries.length; + int nForms = extensionForms.length; + if (nSummaries != nForms) { + throw new UnexpectedErrorException( + "Invalid category step extension."); + } + extensionsCount = nForms; + for (int i = 0; i < extensionsCount; i++) { + extensionSummaries[i] + .addCompletionListener(new ExtensionListener(i)); + extensionForms[i].addCompletionListener(new ResetListener()); + super.add(extensionSummaries[i]); + super.add(extensionForms[i]); + } + super.add(itemCategorySummary); + super.add(addComponent); } @Override - public void register(Page p) { + public void register(Page p + ) { super.register(p); - + p.setVisibleDefault(addComponent, false); // for (int i=0;iJens Pelzetter + */ +// NON JavaDoc: +// copied from c.ad.aplaws.ui (module ccm-ldn-aplaws) in order to avoid a +// dependency from the integration layer for forum-categorised. Otherwise you +// have had to specify the specific integration layer (i.e. ccm-???-aplaws) in +// application.xml for compiling, which may be different for each installation. +public abstract class ACSObjectCategoryPicker extends SimpleContainer { + + private static final Logger LOGGER = LogManager + .getLogger(ACSObjectCategoryPicker.class); + + private final ACSObjectCategoryForm form; + private final LongParameter rootParam; + + public ACSObjectCategoryPicker(final LongParameter rootParam, + final StringParameter mode) { + + form = getForm(rootParam, mode); + this.rootParam = rootParam; + + super.add(form); + form.addCompletionListener(new ItemCategoryFormCompletion()); + } + + protected abstract ACSObjectCategoryForm getForm(LongParameter root, + StringParameter mode); + + protected abstract CcmObject getObject(PageState state); + +// protected List getCurrentCategories(final Domain domain, +// final CcmObject object) { +// +// LOGGER.debug("Getting terms from {} to {}", domain, object); +// final List terms = domain.getRoot().getSubCategories(); +// terms.addEqualsFilter("model.childObjects.id", object.getID()); +// terms.addPath("model.id"); +// +// List current = new ArrayList<>(); +// while (terms.next()) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Got term " + terms.get("model.id")); +// } +// current.add(terms.get("model.id")); +// } +// return current; +// } + // TODO move out of UI code +// public static Collection getCurrentTerms(Domain domain, ACSObject object) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Getting terms from " + domain + " to " + object); +// } +// Collection current = new LinkedList(); +// DomainCollection terms = domain.getTerms(); +// terms.addEqualsFilter("model.childObjects.id", object.getID()); +// terms.addPath("model.id"); +// while (terms.next()) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Got term " + terms.get("model.id")); +// } +// current.add(terms.getDomainObject()); +// } +// return current; +// } +// +// // TODO move out of UI code +// public static Collection getRelatedTerms(Collection src, Domain domain) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Getting related terms to " + domain); +// +// } +// if (src.isEmpty()) { +// // this is a hack, it would be better not to use a completion event listener as +// // this is called even when the form is cancelled... +// return new LinkedList(); +// } +// DomainCollection terms = domain.getTerms(); +// // these next two lines build the query +// terms.addEqualsFilter("model.parents.link.relationType", "related"); +// terms.addFilter("model.parents.id in :ids").set("ids", src); +// +// Collection related = new LinkedList(); +// while (terms.next()) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Got term " + terms.getDomainObject()); +// } +// related.add(terms.getDomainObject()); +// } +// return related; +// } +// protected void clearTerms(Domain domain, ACSObject object) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Removing terms from " + domain + " to " + object); +// } +// Iterator terms = getCurrentTerms(domain, object).iterator(); +// while (terms.hasNext()) { +// Term term = (Term) terms.next(); +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Removing term " + term + " from " + object); +// } +// term.removeObject(object); +// } +// } +// // TODO move out of UI code +// public static void assignTerms(Collection terms, ACSObject object) { +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Assigning terms to " + object); +// } +// Iterator i = terms.iterator(); +// while (i.hasNext()) { +// Term term = (Term) i.next(); +// if (LOGGER.isDebugEnabled()) { +// LOGGER.debug("Assigning term " + term + " to " + object); +// } +// term.addObject(object); +// } +// } + protected Domain getDomain(final PageState state) { + LOGGER.debug("Getting domain for {}", state.getValue(rootParam)); + + final Long domainId = (Long) state.getValue(rootParam); + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final DomainRepository domainRepo = cdiUtil + .findBean(DomainRepository.class); + + final Optional domain = domainRepo + .findById(domainId); + + if (domain.isPresent()) { + return domain.get(); + } else { + LOGGER.warn("No Domain for ID {} found.", domainId); + return null; + } + } + + /** + * + */ + private class ItemCategoryFormCompletion implements ActionListener { + + @Override + public void actionPerformed(final ActionEvent event) { + + final PageState state = event.getPageState(); + final Domain domain = getDomain(state); + final String domainKey = domain.getDomainKey(); + + LOGGER.debug("Saving categories in: {}", domainKey); + + fireCompletionEvent(state); + } + + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/CategoryPickerController.java b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/CategoryPickerController.java new file mode 100644 index 000000000..df1eb1dbc --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/CategoryPickerController.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017 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.london.terms.ui; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.categorization.Categorization; +import org.libreccm.categorization.CategorizationConstants; +import org.libreccm.categorization.CategorizationMarshaller; +import org.libreccm.categorization.Category; +import org.libreccm.categorization.CategoryManager; +import org.libreccm.categorization.CategoryRepository; +import org.libreccm.categorization.Domain; +import org.libreccm.categorization.DomainManager; +import org.libreccm.categorization.DomainRepository; +import org.libreccm.categorization.ObjectNotAssignedToCategoryException; +import org.libreccm.core.CcmObject; +import org.libreccm.core.CcmObjectRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +class CategoryPickerController { + + private final static Logger LOGGER = LogManager + .getLogger(CategoryPickerController.class); + + @Inject + private CategoryManager categoryManager; + + @Inject + private CategoryRepository categoryRepo; + + @Inject + private CcmObjectRepository ccmObjectRepo; + + @Inject + private DomainRepository domainRepo; + + @Inject + private DomainManager domainManager; + + @Transactional(Transactional.TxType.REQUIRED) + protected List getCurrentCategories(final Domain domain, + final CcmObject object) { + + Objects.requireNonNull(domain); + Objects.requireNonNull(object); + + final Domain catDomain = domainRepo + .findById(domain.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Domain with ID %d in the database.", + domain.getObjectId()))); + + final CcmObject ccmObject = ccmObjectRepo + .findById(object.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No CcmObject with ID %d in the database.", + object.getObjectId()))); + + final Category root = catDomain.getRoot(); + return collectAssignedCategories(ccmObject, root); + } + + private List collectAssignedCategories(final CcmObject object, + final Category root) { + + final List categories = new ArrayList<>(); + if (categoryManager.isAssignedToCategory(root, object)) { + categories.add(root); + } + + if (!root.getSubCategories().isEmpty()) { + for (final Category subCategory : root.getSubCategories()) { + categories.addAll(collectAssignedCategories(object, + subCategory)); + } + } + + return categories; + } + + @Transactional(Transactional.TxType.REQUIRED) + protected void clearCategories(final Domain domain, final CcmObject object) { + + Objects.requireNonNull(domain); + Objects.requireNonNull(object); + + final Domain catDomain = domainRepo + .findById(domain.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Domain with ID %d in the database.", + domain.getObjectId()))); + + final CcmObject ccmObject = ccmObjectRepo + .findById(object.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No CcmObject with ID %d in the database.", + object.getObjectId()))); + + final Category root = catDomain.getRoot(); + final List assignedCategories = collectAssignedCategories( + ccmObject, root); + + for (final Category category : assignedCategories) { + try { + categoryManager.removeObjectFromCategory(ccmObject, category); + } catch (ObjectNotAssignedToCategoryException ex) { + LOGGER.warn("Tried to remove category {} from object {} but " + + "the object was not assigned to that category.", + Objects.toString(category), + Objects.toString(ccmObject)); + } + } + } + + @Transactional(Transactional.TxType.REQUIRED) + protected void assignCategories(final List categories, + final CcmObject object) { + + Objects.requireNonNull(categories); + Objects.requireNonNull(object); + + final CcmObject ccmObject = ccmObjectRepo + .findById(object.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No CcmObject with ID %d in the database.", + object.getObjectId()))); + + for (final Category category : categories) { + + final Category cat = categoryRepo + .findById(category.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Category with ID %d in the database.", + category.getObjectId()))); + categoryManager.addObjectToCategory(ccmObject, category, ""); + } + } + + @Transactional(Transactional.TxType.REQUIRED) + protected Category getDomainModelCategory(final Domain domain) { + + Objects.requireNonNull(domain); + + final Domain catDomain = domainRepo + .findById(domain.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Domain with ID %d in the database.", + domain.getObjectId()))); + + return catDomain.getRoot(); + } + + @Transactional(Transactional.TxType.REQUIRED) + protected List getRootCategories(final Domain domain) { + + return getDomainModelCategory(domain).getSubCategories(); + } + + @Transactional(Transactional.TxType.REQUIRED) + protected List getSubCategories(final Category category) { + + Objects.requireNonNull(category); + + final Category cat = categoryRepo + .findById(category.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Category with ID %d in the database.", + category.getObjectId()))); + + return cat.getSubCategories(); + } + + @Transactional(Transactional.TxType.REQUIRED) + protected Category getParentCategory(final Category category) { + + Objects.requireNonNull(category); + + final Category cat = categoryRepo + .findById(category.getObjectId()) + .orElseThrow(() -> new IllegalArgumentException(String + .format("No Category with ID %d in the database.", + category.getObjectId()))); + + return cat.getParentCategory(); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/ItemCategoryPicker.java b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/ItemCategoryPicker.java new file mode 100755 index 000000000..2da9fdf75 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/ItemCategoryPicker.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.arsdigita.london.terms.ui; + +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.bebop.parameters.LongParameter; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.categorization.ui.ACSObjectCategoryForm; +import com.arsdigita.cms.CMS; + +import org.librecms.contentsection.ContentItem; + +import com.arsdigita.cms.ui.authoring.ItemCategoryForm; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.core.CcmObject; + +/** + * Replacement for cms authoring ItemCategoryForm which replaces the category + * widget with a terms based widget. + * + * Provides a ccm-cms specific concrete implementation of + * {@link com.arsdigita.london.terms.ui.ACSObjectCategoryPicker}. + * + * Is is activated / used by pointing the parameter + * {@code com.arsdigita.cms.category_authoring_add_form} to it. +*/ +public class ItemCategoryPicker extends ACSObjectCategoryPicker { + + private static final Logger LOGGER = LogManager + .getLogger(ItemCategoryPicker.class); + + public ItemCategoryPicker(final LongParameter root, + final StringParameter mode) { + super(root, mode); + LOGGER.debug("instantiating ItemCategoryPicker"); + + } + + /* + * @see com.arsdigita.london.terms.ui.ACSObjectCategoryPicker#getForm( + * com.arsdigita.bebop.parameters.BigDecimalParameter, + * com.arsdigita.bebop.parameters.StringParameter) + */ + @Override + protected ACSObjectCategoryForm getForm(final LongParameter root, + final StringParameter mode) { + LOGGER.debug("getForm"); + return new ItemCategoryForm(root, mode, new TermWidget(mode, this)); + } + + + /* + * @see com.arsdigita.london.terms.ui.ACSObjectCategoryPicker#getObject() + */ + @Override + protected CcmObject getObject(final PageState state) { + + return CMS.getContext().getContentItem(); + } + +} diff --git a/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/TermWidget.java b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/TermWidget.java new file mode 100644 index 000000000..24c86f308 --- /dev/null +++ b/ccm-cms/src/main/java/com/arsdigita/london/terms/ui/TermWidget.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 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.london.terms.ui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.form.Widget; +import com.arsdigita.bebop.parameters.ArrayParameter; +import com.arsdigita.bebop.parameters.BigDecimalParameter; +import com.arsdigita.bebop.parameters.StringParameter; + +import org.libreccm.categorization.Category; + +import com.arsdigita.cms.CMS; + +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentSection; +import org.libreccm.categorization.Domain; + +import com.arsdigita.xml.Element; +import com.arsdigita.xml.XML; + +import org.arsdigita.cms.CMSConfig; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.l10n.GlobalizationHelper; + +import java.util.ArrayList; + +/** + * A Widget for selecting Terms. Based heavily on CategoryWidget. + * + * @author mbooth@redhat.com + * @author Chris Gilbert + * @author Jens Pelzetter + * + * Chris Gilbert - updated to identify each node uniquely (correct behaviour for + * polyhierarchical trees) - also, allow ajax update on all branches or just top + * level branch + * + * nb - widget applies to allocation of categories to any ACSObject hence xml + * prefix should be more generic eg bebop rather than cms. cms retained for + * compatibility with existing stylesheets + * + * Jens Pelzetter: Variable naming etc changed to comply with usual Java + * conventions. Adapted to CCM NG. + */ +// NON Javadoc comment: +// Copied from c.ad.aplaws.ui in order to make forum-categorised independend from +// a specific ccm-???-aplaws, i.e. a specific integration layer. +public class TermWidget extends Widget { + + private final StringParameter mode; + private final ACSObjectCategoryPicker picker; + + public TermWidget(final StringParameter mode, + final ACSObjectCategoryPicker picker) { + + super(new ArrayParameter(new BigDecimalParameter("category"))); + + this.mode = mode; + this.picker = picker; + + } + + @Override + protected String getType() { + return "category"; + } + + @Override + public boolean isCompound() { + return false; + } + + @Override + protected void generateWidget(final PageState state, final Element parent) { + final Domain domain = picker.getDomain(state); + + final Element widget = parent.newChildElement("cms:categoryWidget", + CMS.CMS_XML_NS); + exportAttributes(widget); + + widget.addAttribute("mode", (String) state.getValue(mode)); + widget.addAttribute("name", getName()); + + final Set selectedCats = new HashSet<>(); + + //jensp 2015-03-12: In same cases we need to be able to pass the + //selected categories *and* the selected roots for displaying the + //categories nicely. To maintain backwards + //compatibility we check the type of the value and work either with the + //selected categories only or with the selected categories and their + //roots. + final Set selectedAncestors = new HashSet<>(); + + final Long[] values; + final Long[] selAncestorsValues; //selectedAncestors + final Object valueObj = getValue(state); + if (valueObj instanceof Long[][]) { + if (((Long[][]) valueObj).length >= 1) { + values = ((Long[][]) valueObj)[0]; + } else { + throw new IllegalArgumentException( + "Value of TermWidget is of type BigDecimal[][] but the array is empty."); + } + + if (((Long[][]) valueObj).length >= 2) { + selAncestorsValues = ((Long[][]) valueObj)[1]; + } else { + selAncestorsValues = null; + } + } else if (valueObj instanceof Long[]) { + values = (Long[]) valueObj; + selAncestorsValues = null; + } else { + throw new IllegalArgumentException( + "Value of TermWidget is not of type BigDecimal[] or BigDecimal[][]"); + } + + //BigDecimal[] values = (BigDecimal[]) getValue(state); + if (values != null) { + selectedCats.addAll(Arrays.asList(values)); + } + + if (selAncestorsValues != null) { + selectedAncestors.addAll(Arrays.asList(selAncestorsValues)); + } + + final Element selEl = widget.newChildElement( + "cms:selectedCategories", CMS.CMS_XML_NS); + selEl.addAttribute("name", getName()); + final Iterator selCats = selectedCats.iterator(); +// while (selCats.hasNext()) { +// final Element selCat = selEl.newChildElement("cms:category", +// CMS.CMS_XML_NS); +// selCat.addAttribute("id", selCats.next().toString()); +// } + for (Long selectedCat : selectedCats) { + final Element selectedCatElem = selEl.newChildElement( + "cms:category", CMS.CMS_XML_NS); + selectedCatElem.addAttribute("id", selectedCat.toString()); + } + + final Element selAncestorsElem = widget.newChildElement( + "cms:selectedAncestorCategories", CMS.CMS_XML_NS); + selAncestorsElem.addAttribute("name", getName()); + for (Long selAncestor : selectedAncestors) { + final Element selAncestorElem = selAncestorsElem.newChildElement( + "cms:category", CMS.CMS_XML_NS); + selAncestorElem.addAttribute("id", selAncestor.toString()); + } + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final CategoryPickerController controller = cdiUtil + .findBean(CategoryPickerController.class); + + // only root terms at first, the rest is loaded on-demand via AJAX + final List roots = controller.getRootCategories(domain); + + final Element element = generateCategory( + widget, + controller.getDomainModelCategory(domain), + selectedCats, + null); + + if (CMSConfig.getConfig().isCategoryPickerAjaxExpandAll()) { + // add attribute to the parent node, so that in stylesheet + // we can look for any ancestor with this attribute (can't + // add attribute to categoryWidget element as that is not + // visible when subbranches are transformed) + element.addAttribute("expand", "all"); + } + + for (final Category category : roots) { + + generateRootTerm(element, + category, + selectedCats, + category.getCategoryOrder()); + + } + } + + public static Element generateCategory(final Element parent, + final Category category, + final Set selected, + final Long sortKey) { + + final Element element = parent.newChildElement("cms:category", + CMS.CMS_XML_NS); + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final GlobalizationHelper globalizationHelper = cdiUtil + .findBean(GlobalizationHelper.class); + + element.addAttribute("id", XML.format(category.getObjectId())); + element.addAttribute("name", category.getName()); + element.addAttribute( + "description", + globalizationHelper.getValueFromLocalizedString(category + .getDescription())); + if (selected.contains(category.getObjectId())) { + element.addAttribute("isSelected", "1"); + } else { + element.addAttribute("isSelected", "0"); + } + if (category.isAbstractCategory()) { + element.addAttribute("isAbstract", "1"); + } else { + element.addAttribute("isAbstract", "0"); + } + if (category.isEnabled()) { + element.addAttribute("isEnabled", "1"); + } else { + element.addAttribute("isEnabled", "0"); + } + if (sortKey != null) { + element.addAttribute("sortKey", sortKey.toString()); + } + // sort order attribute added to every node so that we can + // correctly transform xml fragments returned by ajax + element.addAttribute("order", "sortKey"); + element.addAttribute("genCat", "true"); + + StringBuilder path = new StringBuilder(parent.getAttribute("fullname")); + if (path.length() > 0) { + path.append(" > "); + + } + path.append(category.getName()); + element.addAttribute("fullname", path.toString()); + + // need to uniquely identify each node in polyhierarchical trees + // so that expand/contract is applied to the correct node by + // javascript getElementByID function + StringBuilder nodeID = new StringBuilder(parent.getAttribute("node-id")); + if (nodeID.length() > 0) { + nodeID.append("-"); + + } + nodeID.append(category.getObjectId()); + element.addAttribute("node-id", nodeID.toString()); + + return element; + } + + public static Element generateTerm(final Element parent, + final Category category, + final Set selected, + final Long sortKey) { + final Element element = generateCategory(parent, + category, + selected, + sortKey); + + element.addAttribute("pid", category.getUniqueId()); + return element; + } + + private static void generateRootTerm(final Element parent, + final Category term, + final Set selected, + final Long sortKey) { + final Element element = generateTerm(parent, term, selected, sortKey); + element.addAttribute("root", "1"); + } + + public static void generateSubtree(final Element parent, + final Category root, + final Set ids) { + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final CategoryPickerController controller = cdiUtil + .findBean(CategoryPickerController.class); + + final List terms = controller.getSubCategories(root); + + final Map> children = new HashMap<>(); + for (final Category term : terms) { + final Long parentId = controller + .getParentCategory(term) + .getObjectId(); + + final List childList; + if (children.containsKey(parentId)) { + childList = children.get(parentId); + } else { + childList = new ArrayList<>(); + children.put(parentId, childList); + } + + childList.add(term); + } + + final Element element = generateCategory(parent, root, ids, null); + element.addAttribute("fullname", root.getName()); + element.addAttribute("node-id", Long.toString(root.getObjectId())); + element.addAttribute("order", "sortKey"); + if (CMSConfig.getConfig().isCategoryPickerAjaxExpandAll()) { + //recognisable attribute has to be in the XML for each snippet that + //is transformed, hence add it to the parent + element.addAttribute("expand", "all"); + } + + if (children.containsKey(root.getObjectId())) { + final List roots = children.get(root.getObjectId()); + for (final Category category : roots) { + generateTermWithChildren(element, + category, + ids, + category.getCategoryOrder(), + children); + } + } + } + + private static void generateTermWithChildren( + final Element parent, + final Category category, + final Set selected, + final Long sortKey, + final Map> children) { + + final Element element = generateCategory(parent, + category, + selected, + sortKey); + + element.addAttribute("pid", category.getUniqueId()); + + if (children.containsKey(category.getObjectId())) { + final List childs = children.get(category.getObjectId()); + for (final Category child : childs) { + if (CMSConfig.getConfig().isCategoryPickerAjaxExpandAll()) { + generateTerm(element, + child, + selected, + child.getCategoryOrder()); + } else { + generateTermWithChildren(element, + child, + selected, + child.getCategoryOrder(), + children); + } + } + } + } +} diff --git a/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java b/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java index c1906a7a3..838266bc9 100644 --- a/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java +++ b/ccm-cms/src/main/java/org/arsdigita/cms/CMSConfig.java @@ -19,6 +19,8 @@ package org.arsdigita.cms; import com.arsdigita.bebop.form.DHTMLEditor; +import com.arsdigita.cms.ui.authoring.ItemCategoryExtension; +import com.arsdigita.cms.ui.authoring.ItemCategoryForm; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.configuration.Configuration; @@ -369,6 +371,16 @@ public class CMSConfig { @Setting private int xmlCacheAge = 60 * 60 * 24; + @Setting + private String categoryAuthoringAddForm = ItemCategoryForm.class.getName(); + + @Setting + private String categoryAuthoringExtension = ItemCategoryExtension.class + .getName(); + + @Setting + private boolean categoryPickerAjaxExpandAll = false; + /** * Max length of the description of a link (in database max length are 4000 * characters) @@ -929,4 +941,32 @@ public class CMSConfig { this.linkDescMaxLength = linkDescMaxLength; } + public String getCategoryAuthoringAddForm() { + return categoryAuthoringAddForm; + } + + public void setCategoryAuthoringAddForm( + final String categoryAuthoringAddForm) { + + this.categoryAuthoringAddForm = categoryAuthoringAddForm; + } + + public String getCategoryAuthoringExtension() { + return categoryAuthoringExtension; + } + + public void setCategoryAuthoringExtension( + final String categoryAuthoringExtension) { + this.categoryAuthoringExtension = categoryAuthoringExtension; + } + + public boolean isCategoryPickerAjaxExpandAll() { + return categoryPickerAjaxExpandAll; + } + + public void setCategoryPickerAjaxExpandAll( + final boolean categoryPickerAjaxExpandAll) { + this.categoryPickerAjaxExpandAll = categoryPickerAjaxExpandAll; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Category.java b/ccm-core/src/main/java/org/libreccm/categorization/Category.java index 36b648ce8..5844c8359 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Category.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Category.java @@ -28,10 +28,10 @@ import org.libreccm.l10n.LocalizedString; import org.libreccm.portation.Portable; import org.libreccm.security.RecursivePermissions; -import javax.persistence.*; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +41,22 @@ import java.util.Objects; import static org.libreccm.categorization.CategorizationConstants.CAT_XML_NS; import static org.libreccm.core.CoreConstants.DB_SCHEMA; +import javax.persistence.AssociationOverride; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.Table; + /** * The category entity represents a single category. Each category is part of a * {@link Domain}. A category can be assigned to multiple {@link CcmObject}s. diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Domain.java b/ccm-core/src/main/java/org/libreccm/categorization/Domain.java index 582c4b7f8..cdb1dec7f 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Domain.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Domain.java @@ -30,17 +30,40 @@ import org.libreccm.portation.Portable; import org.libreccm.security.RecursivePermissions; import org.libreccm.web.CcmApplication; -import javax.persistence.*; import javax.validation.constraints.Pattern; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; + import java.io.Serializable; -import java.util.*; import static org.libreccm.categorization.CategorizationConstants.CAT_XML_NS; import static org.libreccm.core.CoreConstants.DB_SCHEMA; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import javax.persistence.AssociationOverride; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.NamedSubgraph; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + /** * A domain is collection of categories designed a specific purpose. This entity * replaces the {@code Domain} entity from the old {@code ccm-ldn-terms} module