diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AbstractMvcAssetCreateStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AbstractMvcAssetCreateStep.java new file mode 100644 index 000000000..8e5196a16 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AbstractMvcAssetCreateStep.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.Folder; +import org.librecms.contentsection.FolderManager; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.transaction.Transactional; + +/** + * + * @author Jens Pelzetter + * @param + */ +public abstract class AbstractMvcAssetCreateStep + implements MvcAssetCreateStep { + + /** + * Provides operations for folders. + */ + @Inject + private FolderManager folderManager; + + /** + * Provides functions for working with {@link LocalizedString}s. + */ + @Inject + private GlobalizationHelper globalizationHelper; + + private boolean canCreate; + + /** + * The current folder. + */ + private Folder folder; + + /** + * The current content section. + */ + private ContentSection section; + + /** + * Messages to be shown to the user. + */ + private SortedMap messages; + + public AbstractMvcAssetCreateStep() { + messages = new TreeMap<>(); + } + + @Transactional(Transactional.TxType.REQUIRED) + @Override + public Map getAvailableLocales() { + return globalizationHelper + .getAvailableLocales() + .stream() + .collect( + Collectors.toMap( + locale -> locale.toString(), + locale -> locale.toString(), + (value1, value2) -> value1, + () -> new LinkedHashMap() + ) + ); + } + + @Transactional(Transactional.TxType.REQUIRED) + @Override + public ContentSection getContentSection() { + return section; + } + + @Transactional(Transactional.TxType.REQUIRED) + @Override + public void setContentSection(final ContentSection section) { + this.section = section; + } + + @Transactional(Transactional.TxType.REQUIRED) + @Override + public String getContentSectionLabel() { + return section.getLabel(); + } + + @Transactional(Transactional.TxType.REQUIRED) + @Override + public String getContentSectionTitle() { + return globalizationHelper.getValueFromLocalizedString( + section.getTitle() + ); + } + + @Override + public boolean getCanCreate() { + return canCreate; + } + + @Override + public Folder getFolder() { + return folder; + } + + @Override + public void setFolder(final Folder folder) { + this.folder = folder; + } + + @Override + public String getFolderPath() { + if (folder.getParentFolder() == null) { + return ""; + } else { + return folderManager.getFolderPath(folder); + } + } + + @Override + public Map getMessages() { + return Collections.unmodifiableSortedMap(messages); + } + + public void addMessage(final String context, final String message) { + messages.put(context, message); + } + + public void setMessages(final SortedMap messages) { + this.messages = new TreeMap<>(messages); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetEditStepsValidator.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetEditStepsValidator.java new file mode 100644 index 000000000..cc931a324 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetEditStepsValidator.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.librecms.contentsection.Asset; + +import java.util.Optional; + +import javax.enterprise.context.Dependent; +import javax.mvc.Controller; +import javax.ws.rs.Path; + +/** + * + * @author Jens Pelzetter + */ +@Dependent +public class AssetEditStepsValidator { + + private static final Logger LOGGER = LogManager.getLogger( + AssetEditStepsValidator.class + ); + + public boolean validateEditStep(final Class stepClass) { + if (stepClass.getAnnotation(Controller.class) == null) { + LOGGER.warn( + "Class {} is part of a set of asset edit steps, but is not" + + " annotated with {}. The class will be ignored.", + stepClass.getName(), + Controller.class.getName() + ); + return false; + } + + final Path pathAnnotation = stepClass.getAnnotation(Path.class); + if (pathAnnotation == null) { + LOGGER.warn( + "Class {} is part of a set of asset edit steps, but is not " + + "annotated with {}. the class will be ignored.", + stepClass.getName(), + Path.class.getName() + ); + return false; + } + + final String path = pathAnnotation.value(); + if (path == null + || !path.startsWith(MvcAssetEditSteps.PATH_PREFIX)) { + LOGGER.warn( + "Class {} is part of a set of asset edit steps, but the value" + + "of the {} annotation of the class does not start " + + "with {}. The class will be ignored.", + stepClass.getName(), + Path.class.getName(), + MvcAssetEditSteps.PATH_PREFIX + ); + return false; + } + + if (stepClass.getAnnotation(MvcAssetEditStep.class) == null) { + LOGGER.warn( + "Class {} is part of a set of asset edit steps, but is not " + + "annotated with {}. The class will be ignored.", + stepClass.getName(), + MvcAssetEditStep.class + ); + } + + return true; + } + + public boolean supportsAsset(final Class stepClass, final Asset asset) { + return Optional + .ofNullable(stepClass.getAnnotation(MvcAssetEditStep.class)) + .map( + stepAnnotation -> asset.getClass().isAssignableFrom( + stepAnnotation.supportedAssetType() + ) + ) + .orElse(false); + +// final MvcAssetEditStep stepAnnotation = stepClass.getAnnotation( +// MvcAssetEditStep.class +// ); +// +// if (stepAnnotation == null) { +// return false; +// } else { +// return asset.getClass().isAssignableFrom( +// stepAnnotation.supportedAssetType()); +// } + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetStepsDefaultMessagesBundle.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetStepsDefaultMessagesBundle.java new file mode 100644 index 000000000..b8a3337d3 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetStepsDefaultMessagesBundle.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.libreccm.ui.AbstractMessagesBean; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Named; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named("CmsAssetsStepsDefaultMessagesBundle") +public class AssetStepsDefaultMessagesBundle extends AbstractMessagesBean { + + @Override + public String getMessageBundle() { + return MvcAssetStepsConstants.BUNDLE; + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetUi.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetUi.java new file mode 100644 index 000000000..e46cd32c1 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetUi.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetManager; +import org.librecms.contentsection.ContentSection; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.mvc.Models; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class AssetUi { + + @Inject + private AssetManager assetManager; + + /** + * Used to provide data for the views without a named bean. + */ + @Inject + private Models models; + + public String showAccessDenied( + final ContentSection section, + final Asset asset, + final String step + ) { + return showAccessDenied( + section, assetManager.getAssetPath(asset), step + ); + } + + public String showAccessDenied( + final ContentSection section, final String assetPath, final String step + ) { + models.put("section", section.getLabel()); + models.put("assetPath", assetPath); + models.put(step, step); + return "org/librecms/ui/contentsections/assets/access-denied.xhtml"; + } + + public String showAssetNotFound( + final ContentSection section, final String assetPath + ) { + models.put("section", section.getLabel()); + models.put("assetPath", assetPath); + return "/org/librecms/ui/contentsections/assets/asset-not-found.xhtml"; + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetsController.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetsController.java new file mode 100644 index 000000000..8ccd9dcd9 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/AssetsController.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.libreccm.l10n.GlobalizationHelper; +import org.libreccm.security.AuthorizationRequired; +import org.libreccm.security.PermissionChecker; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetManager; +import org.librecms.contentsection.AssetRepository; +import org.librecms.contentsection.ContentItemRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.Folder; +import org.librecms.contentsection.FolderRepository; +import org.librecms.contentsection.FolderType; +import org.librecms.contentsection.privileges.AssetPrivileges; +import org.librecms.ui.contentsections.AssetPermissionsChecker; +import org.librecms.ui.contentsections.ContentSectionModel; +import org.librecms.ui.contentsections.ContentSectionsUi; + +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.mvc.Controller; +import javax.mvc.Models; +import javax.servlet.http.HttpServletRequest; +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Path("/{sectionIdentifier}/assets") +@Controller +public class AssetsController { + + @Inject + private AssetEditStepsValidator stepsValidator; + + @Inject + private AssetManager assetManager; + + @Inject + private ContentSectionModel sectionModel; + + /** + * {@link ContentSectionsUi} instance providing for helper functions for + * dealing with {@link ContentSection}s. + */ + @Inject + private ContentSectionsUi sectionsUi; + + /** + * {@link AssetUi} instance providing some common functions for managing + * assets. + */ + @Inject + private AssetUi assetUi; + + /** + * {@link FolderRepository} instance for retrieving folders. + */ + @Inject + private FolderRepository folderRepo; + + /** + * {@link ContentItemRepository} instance for retrieving content items. + */ + @Inject + private AssetRepository assetRepo; + + @Inject + @Any + private Instance> assetCreateSteps; + + @Inject + private AssetStepsDefaultMessagesBundle defaultStepsMessageBundle; + + /** + * {@link GlobalizationHelper} for working with localized texts etc. + */ + @Inject + private GlobalizationHelper globalizationHelper; + + @Inject + private AssetPermissionsChecker assetPermissionsChecker; + + /** + * Used to make avaiable in the views without a named bean. + */ + @Inject + private Models models; + + /** + * Used to check permissions on content items. + */ + @Inject + private PermissionChecker permissionChecker; + + /** + * Named bean providing access to the properties of the selected asset from + * the view. + */ + @Inject + private SelectedAssetModel selectedAssetModel; + + /* + * Redirect requests to the root path of this controller to the path for + * displaying the content of the root asset folder. The root path of this + * controller has no function. We assume that somebody who access the root + * folders wants to browse all asset in the content section. Therefore we + * redirect these requests to the root folder. + * + * @param sectionIdentifier The identififer of the current content section. + * + * @return A redirect to the root assets folder. + */ + @GET + @Path("/") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String redirectToAssetFolders( + @PathParam("sectionIdentifier") final String sectionIdentifier + ) { + return String.format( + "redirect:/%s/assetfolders/", + sectionIdentifier + ); + } + + /** + * Delegates requests for the path {@code @create} to the create step + * (subresource) of the asset type. The new asset will be created in the + * root folder of the current content section. + * + * @param sectionIdentifier The identifier of the current content section. + * @param assetType The type of the asset to create. + * + * @return The template of the create step. + */ + @GET + @Path("/@create/{assetType}") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String showCreateStep( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("assetType") final String assetType + ) { + return showCreateStep(sectionIdentifier, "", assetType); + } + + @POST + @Path("/@create") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String showCreateStepPost( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @FormParam("documentType") final String assetType + ) { + return String.format( + "redirect:/%s/assets/@create/%s", + sectionIdentifier, + assetType + ); + } + + @GET + @Path("/{folderPath:(.+)?}/@create/{assetType}") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String showCreateStep( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("folderPath") final String folderPath, + @FormParam("assetType") final String assetType + ) { + final CreateStepResult result = findCreateStep( + sectionIdentifier, + folderPath, + assetType + ); + + if (result.isCreateStepAvailable()) { + return result.getCreateStep().showCreateStep(); + } else { + return result.getErrorTemplate(); + } + } + + @POST + @Path("/{folderPath:(.+)?}/@create") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String showCreateStepPost( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("folderPath") final String folderPath, + @FormParam("assetType") final String assetType + ) { + return String.format( + "redirect:/%s/documents/%s/@create/%s", + sectionIdentifier, + folderPath, + assetType + ); + } + + @POST + @Path("/@create/{assetType}") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String createAsset( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("assetType") final String assetType, + @Context final HttpServletRequest request + ) { + return createAsset( + sectionIdentifier, + "", + assetType, + request + ); + } + + @POST + @Path("/{folderPath:(.+)?}/@create/{assetType}") + @AuthorizationRequired + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Transactional(Transactional.TxType.REQUIRED) + public String createAsset( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("folderPath") final String folderPath, + @PathParam("assetType") final String assetType, + @Context final HttpServletRequest request + ) { + final CreateStepResult result = findCreateStep( + sectionIdentifier, + folderPath, + assetType + ); + + if (result.isCreateStepAvailable()) { + return result.getCreateStep().createAsset( + request.getParameterMap() + ); + } else { + return result.getErrorTemplate(); + } + } + + @GET + @Path("/{assetPath:(.+)?") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String editAsset( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("assetPath") final String assetPath + ) { + final Optional sectionResult = sectionsUi + .findContentSection(sectionIdentifier); + if (!sectionResult.isPresent()) { + return sectionsUi.showContentSectionNotFound(sectionIdentifier); + } + final ContentSection section = sectionResult.get(); + + final Optional assetResult = assetRepo + .findByPath(section, assetPath); + if (!assetResult.isPresent()) { + return assetUi.showAssetNotFound(section, assetPath); + } + final Asset asset = assetResult.get(); + if (!permissionChecker.isPermitted(AssetPrivileges.EDIT, asset)) { + return assetUi.showAccessDenied(section, asset, assetPath); + } + + return String.format("redirect:%s", findEditStep(asset, section)); + } + + /** + * Helper method for finding the path fragment for the edit step of an + * asset. + * + * @param asset The asset. + * + * @return The path of the edit step of the asset. + * + */ + private String findEditStep( + final Asset asset, final ContentSection section + ) { + final MvcAssetEditKit editKit = asset + .getClass() + .getAnnotation(MvcAssetEditKit.class); + + final Class step = editKit.editStep(); + final Path pathAnnotation = step.getAnnotation(Path.class); + return pathAnnotation + .value() + .replace( + String.format( + "{%s}", + MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM + ), + section.getLabel() + ) + .replace( + String.format( + "/{%s}", + MvcAssetEditSteps.ASSET_PATH_PATH_PARAM + ), + assetManager.getAssetPath(asset) + ); + } + + /** + * Helper method for showing the "asset folder not found" page if there + * is no folder for the provided path. + * + * @param section The content section. + * @param folderPath The folder path. + * + * @return The template of the "document folder not found" page. + */ + private String showAssetFolderNotFound( + final ContentSection section, final String folderPath + ) { + models.put("contentSection", section.getLabel()); + models.put("folderPath", folderPath); + + return "org/librecms/ui/contentsection/assetfolder/assetfolder-not-found.xhtml"; + } + /** + * Helper method for showing the "asset type not available" page if the + * requested asset type is not available. + * + * @param section The content section. + * @param assetType The asset type. + * + * @return The template of the "asset type not found" page. + */ + public String showAssetTypeNotFound( + final ContentSection section, final String assetType + ) { + models.put("contentSection", section.getLabel()); + models.put("assetType", assetType); + + return "org/librecms/ui/contentsection/assetfolder/asset-type-not-found.xhtml"; + } + + private String showCreateStepNotAvailable( + final ContentSection section, + final String folderPath, + final String assetType + ) { + models.put("contentSection", section.getLabel()); + models.put("folderPath", folderPath); + models.put("assetType", assetType); + + return "org/librecms/ui/contentsection/assetfolder/create-step-not-available.xhtml"; + } + + + private CreateStepResult findCreateStep( + final String sectionIdentifier, + final String folderPath, + final String assetType + ) { + final Optional sectionResult = sectionsUi + .findContentSection(sectionIdentifier); + if (!sectionResult.isPresent()) { + return new CreateStepResult( + sectionsUi.showContentSectionNotFound(sectionIdentifier) + ); + } + final ContentSection section = sectionResult.get(); + sectionModel.setSection(section); + + final Folder folder; + if (folderPath.isEmpty()) { + folder = section.getRootAssetsFolder(); + } else { + final Optional folderResult = folderRepo + .findByPath(section, folderPath, FolderType.ASSETS_FOLDER + ); + if (!folderResult.isPresent()) { + return new CreateStepResult( + showAssetFolderNotFound(section, folderPath) + ); + } + folder = folderResult.get(); + } + + if (!assetPermissionsChecker.canCreateAssets(folder)) { + return new CreateStepResult( + sectionsUi.showAccessDenied( + "sectionidentifier", sectionIdentifier, + "folderPath", folderPath, + "step", defaultStepsMessageBundle.getMessage("create_step") + ) + ); + } + + final Class clazz; + try { + clazz = Class.forName(assetType); + } catch(ClassNotFoundException ex) { + return new CreateStepResult( + showAssetTypeNotFound(section, assetType) + ); + } + @SuppressWarnings("unchecked") + final Class assetClass = (Class) clazz; + + final Optional editKitResult = Optional.ofNullable( + assetClass.getDeclaredAnnotation(MvcAssetEditKit.class) + ); + if (!editKitResult.isPresent()) { + return new CreateStepResult( + showCreateStepNotAvailable(section, folderPath, assetType) + ); + } + final MvcAssetEditKit editKit = editKitResult.get(); + final Class> createStepClass + = editKit.createStep(); + + final Instance> instance + = assetCreateSteps.select(createStepClass); + if (instance.isUnsatisfied() || instance.isAmbiguous()) { + return new CreateStepResult( + showCreateStepNotAvailable(section, folderPath, assetType) + ); + } + final MvcAssetCreateStep createStep = instance.get(); + + createStep.setContentSection(section); + createStep.setFolder(folder); + + return new CreateStepResult(createStep); + } + + private class CreateStepResult { + + private final MvcAssetCreateStep createStep; + + private final boolean createStepAvailable; + + private final String errorTemplate; + + public CreateStepResult( + final MvcAssetCreateStep createStep + ) { + this.createStep = createStep; + createStepAvailable = true; + errorTemplate = null; + } + + public CreateStepResult(final String errorTemplate) { + this.createStep = null; + createStepAvailable = false; + this.errorTemplate = errorTemplate; + } + + public MvcAssetCreateStep getCreateStep() { + return createStep; + } + + public boolean isCreateStepAvailable() { + return createStepAvailable; + } + + public String getErrorTemplate() { + return errorTemplate; + } + + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetCreateStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetCreateStep.java new file mode 100644 index 000000000..f513f5fd8 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetCreateStep.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.Folder; + +import java.util.Map; + +/** + * A create step for an asset. Implmenting classes MUST be CDI beans (request + * scope is recommended). They are are retrieved by the {@link AssetController} + * using CDI. The {@link AssetController} will first call + * {@link #setContentSection(org.librecms.contentsection.ContentSection)} and {@link #setFolder(org.librecms.contentsection.Folder) + * } to provided the current current content section and folder. After that, + * dpending on the request method, either {@link #showCreateStep} or {@link #createAsset(java.util.Map) + * } will be called. + * + * In most cases, {@link AbstractMvcAssetCreateStep} should be used as base for + * implementations. {@link AbstractMvcAssetCreateStep} implements several common + * operations. + * + * @author Jens Pelzetter + * @param The asset type created by the create step. + */ +public interface MvcAssetCreateStep { + + /** + * Return the template for the create step. + * + * @return + */ + String showCreateStep(); + + String createAsset(Map formParams); + + /** + * Should be set by the implementing class to indicate if the current user + * can create document in the current folder. + * + * @return + */ + boolean getCanCreate(); + + /** + * The asset type generated by the create step described by an instance of + * this class. + * + * @return Asset type generated. + */ + String getAssetType(); + + /** + * Localized description of the create step. The current locale as returned + * by {@link GlobalizationHelper#getNegotiatedLocale()} should be used to + * select the language variant to return. + * + * @return The localized description of the create step. + */ + String getDescription(); + + /** + * Returns {@link ResourceBundle} providing the localized description of the + * create step. + * + * @return The {@link ResourceBundle} providing the localized description of + * the create step. + */ + String getBundle(); + + /** + * The locales that can be used for documents. + * + * @return The locales that can be used for documents. + */ + Map getAvailableLocales(); + + /** + * The current content section. + * + * @return The current content section. + */ + ContentSection getContentSection(); + + /** + * Convinient method for getting the label of the current content section. + * + * @return The label of the current content section. + */ + String getContentSectionLabel(); + + /** + * Convinient method for getting the title of the current content section. + * + * @return The title of the current content section for the current locale. + */ + String getContentSectionTitle(); + + /** + * The current content section is provided by the + * {@link DocumentController}. + * + * @param section The current content section. + */ + void setContentSection(final ContentSection section); + + /** + * The parent folder of the new asset. + * + * @return The parent folder of the new asset. + */ + Folder getFolder(); + + /** + * Gets the path the the parent folder of the new asset. + * + * @return The path of the parent folder of the new asset. + */ + String getFolderPath(); + + /** + * The parent folder of the new asset is provided by the + * {@link DocumentController}. + * + * @param folder The parent folder of the new doucment. + */ + void setFolder(final Folder folder); + + /** + * Gets messages from the create step. + * + * @return + */ + Map getMessages(); + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditKit.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditKit.java new file mode 100644 index 000000000..012a200c1 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditKit.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.librecms.contentsection.Asset; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Provides the steps for creating, viewing, and editing an asset. + * + * This annotation can only be used on classes extending the {@link Asset} + * class. + * + * @author Jens Pelzetter + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MvcAssetEditKit { + + Class> createStep(); + + Class editStep(); + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditStep.java new file mode 100644 index 000000000..37b982faa --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditStep.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.librecms.contentsection.Asset; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Metadata of an edit step for assets. + * + * @author Jens Pelzetter + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MvcAssetEditStep { + + /** + * The name of the resource bundle providing the localized values for + * {@link #labelKey} and {@link descriptionKey}. + * + * @return The resource bundle providing the localized labelKey and + * descriptionKey. + */ + String bundle(); + + /** + * The key for the localized description of the step. + * + * @return The key for the localized description of the step. + */ + String descriptionKey(); + + /** + * The key for the localized label of the authoring step.. + * + * @return The key for the localized label of the authoring step... + */ + String labelKey(); + + /** + * Edit steps only support a specific type, and all subtypes. + * + * @return The asset type supported by the edit step. + */ + + Class supportedAssetType(); + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditSteps.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditSteps.java new file mode 100644 index 000000000..66441bc20 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetEditSteps.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import java.util.Collections; +import java.util.Set; + +/** + * + * @author Jens Pelzetter + */ +public interface MvcAssetEditSteps { + + public static final String PATH_PREFIX + = "/{sectionIdentifier}/assets/{assetPath:(.+)?}/@"; + + public static final String SECTION_IDENTIFIER_PATH_PARAM + = "sectionIdentifier"; + + public static final String ASSET_PATH_PATH_PARAM_NAME = "assetPath"; + + public static final String ASSET_PATH_PATH_PARAM + = ASSET_PATH_PATH_PARAM_NAME + ":(.+)?"; + + Set> getClasses(); + + default Set> getResourceClasses() { + return Collections.emptySet(); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetStepsConstants.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetStepsConstants.java new file mode 100644 index 000000000..9c62ce76a --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/MvcAssetStepsConstants.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +/** + * Some constants shared by most asset create and edit steps. + * + * @author Jens Pelzetter + */ +public class MvcAssetStepsConstants { + + private MvcAssetStepsConstants() { + // Nothing + } + + /** + * Fully qualified name of the bundle provdiding texts shared by most asset + * create and edit steps. + */ + public static final String BUNDLE = "org.librecms.ui.MvcAssetStepsBundle"; + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/SelectedAssetModel.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/SelectedAssetModel.java new file mode 100644 index 000000000..9fc0678f2 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/assets/SelectedAssetModel.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 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.ui.contentsections.assets; + +import org.libreccm.l10n.GlobalizationHelper; +import org.libreccm.security.PermissionChecker; +import org.libreccm.security.Shiro; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetManager; +import org.librecms.contentsection.Folder; +import org.librecms.contentsection.FolderManager; +import org.librecms.ui.contentsections.FolderBreadcrumbsModel; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; + +/** + * Model/named bean providing data about the currently selected asset for + * several views. + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named("CmsSelectedAssetModel") +public class SelectedAssetModel { + + /** + * Checks if edit step classes have all required annotations. + */ + @Inject + private AssetEditStepsValidator stepsValidator; + + @Inject + private AssetManager assetManager; + + @Inject + private FolderManager folderManager; + + /** + * Used to retrieve some localized data. + */ + @Inject + private GlobalizationHelper globalizationHelper; + + @Inject + private HttpServletRequest request; + + /** + * Used to check permissions + */ + @Inject + private PermissionChecker permissionChecker; + + /** + * Used to get the current user. + */ + @Inject + private Shiro shiro; + + /** + * The current asset. + */ + private Asset asset; + + /** + * The name of the current asset. + */ + private String assetName; + + /** + * The title of the current asset. This value is determined from + * {@link Asset#title} using {@link GlobalizationHelper#getValueFromLocalizedString(org.libreccm.l10n.LocalizedString) + * }. + */ + private String assetTitle; + + /** + * The path of the current asset. + */ + private String assetPath; + + /** + * The breadcrumb trail of the folder of the current item. + */ + private List parentFolderBreadcrumbs; + + public String getAssetName() { + return assetName; + } + + public String getAssetTitle() { + return assetTitle; + } + + public String getAssetPath() { + return assetPath; + } + + public List getParentFolderBreadcrumbs() { + return Collections.unmodifiableList(parentFolderBreadcrumbs); + } + + /** + * Sets the current asset and sets the properties of this model based on the + * asset. + * + * @param asset + */ + void setAsset(final Asset asset) { + this.asset = Objects.requireNonNull(asset); + assetName = asset.getDisplayName(); + assetTitle = globalizationHelper.getValueFromLocalizedString( + asset.getTitle() + ); + assetPath = assetManager.getAssetPath(asset).substring(1); // Without leasding slash. + parentFolderBreadcrumbs = assetManager + .getAssetFolders(asset) + .stream() + .map(this::buildFolderBreadcrumbsModel) + .collect(Collectors.toList()); + } + + /** + * Helper method for building the breadcrumb trail for the folder of the + * current item. + * + * @param folder The folder of the current item. + * + * @return The breadcrumb trail of the folder. + */ + private FolderBreadcrumbsModel buildFolderBreadcrumbsModel( + final Folder folder + ) { + final FolderBreadcrumbsModel model = new FolderBreadcrumbsModel(); + model.setCurrentFolder(false); + model.setPath(folderManager.getFolderPath(folder)); + model.setPathToken(folder.getName()); + return model; + } + +}