From ee7280dc6bd48f63286b7dd4a24b53c18daa66ce Mon Sep 17 00:00:00 2001 From: jensp Date: Fri, 7 Apr 2017 18:41:40 +0000 Subject: [PATCH] CCM NG/ccm-cms: AssetForm including localisation for assets git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4663 8810af33-2d31-482b-a856-94f89814c4df --- .../arsdigita/cms/ui/assets/AssetForm.java | 280 ++++++++++++-- .../arsdigita/cms/ui/assets/AssetPane.java | 2 +- .../cms/ui/assets/forms/BookmarkForm.java | 78 +++- .../ui/assets/forms/LegalMetadataForm.java | 86 ++++- .../org/librecms/assets/AssetL10NManager.java | 344 ++++++++++++++++++ .../ContentItemL10NManager.java | 33 +- .../ContentItemL10NManagerTest.java | 12 +- 7 files changed, 751 insertions(+), 84 deletions(-) create mode 100644 ccm-cms/src/main/java/org/librecms/assets/AssetL10NManager.java diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetForm.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetForm.java index 2fa891ea1..66ce3a45f 100644 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetForm.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetForm.java @@ -18,18 +18,27 @@ */ package com.arsdigita.cms.ui.assets; +import com.arsdigita.bebop.BoxPanel; import com.arsdigita.bebop.ColumnPanel; import com.arsdigita.bebop.Form; import com.arsdigita.bebop.FormProcessException; import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.Page; import com.arsdigita.bebop.PageState; import com.arsdigita.bebop.SaveCancelSection; import com.arsdigita.bebop.SingleSelectionModel; +import com.arsdigita.bebop.Text; 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.PrintEvent; +import com.arsdigita.bebop.event.PrintListener; +import com.arsdigita.bebop.form.Option; +import com.arsdigita.bebop.form.SingleSelect; +import com.arsdigita.bebop.form.Submit; import com.arsdigita.bebop.form.TextField; +import com.arsdigita.bebop.parameters.StringParameter; import com.arsdigita.globalization.GlobalizedMessage; import com.arsdigita.kernel.KernelConfig; @@ -39,9 +48,16 @@ import org.librecms.contentsection.Asset; import org.librecms.contentsection.AssetRepository; import java.util.Optional; + import org.libreccm.categorization.CategoryManager; +import org.libreccm.core.UnexpectedErrorException; +import org.librecms.assets.AssetL10NManager; import org.librecms.contentsection.Folder; -import org.librecms.contentsection.FolderManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.TooManyListenersException; /** * @@ -55,6 +71,15 @@ public abstract class AssetForm extends Form implements FormInitListener, private final AssetPane assetPane; private final SingleSelectionModel selectionModel; +// private final StringParameter selectedLocaleParameter; + + private BoxPanel showLocalePanel; + private SingleSelect showLocaleSelect; + private Submit showLocaleSubmit; + + private BoxPanel addLocalePanel; + private SingleSelect addLocaleSelect; + private Submit addLocaleSubmit; private TextField title; private SaveCancelSection saveCancelSection; @@ -64,11 +89,146 @@ public abstract class AssetForm extends Form implements FormInitListener, this.assetPane = assetPane; selectionModel = assetPane.getSelectedAssetModel(); +// selectedLocaleParameter = new StringParameter("selected-locale"); initComponents(); } private void initComponents() { + + showLocalePanel = new BoxPanel(BoxPanel.HORIZONTAL); + final Label showLocaleLabel = new Label(new PrintListener() { + + @Override + public void prepare(final PrintEvent event) { + final PageState state = event.getPageState(); + final Optional selectedAsset = getSelectedAsset(state); + final Label target = (Label) event.getTarget(); + if (selectedAsset.isPresent()) { + target.setLabel(new GlobalizedMessage( + "cms.ui.assest.show_locale", + CmsConstants.CMS_BUNDLE)); + } else { + target.setLabel(new GlobalizedMessage( + "cms.ui.assest.initial_locale", + CmsConstants.CMS_BUNDLE)); + } + } + + } + ); + showLocaleSelect = new SingleSelect("selected-locale"); + try { + showLocaleSelect.addPrintListener(new PrintListener() { + + @Override + public void prepare(final PrintEvent event) { + final PageState state = event.getPageState(); + + final Optional selectedAsset + = getSelectedAsset(state); + if (selectedAsset.isPresent()) { + final SingleSelect target = (SingleSelect) event + .getTarget(); + + target.clearOptions();; + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final AssetL10NManager l10nManager = cdiUtil + .findBean(AssetL10NManager.class); + final List availableLocales = new ArrayList<>( + l10nManager.availableLocales(selectedAsset.get())); + availableLocales.sort((locale1, locale2) -> { + return locale1 + .toString() + .compareTo(locale2.toString()); + }); + availableLocales.forEach(locale -> target.addOption( + new Option(locale.toString(), + new Text(locale.toString())))); + } else { + final SingleSelect target = (SingleSelect) event + .getTarget(); + + target.clearOptions(); + + final List langs = new ArrayList<>( + KernelConfig.getConfig().getSupportedLanguages()); + langs.sort((lang1, lang2) -> lang1.compareTo(lang2)); + + langs.forEach(lang -> { + target.addOption(new Option(lang, new Text(lang))); + }); + } + } + + }); + } catch (TooManyListenersException ex) { + throw new UnexpectedErrorException(ex); + } + showLocaleSubmit = new Submit(new GlobalizedMessage( + "cms.ui.asset.show_locale", + CmsConstants.CMS_BUNDLE)); + showLocalePanel.add(showLocaleLabel); + showLocalePanel.add(showLocaleSelect); + showLocalePanel.add(showLocaleSubmit); + add(showLocalePanel); + + addLocalePanel = new BoxPanel(BoxPanel.HORIZONTAL) { + + @Override + public boolean isVisible(final PageState state) { + return getSelectedAsset(state).isPresent(); + } + + }; + final Label addLocaleLabel = new Label( + new GlobalizedMessage("cms.ui.assest.add_locale", + CmsConstants.CMS_BUNDLE)); + addLocaleSelect = new SingleSelect("add-locale-select"); + try { + addLocaleSelect.addPrintListener(new PrintListener() { + + @Override + public void prepare(final PrintEvent event) { + final PageState state = event.getPageState(); + + final Optional selectedAsset + = getSelectedAsset(state); + if (selectedAsset.isPresent()) { + final SingleSelect target = (SingleSelect) event + .getTarget(); + + target.clearOptions(); + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final AssetL10NManager l10nManager = cdiUtil + .findBean(AssetL10NManager.class); + final List creatableLocales = new ArrayList<>( + l10nManager.creatableLocales(selectedAsset.get())); + creatableLocales.sort((locale1, locale2) -> { + return locale1 + .toString() + .compareTo(locale2.toString()); + }); + creatableLocales.forEach(locale -> target.addOption( + new Option(locale.toString(), + new Text(locale.toString())))); + } + } + + }); + } catch (TooManyListenersException ex) { + throw new UnexpectedErrorException(ex); + } + addLocaleSubmit = new Submit(new GlobalizedMessage( + "cms.ui.asset.add_locale", + CmsConstants.CMS_BUNDLE)); + addLocalePanel.add(addLocaleLabel); + addLocalePanel.add(addLocaleSelect); + addLocalePanel.add(addLocaleSubmit); + add(addLocalePanel); + add(new Label(new GlobalizedMessage("cms.ui.asset.title", CmsConstants.CMS_BUNDLE))); title = new TextField(ASSET_TITLE); @@ -88,6 +248,15 @@ public abstract class AssetForm extends Form implements FormInitListener, //Nothing here } +// @Override +// public void register(final Page page) { +// super.register(page); +// +// page.addComponentStateParam(this, selectedLocaleParameter); +// +// page.setVisibleDefault(showLocalePanel, true); +// page.setVisibleDefault(addLocalePanel, true); +// } protected String getTitle(final PageState state) { return (String) title.getValue(state); } @@ -99,13 +268,13 @@ public abstract class AssetForm extends Form implements FormInitListener, } else { final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); final AssetRepository assetRepo = cdiUtil.findBean( - AssetRepository.class); + AssetRepository.class); final Asset asset = assetRepo - .findById(selectionModel.getSelectedKey(state)) - .orElseThrow(() -> new IllegalArgumentException(String. - format( - "No asset with ID %d in the database.", - selectionModel.getSelectedKey(state)))); + .findById(selectionModel.getSelectedKey(state)) + .orElseThrow(() -> new IllegalArgumentException(String. + format( + "No asset with ID %d in the database.", + selectionModel.getSelectedKey(state)))); return Optional.of(asset); } } @@ -118,23 +287,78 @@ public abstract class AssetForm extends Form implements FormInitListener, final Optional selectedAsset = getSelectedAsset(state); if (selectedAsset.isPresent()) { +// showLocalePanel.setVisible(state, true); +// addLocalePanel.setVisible(state, true); + + showLocaleSelect.setValue(state, + KernelConfig + .getConfig() + .getDefaultLocale() + .toString()); + title.setValue(state, selectedAsset - .get() - .getTitle() - .getValue(KernelConfig - .getConfig() - .getDefaultLocale())); + .get() + .getTitle() + .getValue(getSelectedLocale(state))); + } else { + showLocaleSelect.setValue(state, + KernelConfig + .getConfig() + .getDefaultLocale() + .toString()); +// showLocalePanel.setVisible(state, false); +// addLocalePanel.setVisible(state, false); } + initForm(state, selectedAsset); } + protected Locale getSelectedLocale(final PageState state) { + final String selected = (String) showLocaleSelect.getValue(state); + if (selected == null) { + return KernelConfig.getConfig().getDefaultLocale(); + } else { + return new Locale(selected); + } + } + + protected abstract void initForm(final PageState state, + final Optional selectedAsset); + @Override public void process(final FormSectionEvent event) - throws FormProcessException { + throws FormProcessException { final PageState state = event.getPageState(); + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + + if (showLocaleSubmit.isSelected(state)) { + + final Optional selectedAsset = getSelectedAsset(state); + + if (selectedAsset.isPresent()) { + + title.setValue(state, + selectedAsset + .get() + .getTitle() + .getValue(getSelectedLocale(state))); + showLocale(state); + } + return; + } + + if (addLocaleSubmit.isSelected(state)) { + final AssetL10NManager l10nManager = cdiUtil + .findBean(AssetL10NManager.class); + final Locale add = new Locale((String) addLocaleSelect + .getValue(state)); + final Optional selectedAsset = getSelectedAsset(state); + l10nManager.addLanguage(selectedAsset.get(), add); + } + if (saveCancelSection.getSaveButton().isSelected(state)) { final Optional selectedAsset = getSelectedAsset(state); final Asset asset; @@ -145,46 +369,46 @@ public abstract class AssetForm extends Form implements FormInitListener, asset = createAsset(state); } - asset.getTitle().addValue( - KernelConfig.getConfig().getDefaultLocale(), - (String) title.getValue(state)); + asset.getTitle().addValue(getSelectedLocale(state), + (String) title.getValue(state)); - final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); final AssetRepository assetRepo = cdiUtil - .findBean(AssetRepository.class); + .findBean(AssetRepository.class); assetRepo.save(asset); if (!selectedAsset.isPresent()) { //Set display name asset.setDisplayName((String) title.getValue(state)); assetRepo.save(asset); - + //Add new asset to currently selected folder final Folder selectedFolder = assetPane - .getFolderSelectionModel() - .getSelectedObject(state); + .getFolderSelectionModel() + .getSelectedObject(state); final CategoryManager categoryManager = cdiUtil - .findBean(CategoryManager.class); + .findBean(CategoryManager.class); categoryManager.addObjectToCategory( - asset, - selectedFolder, - CmsConstants.CATEGORIZATION_TYPE_FOLDER); + asset, + selectedFolder, + CmsConstants.CATEGORIZATION_TYPE_FOLDER); } assetPane.browseMode(state); } } + protected abstract void showLocale(final PageState state); + protected abstract Asset createAsset(final PageState state) - throws FormProcessException; + throws FormProcessException; protected abstract void updateAsset(final Asset asset, final PageState state) - throws FormProcessException; + throws FormProcessException; @Override public void submitted(final FormSectionEvent event) - throws FormProcessException { + throws FormProcessException { final PageState state = event.getPageState(); diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetPane.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetPane.java index 6c2c6acba..92bd3e9e7 100644 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetPane.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/AssetPane.java @@ -501,7 +501,7 @@ public class AssetPane extends LayoutPanel implements Resettable { actionsSegment.add(newAssetForm); - final MetaForm editAssetForm = new MetaForm(MOVE) { + final MetaForm editAssetForm = new MetaForm("editAsset") { @Override public Form buildForm(final PageState state) { diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/BookmarkForm.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/BookmarkForm.java index 772b56815..4bcf5d2de 100644 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/BookmarkForm.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/BookmarkForm.java @@ -38,6 +38,7 @@ import org.librecms.contentsection.Asset; import java.net.MalformedURLException; import java.net.URL; import java.util.Objects; +import java.util.Optional; /** * @@ -56,8 +57,8 @@ public class BookmarkForm extends AssetForm { protected void addWidgets() { add(new Label( - new GlobalizedMessage("cms.ui.assets.bookmark.description", - CmsConstants.CMS_BUNDLE))); + new GlobalizedMessage("cms.ui.assets.bookmark.description", + CmsConstants.CMS_BUNDLE))); description = new TextArea("bookmark-description"); add(description); @@ -70,7 +71,7 @@ public class BookmarkForm extends AssetForm { @Override public void validate(final FormSectionEvent event) - throws FormProcessException { + throws FormProcessException { final PageState state = event.getPageState(); final FormData data = event.getFormData(); @@ -79,8 +80,8 @@ public class BookmarkForm extends AssetForm { new URL((String) url.getValue(state)); } catch (MalformedURLException ex) { data.addError(new GlobalizedMessage( - "cms.ui.assets.bookmark.url.malformed", - CmsConstants.CMS_BUNDLE)); + "cms.ui.assets.bookmark.url.malformed", + CmsConstants.CMS_BUNDLE)); } } @@ -88,18 +89,63 @@ public class BookmarkForm extends AssetForm { } + @Override + protected void initForm(final PageState state, + final Optional selectedAsset) { + + if (selectedAsset.isPresent()) { + + if (!(selectedAsset.get() instanceof Bookmark)) { + throw new IllegalArgumentException(String.format( + "The provided asset must be an instanceof of class '%s' or " + + "an subclass but is an instanceof of class '%s'.", + Bookmark.class.getName(), + selectedAsset.get().getClass().getName())); + } + + final Bookmark bookmark = (Bookmark) selectedAsset.get(); + + description.setValue(state, + bookmark + .getDescription() + .getValue(getSelectedLocale(state))); + url.setValue(state, bookmark.getUrl()); + + } + + } + + @Override + protected void showLocale(final PageState state) { + final Optional selectedAsset = getSelectedAsset(state); + + if (selectedAsset.isPresent()) { + if (!(getSelectedAsset(state).get() instanceof Bookmark)) { + throw new IllegalArgumentException( + "Selected asset is not a bookmark"); + } + + final Bookmark bookmark = (Bookmark) selectedAsset.get(); + + description.setValue(state, + bookmark + .getDescription() + .getValue(getSelectedLocale(state))); + } + } + @Override protected Asset createAsset(final PageState state) - throws FormProcessException { + throws FormProcessException { Objects.requireNonNull(state); final Bookmark bookmark = new Bookmark(); bookmark - .getDescription() - .addValue(KernelConfig.getConfig().getDefaultLocale(), - (String) description.getValue(state)); + .getDescription() + .addValue(getSelectedLocale(state), + (String) description.getValue(state)); bookmark.setUrl((String) url.getValue(state)); @@ -108,25 +154,25 @@ public class BookmarkForm extends AssetForm { @Override protected void updateAsset(final Asset asset, final PageState state) - throws FormProcessException { + throws FormProcessException { Objects.requireNonNull(asset); Objects.requireNonNull(state); if (!(asset instanceof Bookmark)) { throw new IllegalArgumentException(String.format( - "Provided asset is not an instance of class (or sub class of) " + "Provided asset is not an instance of class (or sub class of) " + "'%s' but is an instance of class '%s'", - Bookmark.class.getName(), - asset.getClass().getName())); + Bookmark.class.getName(), + asset.getClass().getName())); } final Bookmark bookmark = (Bookmark) asset; bookmark - .getDescription() - .addValue(KernelConfig.getConfig().getDefaultLocale(), - (String) description.getValue(state)); + .getDescription() + .addValue(getSelectedLocale(state), + (String) description.getValue(state)); bookmark.setUrl((String) url.getValue(state)); } diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/LegalMetadataForm.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/LegalMetadataForm.java index b1395322f..294179e37 100644 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/LegalMetadataForm.java +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/assets/forms/LegalMetadataForm.java @@ -29,10 +29,13 @@ import com.arsdigita.globalization.GlobalizedMessage; import com.arsdigita.kernel.KernelConfig; import org.librecms.CmsConstants; +import org.librecms.assets.Bookmark; import org.librecms.assets.LegalMetadata; import org.librecms.contentsection.Asset; +import java.util.Locale; import java.util.Objects; +import java.util.Optional; /** * @@ -53,42 +56,87 @@ public class LegalMetadataForm extends AssetForm { protected void addWidgets() { add(new Label(new GlobalizedMessage( - "cms.ui.assets.legalmetadata.rightsholder", - CmsConstants.CMS_BUNDLE))); + "cms.ui.assets.legalmetadata.rightsholder", + CmsConstants.CMS_BUNDLE))); rightsHolder = new TextArea("legalmetadata-rightsholder"); add(rightsHolder); add(new Label(new GlobalizedMessage( - "cms.ui.assets.legalmetadata.rights", - CmsConstants.CMS_BUNDLE))); + "cms.ui.assets.legalmetadata.rights", + CmsConstants.CMS_BUNDLE))); rights = new TextArea("legalmetadata-rights"); add(rights); add(new Label(new GlobalizedMessage( - "cms.ui.assets.legalmetadata.publisher", - CmsConstants.CMS_BUNDLE))); + "cms.ui.assets.legalmetadata.publisher", + CmsConstants.CMS_BUNDLE))); publisher = new TextArea("legalmetadata-rights"); add(publisher); add(new Label(new GlobalizedMessage( - "cms.ui.assets.legalmetadata.creator", - CmsConstants.CMS_BUNDLE))); + "cms.ui.assets.legalmetadata.creator", + CmsConstants.CMS_BUNDLE))); creator = new TextArea("legalmetadata-creator"); add(creator); } + @Override + protected void initForm(final PageState state, + final Optional selectedAsset) { + + if (selectedAsset.isPresent()) { + + if (!(selectedAsset.get() instanceof LegalMetadata)) { + throw new IllegalArgumentException(String.format( + "The provided asset must be an instanceof of class '%s' or " + + "an subclass but is an instanceof of class '%s'.", + LegalMetadata.class.getName(), + selectedAsset.get().getClass().getName())); + } + + final LegalMetadata legalMetadata = (LegalMetadata) selectedAsset + .get(); + + rightsHolder.setValue(state, legalMetadata.getRightsHolder()); + rights.setValue(state, + legalMetadata + .getRights() + .getValue(getSelectedLocale(state))); + publisher.setValue(state, legalMetadata.getPublisher()); + creator.setValue(state, legalMetadata.getCreator()); + } + } + + @Override + protected void showLocale(final PageState state) { + final Optional selectedAsset = getSelectedAsset(state); + + if (selectedAsset.isPresent()) { + if (!(getSelectedAsset(state).get() instanceof LegalMetadata)) { + throw new IllegalArgumentException( + "Selected asset is not a legal metadata"); + } + + final LegalMetadata legalMetadata = (LegalMetadata) selectedAsset.get(); + + rights.setValue(state, + legalMetadata + .getRights() + .getValue(getSelectedLocale(state))); + } + } + @Override protected Asset createAsset(final PageState state) - throws FormProcessException { + throws FormProcessException { Objects.requireNonNull(state); final LegalMetadata legalMetadata = new LegalMetadata(); legalMetadata.setRightsHolder((String) rightsHolder.getValue(state)); - legalMetadata.getRights().addValue( - KernelConfig.getConfig().getDefaultLocale(), - (String) rights.getValue(state)); + legalMetadata.getRights().addValue(getSelectedLocale(state), + (String) rights.getValue(state)); legalMetadata.setPublisher((String) publisher.getValue(state)); legalMetadata.setCreator((String) creator.getValue(state)); @@ -98,25 +146,25 @@ public class LegalMetadataForm extends AssetForm { @Override protected void updateAsset(final Asset asset, final PageState state) - throws FormProcessException { + throws FormProcessException { Objects.requireNonNull(asset); Objects.requireNonNull(state); if (!(asset instanceof LegalMetadata)) { throw new IllegalArgumentException(String.format( - "Provided asset is not an instance of '%s' (or a sub class) " + "Provided asset is not an instance of '%s' (or a sub class) " + "but is an instance of class '%s'.", - LegalMetadata.class.getName(), - asset.getClass().getName())); + LegalMetadata.class + .getName(), + asset.getClass().getName())); } final LegalMetadata legalMetadata = (LegalMetadata) asset; legalMetadata.setRightsHolder((String) rightsHolder.getValue(state)); - legalMetadata.getRights().addValue( - KernelConfig.getConfig().getDefaultLocale(), - (String) rights.getValue(state)); + legalMetadata.getRights().addValue(getSelectedLocale(state), + (String) rights.getValue(state)); legalMetadata.setPublisher((String) publisher.getValue(state)); legalMetadata.setCreator((String) creator.getValue(state)); diff --git a/ccm-cms/src/main/java/org/librecms/assets/AssetL10NManager.java b/ccm-cms/src/main/java/org/librecms/assets/AssetL10NManager.java new file mode 100644 index 000000000..028fc43ef --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/assets/AssetL10NManager.java @@ -0,0 +1,344 @@ +/* + * 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 org.librecms.assets; + +import com.arsdigita.kernel.KernelConfig; + +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.l10n.LocalizedString; +import org.libreccm.security.AuthorizationRequired; +import org.libreccm.security.PermissionChecker; +import org.libreccm.security.RequiresPrivilege; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetRepository; +import org.librecms.contentsection.privileges.AssetPrivileges; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; + +/** + * Manages the language versions of an asset. + * + * @author Jens Pelzetter + */ +@RequestScoped +public class AssetL10NManager { + + @Inject + private ConfigurationManager confManager; + + @Inject + private AssetRepository assetRepo; + + @Inject + private PermissionChecker permissionChecker; + + private Locale defaultLocale; + private List supportedLocales; + + @PostConstruct + private void init() { + final KernelConfig kernelConfig = confManager.findConfiguration( + KernelConfig.class); + defaultLocale = kernelConfig.getDefaultLocale(); + supportedLocales = kernelConfig.getSupportedLanguages() + .stream() + .map(language -> new Locale(language)) + .collect(Collectors.toList()); + } + + private List findLocalizedStringProperties( + final Asset asset) { + + try { + final PropertyDescriptor[] properties = Introspector + .getBeanInfo(asset.getClass()) + .getPropertyDescriptors(); + + return Arrays.stream(properties) + .filter(property -> { + return property + .getPropertyType() + .isAssignableFrom(LocalizedString.class); + }) + .collect(Collectors.toList()); + } catch (IntrospectionException ex) { + throw new UnexpectedErrorException(ex); + } + } + + private LocalizedString readLocalizedString(final Asset asset, + final Method readMethod) { + try { + return (LocalizedString) readMethod.invoke(asset); + } catch (IllegalAccessException + | IllegalArgumentException + | InvocationTargetException ex) { + throw new UnexpectedErrorException(ex); + } + } + + private Set collectLanguages(final Asset asset) { + + final Set locales = new HashSet<>(); + + findLocalizedStringProperties(asset) + .stream() + .map(property -> property.getReadMethod()) + .map(readMethod -> readLocalizedString(asset, readMethod)) + .forEach(str -> locales.addAll(str.getAvailableLocales())); + + return locales; + } + + /** + * Helper method for reading methods in this class for verifying that the + * current user is permitted to read the item. + * + * @param asset The asset for which the read permission is verified + */ + private void checkReadPermission(final Asset asset) { + + final String requiredPrivilege = AssetPrivileges.VIEW; + + permissionChecker.checkPermission(requiredPrivilege, asset); + } + + /** + * Retrieves all languages in which an asset is available. + * + * @param asset The asset. + * + * @return An (unmodifiable) {@link Set} containing all languages in which + * the asset is available. + */ + @Transactional(Transactional.TxType.REQUIRED) + public Set availableLocales(final Asset asset) { + + checkReadPermission(asset); + return Collections.unmodifiableSet(collectLanguages(asset)); + } + + /** + * Checks if an asset has data for particular language. + * + * @param asset The asset to check. + * @param locale The locale to check for. + * + * @return {@link true} if the item has data for the language, {@code false} + * if not. + */ + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public boolean hasLanguage( + @RequiresPrivilege(AssetPrivileges.VIEW) + final Asset asset, + final Locale locale) { + + Objects.requireNonNull(asset, + "Can't check if asset null has a specific locale."); + Objects.requireNonNull(locale, "Can't check for locale null."); + + checkReadPermission(asset); + + return collectLanguages(asset).contains(locale); + } + + /** + * Returns a {@link Set} containing the locales for which the asset does not + * yet have a variant. + * + * @param asset The asset. + * + * @return A {@link Set} with the locales for which the item does not have a + * variant. + */ + @Transactional(Transactional.TxType.REQUIRED) + public Set creatableLocales(final Asset asset) { + checkReadPermission(asset); + + return supportedLocales.stream() + .filter(locale -> !hasLanguage(asset, locale)) + .collect(Collectors.toSet()); + } + + /** + * Adds a language to an asset. The method will retrieve all fields of the + * type {@link LocalizedString} from the asset and add a new entry for the + * provided locale by coping the value for the default language configured + * in {@link KernelConfig}. If a field does not have an entry for the + * default language the first value found is used. + * + * @param asset The asset to which the language variant is added. + * @param locale The locale of the language to add. + */ + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public void addLanguage( + @RequiresPrivilege(AssetPrivileges.EDIT) + final Asset asset, + final Locale locale) { + + Objects.requireNonNull(asset, "Can't add language to asset null."); + Objects.requireNonNull(asset, "Cant't add language null to an asset."); + + findLocalizedStringProperties(asset) + .forEach(property -> addLanguage(asset, locale, property)); + + assetRepo.save(asset); + } + + private void addLanguage(final Asset asset, + final Locale locale, + final PropertyDescriptor property) { + + final Method readMethod = property.getReadMethod(); + final LocalizedString localizedStr = readLocalizedString(asset, + readMethod); + addLanguage(localizedStr, locale); + } + + private void addLanguage(final LocalizedString localizedString, + final Locale locale) { + if (localizedString.hasValue(locale)) { + //Nothing to do + return; + } + + final String value; + if (localizedString.hasValue(defaultLocale)) { + value = localizedString.getValue(defaultLocale); + } else { + value = findValue(localizedString); + } + + localizedString.addValue(locale, value); + } + + private String findValue(final LocalizedString localizedStr) { + final Optional locale = supportedLocales + .stream() + .filter(current -> localizedStr.hasValue(current)) + .findAny(); + + if (locale.isPresent()) { + return localizedStr.getValue(locale.get()); + } else { + return "Lorem ipsum"; + } + } + + /** + * Removes a language variant from an asset. This method will retrieve all + * fields of the type {@link LocalizedString} from the asset and remove the + * entry for the provided locale if the field has an entry for that locale. + * + * @param asset + * @param locale + */ + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public void removeLanguage( + @RequiresPrivilege(AssetPrivileges.EDIT) + final Asset asset, + final Locale locale) { + + Objects.requireNonNull(asset, "Can't remove language to asset null."); + Objects + .requireNonNull(asset, "Cant't remove language null to an asset."); + + findLocalizedStringProperties(asset) + .forEach(property -> removeLanguage(asset, locale, property)); + + assetRepo.save(asset); + } + + private void removeLanguage(final Asset asset, + final Locale locale, + final PropertyDescriptor property) { + + final Method readMethod = property.getReadMethod(); + + final LocalizedString localizedStr = readLocalizedString(asset, + readMethod); + if (localizedStr.hasValue(locale)) { + localizedStr.removeValue(locale); + } + } + + /** + * This method normalises the values of the fields of type + * {@link LocalizedString} of an asset. The method will first retrieve all + * fields of the type {@link LocalizedString} from the asset and than build + * a set with all locales provided by any of the fields. After that the + * method will iterate over all {@link LocalizedString} fields and check if + * the {@link LocalizedString} has an entry for every language in the set. + * If not an entry for the language is added. + * + * @param asset The asset to normalise. + */ + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public void normalizeLanguages( + @RequiresPrivilege(AssetPrivileges.EDIT) + final Asset asset) { + + Objects.requireNonNull(asset, "Can't normalise asset null"); + + final Set languages = collectLanguages(asset); + + findLocalizedStringProperties(asset) + .stream() + .map(property -> property.getReadMethod()) + .map(readMethod -> readLocalizedString(asset, readMethod)) + .forEach(str -> normalize(str, languages)); + } + + private void normalize(final LocalizedString localizedString, + final Set languages) { + + final List missingLangs = languages.stream() + .filter(lang -> !localizedString.hasValue(lang)) + .collect(Collectors.toList()); + + if (!missingLangs.isEmpty()) { + missingLangs.stream() + .forEach(lang -> addLanguage(localizedString, lang)); + } + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemL10NManager.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemL10NManager.java index 2125cfaa1..6cf94e015 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemL10NManager.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentItemL10NManager.java @@ -83,11 +83,16 @@ public class ContentItemL10NManager { final ContentItem item) { try { - return Arrays.stream( - Introspector.getBeanInfo(item.getClass()) - .getPropertyDescriptors()) - .filter(property -> property.getPropertyType().isAssignableFrom( - LocalizedString.class)) + final PropertyDescriptor[] properties = Introspector + .getBeanInfo(item.getClass()) + .getPropertyDescriptors(); + + return Arrays.stream(properties) + .filter(property -> { + return property + .getPropertyType() + .isAssignableFrom(LocalizedString.class); + }) .collect(Collectors.toList()); } catch (IntrospectionException ex) { throw new UnexpectedErrorException(ex); @@ -106,6 +111,7 @@ public class ContentItemL10NManager { } private Set collectLanguages(final ContentItem item) { + final Set locales = new HashSet<>(); findLocalizedStringProperties(item) @@ -146,7 +152,7 @@ public class ContentItemL10NManager { } /** - * Retrieves all languages in which content item is available. + * Retrieves all languages in which a content item is available. * * @param item The item. * @@ -154,8 +160,7 @@ public class ContentItemL10NManager { * the item is available. */ @Transactional(Transactional.TxType.REQUIRED) - public Set availableLanguages( - final ContentItem item) { + public Set availableLanguages(final ContentItem item) { checkReadPermission(item); return Collections.unmodifiableSet(collectLanguages(item)); @@ -205,7 +210,7 @@ public class ContentItemL10NManager { checkReadPermission(item); return supportedLocales.stream() - .filter(locale -> hasLanguage(item, locale)) + .filter(locale -> !hasLanguage(item, locale)) .collect(Collectors.toSet()); } @@ -329,7 +334,7 @@ public class ContentItemL10NManager { } findLocalizedStringProperties(item) - .forEach(property -> ContentItemL10NManager.this.removeLanguage(item, locale, property)); + .forEach(property -> removeLanguage(item, locale, property)); itemRepo.save(item); } @@ -357,13 +362,13 @@ public class ContentItemL10NManager { * not an entry for the language is added. * * @param item The item to normalise. The item must be the - * draft version of the item! If a live - * version or a pending version is provided the - * method will throw an {@link IllegalArgumentException}! + * draft version of the item! If a live version or + * a pending version is provided the method will throw an + * {@link IllegalArgumentException}! */ @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) - public void normalizedLanguages( + public void normalizeLanguages( @RequiresPrivilege(ItemPrivileges.EDIT) final ContentItem item) { diff --git a/ccm-cms/src/test/java/org/librecms/contentsection/ContentItemL10NManagerTest.java b/ccm-cms/src/test/java/org/librecms/contentsection/ContentItemL10NManagerTest.java index a5950e4b9..c59537713 100644 --- a/ccm-cms/src/test/java/org/librecms/contentsection/ContentItemL10NManagerTest.java +++ b/ccm-cms/src/test/java/org/librecms/contentsection/ContentItemL10NManagerTest.java @@ -392,7 +392,7 @@ public class ContentItemL10NManagerTest { /** * Tries to normalise the languages of a content item by using - * {@link ContentItemL10NManager#normalizedLanguages(org.librecms.contentsection.ContentItem)} + * {@link ContentItemL10NManager#normalizeLanguages(org.librecms.contentsection.ContentItem)} */ @Test @InSequence(120) @@ -406,12 +406,12 @@ public class ContentItemL10NManagerTest { final Optional item = itemRepo.findById(-10200L); assertThat(item.isPresent(), is(true)); - l10nManager.normalizedLanguages(item.get()); + l10nManager.normalizeLanguages(item.get()); } /** * Verifies that calling - * {@link ContentItemL10NManager#normalizedLanguages(org.librecms.contentsection.ContentItem)} + * {@link ContentItemL10NManager#normalizeLanguages(org.librecms.contentsection.ContentItem)} * for already normalised item has not effect. */ @Test @@ -425,13 +425,13 @@ public class ContentItemL10NManagerTest { final Optional item = itemRepo.findById(-10100L); assertThat(item.isPresent(), is(true)); - l10nManager.normalizedLanguages(item.get()); + l10nManager.normalizeLanguages(item.get()); } /** * Verifies that - * {@link ContentItemL10NManager#normalizedLanguages(org.librecms.contentsection.ContentItem)} + * {@link ContentItemL10NManager#normalizeLanguages(org.librecms.contentsection.ContentItem)} * throws an {@link IllegalArgumentException} if called with {@code null} * for the item. */ @@ -446,7 +446,7 @@ public class ContentItemL10NManagerTest { public void normalizeItemNull() { final ContentItem item = null; - l10nManager.normalizedLanguages(item); + l10nManager.normalizeLanguages(item); } }