Merge pull request 'ccm-admin' (#7) from ccm-admin into master

Reviewed-on: #7

Former-commit-id: ebbf951981
pull/8/head
jensp 2020-12-19 16:56:31 +01:00
commit dfc66b56a6
274 changed files with 31713 additions and 597 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ target
.settings .settings
.tscache .tscache
*.vscode *.vscode
/ccm-core/nbproject/

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scene Scope="Project" version="2">
<Scope Scope="Faces Configuration Only"/>
<Scope Scope="Project"/>
<Scope Scope="All Faces Configurations"/>
</Scene>

View File

@ -213,7 +213,15 @@
<artifactId>ccm-core</artifactId> <artifactId>ccm-core</artifactId>
<type>jar</type> <type>jar</type>
<includes> <includes>
<include>views/</include> <include>WEB-INF/</include>
</includes>
</overlay>
<overlay>
<groupId>org.libreccm</groupId>
<artifactId>ccm-shortcuts</artifactId>
<type>jar</type>
<includes>
<include>WEB-INF/</include>
</includes> </includes>
</overlay> </overlay>
<overlay> <overlay>
@ -224,6 +232,14 @@
<include>resources/</include> <include>resources/</include>
</includes> </includes>
</overlay> </overlay>
<overlay>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId>
<type>jar</type>
<includes>
<include>WEB-INF/</include>
</includes>
</overlay>
<overlay> <overlay>
<groupId>org.librecms</groupId> <groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId> <artifactId>ccm-cms</artifactId>

View File

@ -101,5 +101,11 @@
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver" <Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
level="debug"> level="debug">
</Logger> </Logger>
<Logger name = "org.libreccm.ui.admin.AdminApplication"
level="debug">
</Logger>
<Logger name="org.libreccm.ui.admin.applications.ApplicationsPage"
level="debug">
</Logger>
</Loggers> </Loggers>
</Configuration> </Configuration>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<jboss-web> <jboss-web>
<context-root>/libreccm</context-root> <context-root>/libreccm</context-root>
<default-encoding>UTF-8</default-encoding>
</jboss-web> </jboss-web>

View File

@ -15,6 +15,10 @@
<param-name>ccm.distribution</param-name> <param-name>ccm.distribution</param-name>
<param-value>libreccm</param-value> <param-value>libreccm</param-value>
</context-param> </context-param>
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value>
</context-param>
<!-- No JSESSIONID!!! --> <!-- No JSESSIONID!!! -->
<session-config> <session-config>
@ -65,8 +69,12 @@
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name> <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value> <param-value>true</param-value>
</context-param> </context-param>
<context-param>
<param-name>PARAMETER_ENCODING</param-name>
<param-value>UTF-8</param-value>
</context-param>
<!-- <servlet> <!-- <servlet>
<servlet-name>vaadin-servlet</servlet-name> <servlet-name>vaadin-servlet</servlet-name>
<servlet-class>com.vaadin.cdi.server.VaadinCDIServlet</servlet-class> <servlet-class>com.vaadin.cdi.server.VaadinCDIServlet</servlet-class>
</servlet> </servlet>

View File

@ -71,6 +71,22 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.mvc</groupId>
<artifactId>javax.mvc-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.krazo</groupId>
<artifactId>krazo-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.krazo.ext</groupId>
<artifactId>krazo-freemarker</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId> <groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId> <artifactId>jackson-jaxrs-json-provider</artifactId>

View File

@ -39,49 +39,71 @@ import java.util.List;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@Configuration @Configuration(
descBundle = "com.arsdigita.cms.CMSConfig",
descKey = "description",
titleKey = "title"
)
public class CMSConfig { public class CMSConfig {
/** /**
* Path for the default item template. Path is relative to the Template Root * Path for the default item template. Path is relative to the Template Root
* path. * path.
*/ */
@Setting @Setting(
descKey = "defaultItemTemplatePath.desc",
labelKey = "defaultItemTemplatePath.label"
)
private String defaultItemTemplatePath = "/default/item.jsp"; private String defaultItemTemplatePath = "/default/item.jsp";
/** /**
* Path for the default folder template. Path is relative to the Template * Path for the default folder template. Path is relative to the Template
* Root path. * Root path.
*/ */
@Setting @Setting(
descKey = "defaultFolderTemplatePath.desc",
labelKey = "defaultFolderTemplatePath.label"
)
private String defaultFolderTemplatePath = "/default/folder.jsp"; private String defaultFolderTemplatePath = "/default/folder.jsp";
/** /**
* Path or the root folder for template folders. Path is relative to webapp * Path or the root folder for template folders. Path is relative to webapp
* root. Modify with care! Usually modified by developers only! * root. Modify with care! Usually modified by developers only!
*/ */
@Setting @Setting(
descKey = "templateRootPath.desc",
labelKey = "templateRootPath.label"
)
private String templateRootPath = "/templates/ccm-cms/content-section/"; private String templateRootPath = "/templates/ccm-cms/content-section/";
/** /**
* Item Adapters File, path to an XML resource containing adapter * Item Adapters File, path to an XML resource containing adapter
* specifications. Path is relative to webapp root. * specifications. Path is relative to webapp root.
*/ */
@Setting @Setting(
descKey = "itemAdapters.desc",
labelKey = "itemAdapters.label"
)
private String itemAdapters = "/WEB-INF/resources/cms-item-adapters.xml"; private String itemAdapters = "/WEB-INF/resources/cms-item-adapters.xml";
/** /**
* Use streamlined content creation: upon item creation, automatically open * Use streamlined content creation: upon item creation, automatically open
* authoring steps and forward to the next step * authoring steps and forward to the next step
*/ */
@Setting @Setting(
descKey = "useStreamlinedCreation.desc",
labelKey = "useStreamlinedCreation.label"
)
private boolean useStreamlinedCreation = true; private boolean useStreamlinedCreation = true;
/** /**
* DHTML Editor Configuration for use in CMS module, lists the configuration * DHTML Editor Configuration for use in CMS module, lists the configuration
* object name and Javascript source location for its definition. * object name and Javascript source location for its definition.
*/ */
@Setting @Setting(
descKey = "dhtmlEditorConfig.desc",
labelKey = "dhtmlEditorConfig.label"
)
private List<String> dhtmlEditorConfig = Arrays.asList(new String[]{ private List<String> dhtmlEditorConfig = Arrays.asList(new String[]{
"TinyMCE.Config", "scripts/ccm-cms/tinymce-loader.js" "TinyMCE.Config", "scripts/ccm-cms/tinymce-loader.js"
}); });
@ -91,9 +113,12 @@ public class CMSConfig {
* Defines which plugins to use, e.g.TableOperations,CSS Format: * Defines which plugins to use, e.g.TableOperations,CSS Format:
* [string,string,string] * [string,string,string]
*/ */
@Setting @Setting(
descKey = "dhtmlEditorPlugins.desc",
labelKey = "dhtmlEditorPlugins.label"
)
private List<String> dhtmlEditorPlugins = Arrays.asList(new String[]{ private List<String> dhtmlEditorPlugins = Arrays.asList(new String[]{
// "scripts/ccm-cms/tinymce/plugins/insertimage.js" // "scripts/ccm-cms/tinymce/plugins/insertimage.js"
"http://localhost/web/ccm-cms-tinymce/insertimage.js" "http://localhost/web/ccm-cms-tinymce/insertimage.js"
}); });
@ -101,64 +126,94 @@ public class CMSConfig {
* Prevent undesirable functions from being made available, eg images should * Prevent undesirable functions from being made available, eg images should
* only be added through the cms methods. * only be added through the cms methods.
*/ */
@Setting @Setting(
descKey = "dhtmlEditorHiddenButtons.desc",
labelKey = "dhtmlEditorHiddenButtons.label"
)
private List<String> dhtmlEditorHiddenButtons = Collections.emptyList(); private List<String> dhtmlEditorHiddenButtons = Collections.emptyList();
/** /**
* Hide section admin tabs from users without administrative rights. * Hide section admin tabs from users without administrative rights.
*/ */
@Setting @Setting(
descKey = "hideAdminTabs.desc",
labelKey = "hideAdminTabs.label"
)
private boolean hideAdminTabs = true; private boolean hideAdminTabs = true;
/** /**
* Hide Folder Index Checkbox from folder view * Hide Folder Index Checkbox from folder view
*/ */
@Setting @Setting(
descKey = "hideFolderIndexCheckbox.desc",
labelKey = "hideFolderIndexCheckbox.label"
)
private boolean hideFolderIndexCheckbox = true; private boolean hideFolderIndexCheckbox = true;
/** /**
* Hide launch date parameter on all forms and displays where it's used. * Hide launch date parameter on all forms and displays where it's used.
*/ */
@Setting @Setting(
descKey = "hideLaunchDate.desc",
labelKey = "hideLaunchDate.label"
)
private boolean hideLaunchDate = true; private boolean hideLaunchDate = true;
/** /**
* Require the launch date parameter to be set by the content author. * Require the launch date parameter to be set by the content author.
*/ */
@Setting @Setting(
descKey = "requireLaunchDate.desc",
labelKey = "requireLaunchDate.label"
)
private boolean requireLaunchDate = true; private boolean requireLaunchDate = true;
/** /**
* Hide the templates tab on the item admin page. * Hide the templates tab on the item admin page.
*/ */
@Setting @Setting(
descKey = "hideTemplatesTab.desc",
labelKey = "hideTemplatesTab.label"
)
private boolean hideTemplatesTab = false; private boolean hideTemplatesTab = false;
/** /**
* Hide the upload file link in the editing of a text asset. * Hide the upload file link in the editing of a text asset.
*/ */
@Setting @Setting(
descKey = "hideTextAssetUploadFile.desc",
labelKey = "hideTextAssetUploadFile.label"
)
private boolean hideTextAssetUploadFile = false; private boolean hideTextAssetUploadFile = false;
/** /**
* Hide timezone labels (if, for example, all users will be in the same * Hide timezone labels (if, for example, all users will be in the same
* timezone and such information would be unnecessary) * timezone and such information would be unnecessary)
*/ */
@Setting @Setting(
descKey = "hideTimezone.desc",
labelKey = "hideTimezone.label"
)
private boolean hideTimezone = false; private boolean hideTimezone = false;
/** /**
* Whether the Wysiwyg editor should clear the text of MSWord tags, * Whether the Wysiwyg editor should clear the text of MSWord tags,
* everytime the user clicks on 'Save' * everytime the user clicks on 'Save'
*/ */
@Setting @Setting(
descKey = "saveTextCleansWordTags.desc",
labelKey = "saveTextCleansWordTags.label"
)
private boolean saveTextCleansWordTags = true; private boolean saveTextCleansWordTags = true;
/** /**
* Get the search indexing not to process FileAssets, eg to avoid PDF * Get the search indexing not to process FileAssets, eg to avoid PDF
* slowdowns * slowdowns
*/ */
@Setting @Setting(
descKey = "disableFileAssetExtraction.desc",
labelKey = "disableFileAssetExtraction.label"
)
private boolean disableFileAssetExtraction = false; private boolean disableFileAssetExtraction = false;
/** /**
@ -172,55 +227,79 @@ public class CMSConfig {
* link to restart a workflow will not work. * link to restart a workflow will not work.
* *
*/ */
@Setting @Setting(
descKey = "deleteWorkflowAfterPublication.desc",
labelKey = "deleteWorkflowAfterPublication.label"
)
private boolean deleteWorkflowAfterPublication = false; private boolean deleteWorkflowAfterPublication = false;
/** /**
* Defines the number of days ahead that are covered in the 'Soon Expired' * Defines the number of days ahead that are covered in the 'Soon Expired'
* tab * tab
*/ */
@Setting @Setting(
descKey = "soonExpiredTimespanDays.desc",
labelKey = "soonExpiredTimespanDays.label"
)
private int soonExpiredTimespanDays = 14; private int soonExpiredTimespanDays = 14;
/** /**
* Defines the number of months ahead that are covered in the 'Soon Expired' * Defines the number of months ahead that are covered in the 'Soon Expired'
* tab * tab
*/ */
@Setting @Setting(
descKey = "soonExpiredTimespanMonths.desc",
labelKey = "soonExpiredTimespanMonths.label"
)
private int soonExpiredTimespanMonths = 1; private int soonExpiredTimespanMonths = 1;
/** /**
* Does a redirect to the unpublished item generate not found error? * Does a redirect to the unpublished item generate not found error?
*/ */
@Setting @Setting(
descKey = "unpublishedNotFound.desc",
labelKey = "unpublishedNotFound.label"
)
private boolean unpublishedNotFound = true; private boolean unpublishedNotFound = true;
/** /**
* Links created through browse interfaces should only be within the same * Links created through browse interfaces should only be within the same
* subsite * subsite
*/ */
@Setting @Setting(
descKey = "linksOnlyInSameSubsite.desc",
labelKey = "linksOnlyInSameSubsite.label"
)
private boolean linksOnlyInSameSubsite = false; private boolean linksOnlyInSameSubsite = false;
/** /**
* Link available to reset lifecycle on republish. If false don't display * Link available to reset lifecycle on republish. If false don't display
* the link otherwise display. * the link otherwise display.
*/ */
@Setting @Setting(
descKey = "hideResetLifecycleLink.desc",
labelKey = "hideResetLifecycleLink.label"
)
private boolean hideResetLifecycleLink = true; private boolean hideResetLifecycleLink = true;
/** /**
* Whether to include INPATH operators to contains clause in intermedia * Whether to include INPATH operators to contains clause in intermedia
* search * search
*/ */
@Setting @Setting(
descKey = "scoreTitleAndKeywords.desc",
labelKey = "scoreTitleAndKeywords.label"
)
private boolean scoreTitleAndKeywords = false; private boolean scoreTitleAndKeywords = false;
/** /**
* Title Weight, the relative weight given to title element within cms:item * Title Weight, the relative weight given to title element within cms:item
* when ranking search results (only used by interMedia) * when ranking search results (only used by interMedia)
*/ */
@Setting @Setting(
descKey = "titleWeight.desc",
labelKey = "titleWeight.label"
)
private int titleWeight = 1; private int titleWeight = 1;
/** /**
@ -228,13 +307,19 @@ public class CMSConfig {
* within dublinCore element within cms:item element when ranking search * within dublinCore element within cms:item element when ranking search
* results (only used by interMedia) * results (only used by interMedia)
*/ */
@Setting @Setting(
descKey = "keywordWeight.desc",
labelKey = "keywordWeight.label"
)
private int keywordWeight = 1; private int keywordWeight = 1;
/** /**
* Limit the item search to current content section * Limit the item search to current content section
*/ */
@Setting @Setting(
descKey = "limitItemSearchToContentSection.desc",
labelKey = "limitItemSearchToContentSection.label"
)
private boolean limitItemSearchToContentSection = true; private boolean limitItemSearchToContentSection = true;
/** /**
@ -245,60 +330,87 @@ public class CMSConfig {
* Second string is the name of the bebop step component eg * Second string is the name of the bebop step component eg
* com.arsdigita.cms.contenttypes.ui.ImageStep * com.arsdigita.cms.contenttypes.ui.ImageStep
*/ */
@Setting @Setting(
descKey = "skipAssetSteps.desc",
labelKey = "skipAssetSteps.label"
)
private List<String> skipAssetSteps = Collections.emptyList(); private List<String> skipAssetSteps = Collections.emptyList();
/** /**
* Mandatory Descriptions Content types may refer to this to decide whether * Mandatory Descriptions Content types may refer to this to decide whether
* to validate against empty descriptions * to validate against empty descriptions
*/ */
@Setting @Setting(
descKey = "mandatoryDescriptions.desc",
labelKey = "mandatoryDescriptions.label"
)
private boolean mandatoryDescriptions = false; private boolean mandatoryDescriptions = false;
/** /**
* Delete Finished Lifecycles. Decide whether lifecycles and their phases * Delete Finished Lifecycles. Decide whether lifecycles and their phases
* should be deleted from the system when finished. * should be deleted from the system when finished.
*/ */
@Setting @Setting(
descKey = "deleteLifecycleWhenComplete.desc",
labelKey = "deleteLifecycleWhenComplete.label"
)
private boolean deleteLifecycleWhenComplete = false; private boolean deleteLifecycleWhenComplete = false;
/** /**
* Delete Sent Workflow Notifications. Decide whether successfully sent * Delete Sent Workflow Notifications. Decide whether successfully sent
* notifications and messages should be deleted from the system * notifications and messages should be deleted from the system
*/ */
@Setting @Setting(
descKey = "deleteWorkflowNotificationWhenSend.desc",
labelKey = "deleteWorkflowNotificationWhenSend.label"
)
private boolean deleteWorkflowNotificationWhenSend = false; private boolean deleteWorkflowNotificationWhenSend = false;
/** /**
* Decide whether successfully sent notifications and messages should be * Decide whether successfully sent notifications and messages should be
* deleted from the system * deleted from the system
*/ */
@Setting @Setting(
descKey = "deleteExpiryNotificationsWhenSent.desc",
labelKey = "deleteExpiryNotificationsWhenSent.label"
)
private boolean deleteExpiryNotificationsWhenSent = false; private boolean deleteExpiryNotificationsWhenSent = false;
/** /**
* Amount of time (in hours) before the expiration of a content item that * Amount of time (in hours) before the expiration of a content item that
* users in the Alert Recipient role are alerted via email * users in the Alert Recipient role are alerted via email
*/ */
@Setting @Setting(
descKey = "defaultNotificationTime.desc",
labelKey = "defaultNotificationTime.label"
)
private int defaultNotificationTime = 0; private int defaultNotificationTime = 0;
/** /**
* Whether a content item's author should be notified by the item's * Whether a content item's author should be notified by the item's
* LifecycleListener; defaults to true * LifecycleListener; defaults to true
*/ */
@Setting @Setting(
descKey = "notifyAuthorOnLifecycle.desc",
labelKey = "notifyAuthorOnLifecycle.label"
)
private boolean notifyAuthorOnLifecycle = false; private boolean notifyAuthorOnLifecycle = false;
/** /**
* XML Mapping of the content center tabs to URLs, see * XML Mapping of the content center tabs to URLs, see
* {@link ContentCenterDispatcher} * {@link ContentCenterDispatcher}
*/ */
@Setting @Setting(
descKey = "contentCenterMap.desc",
labelKey = "contentCenterMap.label"
)
private String contentCenterMap private String contentCenterMap
= "/WEB-INF/resources/content-center-map.xml"; = "/WEB-INF/resources/content-center-map.xml";
@Setting @Setting(
descKey = "defaultItemResolverClassNames.desc",
labelKey = "defaultItemResolverClassNames.label"
)
private List<String> defaultItemResolverClassNames = Arrays.asList( private List<String> defaultItemResolverClassNames = Arrays.asList(
new String[]{ new String[]{
SimpleItemResolver.class.getName() SimpleItemResolver.class.getName()
@ -310,87 +422,165 @@ public class CMSConfig {
// DefaultTemplateResolver.class.getName(), // DefaultTemplateResolver.class.getName(),
// TemplateResolver.class.getName() // TemplateResolver.class.getName()
// }); // });
@Setting @Setting(
descKey = "itemSearchDefaultTab.desc",
labelKey = "itemSearchDefaultTab.label"
)
private String itemSearchDefaultTab = "flatBrowse"; private String itemSearchDefaultTab = "flatBrowse";
@Setting @Setting(
descKey = "itemSearchFlatBrowsePanePageSize.desc",
labelKey = "itemSearchFlatBrowsePanePageSize.label"
)
private int itemSearchFlatBrowsePanePageSize = 20; private int itemSearchFlatBrowsePanePageSize = 20;
@Setting @Setting(
descKey = "folderBrowseListSize.desc",
labelKey = "folderBrowseListSize.label"
)
private int folderBrowseListSize = 20; private int folderBrowseListSize = 20;
@Setting @Setting(
descKey = "folderAtoZShowLimit.desc",
labelKey = "folderAtoZShowLimit.label"
)
private int folderAtoZShowLimit = 100; private int folderAtoZShowLimit = 100;
@Setting @Setting(
descKey = "useOldStyleItemLifecycleItemPane.desc",
labelKey = "useOldStyleItemLifecycleItemPane.label"
)
private boolean useOldStyleItemLifecycleItemPane = false; private boolean useOldStyleItemLifecycleItemPane = false;
@Setting @Setting(
descKey = "threadPublishing.desc",
labelKey = "threadPublishing.label"
)
private boolean threadPublishing = false; private boolean threadPublishing = false;
@Setting @Setting(
descKey = "publishingFailureSender.desc",
labelKey = "publishingFailureSender.label"
)
private String publishingFailureSender = ""; private String publishingFailureSender = "";
@Setting @Setting(
descKey = "publishingFailureReceiver.desc",
labelKey = "publishingFailureReceiver.label"
)
private String publishingFailureReceiver = ""; private String publishingFailureReceiver = "";
@Setting @Setting(
descKey = "imageBrowserThumbnailMaxWidth.desc",
labelKey = "imageBrowserThumbnailMaxWidth.label"
)
private int imageBrowserThumbnailMaxWidth = 50; private int imageBrowserThumbnailMaxWidth = 50;
@Setting @Setting(
descKey = "imageBrowserThumbnailMaxHeight.desc",
labelKey = "imageBrowserThumbnailMaxHeight.label"
)
private int imageBrowserThumbnailMaxHeight = 50; private int imageBrowserThumbnailMaxHeight = 50;
@Setting @Setting(
descKey = "imageBrowserCaptionSize.desc",
labelKey = "imageBrowserCaptionSize.label"
)
private int imageBrowserCaptionSize = 50; private int imageBrowserCaptionSize = 50;
@Setting @Setting(
descKey = "imageBrowserDescriptionSize.desc",
labelKey = "imageBrowserDescriptionSize.label"
)
private int imageBrowserDescriptionSize = 400; private int imageBrowserDescriptionSize = 400;
@Setting @Setting(
descKey = "imageBrowserTitleSize.desc",
labelKey = "imageBrowserTitleSize.label"
)
private int imageBrowserTitleSize = 200; private int imageBrowserTitleSize = 200;
@Setting @Setting(
descKey = "imageCacheEnabled.desc",
labelKey = "imageCacheEnabled.label"
)
private boolean imageCacheEnabled = true; private boolean imageCacheEnabled = true;
@Setting @Setting(
descKey = "imageCachePrefetchEnabled.desc",
labelKey = "imageCachePrefetchEnabled.label"
)
private boolean imageCachePrefetchEnabled = false; private boolean imageCachePrefetchEnabled = false;
@Setting @Setting(
descKey = "imageCacheMaxSize.desc",
labelKey = "imageCacheMaxSize.label"
)
private int imageCacheMaxSize = 100; private int imageCacheMaxSize = 100;
@Setting @Setting(
descKey = "imageCacheMaxAge.desc",
labelKey = "imageCacheMaxAge.label"
)
private int imageCacheMaxAge = 300; private int imageCacheMaxAge = 300;
@Setting @Setting(
descKey = "attachPersonOrgaUnitsStep.desc",
labelKey = "attachPersonOrgaUnitsStep.label"
)
private boolean attachPersonOrgaUnitsStep = true; private boolean attachPersonOrgaUnitsStep = true;
@Setting @Setting(
descKey = "personOrgaUnitsStepSortKey.desc",
labelKey = "personOrgaUnitsStepSortKey.label"
)
private int personOrgaUnitsStepSortKey = 20; private int personOrgaUnitsStepSortKey = 20;
@Setting @Setting(
descKey = "enableXmlCache.desc",
labelKey = "enableXmlCache.label"
)
private boolean enableXmlCache = false; private boolean enableXmlCache = false;
@Setting @Setting(
descKey = "xmlCacheSize.desc",
labelKey = "xmlCacheSize.label"
)
private int xmlCacheSize = 2500; private int xmlCacheSize = 2500;
@Setting @Setting(
descKey = "xmlCacheAge.desc",
labelKey = "xmlCacheAge.label"
)
private int xmlCacheAge = 60 * 60 * 24; private int xmlCacheAge = 60 * 60 * 24;
@Setting @Setting(
descKey = "categoryAuthoringAddForm.desc",
labelKey = "categoryAuthoringAddForm.label"
)
private String categoryAuthoringAddForm = ItemCategoryPicker.class.getName(); private String categoryAuthoringAddForm = ItemCategoryPicker.class.getName();
@Setting @Setting(
descKey = "categoryAuthoringExtension.desc",
labelKey = "categoryAuthoringExtension.label"
)
private String categoryAuthoringExtension = ItemCategoryExtension.class private String categoryAuthoringExtension = ItemCategoryExtension.class
.getName(); .getName();
@Setting @Setting(
descKey = "categoryPickerAjaxExpandAll.desc",
labelKey = "categoryPickerAjaxExpandAll.label"
)
private boolean categoryPickerAjaxExpandAll = false; private boolean categoryPickerAjaxExpandAll = false;
/** /**
* Max length of the description of a link (in database max length are 4000 * Max length of the description of a link (in database max length are 4000
* characters) * characters)
*/ */
@Setting @Setting(
descKey = "linkDescMaxLength.desc",
labelKey = "linkDescMaxLength.label"
)
private int linkDescMaxLength = 400; private int linkDescMaxLength = 400;
public static CMSConfig getConfig() { public static CMSConfig getConfig() {

View File

@ -0,0 +1,127 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.CmsConstants;
import java.text.MessageFormat;
import java.util.AbstractMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("ContentSectionAdminMessages")
public class ContentSectionAdminMessages extends AbstractMap<String, String> {
/**
* Provides access to the locale negoiated by LibreCCM.
*/
@Inject
private GlobalizationHelper globalizationHelper;
/**
* The {@link ResourceBundle} to use.
*/
private ResourceBundle messages;
/**
* Loads the resource bundle.
*/
@PostConstruct
private void init() {
messages = ResourceBundle.getBundle(
CmsConstants.CONTENT_SECTION_DESC_BUNDLE,
globalizationHelper.getNegotiatedLocale()
);
}
/**
* Retrieves a message from the resource bundle.
*
* @param key The key of the message.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(final String key) {
if (messages.containsKey(key)) {
return messages.getString(key);
} else {
return String.format("???%s???", key);
}
}
/**
* Retrieves a message with placeholders.
*
* @param key The key of the message.
* @param parameters The parameters for the placeholders.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(
final String key, final List<Object> parameters
) {
return getMessage(key, parameters.toArray());
}
/**
* The translated message or {@code ???message???} if the the key is not
* found in the resource bundle (message is replaced with the key).
*
* @param key The key of the message.
* @param parameters The parameters for the placeholders.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(
final String key, final Object[] parameters
) {
if (messages.containsKey(key)) {
return MessageFormat.format(messages.getString(key), parameters);
} else {
return String.format("???%s???", key);
}
}
@Override
public String get(final Object key) {
return get((String) key);
}
public String get(final String key) {
return getMessage(key);
}
@Override
public Set<Entry<String, String>> entrySet() {
return messages
.keySet()
.stream()
.collect(
Collectors.toMap(key -> key, key -> messages.getString(key))
)
.entrySet();
}
}

View File

@ -0,0 +1,134 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.ui.admin.applications.ApplicationController;
import org.librecms.contentsection.ContentSection;
import org.librecms.contentsection.ContentSectionManager;
import org.librecms.contentsection.ContentSectionRepository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
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;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/applications/content-sections")
public class ContentSectionApplicationController
implements ApplicationController {
@Inject
private Models models;
@Inject
private ContentSectionManager sectionManager;
@Inject
private ContentSectionRepository sectionRepository;
@GET
@Path("/")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String getApplication() {
final List<ContentSection> contentSections = sectionRepository.findAll();
models.put(
"sections",
sectionRepository
.findAll()
.stream()
.map(this::buildContentSectionTableRow)
.sorted()
.collect(Collectors.toList())
);
return "org/libreccm/ui/admin/applications/content-sections/content-sections.xhtml";
}
@POST
@Path("/add")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String addContentSection(
@FormParam("label") final String label
) {
sectionManager.createContentSection(label);
return "redirect:applications/content-sections";
}
@POST
@Path("/{sectionId}/update")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateContentSection(
@PathParam("sectionId") final long sectionId,
@FormParam("label") final String label
) {
final Optional<ContentSection> result = sectionRepository
.findById(sectionId);
if (result.isPresent()) {
sectionManager.renameContentSection(result.get(), label);
}
return "redirect:applications/content-sections";
}
@POST
@Path("/{sectionId}/delete")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String deleteContentSection(
@PathParam("sectionId") final long sectionId,
@FormParam("confirmed") final String confirmed
) {
final Optional<ContentSection> result = sectionRepository
.findById(sectionId);
if (result.isPresent() && "true".equals(confirmed)) {
sectionRepository.delete(result.get());
}
return "redirect:applications/content-sections";
}
private ContentSectionTableRow buildContentSectionTableRow(
final ContentSection section
) {
final ContentSectionTableRow row = new ContentSectionTableRow();
row.setSectionId(section.getObjectId());
row.setUuid(section.getUuid());
row.setLabel(section.getLabel());
return row;
}
}

View File

@ -0,0 +1,57 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import java.util.Comparator;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ContentSectionTableRow implements
Comparable<ContentSectionTableRow> {
private long sectionId;
private String uuid;
private String label;
public long getSectionId() {
return sectionId;
}
protected void setSectionId(long sectionId) {
this.sectionId = sectionId;
}
public String getUuid() {
return uuid;
}
protected void setUuid(final String uuid) {
this.uuid = uuid;
}
public String getLabel() {
return label;
}
protected void setLabel(final String label) {
this.label = label;
}
@Override
public int compareTo(final ContentSectionTableRow other) {
return Comparator
.nullsFirst(
Comparator
.comparing(ContentSectionTableRow::getLabel)
.thenComparing(ContentSectionTableRow::getSectionId)
).compare(this, other);
}
}

View File

@ -23,6 +23,7 @@ import org.libreccm.modules.RequiredModule;
import org.libreccm.modules.ShutdownEvent; import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent; import org.libreccm.modules.UnInstallEvent;
import org.libreccm.pagemodel.PageModelComponentModel; import org.libreccm.pagemodel.PageModelComponentModel;
import org.libreccm.ui.admin.contentsections.ContentSectionApplicationController;
import org.libreccm.web.ApplicationType; import org.libreccm.web.ApplicationType;
import org.libreccm.web.CcmApplication; import org.libreccm.web.CcmApplication;
import org.librecms.assets.AssetTypes; import org.librecms.assets.AssetTypes;
@ -80,7 +81,8 @@ import java.util.Properties;
settingsPane = SettingsPane.class, settingsPane = SettingsPane.class,
descBundle = CmsConstants.CONTENT_SECTION_DESC_BUNDLE, descBundle = CmsConstants.CONTENT_SECTION_DESC_BUNDLE,
creator = ContentSectionCreator.class, creator = ContentSectionCreator.class,
servletPath = "/templates/servlet/content-section" servletPath = "/templates/servlet/content-section",
applicationController = ContentSectionApplicationController.class
), ),
@ApplicationType( @ApplicationType(
name = "org.librecms.pages.Pages", name = "org.librecms.pages.Pages",
@ -93,6 +95,9 @@ import java.util.Properties;
servletPath = "/templates/servlet/pages" servletPath = "/templates/servlet/pages"
) )
}, },
configurations = {
org.arsdigita.cms.CMSConfig.class
},
pageModelComponentModels = { pageModelComponentModels = {
@PageModelComponentModel( @PageModelComponentModel(
modelClass = CategorizedItemComponent.class, modelClass = CategorizedItemComponent.class,

View File

@ -10,6 +10,7 @@ import org.libreccm.imexport.AbstractEntityImExporter;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
@ -42,4 +43,19 @@ public abstract class AbstractContentItemImExporter<T extends ContentItem>
itemRepository.save(entity); itemRepository.save(entity);
} }
@Override
protected T reloadEntity(final T entity) {
return itemRepository
.findById(
Objects.requireNonNull(entity).getObjectId(), getEntityClass()
).orElseThrow(
() -> new IllegalArgumentException(
String.format(
"ContentItem entity %s not found in database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -0,0 +1,62 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.librecms.contentsection;
import org.libreccm.imexport.AbstractEntityImExporter;
import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Processes(ContentSection.class)
public class ContentSectionImExporter
extends AbstractEntityImExporter<ContentSection> {
@Inject
private ContentSectionRepository sectionRepository;
@Override
public Class<ContentSection> getEntityClass() {
return ContentSection.class;
}
@Override
protected Set<Class<? extends Exportable>> getRequiredEntities() {
return Collections.emptySet();
}
@Override
@Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final ContentSection entity) {
sectionRepository.save(entity);
}
@Override
protected ContentSection reloadEntity(final ContentSection entity) {
return sectionRepository
.findById(Objects.requireNonNull(entity).getObjectId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"ContentSection entity %s not found in database.",
Objects.toString(entity)
)
)
);
}
}

View File

@ -19,7 +19,7 @@ import javax.enterprise.context.RequestScoped;
public class ArticleImExporter extends AbstractContentItemImExporter<Article> { public class ArticleImExporter extends AbstractContentItemImExporter<Article> {
@Override @Override
protected Class<Article> getEntityClass() { public Class<Article> getEntityClass() {
return Article.class; return Article.class;
} }

View File

@ -19,7 +19,7 @@ import javax.enterprise.context.RequestScoped;
public class EventImExporter extends AbstractContentItemImExporter<Event> { public class EventImExporter extends AbstractContentItemImExporter<Event> {
@Override @Override
protected Class<Event> getEntityClass() { public Class<Event> getEntityClass() {
return Event.class; return Event.class;
} }

View File

@ -20,7 +20,7 @@ public class MultiPartArticleImExporter
extends AbstractContentItemImExporter<MultiPartArticle> { extends AbstractContentItemImExporter<MultiPartArticle> {
@Override @Override
protected Class<MultiPartArticle> getEntityClass() { public Class<MultiPartArticle> getEntityClass() {
return MultiPartArticle.class; return MultiPartArticle.class;
} }

View File

@ -19,7 +19,7 @@ import javax.enterprise.context.RequestScoped;
public class NewsImExporter extends AbstractContentItemImExporter<News> { public class NewsImExporter extends AbstractContentItemImExporter<News> {
@Override @Override
protected Class<News> getEntityClass() { public Class<News> getEntityClass() {
return News.class; return News.class;
} }

View File

@ -0,0 +1,115 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:libreccm="http://xmlns.jcp.org/jsf/composite/components/libreccm"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/ccm-admin.xhtml">
<ui:param name="activePage" value="applications" />
<ui:param name="title" value="#{AdminMessages['applications.label']}" />
<ui:define name="breadcrumb">
<li class="breadcrumb-item">
#{AdminMessages['applications.label']}
</li>
<li class="breadcrumb-item active">
#{ContentSectionAdminMessages['application_title']}
</li>
</ui:define>
<ui:define name="main">
<div class="container">
<h1>#{ContentSectionAdminMessages['application_title']}</h1>
<div class="mb-2">
<bootstrap:modalForm actionTarget="#{mvc.uri('ContentSectionApplicationController#addContentSection')}"
buttonIcon="plus-circle"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add']}"
buttonTextClass="text-right"
dialogId="contentsection-add-form"
>
<f:facet name="title">
<h3>#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.title']}</h3>
</f:facet>
<f:facet name="body">
<bootstrap:formGroupText help="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.name.help']}"
inputId="contentsection-add-form-name"
label="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.name.label']}"
name="label"
pattern="[\\w-.]"/>
</f:facet>
<f:facet name="footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.cancel']}
</button>
<button type="submit" class="btn btn-primary">
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.submit']}
</button>
</f:facet>
</bootstrap:modalForm>
</div>
<table class="table table-hover">
<thead>
<tr>
<th>#{ContentSectionAdminMessages['contentsections.ui.admin.instances_table.col_name.header']}</th>
<th class="text-center" colspan="2">#{ContentSectionAdminMessages['contentsections.ui.admin.instances_table.col_name.actions']}</th>
</tr>
</thead>
<tbody>
<c:forEach items="#{sections}" var="section">
<tr>
<td>#{section.label}</td>
<td class="text-center">
<bootstrap:modalForm actionTarget="#{mvc.uri('ContentSectionApplicationController#updateContentSection', { 'sectionId': section.sectionId })}"
buttonIcon="plus-circle"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit']}"
buttonTextClass="text-right"
dialogId="contentsection-#{section.sectionId}-edit-form"
>
<f:facet name="title">
<h3>#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.title']}</h3>
</f:facet>
<f:facet name="body">
<bootstrap:formGroupText help="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.name.help']}"
inputId="contentsection-add-form-name"
label="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.name.label']}"
name="label"
pattern="[\\w-.]"
value="#{section.label}" />
</f:facet>
<f:facet name="footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.cancel']}
</button>
<button type="submit" class="btn btn-primary">
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.submit']}
</button>
</f:facet>
</bootstrap:modalForm>
</td>
<td class="text-center">
<libreccm:deleteDialog actionTarget="#{mvc.uri('ContentSectionApplicationController#deleteContentSection', { 'sectionId': section.sectionId })}"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.button_text']}"
cancelLabel="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.cancel']}"
confirmLabel="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.confirm']}"
dialogId="contentsection-#{section.sectionId}-deletedialog"
dialogTitle="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.title']}"
message="#{ContentSectionAdminMessages.getMessage('contentsections.ui.admin.instances.delete_dialog.message', [section.label])}" />
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</ui:define>
</ui:composition>
</html>

View File

@ -0,0 +1,126 @@
title=CMS Configuration
description=Various settings for customizing content handling
defaultItemTemplatePath.desc=Path of the default JSP template for Content Items. Only used by the legacy presentation system.
defaultItemTemplatePath.label=Default Item Template Path
defaultFolderTemplatePath.label=Default Folder Template Path
defaultFolderTemplatePath.desc=Path of the default JSP template for folders. Only used by the legacy presentation system.
templateRootPath.label=Template Root Path
templateRootPath.desc=Where to look for JSP templates. Only used by the legacy presentation system.
itemAdapters.label=Item Adapters
itemAdapters.desc=Path of the the item adapter file
useStreamlinedCreation.label=Use Streamlined Creation?
useStreamlinedCreation.desc=
dhtmlEditorConfig.label=DHTML Editor Configuration
dhtmlEditorConfig.desc=Configuration of the Rich Text Editor
dhtmlEditorPlugins.label=DHTML Editor Plugins
dhtmlEditorPlugins.desc=
dhtmlEditorHiddenButtons.label=DHTML Editor Hidden Buttons
dhtmlEditorHiddenButtons.desc=
hideAdminTabs.label=Hide Admin Tabs?
hideAdminTabs.desc=Hide Admin Tabs for none admin users?
hideFolderIndexCheckbox.label=Hide Folder Index Checkbox?
hideFolderIndexCheckbox.desc=
hideLaunchDate.label=Hide Launch Date?
hideLaunchDate.desc=
requireLaunchDate.label=Require Launch Date?
requireLaunchDate.desc=
hideTemplatesTab.label=Hide Templates Tab?
hideTemplatesTab.desc=
hideTextAssetUploadFile.label=Hide Text Asset Upload File?
hideTextAssetUploadFile.desc=
hideTimezone.label=Hide Timezone?
hideTimezone.desc=
saveTextCleansWordTags.label=Save Text Cleans Word Tags?
saveTextCleansWordTags.desc=
disableFileAssetExtraction.desc=
disableFileAssetExtraction.label=Disable File Asset Extraction?
deleteWorkflowAfterPublication.label=Delete Workflow after publication?
deleteWorkflowAfterPublication.desc=Whether an item's workflow should be deleted, once the item has been \n(re)published.
soonExpiredTimespanDays.desc=
soonExpiredTimespanDays.label=Soon Expired Timespan Days
soonExpiredTimespanMonths.label=Soon expired timespan months
soonExpiredTimespanMonths.desc=
unpublishedNotFound.label=Unpublished not found?
unpublishedNotFound.desc=Does a redirect to the unpublished item generate not found error?
linksOnlyInSameSubsite.desc=
linksOnlyInSameSubsite.label=Links Only In Same Subsite?
hideResetLifecycleLink.desc=
hideResetLifecycleLink.label=Hide Reset Lifecycle Link?
scoreTitleAndKeywords.label=Score title and keywords?
scoreTitleAndKeywords.desc=
titleWeight.desc=
titleWeight.label=Title Weight
keywordWeight.label=Keyword Weight
keywordWeight.desc=
limitItemSearchToContentSection.desc=
limitItemSearchToContentSection.label=Limit Item Search to Content Section?
skipAssetSteps.label=Skip Asset Steps?
skipAssetSteps.desc=
mandatoryDescriptions.label=Mandatory Descriptions?
mandatoryDescriptions.desc=
deleteLifecycleWhenComplete.label=Delete Lifecycle when complete?
deleteLifecycleWhenComplete.desc=
deleteWorkflowNotificationWhenSend.label=Delete Workflow Notification when send?
deleteWorkflowNotificationWhenSend.desc=
deleteExpiryNotificationsWhenSent.label=Delete Expiry Notifications when sent
deleteExpiryNotificationsWhenSent.desc=
defaultNotificationTime.label=Default Notification Time
defaultNotificationTime.desc=Amount of time (in hours) before the expiration of a content item that users in the Alert Recipient role are alerted via email
notifyAuthorOnLifecycle.label=Notify author on lifecycle?
notifyAuthorOnLifecycle.desc=
contentCenterMap.label=Content Center Map
contentCenterMap.desc=
defaultItemResolverClassNames.label=Default Item Resolver class names
defaultItemResolverClassNames.desc=
itemSearchDefaultTab.label=Item Search Default Tab
itemSearchDefaultTab.desc=
itemSearchFlatBrowsePanePageSize=Item Search Flat Browse Pane Page Size
folderBrowseListSize.label=Folder Browse List Size
folderBrowseListSize.desc=
folderAtoZShowLimit.label=Folder A to Z Show Limit
folderAtoZShowLimit.desc=
useOldStyleItemLifecycleItemPane.label=Use Old Style Item Lifecycle Item Pane?
useOldStyleItemLifecycleItemPane.desc=
threadPublishing.label=Thread Publishing
threadPublishing.desc=
publishingFailureSender.label=Publishing Failure Sender
publishingFailureSender.desc=
publishingFailureReceiver.label=Publishing Failure Receiver
publishingFailureReceiver.desc=
imageBrowserThumbnailMaxWidth.label=Image Browser Thumbnail Max Width
imageBrowserThumbnailMaxWidth.desc=
imageBrowserThumbnailMaxHeight.label=Image Browser Thumbnail Max Height
imageBrowserThumbnailMaxHeight.desc=
imageBrowserCaptionSize.label=Image Browser Caption Size
imageBrowserCaptionSize.desc=
imageBrowserDescriptionSize.label=Image Browser Description Size
imageBrowserDescriptionSize.desc=
imageBrowserTitleSize.label=Image Browser Title Size
imageBrowserTitleSize.desc=
imageCacheEnabled.label=Image Cache Enabled?
imageCacheEnabled.desc=
imageCachePrefetchEnabled.label=Image Cache Prefetch Enabled
imageCachePrefetchEnabled.desc=
imageCacheMaxSize.label=Image Cache Max Size
imageCacheMaxSize.desc=
imageCacheMaxAge.label=Image Cache Max Age
imageCacheMaxAge.desc=
attachPersonOrgaUnitsStep.label=Attach Person Orga Units Step enabled?
attachPersonOrgaUnitsStep.desc=
personOrgaUnitsStepSortKey.label=Person Orga Units Step Sort Key
personOrgaUnitsStepSortKey.desc=
enableXmlCache.label=Enable XML Cache?
enableXmlCache.desc=
xmlCacheSize.label=XML Cache Size
xmlCacheSize.desc=
xmlCacheAge.label=XML Cache Age
xmlCacheAge.desc=
categoryAuthoringAddForm.label=Category Authoring Add Form
categoryAuthoringAddForm.desc=
categoryAuthoringExtension.label=Category Authoring Extension
categoryAuthoringExtension.desc=
categoryPickerAjaxExpandAll.label=Category Picker AJAX Expand All?
categoryPickerAjaxExpandAll.desc=
linkDescMaxLength.label=Link description max length
linkDescMaxLength.desc=

View File

@ -0,0 +1,126 @@
title=CMS Konfiguration
description=Einstellungen bez\u00fcglich Bearbeitung und Anzeige von Inhalten
defaultItemTemplatePath.desc=Pfad der Standard-JSP-Template f\u00fcr Content Items. Nur vom alten Pr\u00e4sentationssystem genutzt.
defaultItemTemplatePath.label=Pfad der Standard Template f\u00fcr Content Items
defaultFolderTemplatePath.label=Pfad der Standard-JSP-Template f\u00fcr Ordner
defaultFolderTemplatePath.desc=Pfad der Standard-JSP-Template f\u00fcr Ordner. Nur vom alten Pr\u00e4sentationssystem genutzt.
templateRootPath.label=Suchpfad f\u00fcr Templates
templateRootPath.desc=Ordner in dem nach JSP-Templates gesucht wird. Nur vom alten Pr\u00e4sentationssystem genutzt.
itemAdapters.label=Item Adapter
itemAdapters.desc=Pfad der Item-Adapter-Datei
useStreamlinedCreation.label=Use Streamlined Creation?
useStreamlinedCreation.desc=
dhtmlEditorConfig.label=DHTML Editor Konfiguration
dhtmlEditorConfig.desc=Konfiguration des Text Editors
dhtmlEditorPlugins.label=DHTML Editor Plugins
dhtmlEditorPlugins.desc=
dhtmlEditorHiddenButtons.label=DHTML Editor Deaktivierte Funktionen
dhtmlEditorHiddenButtons.desc=
hideAdminTabs.label=Admin Tabs verstecken?
hideAdminTabs.desc=Sollen die Admin-Tabs f\u00fcr Benutzer*innen ohnen Administrations-Rechte sichtbar sein?
hideFolderIndexCheckbox.label=Index-Checkbox f\u00fcr Ordner verstecken?
hideFolderIndexCheckbox.desc=
hideLaunchDate.label=Start-Datum verstecken?
hideLaunchDate.desc=
requireLaunchDate.label=Startdatum erforderlich?
requireLaunchDate.desc=
hideTemplatesTab.label=Vorlaben Tab verbergen?
hideTemplatesTab.desc=
hideTextAssetUploadFile.label=Hochladen f\u00fcr Text-Assets verbergen?
hideTextAssetUploadFile.desc=
hideTimezone.label=Zeitzone verstecken?
hideTimezone.desc=
saveTextCleansWordTags.label=Speichern bereinigt Word Tags?
saveTextCleansWordTags.desc=
disableFileAssetExtraction.desc=
disableFileAssetExtraction.label=Analyse von hochgeladenen Dateien deaktivieren?
deleteWorkflowAfterPublication.label=Arbeitsablauf nach dem Publizieren l\u00f6schen?
deleteWorkflowAfterPublication.desc=
soonExpiredTimespanDays.desc=
soonExpiredTimespanDays.label=Soon Expired Timespan Days
soonExpiredTimespanMonths.label=Soon expired timespan months
soonExpiredTimespanMonths.desc=
unpublishedNotFound.label=Unpublished not found?
unpublishedNotFound.desc=Does a redirect to the unpublished item generate not found error?
linksOnlyInSameSubsite.desc=
linksOnlyInSameSubsite.label=Links Only In Same Subsite?
hideResetLifecycleLink.desc=
hideResetLifecycleLink.label=Hide Reset Lifecycle Link?
scoreTitleAndKeywords.label=Score title and keywords?
scoreTitleAndKeywords.desc=
titleWeight.desc=
titleWeight.label=Title Weight
keywordWeight.label=Keyword Weight
keywordWeight.desc=
limitItemSearchToContentSection.desc=
limitItemSearchToContentSection.label=Limit Item Search to Content Section?
skipAssetSteps.label=Skip Asset Steps?
skipAssetSteps.desc=
mandatoryDescriptions.label=Mandatory Descriptions?
mandatoryDescriptions.desc=
deleteLifecycleWhenComplete.label=Delete Lifecycle when complete?
deleteLifecycleWhenComplete.desc=
deleteWorkflowNotificationWhenSend.label=Delete Workflow Notification when send?
deleteWorkflowNotificationWhenSend.desc=
deleteExpiryNotificationsWhenSent.label=Delete Expiry Notifications when sent
deleteExpiryNotificationsWhenSent.desc=
defaultNotificationTime.label=Default Notification Time
defaultNotificationTime.desc=Amount of time (in hours) before the expiration of a content item that users in the Alert Recipient role are alerted via email
notifyAuthorOnLifecycle.label=Notify author on lifecycle?
notifyAuthorOnLifecycle.desc=
contentCenterMap.label=Content Center Map
contentCenterMap.desc=
defaultItemResolverClassNames.label=Default Item Resolver class names
defaultItemResolverClassNames.desc=
itemSearchDefaultTab.label=Item Search Default Tab
itemSearchDefaultTab.desc=
itemSearchFlatBrowsePanePageSize=Item Search Flat Browse Pane Page Size
folderBrowseListSize.label=Folder Browse List Size
folderBrowseListSize.desc=
folderAtoZShowLimit.label=Folder A to Z Show Limit
folderAtoZShowLimit.desc=
useOldStyleItemLifecycleItemPane.label=Use Old Style Item Lifecycle Item Pane?
useOldStyleItemLifecycleItemPane.desc=
threadPublishing.label=Thread Publishing
threadPublishing.desc=
publishingFailureSender.label=Publishing Failure Sender
publishingFailureSender.desc=
publishingFailureReceiver.label=Publishing Failure Receiver
publishingFailureReceiver.desc=
imageBrowserThumbnailMaxWidth.label=Image Browser Thumbnail Max Width
imageBrowserThumbnailMaxWidth.desc=
imageBrowserThumbnailMaxHeight.label=Image Browser Thumbnail Max Height
imageBrowserThumbnailMaxHeight.desc=
imageBrowserCaptionSize.label=Image Browser Caption Size
imageBrowserCaptionSize.desc=
imageBrowserDescriptionSize.label=Image Browser Description Size
imageBrowserDescriptionSize.desc=
imageBrowserTitleSize.label=Image Browser Title Size
imageBrowserTitleSize.desc=
imageCacheEnabled.label=Image Cache Enabled?
imageCacheEnabled.desc=
imageCachePrefetchEnabled.label=Image Cache Prefetch Enabled
imageCachePrefetchEnabled.desc=
imageCacheMaxSize.label=Image Cache Max Size
imageCacheMaxSize.desc=
imageCacheMaxAge.label=Image Cache Max Age
imageCacheMaxAge.desc=
attachPersonOrgaUnitsStep.label=Attach Person Orga Units Step enabled?
attachPersonOrgaUnitsStep.desc=
personOrgaUnitsStepSortKey.label=Person Orga Units Step Sort Key
personOrgaUnitsStepSortKey.desc=
enableXmlCache.label=Enable XML Cache?
enableXmlCache.desc=
xmlCacheSize.label=XML Cache Size
xmlCacheSize.desc=
xmlCacheAge.label=XML Cache Age
xmlCacheAge.desc=
categoryAuthoringAddForm.label=Category Authoring Add Form
categoryAuthoringAddForm.desc=
categoryAuthoringExtension.label=Category Authoring Extension
categoryAuthoringExtension.desc=
categoryPickerAjaxExpandAll.label=Category Picker AJAX Expand All?
categoryPickerAjaxExpandAll.desc=
linkDescMaxLength.label=Link description max length
linkDescMaxLength.desc=

View File

@ -17,3 +17,21 @@
application_title=Content Section application_title=Content Section
application_desc=A content section is used to group similar content. application_desc=A content section is used to group similar content.
contentsections.ui.admin.instances_table.col_name.header=Name
contentsections.ui.admin.instances_table.col_name.actions=Actions
contentsections.ui.admin.instances.add_form.title=Create new Content Section
contentsections.ui.admin.instances.add=Add Content Section
contentsections.ui.admin.instances.add_form.name.help=The name of the new Content Section. Also used as URL stub, therefore only characters in an URL can be used.
contentsections.ui.admin.instances.add_form.name.label=Name
contentsections.ui.admin.instances.add_form.cancel=Cancel
contentsections.ui.admin.instances.add_form.submit=Create Content Section
contentsections.ui.admin.instances.delete_dialog.button_text=Delete
contentsections.ui.admin.instances.delete_dialog.cancel=Cancel
contentsections.ui.admin.instances.delete_dialog.confirm=Delete Content Section
contentsections.ui.admin.instances.delete_dialog.title=Content Section l\u00f6schen best\u00e4tigen
contentsections.ui.admin.instances.delete_dialog.message=Are you sure to delete the Content Section {0}?
contentsections.ui.admin.instances.edit=Edit
contentsections.ui.admin.instances.edit_form.name.help=The name of the Content Section. Also used as URL stub, therefore only characters in an URL can be used.
contentsections.ui.admin.instances.edit_form.name.label=Name
contentsections.ui.admin.instances.edit_form.cancel=Cancel
contentsections.ui.admin.instances.edit_form.submit=Save

View File

@ -17,3 +17,21 @@
application_title=Content Section application_title=Content Section
application_desc=A content section is used to group similar content. application_desc=A content section is used to group similar content.
contentsections.ui.admin.instances_table.col_name.header=Name
contentsections.ui.admin.instances_table.col_name.actions=Aktionen
contentsections.ui.admin.instances.add_form.title=Neue Content Section anlegen
contentsections.ui.admin.instances.add=Content Section hinzuf\u00fcgen
contentsections.ui.admin.instances.add_form.name.help=Der Name der neuen Content Section. Wird auch als URL-Fragment genutzt, daher sind Zeichen erlaubt, die in einer URL vorkommen d\u00fcrfen.
contentsections.ui.admin.instances.add_form.name.label=Name
contentsections.ui.admin.instances.add_form.cancel=Abbrechen
contentsections.ui.admin.instances.add_form.submit=Content Section anlegen
contentsections.ui.admin.instances.delete_dialog.button_text=L\u00f6schen
contentsections.ui.admin.instances.delete_dialog.cancel=Abbrechen
contentsections.ui.admin.instances.delete_dialog.confirm=Content Section l\u00f6schen
contentsections.ui.admin.instances.delete_dialog.title=Content Section l\u00f6schen best\u00e4tigen
contentsections.ui.admin.instances.delete_dialog.message=Sind Sie sicher dass Sie die Content Section {0} l\u00f6schen wollen?
contentsections.ui.admin.instances.edit=Bearbeiten
contentsections.ui.admin.instances.edit_form.name.help=Der Name der Content Section. Wird auch als URL-Fragment genutzt, daher sind Zeichen erlaubt, die in einer URL vorkommen d\u00fcrfen.
contentsections.ui.admin.instances.edit_form.name.label=Name
contentsections.ui.admin.instances.edit_form.cancel=Abbrechen
contentsections.ui.admin.instances.edit_form.submit=Speichern

6979
ccm-core/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "ccm-core",
"version": "7.0.0",
"description": "JavaScript stuff for ccm-core",
"main": "index.js",
"scripts": {
"build": "npm-run-all build:*:*",
"build:ccm-admin:js": "parcel build --out-dir target/generated-resources/assets/@admin src/main/typescript/ccm-admin/ccm-admin.ts",
"build:ccm-admin:css": "sass src/main/scss/ccm-admin/ccm-admin.scss target/generated-resources/assets/@admin/ccm-admin.css"
},
"author": "Jens Pelzetter",
"license": "LGPL-3.0-or-later",
"devDependencies": {
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.4",
"sass": "^1.26.10",
"typescript": "^4.0.2"
},
"dependencies": {
"bootstrap": "^4.5.2",
"bootstrap-icons": "^1.0.0",
"jquery": "^3.5.1",
"popper.js": "^1.16.1"
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -151,6 +152,10 @@
<groupId>commons-lang</groupId> <groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId> <artifactId>commons-lang</artifactId>
</dependency> </dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>oro</groupId> <groupId>oro</groupId>
@ -307,6 +312,9 @@
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
<resource>
<directory>./target/generated-resources</directory>
</resource>
</resources> </resources>
<testResources> <testResources>
@ -330,6 +338,39 @@
<encoding>${project.build.sourceEncoding}</encoding> <encoding>${project.build.sourceEncoding}</encoding>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<configuration>
<installDirectory>../node</installDirectory>
</configuration>
<executions>
<execution>
<id>Install node.js and NPM</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v12.18.3</nodeVersion>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
</execution>
<execution>
<id>build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>

View File

@ -41,32 +41,59 @@ import java.util.stream.Collectors;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@Configuration( @Configuration(
descKey = "bebop.config.description") descBundle = "com.arsdigita.bebop.BebopConfig",
descKey = "description",
titleKey = "title"
)
public final class BebopConfig { public final class BebopConfig {
@Setting @Setting(
descKey = "presenterClassName.desc",
labelKey = "presenterClassName.label"
)
private String presenterClassName = PageTransformer.class.getName(); private String presenterClassName = PageTransformer.class.getName();
@Setting @Setting(
descKey = "basePageClassName.desc",
labelKey = "basePageClassName.label"
)
private String basePageClassName = SimplePage.class.getName(); private String basePageClassName = SimplePage.class.getName();
@Setting @Setting(
descKey = "tidyConfigFile.desc",
labelKey = "tidyConfigFile.label"
)
private String tidyConfigFile private String tidyConfigFile
= "com/arsdigita/bebop/parameters/tidy.properties"; = "com/arsdigita/bebop/parameters/tidy.properties";
@Setting @Setting(
descKey = "fancyErrors.desc",
labelKey = "fancyErrors.label"
)
private Boolean fancyErrors = false; private Boolean fancyErrors = false;
@Setting @Setting(
descKey = "dcpOnButtons.desc",
labelKey = "dcpOnButtons.label"
)
private Boolean dcpOnButtons = true; private Boolean dcpOnButtons = true;
@Setting @Setting(
descKey = "dcpOnLinks.desc",
labelKey = "dcpOnLinks.label"
)
private Boolean dcpOnLinks = false; private Boolean dcpOnLinks = false;
@Setting @Setting(
descKey = "treeSelectEnabled.desc",
labelKey = "treeSelectEnabled.label"
)
private Boolean treeSelectEnabled = false; private Boolean treeSelectEnabled = false;
@Setting @Setting(
descKey = "dhtmlEditors.desc",
labelKey = "dhtmlEditors.label"
)
private Set<String> dhtmlEditors = new HashSet<>( private Set<String> dhtmlEditors = new HashSet<>(
Arrays.asList(new String[]{BebopConstants.BEBOP_XINHAEDITOR, Arrays.asList(new String[]{BebopConstants.BEBOP_XINHAEDITOR,
BebopConstants.BEBOP_FCKEDITOR, BebopConstants.BEBOP_FCKEDITOR,
@ -74,14 +101,23 @@ public final class BebopConfig {
BebopConstants.BEBOP_CCMEDITOR, BebopConstants.BEBOP_CCMEDITOR,
BebopConstants.BEBOP_TINYMCE_EDITOR})); BebopConstants.BEBOP_TINYMCE_EDITOR}));
@Setting @Setting(
descKey = "defaultDhtmlEditor.desc",
labelKey = "defaultDhtmlEditor.label"
)
private String defaultDhtmlEditor = BebopConstants.BEBOP_TINYMCE_EDITOR; private String defaultDhtmlEditor = BebopConstants.BEBOP_TINYMCE_EDITOR;
@Setting @Setting(
descKey = "dhtmlEditorSrcFile.desc",
labelKey = "dhtmlEditorSrcFile.label"
)
// private String dhtmlEditorSrcFile = "/ccm-editor/ccm-editor-loader.js"; // private String dhtmlEditorSrcFile = "/ccm-editor/ccm-editor-loader.js";
private String dhtmlEditorSrcFile = "/webjars/tinymce/4.8.2/tinymce.js"; private String dhtmlEditorSrcFile = "/webjars/tinymce/4.8.2/tinymce.js";
@Setting @Setting(
descKey = "showClassName.desc",
labelKey = "showClassName.label"
)
private Boolean showClassName = false; private Boolean showClassName = false;
public static BebopConfig getConfig() { public static BebopConfig getConfig() {

View File

@ -28,19 +28,35 @@ import org.libreccm.configuration.Setting;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@Configuration @Configuration(
descBundle = "com.arsdigita.dispatcher.DispatcherConfig",
descKey = "description",
titleKey = "title"
)
public final class DispatcherConfig { public final class DispatcherConfig {
@Setting @Setting(
descKey = "cachingActive.desc",
labelKey = "cachingActive.label"
)
private Boolean cachingActive = true; private Boolean cachingActive = true;
@Setting @Setting(
descKey = "defaultExpiry.desc",
labelKey = "defaultExpiry.label"
)
private Integer defaultExpiry = 259200; private Integer defaultExpiry = 259200;
@Setting @Setting(
descKey = "staticUrlPrefix.desc",
labelKey = "statusUrlPrefix.label"
)
private String staticUrlPrefix = "/STATICII/"; private String staticUrlPrefix = "/STATICII/";
@Setting @Setting(
descKey = "defaultPageClass.desc",
labelKey = "defaultPageClass.label"
)
private String defaultPageClass = "com.arsdigita.bebop.Page"; private String defaultPageClass = "com.arsdigita.bebop.Page";
public static DispatcherConfig getConfig() { public static DispatcherConfig getConfig() {

View File

@ -35,6 +35,8 @@ import org.libreccm.categorization.DomainManager;
import org.libreccm.categorization.DomainRepository; import org.libreccm.categorization.DomainRepository;
import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.cdi.utils.CdiUtil;
import java.time.LocalDate;
import static com.arsdigita.ui.admin.AdminUiConstants.*; import static com.arsdigita.ui.admin.AdminUiConstants.*;
/** /**
@ -187,7 +189,8 @@ class DomainForm extends Form {
} }
final String versionData = data.getString(VERSION); final String versionData = data.getString(VERSION);
final java.util.Date releasedData = (java.util.Date) data.get( final java.util.Date releasedData = (java.util.Date) data.get(
RELEASED); RELEASED
);
final String rootCategoryNameData = data.getString( final String rootCategoryNameData = data.getString(
ROOT_CATEGORY_NAME); ROOT_CATEGORY_NAME);
@ -207,7 +210,7 @@ class DomainForm extends Form {
domain.setDomainKey(domainKeyData); domain.setDomainKey(domainKeyData);
domain.setUri(domainUriData); domain.setUri(domainUriData);
domain.setVersion(versionData); domain.setVersion(versionData);
domain.setReleased(releasedData); domain.setReleased(LocalDate.from(releasedData.toInstant()));
domainRepository.save(domain); domainRepository.save(domain);
} }

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.api;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public final class ApiConstants {
private ApiConstants() {
// Nothing
}
public static final String IDENTIFIER_PREFIX_ID = "ID-";
public static final String IDENTIFIER_PREFIX_UUID = "UUID-";
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.api;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class Identifier {
private final IdentifierType type;
private final String identifier;
protected Identifier(
final IdentifierType type, final String identifier
) {
this.type = type;
this.identifier = identifier;
}
public IdentifierType getType() {
return type;
}
public String getIdentifier() {
return identifier;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.api;
import java.util.Objects;
import javax.enterprise.context.Dependent;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Dependent
public class IdentifierParser {
public Identifier parseIdentifier(final String identifierParam) {
Objects.requireNonNull(identifierParam, "identifier param is null.");
if (identifierParam.startsWith(ApiConstants.IDENTIFIER_PREFIX_ID)) {
final String identifier = identifierParam
.substring(ApiConstants.IDENTIFIER_PREFIX_ID.length());
return new Identifier(IdentifierType.ID, identifier);
} else if (identifierParam.startsWith(
ApiConstants.IDENTIFIER_PREFIX_UUID)) {
final String identifier = identifierParam
.substring(ApiConstants.IDENTIFIER_PREFIX_UUID.length());
return new Identifier(IdentifierType.UUID, identifier);
} else {
return new Identifier(
IdentifierType.PROPERTY, identifierParam
);
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.api;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public enum IdentifierType {
ID,
UUID,
PROPERTY
}

View File

@ -54,6 +54,11 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "CATEGORIZATIONS", schema = DB_SCHEMA) @Table(name = "CATEGORIZATIONS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(
name = "Categorization.findById",
query
= "SELECT c FROM Categorization c WHERE c.categorizationId = :categorizationId"
),
@NamedQuery( @NamedQuery(
name = "Categorization.findByUuid", name = "Categorization.findByUuid",
query = "SELECT c FROM Categorization c WHERE c.uuid = :uuid" query = "SELECT c FROM Categorization c WHERE c.uuid = :uuid"
@ -62,36 +67,31 @@ import javax.persistence.Table;
name = "Categorization.find", name = "Categorization.find",
query = "SELECT c FROM Categorization c " query = "SELECT c FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.categorizedObject = :object") + "AND c.categorizedObject = :object"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.isAssignedTo", name = "Categorization.isAssignedTo",
query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) "
+ "FROM Categorization c " + "FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.categorizedObject = :object") + "AND c.categorizedObject = :object"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.isAssignedToWithType", name = "Categorization.isAssignedToWithType",
query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) "
+ "FROM Categorization c " + "FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.categorizedObject = :object " + "AND c.categorizedObject = :object "
+ "AND c.type = :type") + "AND c.type = :type"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.findIndexObject", name = "Categorization.findIndexObject",
query = "SELECT c.categorizedObject FROM Categorization c " query = "SELECT c.categorizedObject FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.indexObject = TRUE") + "AND c.indexObject = TRUE"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.findIndexObjectCategorization", name = "Categorization.findIndexObjectCategorization",
query = "SELECT c FROM Categorization c " query = "SELECT c FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.indexObject = TRUE" + "AND c.indexObject = TRUE"
) ),
,
@NamedQuery( @NamedQuery(
name = "Categorization.hasIndexObject", name = "Categorization.hasIndexObject",
query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true " query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true "

View File

@ -23,12 +23,15 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Instance; import javax.enterprise.inject.Instance;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
@ -48,14 +51,12 @@ public class CategorizationImExporter
private Instance<CategorizationImExporterDependenciesProvider> dependenciesProviders; private Instance<CategorizationImExporterDependenciesProvider> dependenciesProviders;
@Override @Override
protected Class<Categorization> getEntityClass() { public Class<Categorization> getEntityClass() {
return Categorization.class; return Categorization.class;
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> entities = new HashSet<>(); final Set<Class<? extends Exportable>> entities = new HashSet<>();
entities.add(Category.class); entities.add(Category.class);
@ -69,8 +70,27 @@ public class CategorizationImExporter
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final Categorization entity) { protected void saveImportedEntity(final Categorization entity) {
entityManager.persist(entity); entityManager.persist(entity);
} }
@Override
protected Categorization reloadEntity(final Categorization entity) {
try {
return entityManager.createNamedQuery(
"Categorization.findById",
Categorization.class
).setParameter(
"categorizationId",
Objects.requireNonNull(entity).getCategorizationId()
).getSingleResult();
} catch (NoResultException ex) {
throw new IllegalArgumentException(
String.format(
"Categorization entity %s was not found in the database.",
Objects.toString(entity)
)
);
}
}
} }

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -42,14 +43,12 @@ public class CategoryImExporter extends AbstractEntityImExporter<Category> {
private CategoryRepository categoryRepository; private CategoryRepository categoryRepository;
@Override @Override
protected Class<Category> getEntityClass() { public Class<Category> getEntityClass() {
return Category.class; return Category.class;
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> entities = new HashSet<>(); final Set<Class<? extends Exportable>> entities = new HashSet<>();
entities.add(Domain.class); entities.add(Domain.class);
@ -59,8 +58,21 @@ public class CategoryImExporter extends AbstractEntityImExporter<Category> {
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final Category entity) { protected void saveImportedEntity(final Category entity) {
categoryRepository.save(entity); categoryRepository.save(entity);
} }
@Override
protected Category reloadEntity(final Category entity) {
return categoryRepository
.findById(Objects.requireNonNull(entity).getObjectId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Category entity %s does not exist in the database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -872,7 +872,7 @@ public class CategoryManager implements Serializable {
* The first entry in the list is the root category, the last entry is the * The first entry in the list is the root category, the last entry is the
* provided category. * provided category.
* *
* @param ofCategory The category for whic the tree is generated. * @param ofCategory The category for which the tree is generated.
* *
* @return A list of a categories in the path of the provided category. * @return A list of a categories in the path of the provided category.
*/ */

View File

@ -42,9 +42,10 @@ import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -63,8 +64,6 @@ import javax.persistence.NamedQuery;
import javax.persistence.NamedSubgraph; import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
/** /**
@ -83,14 +82,26 @@ import javax.validation.constraints.NotBlank;
@Entity @Entity
@Table(name = "CATEGORY_DOMAINS", schema = DB_SCHEMA) @Table(name = "CATEGORY_DOMAINS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "Domain.findByKey", @NamedQuery(
query = "SELECT d FROM Domain d WHERE d.domainKey = :key"), name = "Domain.findByKey",
@NamedQuery(name = "Domain.findByUri", query = "SELECT d FROM Domain d WHERE d.domainKey = :key"
query = "SELECT d FROM Domain d WHERE d.uri = :uri"), ),
@NamedQuery(name = "Domain.findByUuid", @NamedQuery(
query = "SELECT d FROM Domain d WHERE d.uuid = :uuid"), name = "Domain.findByUri",
@NamedQuery(name = "Domain.findAll", query = "SELECT d FROM Domain d WHERE d.uri = :uri"
query = "SELECT d FROM Domain d ORDER BY d.domainKey"), ),
@NamedQuery(
name = "Domain.findByUuid",
query = "SELECT d FROM Domain d WHERE d.uuid = :uuid"
),
@NamedQuery(
name = "Domain.findByRootCategory",
query = "SELECT d FROM Domain d WHERE d.root = :root"
),
@NamedQuery(
name = "Domain.findAll",
query = "SELECT d FROM Domain d ORDER BY d.domainKey"
),
@NamedQuery( @NamedQuery(
name = "Domain.search", name = "Domain.search",
query query
@ -188,7 +199,6 @@ public class Domain extends CcmObject implements Serializable, Exportable {
* A version string for the {@code Domain}. * A version string for the {@code Domain}.
*/ */
@Column(name = "VERSION", nullable = true) @Column(name = "VERSION", nullable = true)
@NotBlank
@XmlElement(name = "version", namespace = CAT_XML_NS) @XmlElement(name = "version", namespace = CAT_XML_NS)
private String version; private String version;
@ -196,9 +206,8 @@ public class Domain extends CcmObject implements Serializable, Exportable {
* A timestamp for the release date of the {@code Domain}. * A timestamp for the release date of the {@code Domain}.
*/ */
@Column(name = "RELEASED") @Column(name = "RELEASED")
@Temporal(TemporalType.TIMESTAMP)
@XmlElement(name = "released", namespace = CAT_XML_NS) @XmlElement(name = "released", namespace = CAT_XML_NS)
private Date released; private LocalDate released;
/** /**
* The root category of the domain. * The root category of the domain.
@ -267,20 +276,12 @@ public class Domain extends CcmObject implements Serializable, Exportable {
this.version = version; this.version = version;
} }
public Date getReleased() { public LocalDate getReleased() {
if (released == null) { return released;
return null;
} else {
return new Date(released.getTime());
}
} }
public void setReleased(final Date released) { public void setReleased(final LocalDate released) {
if (released == null) { this.released = released;
this.released = null;
} else {
this.released = new Date(released.getTime());
}
} }
public Category getRoot() { public Category getRoot() {
@ -399,18 +400,24 @@ public class Domain extends CcmObject implements Serializable, Exportable {
@Override @Override
public String toString(final String data) { public String toString(final String data) {
final String releasedStr;
if (released == null) {
releasedStr = "";
} else {
releasedStr = DateTimeFormatter.ISO_DATE.format(released);
}
return String.format( return String.format(
", domainKey = \"%s\", " ", domainKey = \"%s\", "
+ "uri = \"%s\", " + "uri = \"%s\", "
+ "title = \"%s\", " + "title = \"%s\", "
+ "version = \"%s\", " + "version = \"%s\", "
+ "released = %tF %<tT, " + "released = %s, "
+ "root = \"%s\"%s", + "root = \"%s\"%s",
domainKey, domainKey,
Objects.toString(uri), Objects.toString(uri),
Objects.toString(title), Objects.toString(title),
version, version,
released, releasedStr,
Objects.toString(root), Objects.toString(root),
data data
); );

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -41,7 +42,7 @@ public class DomainImExporter extends AbstractEntityImExporter<Domain> {
private DomainRepository domainRepository; private DomainRepository domainRepository;
@Override @Override
protected Class<Domain> getEntityClass() { public Class<Domain> getEntityClass() {
return Domain.class; return Domain.class;
} }
@ -58,6 +59,18 @@ public class DomainImExporter extends AbstractEntityImExporter<Domain> {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
protected Domain reloadEntity(final Domain entity) {
return domainRepository
.findById(Objects.requireNonNull(entity).getObjectId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Domain entity %s was not found in the database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -119,6 +119,41 @@ public class DomainManager implements Serializable {
domainRepo.save(domain); domainRepo.save(domain);
} }
/**
* Adds a {@code CcmApplication} to the owners of a {@link Domain}.If the
provided {@code CcmApplication} is already an owner of the provided
{@code Domain} the method does nothing.
*
* @param application The {@code CcmApplication} to add to the owners of the
* {@code Domain}.
* @param domain The {@code Domain} to which owners the
* {@code CcmApplication is added}.
* @param context Context for the mapping
*/
@AuthorizationRequired
@RequiresPrivilege(CategorizationConstants.PRIVILEGE_MANAGE_DOMAINS)
@Transactional(Transactional.TxType.REQUIRED)
public void addDomainOwner(
final CcmApplication application,
final Domain domain,
final String context
) {
final DomainOwnership ownership = new DomainOwnership();
ownership.setUuid(UUID.randomUUID().toString());
ownership.setDomain(domain);
ownership.setOwner(application);
ownership.setContext(context);
ownership.setOwnerOrder(domain.getOwners().size() + 1);
ownership.setDomainOrder(application.getDomains().size() + 1);
application.addDomain(ownership);
domain.addOwner(ownership);
entityManager.persist(ownership);
applicationRepo.save(application);
domainRepo.save(domain);
}
/** /**
* Removes a {@code CcmApplication} from the owners of a {@code Domain}. If * Removes a {@code CcmApplication} from the owners of a {@code Domain}. If
* the provided {@code CcmApplication} is not an owner of the provided * the provided {@code CcmApplication} is not an owner of the provided

View File

@ -54,11 +54,15 @@ import javax.xml.bind.annotation.XmlElement;
@Entity @Entity
@Table(name = "DOMAIN_OWNERSHIPS", schema = DB_SCHEMA) @Table(name = "DOMAIN_OWNERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(
name = "DomainOwnership.findById",
query
= "SELECT o FROM DomainOwnership o WHERE o.ownershipId = :ownershipId"
),
@NamedQuery( @NamedQuery(
name = "DomainOwnership.findByUuid", name = "DomainOwnership.findByUuid",
query = "SELECT o FROM DomainOwnership o WHERE o.uuid = :uuid" query = "SELECT o FROM DomainOwnership o WHERE o.uuid = :uuid"
) ),
,
@NamedQuery( @NamedQuery(
name = "DomainOwnership.findByOwnerAndDomain", name = "DomainOwnership.findByOwnerAndDomain",
query = "SELECT o FROM DomainOwnership o " query = "SELECT o FROM DomainOwnership o "

View File

@ -24,11 +24,13 @@ import org.libreccm.imexport.Processes;
import org.libreccm.web.CcmApplication; import org.libreccm.web.CcmApplication;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
@ -46,21 +48,18 @@ public class DomainOwnershipImExporter
private EntityManager entityManager; private EntityManager entityManager;
@Override @Override
protected Class<DomainOwnership> getEntityClass() { public Class<DomainOwnership> getEntityClass() {
return DomainOwnership.class; return DomainOwnership.class;
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final DomainOwnership entity) { protected void saveImportedEntity(final DomainOwnership entity) {
entityManager.persist(entity); entityManager.persist(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> classes = new HashSet<>(); final Set<Class<? extends Exportable>> classes = new HashSet<>();
classes.add(CcmApplication.class); classes.add(CcmApplication.class);
classes.add(Domain.class); classes.add(Domain.class);
@ -68,4 +67,26 @@ public class DomainOwnershipImExporter
return classes; return classes;
} }
@Override
protected DomainOwnership reloadEntity(final DomainOwnership entity) {
try {
return entityManager
.createNamedQuery(
"DomainOwnership.findById",
DomainOwnership.class
)
.setParameter(
"ownershipId",
Objects.requireNonNull(entity.getOwnershipId())
).getSingleResult();
} catch (NoResultException ex) {
throw new IllegalArgumentException(
String.format(
"DomainOwnership entity %s not found in database.",
Objects.toString(entity)
)
);
}
}
} }

View File

@ -87,6 +87,7 @@ public class DomainRepository extends AbstractEntityRepository<Long, Domain> {
* @return The {@code Domain} identified by {@code domainKey} or * @return The {@code Domain} identified by {@code domainKey} or
* {@code null} if there is no such {@code Domain}. * {@code null} if there is no such {@code Domain}.
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Domain> findByDomainKey(final String domainKey) { public Optional<Domain> findByDomainKey(final String domainKey) {
final TypedQuery<Domain> query = getEntityManager() final TypedQuery<Domain> query = getEntityManager()
.createNamedQuery("Domain.findByKey", Domain.class); .createNamedQuery("Domain.findByKey", Domain.class);
@ -111,6 +112,7 @@ public class DomainRepository extends AbstractEntityRepository<Long, Domain> {
* @return The {@code Domain} identified by the provided URI or {@code null} * @return The {@code Domain} identified by the provided URI or {@code null}
* if there is so such {@code Domain}. * if there is so such {@code Domain}.
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public Domain findByUri(final URI uri) { public Domain findByUri(final URI uri) {
final TypedQuery<Domain> query = getEntityManager() final TypedQuery<Domain> query = getEntityManager()
.createNamedQuery("Domain.findByUri", Domain.class); .createNamedQuery("Domain.findByUri", Domain.class);
@ -126,18 +128,37 @@ public class DomainRepository extends AbstractEntityRepository<Long, Domain> {
* *
* @return An optional either with the found item or empty * @return An optional either with the found item or empty
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Domain> findByUuid(final String uuid) { public Optional<Domain> findByUuid(final String uuid) {
final TypedQuery<Domain> query = getEntityManager()
.createNamedQuery("Domain.findByUuid", Domain.class);
query.setParameter("uuid", uuid);
try { try {
return Optional.of(query.getSingleResult()); return Optional.of(
getEntityManager()
.createNamedQuery("Domain.findByUuid", Domain.class)
.setParameter("uuid", uuid)
.getSingleResult()
);
} catch (NoResultException ex) { } catch (NoResultException ex) {
return Optional.empty(); return Optional.empty();
} }
} }
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Domain> findByRootCategory(final Category rootCategory) {
try {
return Optional.of(
getEntityManager()
.createNamedQuery(
"Domain.findByRootCategory", Domain.class
)
.setParameter("root", rootCategory)
.getSingleResult()
);
} catch (NoResultException ex) {
return Optional.empty();
}
}
@Transactional(Transactional.TxType.REQUIRED)
public List<Domain> search(final String term) { public List<Domain> search(final String term) {
final TypedQuery<Domain> query = getEntityManager() final TypedQuery<Domain> query = getEntityManager()
.createNamedQuery("Domain.search", Domain.class); .createNamedQuery("Domain.search", Domain.class);

View File

@ -18,15 +18,27 @@
*/ */
package org.libreccm.configuration; package org.libreccm.configuration;
import org.hibernate.validator.constraints.NotBlank;
import javax.persistence.*;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import static org.libreccm.core.CoreConstants.DB_SCHEMA; import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotBlank;
/** /**
* Abstract base class for all settings. * Abstract base class for all settings.
* *

View File

@ -21,7 +21,15 @@ package org.libreccm.configuration;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.*; import java.util.Collections;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.TreeMap;
/** /**
* Describes a configuration. Useful for generating user interfaces. * Describes a configuration. Useful for generating user interfaces.

View File

@ -22,18 +22,25 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.Strings;
import org.libreccm.core.CoreConstants; import org.libreccm.core.CoreConstants;
import org.libreccm.l10n.LocalizedString;
import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege; import org.libreccm.security.RequiresPrivilege;
import java.lang.reflect.Constructor;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* *
@ -106,7 +113,7 @@ public class SettingManager {
/** /**
* Create a {@link SettingInfo} instance for a setting. * Create a {@link SettingInfo} instance for a setting.
* *
* @param configuration The configuration class to which the settings * @param configurationClass The configuration class to which the settings
* belongs. * belongs.
* @param name The name of the setting for which the * @param name The name of the setting for which the
* {@link SettingInfo} is generated. * {@link SettingInfo} is generated.
@ -117,28 +124,29 @@ public class SettingManager {
"PMD.CyclomaticComplexity", "PMD.CyclomaticComplexity",
"PMD.StandardCyclomaticComplexity"}) "PMD.StandardCyclomaticComplexity"})
public SettingInfo getSettingInfo( public SettingInfo getSettingInfo(
final Class<?> configuration, final Class<?> configurationClass,
final String name) { final String name
if (configuration == null) { ) {
if (configurationClass == null) {
throw new IllegalArgumentException("Configuration can't be null"); throw new IllegalArgumentException("Configuration can't be null");
} }
if (configuration.getAnnotation(Configuration.class) == null) { if (configurationClass.getAnnotation(Configuration.class) == null) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"The class \"%s\" of the provided object is not annotated " "The class \"%s\" of the provided object is not annotated "
+ "with \"%s\".", + "with \"%s\".",
configuration.getClass().getName(), configurationClass.getClass().getName(),
Configuration.class.getName())); Configuration.class.getName()));
} }
final Field field; final Field field;
try { try {
field = configuration.getDeclaredField(name); field = configurationClass.getDeclaredField(name);
} catch (SecurityException | NoSuchFieldException ex) { } catch (SecurityException | NoSuchFieldException ex) {
LOGGER.warn(String.format( LOGGER.warn(String.format(
"Failed to generate SettingInfo for field \"%s\" of " "Failed to generate SettingInfo for field \"%s\" of "
+ "configuration \"%s\". Ignoring field.", + "configuration \"%s\". Ignoring field.",
configuration.getClass().getName(), configurationClass.getClass().getName(),
name), name),
ex); ex);
return null; return null;
@ -163,23 +171,61 @@ public class SettingManager {
settingInfo.setValueType(field.getType().getName()); settingInfo.setValueType(field.getType().getName());
try { try {
final Object conf = configuration.newInstance(); final Constructor<?> constructor = configurationClass
settingInfo.setDefaultValue(Objects.toString(field.get(conf))); .getConstructor();
} catch (InstantiationException | IllegalAccessException ex) { final Object configuration = constructor.newInstance();
final Object defaultValueObj = field.get(configuration);
final String defaultValue;
if (defaultValueObj instanceof List) {
@SuppressWarnings("unchecked")
final List<String> defaultValueList
= (List<String>) defaultValueObj;
defaultValue = defaultValueList
.stream()
.collect(Collectors.joining("\n"));
} else if (defaultValueObj instanceof LocalizedString) {
final LocalizedString defaultValueLstr
= (LocalizedString) defaultValueObj;
defaultValue = defaultValueLstr
.getValues()
.entrySet()
.stream()
.map(
entry -> String.format(
"%s: %s",
entry.getKey().toString(), entry.getValue()
)
)
.collect(Collectors.joining("\n"));
} else if (defaultValueObj instanceof Set) {
@SuppressWarnings("unchecked")
final Set<String> defaultValueSet
= (Set<String>) defaultValueObj;
defaultValue = defaultValueSet
.stream()
.collect(Collectors.joining("\n"));
} else {
defaultValue = Objects.toString(defaultValueObj);
}
settingInfo.setDefaultValue(defaultValue);
} catch (NoSuchMethodException
| InstantiationException
| InvocationTargetException
| IllegalAccessException ex) {
LOGGER.warn(String.format("Failed to create instance of \"%s\" to " LOGGER.warn(String.format("Failed to create instance of \"%s\" to "
+ "get default values.", + "get default values.",
configuration.getName()), configurationClass.getName()),
ex); ex);
} }
settingInfo.setConfClass(configuration.getName()); settingInfo.setConfClass(configurationClass.getName());
settingInfo.setDescBundle(getDescBundle(configuration)); settingInfo.setDescBundle(getDescBundle(configurationClass));
if (Strings.isBlank(settingAnnotation.labelKey())) { if (Strings.isBlank(settingAnnotation.labelKey())) {
settingInfo.setLabelKey(String.join(".", field.getName(), settingInfo.setLabelKey(String.join(".", field.getName(),
"label")); "label"));
} else { } else {
settingInfo.setLabelKey(name); settingInfo.setLabelKey(settingAnnotation.labelKey());
} }
if (Strings.isBlank(settingAnnotation.descKey())) { if (Strings.isBlank(settingAnnotation.descKey())) {
@ -227,6 +273,61 @@ public class SettingManager {
} }
} }
public Object getDefaultValue(
final Class<?> configurationClass,
final String settingName
) {
if (configurationClass == null) {
throw new IllegalArgumentException("Configuration can't be null");
}
if (configurationClass.getAnnotation(Configuration.class) == null) {
throw new IllegalArgumentException(String.format(
"The class \"%s\" of the provided object is not annotated "
+ "with \"%s\".",
configurationClass.getClass().getName(),
Configuration.class.getName()));
}
final Field field;
try {
field = configurationClass.getDeclaredField(settingName);
} catch (SecurityException | NoSuchFieldException ex) {
LOGGER.warn(String.format(
"Failed to generate SettingInfo for field \"%s\" of "
+ "configuration \"%s\". Ignoring field.",
configurationClass.getClass().getName(),
settingName),
ex);
return null;
}
//Make the field accessible even if it has a private modifier
field.setAccessible(true);
if (field.getAnnotation(Setting.class) == null) {
return null;
}
try {
final Constructor<?> constructor = configurationClass
.getConstructor();
final Object configuration = constructor.newInstance();
final Object defaultValueObj = field.get(configuration);
return defaultValueObj;
} catch (NoSuchMethodException
| IllegalAccessException
| InstantiationException
| InvocationTargetException ex) {
LOGGER.warn(String.format("Failed to create instance of \"%s\" to "
+ "get default values.",
configurationClass.getName()),
ex);
return null;
}
}
/** /**
* Low level method of saving a setting. * Low level method of saving a setting.
* *

View File

@ -47,8 +47,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
/** /**
* Describes the {@code ccm-core} module. * Describes the {@code ccm-core} module.
* *
@ -59,22 +57,19 @@ import java.util.Properties;
descBundle = "com.arsdigita.ui.login.LoginResources", descBundle = "com.arsdigita.ui.login.LoginResources",
singleton = true, singleton = true,
creator = LoginApplicationCreator.class, creator = LoginApplicationCreator.class,
servlet = LoginServlet.class) servlet = LoginServlet.class),
,
@ApplicationType(name = AdminUiConstants.ADMIN_APP_TYPE, @ApplicationType(name = AdminUiConstants.ADMIN_APP_TYPE,
descBundle = "com.arsdigita.ui.admin.AdminResources", descBundle = "com.arsdigita.ui.admin.AdminResources",
singleton = true, singleton = true,
creator = AdminApplicationCreator.class, creator = AdminApplicationCreator.class,
servlet = AdminServlet.class) servlet = AdminServlet.class),
,
@ApplicationType(name = "org.libreccm.ui.admin.AdminFaces", @ApplicationType(name = "org.libreccm.ui.admin.AdminFaces",
descBundle = "com.arsdigita.ui.admin.AdminResources", descBundle = "com.arsdigita.ui.admin.AdminResources",
singleton = true, singleton = true,
creator = AdminJsfApplicationCreator.class, creator = AdminJsfApplicationCreator.class,
servletPath = "/admin-jsf/admin.xhtml")}, servletPath = "/admin-jsf/admin.xhtml")
pageModelComponentModels = {
}, },
pageModelComponentModels = {},
configurations = { configurations = {
com.arsdigita.bebop.BebopConfig.class, com.arsdigita.bebop.BebopConfig.class,
com.arsdigita.dispatcher.DispatcherConfig.class, com.arsdigita.dispatcher.DispatcherConfig.class,

View File

@ -18,8 +18,6 @@
*/ */
package org.libreccm.core; package org.libreccm.core;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
@ -33,6 +31,8 @@ import static org.libreccm.core.CoreConstants.CORE_XML_NS;
import javax.json.Json; import javax.json.Json;
import javax.json.JsonObjectBuilder; import javax.json.JsonObjectBuilder;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/** /**
* An embeddable entity for storing email addresses. * An embeddable entity for storing email addresses.

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
@ -39,23 +40,37 @@ public class ResourceTypeImExporter
private ResourceTypeRepository repository; private ResourceTypeRepository repository;
@Override @Override
protected Class<ResourceType> getEntityClass() { public Class<ResourceType> getEntityClass() {
return ResourceType.class; return ResourceType.class;
} }
@Override @Override
protected void saveImportedEntity(final ResourceType entity) { protected void saveImportedEntity(final ResourceType entity) {
repository.save(entity); repository.save(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
protected ResourceType reloadEntity(final ResourceType entity) {
return repository
.findById(
Objects.requireNonNull(entity).getResourceTypeId()
)
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"The provided ResourceType %s was not found in the "
+ "database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -172,9 +172,10 @@ public class CcmFiles {
if (adapter.isConfigured()) { if (adapter.isConfigured()) {
return adapter; return adapter;
} else { } else {
throw new UnexpectedErrorException( throw new CcmFilesNotConfiguredException(
"Only the default FileSystemAdapter is available but is " "Only the default FileSystemAdapter is available but is "
+ "not correctly configured."); + "not correctly configured."
);
} }
} }
} }
@ -196,7 +197,9 @@ public class CcmFiles {
final String dataPath = filesConf.getDataPath(); final String dataPath = filesConf.getDataPath();
if (dataPath == null || dataPath.trim().isEmpty()) { if (dataPath == null || dataPath.trim().isEmpty()) {
throw new UnexpectedErrorException("dataPath is not configured."); throw new CcmFilesNotConfiguredException(
"dataPath is not configured."
);
} }
if (dataPath.endsWith("/")) { if (dataPath.endsWith("/")) {

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.files;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CcmFilesNotConfiguredException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* Creates a new instance of <code>CcmFilesNotConfiguredException</code> without detail message.
*/
public CcmFilesNotConfiguredException() {
super();
}
/**
* Constructs an instance of <code>CcmFilesNotConfiguredException</code> with the specified detail message.
*
* @param msg The detail message.
*/
public CcmFilesNotConfiguredException(final String msg) {
super(msg);
}
/**
* Constructs an instance of <code>CcmFilesNotConfiguredException</code> which wraps the
* specified exception.
*
* @param exception The exception to wrap.
*/
public CcmFilesNotConfiguredException(final Exception exception) {
super(exception);
}
/**
* Constructs an instance of <code>CcmFilesNotConfiguredException</code> with the specified message which also wraps the
* specified exception.
*
* @param msg The detail message.
* @param exception The exception to wrap.
*/
public CcmFilesNotConfiguredException(final String msg, final Exception exception) {
super(msg, exception);
}
}

View File

@ -53,7 +53,7 @@ public abstract class AbstractEntityImExporter<T extends Exportable> {
* *
* @return The Entity class which is handled by the implementation. * @return The Entity class which is handled by the implementation.
*/ */
protected abstract Class<T> getEntityClass(); public abstract Class<T> getEntityClass();
/** /**
* A set of entities which should be processed before this implementation is * A set of entities which should be processed before this implementation is
@ -62,7 +62,6 @@ public abstract class AbstractEntityImExporter<T extends Exportable> {
* containers usually create a {@link java.lang.reflect.Proxy} class and * containers usually create a {@link java.lang.reflect.Proxy} class and
* there is no portable way to unproxy a class. * there is no portable way to unproxy a class.
* *
*
* @return A {@link Set} of exportable entity classes which should be * @return A {@link Set} of exportable entity classes which should be
* processed before the entities which are processed by this * processed before the entities which are processed by this
* implementation. If the implementation has no dependencies an * implementation. If the implementation has no dependencies an
@ -72,7 +71,6 @@ public abstract class AbstractEntityImExporter<T extends Exportable> {
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public T importEntity(final String data) throws ImportExpection { public T importEntity(final String data) throws ImportExpection {
try { try {
final T entity = objectMapper.readValue(data, getEntityClass()); final T entity = objectMapper.readValue(data, getEntityClass());
saveImportedEntity(entity); saveImportedEntity(entity);
@ -80,14 +78,26 @@ public abstract class AbstractEntityImExporter<T extends Exportable> {
} catch (IOException ex) { } catch (IOException ex) {
throw new ImportExpection(ex); throw new ImportExpection(ex);
} }
} }
protected abstract void saveImportedEntity(T entity);
/**
* Export an entity (as JSON). There should be no need to overwrite this
* method.
*
* @param entity The entity to export.
*
* @return The entity as JSON
*
* @throws ExportException If an error occurs.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String exportEntity(final Exportable entity) throws ExportException { public String exportEntity(final Exportable entity) throws ExportException {
@SuppressWarnings("unchecked")
final T export = reloadEntity((T) entity);
try { try {
return objectMapper.writeValueAsString(entity); return objectMapper.writeValueAsString(export);
} catch (JsonProcessingException ex) { } catch (JsonProcessingException ex) {
throw new ExportException(String.format( throw new ExportException(String.format(
"Failed to export entity \"%s\" of type \"%s\".", "Failed to export entity \"%s\" of type \"%s\".",
@ -95,9 +105,17 @@ public abstract class AbstractEntityImExporter<T extends Exportable> {
getEntityClass().getName()), getEntityClass().getName()),
ex); ex);
} }
} }
protected abstract void saveImportedEntity(T entity); /**
* Reloads the entity to export. Entities become detacted for several
* reasons before they are passed to the null {@link #exportEntity(org.libreccm.imexport.Exportable) method. The
* implementation of this should reload the passed entity.
*
* @param entity The entity to reload.
*
* @return The reloaded entity
*/
protected abstract T reloadEntity(final T entity);
} }

View File

@ -254,16 +254,12 @@ final class EntityImExporterTreeManager {
//Check if the nodes list has an entry for the required module. //Check if the nodes list has an entry for the required module.
if (!nodes.containsKey(requiredClass.getName())) { if (!nodes.containsKey(requiredClass.getName())) {
LOGGER.fatal("Required EntityImExporter for \"{}\" no found.", LOGGER.fatal("Required EntityImExporter for \"{}\" no found.",
requiredClass.getName()); requiredClass.getName());
throw new DependencyException(String.format( throw new DependencyException(String.format(
"EntityImExporter for type \"%s\" depends on type \"%s\" " "EntityImExporter for type \"%s\" depends on type \"%s\" "
+ "but no EntityImExporter for type \"%s\" is available.", + "but no EntityImExporter for type \"%s\" is available.",
node node.getEntityImExporter().getEntityClass(),
.getEntityImExporter()
.getClass()
.getAnnotation(Processes.class).value().getName(),
requiredClass.getName(), requiredClass.getName(),
requiredClass.getName())); requiredClass.getName()));
} }

View File

@ -22,13 +22,14 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* A node in the dependency tree managed by {@link EntityImExporterTreeManager}. * A node in the dependency tree managed by {@link EntityImExporterTreeManager}.
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
final class EntityImExporterTreeNode { public final class EntityImExporterTreeNode {
private AbstractEntityImExporter<?> entityImExporter; private AbstractEntityImExporter<?> entityImExporter;
@ -36,8 +37,7 @@ final class EntityImExporterTreeNode {
private List<EntityImExporterTreeNode> dependsOn; private List<EntityImExporterTreeNode> dependsOn;
public EntityImExporterTreeNode() { protected EntityImExporterTreeNode() {
super(); super();
dependentImExporters = new ArrayList<>(); dependentImExporters = new ArrayList<>();
@ -105,9 +105,10 @@ final class EntityImExporterTreeNode {
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 7; int hash = 7;
hash = 47 hash = 47 * hash
* hash + Objects.hashCode(
+ Objects.hashCode(this.entityImExporter.getClass().getName()); this.entityImExporter.getClass().getName()
);
return hash; return hash;
} }
@ -128,6 +129,29 @@ final class EntityImExporterTreeNode {
other.getEntityImExporter().getClass().getName()); other.getEntityImExporter().getClass().getName());
} }
@Override
public String toString() {
return String.format(
"%s{ "
+ "entityImExporter: %s, "
+ "dependentImExporters: [%s], "
+ "dependsOn: [%s]"
+ " }",
super.toString(),
entityImExporter.getEntityClass().toString(),
dependentImExporters
.stream()
.map(EntityImExporterTreeNode::getEntityImExporter)
.map(AbstractEntityImExporter::getEntityClass)
.map(Class::getName)
.collect(Collectors.joining(", ")),
dependsOn
.stream()
.map(EntityImExporterTreeNode::getEntityImExporter)
.map(AbstractEntityImExporter::getEntityClass)
.map(Class::getName)
.collect(Collectors.joining(", "))
);
}
} }

View File

@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -59,6 +60,7 @@ import javax.json.JsonObjectBuilder;
import javax.json.JsonReader; import javax.json.JsonReader;
import javax.json.JsonString; import javax.json.JsonString;
import javax.json.JsonWriter; import javax.json.JsonWriter;
import javax.transaction.Transactional;
/** /**
* Central service for importing and exporting entities. * Central service for importing and exporting entities.
@ -75,6 +77,23 @@ public class ImportExport {
@Any @Any
private Instance<AbstractEntityImExporter<?>> imExporters; private Instance<AbstractEntityImExporter<?>> imExporters;
public List<EntityImExporterTreeNode> getExportableEntityTypes() {
try {
final EntityImExporterTreeManager treeManager
= new EntityImExporterTreeManager();
final List<EntityImExporterTreeNode> tree = treeManager
.generateTree(
imExporters
.stream()
.collect(Collectors.toList())
);
return tree;
} catch (DependencyException ex) {
throw new UnexpectedErrorException(ex);
}
}
/** /**
* Exports the provided entities. The export will be written to a to the * Exports the provided entities. The export will be written to a to the
* {@code exports} directory in the CCM files directory. If {@code split} is * {@code exports} directory in the CCM files directory. If {@code split} is
@ -89,9 +108,10 @@ public class ImportExport {
* *
* @see CcmFilesConfiguration#dataPath * @see CcmFilesConfiguration#dataPath
*/ */
public void exportEntities(final List<Exportable> entities, @Transactional(Transactional.TxType.REQUIRED)
final String exportName) { public void exportEntities(
final Collection<Exportable> entities, final String exportName
) {
final JsonObjectBuilder manifestBuilder = Json.createObjectBuilder(); final JsonObjectBuilder manifestBuilder = Json.createObjectBuilder();
manifestBuilder.add("created", manifestBuilder.add("created",
LocalDateTime.now(ZoneId.of("UTC")).toString()); LocalDateTime.now(ZoneId.of("UTC")).toString());
@ -145,83 +165,12 @@ public class ImportExport {
for (final Map.Entry<String, List<Exportable>> entry for (final Map.Entry<String, List<Exportable>> entry
: typeEntityMap.entrySet()) { : typeEntityMap.entrySet()) {
createExportedEntities(exportName, createExportedEntities(exportName,
entry.getKey(), entry.getKey(),
entry.getValue()); entry.getValue());
} }
} }
@SuppressWarnings("unchecked")
private JsonArrayBuilder createExportedEntities(
final String exportName,
final String type,
final List<Exportable> entities) {
final JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder();
final Class<? extends Exportable> clazz;
try {
clazz = (Class<? extends Exportable>) Class.forName(type);
} catch (ClassNotFoundException ex) {
throw new UnexpectedErrorException(ex);
}
final Instance<AbstractEntityImExporter<?>> instance = imExporters
.select(new ProcessesLiteral(clazz));
final AbstractEntityImExporter<?> imExporter;
if (instance.isUnsatisfied()) {
throw new UnexpectedErrorException(String.format(
"No EntityImExporter for entity type \"%s\" available.",
type));
} else if (instance.isAmbiguous()) {
throw new UnexpectedErrorException(String.format(
"Instance reference for EntityImExporter for entity "
+ "type \"%s\" is ambiguous.",
type));
} else {
imExporter = instance.get();
}
for (Exportable entity : entities) {
final String filename = String.format("%s.json", entity.getUuid());
final OutputStream outputStream;
try {
outputStream = ccmFiles.createOutputStream(String.format(
"exports/%s/%s/%s",
exportName,
type,
filename));
filesArrayBuilder.add(filename);
} catch (FileAccessException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
final String exportedEntity;
try {
exportedEntity = imExporter.exportEntity(entity);
} catch (ExportException ex) {
throw new UnexpectedErrorException(ex);
}
try (final OutputStreamWriter writer = new OutputStreamWriter(
outputStream, StandardCharsets.UTF_8)) {
writer.write(exportedEntity);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
// try (JsonWriter writer = Json.createWriter(outputStream)) {
// writer.writeObject(exportedEntity);
// }
}
return filesArrayBuilder;
}
/** /**
* Imports all entities from the files in the {@link imports} directory * Imports all entities from the files in the {@link imports} directory
* inside the CCM files data directory. The data to import can either be a * inside the CCM files data directory. The data to import can either be a
@ -235,8 +184,8 @@ public class ImportExport {
* *
* @see CcmFilesConfiguration#dataPath * @see CcmFilesConfiguration#dataPath
*/ */
@Transactional(Transactional.TxType.REQUIRED)
public void importEntities(final String importName) { public void importEntities(final String importName) {
final String importsPath = String.format("imports/%s", importName); final String importsPath = String.format("imports/%s", importName);
try { try {
@ -253,15 +202,15 @@ public class ImportExport {
throw new UnexpectedErrorException(ex); throw new UnexpectedErrorException(ex);
} }
final List<AbstractEntityImExporter<?>> imExportersList
= new ArrayList<>();
imExporters.forEach(imExporter -> imExportersList.add(imExporter));
try { try {
final EntityImExporterTreeManager treeManager final EntityImExporterTreeManager treeManager
= new EntityImExporterTreeManager(); = new EntityImExporterTreeManager();
final List<EntityImExporterTreeNode> tree = treeManager final List<EntityImExporterTreeNode> tree = treeManager
.generateTree(imExportersList); .generateTree(
imExporters
.stream()
.collect(Collectors.toList())
);
final List<EntityImExporterTreeNode> orderedNodes = treeManager final List<EntityImExporterTreeNode> orderedNodes = treeManager
.orderImExporters(tree); .orderImExporters(tree);
@ -339,8 +288,6 @@ public class ImportExport {
.lines() .lines()
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
// final JsonReader reader = Json.createReader(inputStream);
// final JsonObject data = reader.readObject();
imExporter.importEntity(data); imExporter.importEntity(data);
} catch (IOException } catch (IOException
@ -372,6 +319,71 @@ public class ImportExport {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@SuppressWarnings("unchecked")
private JsonArrayBuilder createExportedEntities(
final String exportName,
final String type,
final List<Exportable> entities) {
final Class<? extends Exportable> clazz;
try {
clazz = (Class<? extends Exportable>) Class.forName(type);
} catch (ClassNotFoundException ex) {
throw new UnexpectedErrorException(ex);
}
final Instance<AbstractEntityImExporter<?>> instance = imExporters
.select(new ProcessesLiteral(clazz));
final AbstractEntityImExporter<?> imExporter;
if (instance.isUnsatisfied()) {
throw new UnexpectedErrorException(String.format(
"No EntityImExporter for entity type \"%s\" available.",
type));
} else if (instance.isAmbiguous()) {
throw new UnexpectedErrorException(String.format(
"Instance reference for EntityImExporter for entity "
+ "type \"%s\" is ambiguous.",
type));
} else {
imExporter = instance.get();
}
final JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder();
for (Exportable entity : entities) {
final String filename = String.format("%s.json", entity.getUuid());
final OutputStream outputStream;
try {
outputStream = ccmFiles.createOutputStream(String.format(
"exports/%s/%s/%s",
exportName,
type,
filename));
filesArrayBuilder.add(filename);
} catch (FileAccessException
| InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
final String exportedEntity;
try {
exportedEntity = imExporter.exportEntity(entity);
} catch (ExportException ex) {
throw new UnexpectedErrorException(ex);
}
try (final OutputStreamWriter writer = new OutputStreamWriter(
outputStream, StandardCharsets.UTF_8)) {
writer.write(exportedEntity);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
return filesArrayBuilder;
}
private boolean isImportArchive(final String path) { private boolean isImportArchive(final String path) {
final String manifestPath = String.format("imports/%s/ccm-export.json", final String manifestPath = String.format("imports/%s/ccm-export.json",
@ -390,8 +402,9 @@ public class ImportExport {
private ImportManifest createImportManifest(final String path) { private ImportManifest createImportManifest(final String path) {
final String manifestPath = String.format("imports/%s/ccm-export.json", final String manifestPath = String.format(
path); "imports/%s/ccm-export.json", path
);
try (final InputStream inputStream = ccmFiles try (final InputStream inputStream = ccmFiles
.createInputStream(manifestPath)) { .createInputStream(manifestPath)) {
@ -400,24 +413,33 @@ public class ImportExport {
final JsonObject manifestJson = reader.readObject(); final JsonObject manifestJson = reader.readObject();
if (!manifestJson.containsKey("created")) { if (!manifestJson.containsKey("created")) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(
String.format(
"The manifest file \"%s\" is malformed. " "The manifest file \"%s\" is malformed. "
+ "Key \"created\" is missing.", + "Key \"created\" is missing.",
manifestPath)); manifestPath
)
);
} }
if (!manifestJson.containsKey("onServer")) { if (!manifestJson.containsKey("onServer")) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(
String.format(
"The manifest file \"%s\" is malformed. " "The manifest file \"%s\" is malformed. "
+ "Key \"onServer\" is missing.", + "Key \"onServer\" is missing.",
manifestPath)); manifestPath
)
);
} }
if (!manifestJson.containsKey("types")) { if (!manifestJson.containsKey("types")) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(
String.format(
"The manifest file \"%s\" is malformed. " "The manifest file \"%s\" is malformed. "
+ "Key \"types\" is missing.", + "Key \"types\" is missing.",
manifestPath)); manifestPath
)
);
} }
final LocalDateTime created = LocalDateTime final LocalDateTime created = LocalDateTime
@ -429,14 +451,16 @@ public class ImportExport {
// .collect(Collectors.toList()); // .collect(Collectors.toList());
final JsonArray typesArray = manifestJson.getJsonArray("types"); final JsonArray typesArray = manifestJson.getJsonArray("types");
final List<String> types = new ArrayList<>(); final List<String> types = new ArrayList<>();
for(int i = 0; i < typesArray.size(); i++) { for (int i = 0; i < typesArray.size(); i++) {
types.add(typesArray.getString(i)); types.add(typesArray.getString(i));
} }
return new ImportManifest( return new ImportManifest(
path,
Date.from(created.atZone(ZoneId.of("UTC")).toInstant()), Date.from(created.atZone(ZoneId.of("UTC")).toInstant()),
onServer, onServer,
types); types
);
} catch (IOException } catch (IOException
| FileAccessException | FileAccessException
| FileDoesNotExistException | FileDoesNotExistException

View File

@ -29,19 +29,30 @@ import java.util.List;
*/ */
public class ImportManifest { public class ImportManifest {
private final String importName;
private final Date created; private final Date created;
private final String onServer; private final String onServer;
private final List<String> types; private final List<String> types;
public ImportManifest(final Date created, public ImportManifest(
final String importName,
final Date created,
final String onServer, final String onServer,
final List<String> types) { final List<String> types
) {
this.importName = importName;
this.created = created; this.created = created;
this.onServer = onServer; this.onServer = onServer;
this.types = types; this.types = types;
} }
public String getImportName() {
return importName;
}
public Date getCreated() { public Date getCreated() {
return new Date(created.getTime()); return new Date(created.getTime());
} }

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -46,23 +47,34 @@ public class GroupImExporter extends AbstractEntityImExporter<Group> {
private GroupRepository groupRepository; private GroupRepository groupRepository;
@Override @Override
protected Class<Group> getEntityClass() { public Class<Group> getEntityClass() {
return Group.class; return Group.class;
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final Group entity) { protected void saveImportedEntity(final Group entity) {
entity.setPartyId(0); entity.setPartyId(0);
// groupRepository.save(entity);
entityManager.persist(entity); entityManager.persist(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
protected Group reloadEntity(final Group entity) {
return groupRepository
.findById(Objects.requireNonNull(entity).getPartyId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Group entity %s was not found in database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -56,14 +56,25 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA) @Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "GroupMembership.findByUuid", @NamedQuery(
query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid"), name = "GroupMembership.findById",
@NamedQuery(name = "GroupMembership.findByGroupAndUser", query
= "SELECT m FROM GroupMembership m WHERE m.membershipId = :membershipId"
),
@NamedQuery(
name = "GroupMembership.findByUuid",
query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid"
),
@NamedQuery(
name = "GroupMembership.findByGroupAndUser",
query = "SELECT m FROM GroupMembership m " query = "SELECT m FROM GroupMembership m "
+ "WHERE m.member = :member AND m.group = :group")}) + "WHERE m.member = :member AND m.group = :group")
})
@XmlRootElement(name = "group-membership", namespace = CORE_XML_NS) @XmlRootElement(name = "group-membership", namespace = CORE_XML_NS)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, @JsonIdentityInfo(
property = "uuid") generator = ObjectIdGenerators.PropertyGenerator.class,
property = "uuid"
)
public class GroupMembership implements Serializable, Exportable { public class GroupMembership implements Serializable, Exportable {
private static final long serialVersionUID = 83192968306850665L; private static final long serialVersionUID = 83192968306850665L;

View File

@ -23,10 +23,12 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
@ -43,14 +45,12 @@ public class GroupMembershipImExporter
private EntityManager entityManager; private EntityManager entityManager;
@Override @Override
protected Class<GroupMembership> getEntityClass() { public Class<GroupMembership> getEntityClass() {
return GroupMembership.class; return GroupMembership.class;
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> entities = new HashSet<>(); final Set<Class<? extends Exportable>> entities = new HashSet<>();
entities.add(User.class); entities.add(User.class);
entities.add(Group.class); entities.add(Group.class);
@ -61,9 +61,30 @@ public class GroupMembershipImExporter
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final GroupMembership entity) { protected void saveImportedEntity(final GroupMembership entity) {
entity.setMembershipId(0); entity.setMembershipId(0);
entityManager.persist(entity); entityManager.persist(entity);
} }
@Override
protected GroupMembership reloadEntity(final GroupMembership entity) {
try {
return entityManager
.createNamedQuery(
"GroupMembership.findById", GroupMembership.class
)
.setParameter(
"membershipId",
Objects.requireNonNull(entity).getMembershipId()
)
.getSingleResult();
} catch (NoResultException ex) {
throw new IllegalArgumentException(
String.format(
"GroupMembership entity %s does not exist in the database.",
Objects.toString(entity)
)
);
}
}
} }

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -35,31 +36,41 @@ import javax.inject.Inject;
*/ */
@RequestScoped @RequestScoped
@Processes(Permission.class) @Processes(Permission.class)
public class PermissionImExporter extends AbstractEntityImExporter<Permission>{ public class PermissionImExporter extends AbstractEntityImExporter<Permission> {
@Inject @Inject
private PermissionRepository permissionRepository; private PermissionRepository permissionRepository;
@Override @Override
protected Class<Permission> getEntityClass() { public Class<Permission> getEntityClass() {
return Permission.class; return Permission.class;
} }
@Override @Override
protected void saveImportedEntity(final Permission entity) { protected void saveImportedEntity(final Permission entity) {
permissionRepository.save(entity); permissionRepository.save(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> classes = new HashSet<>(); final Set<Class<? extends Exportable>> classes = new HashSet<>();
classes.add(Role.class); classes.add(Role.class);
return classes; return classes;
} }
@Override
protected Permission reloadEntity(final Permission entity) {
return permissionRepository
.findById(Objects.requireNonNull(entity).getPermissionId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Permission entity %s not found in the database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -18,42 +18,85 @@
*/ */
package org.libreccm.security; package org.libreccm.security;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.imexport.AbstractEntityImExporter; import org.libreccm.imexport.AbstractEntityImExporter;
import org.libreccm.imexport.ExportException;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.Dependent;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
/** /**
* Exporter/Importer for {@link Role}s. * Exporter/Importer for {@link Role}s.
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@Dependent
@Processes(Role.class) @Processes(Role.class)
public class RoleImExporter extends AbstractEntityImExporter<Role> { public class RoleImExporter extends AbstractEntityImExporter<Role> {
private static final Logger LOGGER = LogManager.getLogger(
RoleImExporter.class);
@Inject
private EntityManager entityManager;
@Inject @Inject
private RoleRepository roleRepository; private RoleRepository roleRepository;
@Override @Override
protected Class<Role> getEntityClass() { public Class<Role> getEntityClass() {
return Role.class; return Role.class;
} }
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String exportEntity(final Exportable entity) throws ExportException {
final Role role = roleRepository
.findById(((Role) entity).getRoleId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Provided entity %d does not exist in database.",
entity)
)
);
role.getDescription().getValues().forEach((locale, value) -> LOGGER
.info("{}: {}", locale, value));
return super.exportEntity(entity);
}
@Override @Override
protected void saveImportedEntity(final Role entity) { protected void saveImportedEntity(final Role entity) {
roleRepository.save(entity); roleRepository.save(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
protected Role reloadEntity(final Role entity) {
return roleRepository
.findById(Objects.requireNonNull(entity).getRoleId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"Role entity %s not found in database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -56,11 +56,20 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA) @Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "RoleMembership.findByUuid", @NamedQuery(
query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"), name = "RoleMembership.findById",
@NamedQuery(name = "RoleMembership.findByRoleAndMember", query
query = "SELECT m FROM RoleMembership m " = "SELECT m FROM RoleMembership m WHERE m.membershipId = :membershipId"
+ "WHERE m.member = :member AND m.role = :role") ),
@NamedQuery(
name = "RoleMembership.findByUuid",
query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"
),
@NamedQuery(
name = "RoleMembership.findByRoleAndMember",
query
= "SELECT m FROM RoleMembership m WHERE m.member = :member AND m.role = :role"
)
}) })
@XmlRootElement(name = "role-membership", namespace = CORE_XML_NS) @XmlRootElement(name = "role-membership", namespace = CORE_XML_NS)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,

View File

@ -23,10 +23,12 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
@ -42,21 +44,18 @@ public class RoleMembershipImExporter
private EntityManager entityManager; private EntityManager entityManager;
@Override @Override
protected Class<RoleMembership> getEntityClass() { public Class<RoleMembership> getEntityClass() {
return RoleMembership.class; return RoleMembership.class;
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final RoleMembership entity) { protected void saveImportedEntity(final RoleMembership entity) {
entityManager.persist(entity); entityManager.persist(entity);
} }
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
final Set<Class<? extends Exportable>> classes = new HashSet<>(); final Set<Class<? extends Exportable>> classes = new HashSet<>();
classes.add(User.class); classes.add(User.class);
classes.add(Group.class); classes.add(Group.class);
@ -65,4 +64,25 @@ public class RoleMembershipImExporter
return classes; return classes;
} }
@Override
protected RoleMembership reloadEntity(final RoleMembership entity) {
try {
return entityManager
.createNamedQuery(
"RoleMembership.findById", RoleMembership.class
)
.setParameter(
"membershipId",
Objects.requireNonNull(entity).getMembershipId()
).getSingleResult();
} catch (NoResultException ex) {
throw new IllegalArgumentException(
String.format(
"RoleMembeship entity %s not found in database.",
Objects.toString(entity)
)
);
}
}
} }

View File

@ -59,6 +59,7 @@ import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery; import javax.persistence.NamedQuery;
import javax.persistence.NamedSubgraph; import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table; import javax.persistence.Table;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
@ -186,6 +187,7 @@ public class User extends Party implements Serializable, Exportable {
* Additional email addresses of the user. * Additional email addresses of the user.
*/ */
@ElementCollection(fetch = FetchType.EAGER) @ElementCollection(fetch = FetchType.EAGER)
@OrderBy("address")
@CollectionTable(name = "USER_EMAIL_ADDRESSES", @CollectionTable(name = "USER_EMAIL_ADDRESSES",
schema = DB_SCHEMA, schema = DB_SCHEMA,
joinColumns = { joinColumns = {
@ -363,7 +365,8 @@ public class User extends Party implements Serializable, Exportable {
@Override @Override
public JsonObjectBuilder buildJson() { public JsonObjectBuilder buildJson() {
final JsonArrayBuilder emailAddressesArrayBuilder = Json.createArrayBuilder(); final JsonArrayBuilder emailAddressesArrayBuilder = Json
.createArrayBuilder();
emailAddresses emailAddresses
.stream() .stream()

View File

@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable;
import org.libreccm.imexport.Processes; import org.libreccm.imexport.Processes;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -46,14 +47,13 @@ public class UserImExporter extends AbstractEntityImExporter<User> {
private UserRepository userRepository; private UserRepository userRepository;
@Override @Override
protected Class<User> getEntityClass() { public Class<User> getEntityClass() {
return User.class; return User.class;
} }
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void saveImportedEntity(final User entity) { protected void saveImportedEntity(final User entity) {
// Reset partyId. // Reset partyId.
entity.setPartyId(0); entity.setPartyId(0);
// userRepository.save(entity); // userRepository.save(entity);
@ -62,8 +62,21 @@ public class UserImExporter extends AbstractEntityImExporter<User> {
@Override @Override
protected Set<Class<? extends Exportable>> getRequiredEntities() { protected Set<Class<? extends Exportable>> getRequiredEntities() {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
protected User reloadEntity(final User entity) {
return userRepository
.findById(Objects.requireNonNull(entity).getPartyId())
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"User entity %s was not found in database.",
Objects.toString(entity)
)
)
);
}
} }

View File

@ -44,6 +44,10 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "SITES", schema = DB_SCHEMA) @Table(name = "SITES", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(
name = "Site.findByUuid",
query = "SELECT s FROM Site s WHERE s.uuid = :uuid"
),
@NamedQuery( @NamedQuery(
name = "Site.findByDomain", name = "Site.findByDomain",
query = "SELECT s FROM Site s " query = "SELECT s FROM Site s "

View File

@ -67,7 +67,7 @@ public class SiteAwareApplication extends CcmApplication {
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 3; int hash = super.hashCode();
if (site != null) { if (site != null) {
hash = 59 * hash + Objects.hashCode(site.getDomainOfSite()); hash = 59 * hash + Objects.hashCode(site.getDomainOfSite());
hash = 59 * hash + Objects.hashCode(site.isDefaultSite()); hash = 59 * hash + Objects.hashCode(site.isDefaultSite());
@ -84,6 +84,9 @@ public class SiteAwareApplication extends CcmApplication {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof SiteAwareApplication)) { if (!(obj instanceof SiteAwareApplication)) {
return false; return false;
} }

View File

@ -41,6 +41,28 @@ public class SiteRepository extends AbstractEntityRepository<Long, Site> {
private static final long serialVersionUID = 3120528987720524155L; private static final long serialVersionUID = 3120528987720524155L;
/**
* Retrieve a {@link Site} by its UUID.
*
* @param uuid The UUID of the site.
*
* @return An {@link Optional} containing the {@link Site} if a site for the
* provided UUID exists.
*/
@Transactional(Transactional.TxType.REQUIRED)
public Optional<Site> findByUuid(final String uuid) {
try {
return Optional.of(
getEntityManager()
.createNamedQuery("Site.findByUuid", Site.class)
.setParameter("uuid", uuid)
.getSingleResult()
);
} catch (NoResultException ex) {
return Optional.empty();
}
}
/** /**
* Retrieve the {@link Site} for a specific domain. * Retrieve the {@link Site} for a specific domain.
* *

View File

@ -18,8 +18,11 @@
*/ */
package org.libreccm.theming; package org.libreccm.theming;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.core.UnexpectedErrorException; import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.files.CcmFiles; import org.libreccm.files.CcmFiles;
import org.libreccm.files.CcmFilesNotConfiguredException;
import org.libreccm.files.DirectoryNotEmptyException; import org.libreccm.files.DirectoryNotEmptyException;
import org.libreccm.files.FileAccessException; import org.libreccm.files.FileAccessException;
import org.libreccm.files.FileAlreadyExistsException; import org.libreccm.files.FileAlreadyExistsException;
@ -54,6 +57,10 @@ public class FileSystemThemeProvider implements ThemeProvider {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LogManager.getLogger(
FileSystemThemeProvider.class
);
private static final String BASE_PATH = "/themes"; private static final String BASE_PATH = "/themes";
private static final String DRAFT_THEMES_PATH = BASE_PATH + "/draft"; private static final String DRAFT_THEMES_PATH = BASE_PATH + "/draft";
private static final String LIVE_THEMES_PATH = BASE_PATH + "/live"; private static final String LIVE_THEMES_PATH = BASE_PATH + "/live";
@ -70,11 +77,15 @@ public class FileSystemThemeProvider implements ThemeProvider {
@Inject @Inject
private ThemeFileInfoUtil themeFileInfoUtil; private ThemeFileInfoUtil themeFileInfoUtil;
@Override
public String getName() {
return "FileSystemThemeProvider";
}
@Override @Override
public List<ThemeInfo> getThemes() { public List<ThemeInfo> getThemes() {
try { try {
if (!ccmFiles.isDirectory(BASE_PATH) if (!ccmFiles.isDirectory(BASE_PATH)
|| !ccmFiles.isDirectory(DRAFT_THEMES_PATH)) { || !ccmFiles.isDirectory(DRAFT_THEMES_PATH)) {
@ -92,8 +103,10 @@ public class FileSystemThemeProvider implements ThemeProvider {
} catch (FileAccessException } catch (FileAccessException
| FileDoesNotExistException | FileDoesNotExistException
| InsufficientPermissionsException ex) { | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex); throw new UnexpectedErrorException(ex);
} catch(CcmFilesNotConfiguredException ex) {
LOGGER.warn(ex);
return Collections.emptyList();
} }
} }
@ -119,6 +132,9 @@ public class FileSystemThemeProvider implements ThemeProvider {
| InsufficientPermissionsException ex) { | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex); throw new UnexpectedErrorException(ex);
} catch(CcmFilesNotConfiguredException ex) {
LOGGER.warn(ex);
return Collections.emptyList();
} }
} }

View File

@ -80,6 +80,11 @@ public class StaticThemeProvider implements ThemeProvider {
@Inject @Inject
private ThemeFileInfoUtil themeFileInfoUtil; private ThemeFileInfoUtil themeFileInfoUtil;
@Override
public String getName() {
return "StaticThemeProvider";
}
@Override @Override
public List<ThemeInfo> getThemes() { public List<ThemeInfo> getThemes() {

View File

@ -36,6 +36,12 @@ import javax.enterprise.context.RequestScoped;
*/ */
public interface ThemeProvider extends Serializable { public interface ThemeProvider extends Serializable {
/**
* A human readable name for the {@code ThemeProvider} implementation.
* @return
*/
String getName();
/** /**
* Provides a list of all themes provided by this theme provider. The list * Provides a list of all themes provided by this theme provider. The list
* should be ordered by the name of the theme. * should be ordered by the name of the theme.

View File

@ -28,7 +28,9 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Any; import javax.enterprise.inject.Any;
@ -53,6 +55,7 @@ public class Themes implements Serializable {
@Any @Any
private Instance<ThemeProvider> providers; private Instance<ThemeProvider> providers;
// //
@Inject @Inject
private ThemeProcessors themeProcessors; private ThemeProcessors themeProcessors;
@ -108,6 +111,100 @@ public class Themes implements Serializable {
return Optional.empty(); return Optional.empty();
} }
public List<ThemeProvider> getThemeProviders() {
return providers
.stream()
.collect(Collectors.toList());
}
public Optional<ThemeProvider> findThemeProviderInstance(
final Class<? extends ThemeProvider> ofClazz
) {
final Instance<? extends ThemeProvider> instance = providers
.select(ofClazz);
if (instance.isResolvable()) {
return Optional.of(instance.get());
} else {
return Optional.empty();
}
}
public ThemeInfo createTheme(
final String themeName, final String providerName
) {
final Class<ThemeProvider> providerClass = getThemeProviderClass(
providerName
);
return createTheme(
themeName,
findThemeProviderInstance(providerClass).orElseThrow(
() -> new IllegalArgumentException(
String.format(
"No instance of ThemeProvider implementation %s available.",
providerName
)
)
)
);
}
public ThemeInfo createTheme(
final String themeName, final ThemeProvider themeProvider
) {
return themeProvider.createTheme(themeName);
}
public void deleteTheme(final String themeName) {
final Optional<ThemeProvider> provider = findProviderOfTheme(
Objects.requireNonNull(
themeName,
"Can't delete theme null."
)
);
if (provider.isPresent()) {
provider.get().deleteTheme(themeName);
} else {
throw new IllegalArgumentException(
String.format(
"No provider providing a theme named %s found.",
themeName
)
);
}
}
public void publishTheme(final String themeName) {
final Optional<ThemeProvider> provider = findProviderOfTheme(themeName);
if (provider.isPresent()) {
provider.get().publishTheme(themeName);
} else {
throw new IllegalArgumentException(
String.format(
"No provider providing a theme named %s found.",
themeName
)
);
}
}
public void unpublishTheme(final String themeName) {
final Optional<ThemeProvider> provider = findProviderOfTheme(themeName);
if (provider.isPresent()) {
provider.get().unpublishTheme(themeName);
} else {
throw new IllegalArgumentException(
String.format(
"No provider providing a theme named %s found.",
themeName
)
);
}
}
/** /**
* Creates HTML from the result of rendering a {@link PageModel}. * Creates HTML from the result of rendering a {@link PageModel}.
* *
@ -240,7 +337,37 @@ public class Themes implements Serializable {
final ThemeProvider provider = forTheme.get(); final ThemeProvider provider = forTheme.get();
provider.deleteThemeFile(theme.getName(), path); provider.deleteThemeFile(theme.getName(), path);
}
@SuppressWarnings("unchecked")
private Class<ThemeProvider> getThemeProviderClass(
final String providerName
) {
try {
return (Class<ThemeProvider>) Class.forName(providerName);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(
String.format(
"No ThemeProvider implementation %s available.",
providerName
)
);
}
}
private Optional<ThemeProvider> findProviderOfTheme(
final String themeName
) {
final List<ThemeProvider> providersList = new ArrayList<>();
providers.forEach(provider -> providersList.add(provider));
return providersList
.stream()
.filter(
current -> current.providesTheme(
themeName, ThemeVersion.DRAFT
)
).findAny();
} }
} }

View File

@ -71,6 +71,11 @@ public class DatabaseThemeProvider implements ThemeProvider {
@Inject @Inject
private ThemeRepository themeRepository; private ThemeRepository themeRepository;
@Override
public String getName() {
return "DatabaseThemeProvider";
}
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<ThemeInfo> getThemes() { public List<ThemeInfo> getThemes() {
@ -419,6 +424,7 @@ public class DatabaseThemeProvider implements ThemeProvider {
private class DataFileOutputStream extends OutputStream { private class DataFileOutputStream extends OutputStream {
private final DataFile dataFile; private final DataFile dataFile;
private final ByteArrayOutputStream outputStream; private final ByteArrayOutputStream outputStream;
private DataFileOutputStream(final DataFile dataFile) { private DataFileOutputStream(final DataFile dataFile) {

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui;
import org.libreccm.security.Shiro;
import java.io.IOException;
import java.net.URI;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response;
/**
* Filter for securing EE MVC UIs. Checks if the current user is authenticated.
* If not the user is redirected to the login application.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@PreMatching
public class IsAuthenticatedFilter implements ContainerRequestFilter {
@Inject
private ServletContext servletContext;
@Inject
private Shiro shiro;
@Override
public void filter(final ContainerRequestContext requestContext)
throws IOException {
if (!shiro.getSubject().isAuthenticated()) {
final String contextPath = servletContext.getContextPath();
final String returnUrl = requestContext
.getUriInfo()
.getRequestUri()
.getPath();
requestContext.abortWith(
Response.temporaryRedirect(
URI.create(
String.format(
"/%s/ccm/register?return_url=%s",
contextPath,
returnUrl
)
)
).build()
);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui;
/**
* Stores a message to be displayed in the UI.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class Message {
/**
* The message (or the translation key for the message).
*/
private final String message;
/**
* The type of the message.
*/
private final MessageType messageType;
public Message(String message, MessageType messageType) {
this.message = message;
this.messageType = messageType;
}
public String getMessage() {
return message;
}
public MessageType getMessageType() {
return messageType;
}
public String getMessageTypeClass() {
return messageType.toString().toLowerCase();
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui;
/**
* Possible message types. The types are equivalent to the contextual classes
* of the Bootstrap framework.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public enum MessageType {
PRIMARY,
SECONDARY,
SUCCESS,
DANGER,
WARNING,
INFO,
LIGHT,
DARK,
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui;
import org.libreccm.l10n.GlobalizationHelper;
import java.util.Locale;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.locale.LocaleResolver;
import javax.mvc.locale.LocaleResolverContext;
/**
* A locale resolver implementation that simply passes the locale negoiated by
* LibreCCM to Jakarta EE MVC.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class MvcLocaleResolver implements LocaleResolver {
@Inject
private GlobalizationHelper globalizationHelper;
@Override
public Locale resolveLocale(final LocaleResolverContext context) {
return globalizationHelper.getNegotiatedLocale();
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.ui.IsAuthenticatedFilter;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
/**
* Collects the controllers for the admin application and registers them with
* JAX-RS.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationPath("/@admin")
public class AdminApplication extends Application {
private static final Logger LOGGER = LogManager.getLogger(
AdminApplication.class
);
/**
* Injection point for the admin pages.
*/
@Inject
private Instance<AdminPage> adminPages;
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(IsAuthenticatedFilter.class);
classes.addAll(
adminPages
.stream()
.map(AdminPage::getControllerClasses)
.flatMap(controllers -> controllers.stream())
.collect(Collectors.toSet())
);
LOGGER.debug(
"Adding classes to AdminApplication: {}", Objects.toString(classes)
);
return classes;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
/**
* Some constants for the admin application
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class AdminConstants {
private AdminConstants() {
// Nothing
}
/**
* Bundle that provides the translations for the admin application.
*/
public static final String ADMIN_BUNDLE = "org.libreccm.ui.AdminBundle";
}

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
import org.libreccm.l10n.GlobalizationHelper;
import java.text.MessageFormat;
import java.util.AbstractMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Provides simple access to the messages in the admin bundle. The make it as
* easy as possible to access the messages this class is implemented as a map a
* made available as named bean. For simple messages, {@code AdminMesssages} can
* be used like a map in a facelets template:
*
* <pre>
* #{AdminMessages['some.message.key'])
* </pre>
*
* Messages with placeholders can be retrieved using
* {@link #getMessage(java.lang.String, java.util.List)} or
* {@link #getMessage(java.lang.String, java.lang.Object[])}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("AdminMessages")
public class AdminMessages extends AbstractMap<String, String> {
/**
* Provides access to the locale negoiated by LibreCCM.
*/
@Inject
private GlobalizationHelper globalizationHelper;
/**
* The {@link ResourceBundle} to use.
*/
private ResourceBundle messages;
/**
* Loads the resource bundle.
*/
@PostConstruct
private void init() {
messages = ResourceBundle.getBundle(
AdminConstants.ADMIN_BUNDLE,
globalizationHelper.getNegotiatedLocale()
);
}
/**
* Retrieves a message from the resource bundle.
*
* @param key The key of the message.
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the key).
*/
public String getMessage(final String key) {
if (messages.containsKey(key)) {
return messages.getString(key);
} else {
return String.format("???%s???", key);
}
}
/**
* Retrieves a message with placeholders.
*
* @param key The key of the message.
* @param parameters The parameters for the placeholders.
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the key).
*/
public String getMessage(
final String key, final List<Object> parameters
) {
return getMessage(key, parameters.toArray());
}
/**
* The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the key).
*
@param key The key of the message.
* @param parameters The parameters for the placeholders.
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the key).
*/
public String getMessage(
final String key, final Object[] parameters
) {
if (messages.containsKey(key)) {
return MessageFormat.format(messages.getString(key), parameters);
} else {
return String.format("???%s???", key);
}
}
@Override
public String get(final Object key) {
return get((String) key);
}
public String get(final String key) {
return getMessage(key);
}
@Override
public Set<Entry<String, String>> entrySet() {
return messages
.keySet()
.stream()
.collect(
Collectors.toMap(key -> key, key -> messages.getString(key))
)
.entrySet();
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
import java.util.Set;
import javax.mvc.MvcContext;
/**
* Implementations of this interface provide the controllers etc. for an admin
* page.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public interface AdminPage {
/**
* Classes implementing the controllers of the page.
*
* @return A set of controllers to be added to the {@link AdminApplication}.
*/
Set<Class<?>> getControllerClasses();
/**
* A identifier to use by {@link MvcContext#uri(java.lang.String)} to
* generate the URI of the page. The identifier has the same format as used
* in JavaDoc:
* <pre>
* ControllerSimpleClassName#methodName
* </pre>
*
* @return The identifier to use for generating the URL of the page
*/
String getUriIdentifier();
/**
* Gets the resourcebundle which provides the label of the admin page.
*
* @return The bundle to use for retrieving the label of the page.
*/
String getLabelBundle();
/**
* Gets the key for retrieving the label of the page from the label bundle.
*
* @return The key of the label.
*/
String getLabelKey();
/**
* Gets the resourcebundle which provides the description of the admin page.
*
* @return The bundle to use for retrieving the label of the page.
*/
String getDescriptionBundle();
/**
* Gets the key for retrieving the description of the page from the
* description bundle.
*
* @return The key of the label.
*/
String getDescriptionKey();
/**
* Name of icon to use.
*
* @return The icon to use for the page.
*/
String getIcon();
/**
* Gets the position of the page in the admin nav bar.
*
* @return The position of the page in the admin navigation.
*/
int getPosition();
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
import java.util.Comparator;
/**
* Model for the data of an admin page.
*
* @see AdminPage
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class AdminPageModel implements Comparable<AdminPageModel> {
private String uriIdentifier;
private String label;
private String description;
private String icon;
private long position;
public String getUriIdentifier() {
return uriIdentifier;
}
protected void setUriIdentifier(final String uriIdentifier) {
this.uriIdentifier = uriIdentifier;
}
public String getLabel() {
return label;
}
protected void setLabel(final String label) {
this.label = label;
}
public String getDescription() {
return description;
}
protected void setDescription(final String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
protected void setIcon(final String icon) {
this.icon = icon;
}
public long getPosition() {
return position;
}
protected void setPosition(final long position) {
this.position = position;
}
@Override
public int compareTo(final AdminPageModel other) {
return Comparator
.nullsFirst(
Comparator
.comparing(AdminPageModel::getPosition)
.thenComparing(AdminPageModel::getLabel)
).compare(this, other);
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin;
import org.libreccm.l10n.GlobalizationHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Model for the available admin pages.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("AdminPagesModel")
public class AdminPagesModel {
/**
* Injection point for the admin pages.
*/
@Inject
private Instance<AdminPage> adminPages;
@Inject
private GlobalizationHelper globalizationHelper;
/**
* Cache for bundles
*/
private final Map<String, ResourceBundle> bundles = new HashMap<>();
/**
* Retrieves the available admin pages and converts them into
* {@link AdminPageModel}s for usage in the views.
*
* @return A list of the available admin pages.
*/
public List<AdminPageModel> getAdminPages() {
return adminPages
.stream()
.sorted(
(page1, page2) -> Integer.compare(
page1.getPosition(), page2.getPosition()
)
)
.map(this::buildAdminPageModel)
.collect(Collectors.toList());
}
private AdminPageModel buildAdminPageModel(final AdminPage fromAdminPage) {
final ResourceBundle labelBundle = getBundle(
fromAdminPage.getLabelBundle()
);
final ResourceBundle descriptionBundle = getBundle(
fromAdminPage.getDescriptionBundle()
);
final AdminPageModel model = new AdminPageModel();
model.setUriIdentifier(fromAdminPage.getUriIdentifier());
model.setLabel(labelBundle.getString(fromAdminPage.getLabelKey()));
model.setDescription(
descriptionBundle.getString(
fromAdminPage.getDescriptionKey()
)
);
model.setIcon(fromAdminPage.getIcon());
model.setPosition(fromAdminPage.getPosition());
return model;
}
private ResourceBundle getBundle(final String bundleName) {
if (bundles.containsKey(bundleName)) {
return bundles.get(bundleName);
} else {
final ResourceBundle bundle = ResourceBundle.getBundle(
bundleName,
globalizationHelper.getNegotiatedLocale()
);
bundles.put(bundleName, bundle);
return bundle;
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.applications;
/**
* Interface for controllers providing the UI for managing the instances of
* an application.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public interface ApplicationController {
String getApplication();
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.applications;
import java.util.Objects;
/**
* Data Transfer Object providing the information about an application. Used for
* rendering the informations the available applications in UI.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ApplicationTypeInfoItem implements
Comparable<ApplicationTypeInfoItem> {
/**
* Name of the application.
*/
private String name;
/**
* Localized title of the application, if available in the language of the
* current user.
*/
private String title;
/**
* Localized title of the application, if available in the language of the
* current user.
*/
private String description;
/**
* Is the application a singleton application?
*/
private boolean singleton;
/**
* Number of existing instances of the application.
*/
private long numberOfInstances;
/**
* Link the {@link ApplicationController} implementation of the application,
* if an implementation is available.
*/
private String controllerLink;
protected ApplicationTypeInfoItem() {
// Nothing
}
public String getName() {
return name;
}
protected void setName(final String name) {
this.name = name;
}
public String getTitle() {
return title;
}
protected void setTitle(final String title) {
this.title = title;
}
public String getDescription() {
return description;
}
protected void setDescription(final String description) {
this.description = description;
}
public boolean isSingleton() {
return singleton;
}
protected void setSingleton(final boolean singleton) {
this.singleton = singleton;
}
public long getNumberOfInstances() {
return numberOfInstances;
}
protected void setNumberOfInstances(final long numberOfInstances) {
this.numberOfInstances = numberOfInstances;
}
public String getControllerLink() {
return controllerLink;
}
protected void setControllerLink(final String controllerLink) {
this.controllerLink = controllerLink;
}
@Override
public int compareTo(final ApplicationTypeInfoItem other) {
if (other == null) {
return 1;
}
int result = Objects.compare(title, other.getTitle(), String::compareTo);
if (result == 0) {
result = Objects.compare(name, other.getName(), String::compareTo);
}
return result;
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.applications;
import org.libreccm.core.CoreConstants;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.l10n.LocalizedTextsUtil;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.web.ApplicationManager;
import org.libreccm.web.ApplicationRepository;
import org.libreccm.web.ApplicationType;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
/**
* Controller for the UI for managing application instances.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/applications")
public class ApplicationsController {
@Inject
private ApplicationManager appManager;
@Inject
private ApplicationRepository appRepository;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private Models models;
/**
* Retrives the avaiable application types, creates
* {@link ApplicationTypeInfoItem}s for them and makes them available using
* {@link #models}.
*
* @return The template to render.
*/
@GET
@Path("/")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String getApplicationTypes() {
final List<ApplicationTypeInfoItem> appTypes = appManager
.getApplicationTypes()
.entrySet()
.stream()
.map(Map.Entry::getValue)
.map(this::buildTypeInfoItem)
.sorted()
.collect(Collectors.toList());
models.put("applicationTypes", appTypes);
return "org/libreccm/ui/admin/applications/applicationtypes.xhtml";
}
/**
* Helper method for building an {@link ApplicationTypeInfoItem} for an
* {@link ApplicationType}.
*
* @param applicationType The application type.
*
* @return An {@link ApplicationTypeInfoItem} for the provided application
* type.
*/
private ApplicationTypeInfoItem buildTypeInfoItem(
final ApplicationType applicationType
) {
final ApplicationTypeInfoItem item = new ApplicationTypeInfoItem();
item.setName(applicationType.name());
final LocalizedTextsUtil textsUtil = globalizationHelper
.getLocalizedTextsUtil(applicationType.descBundle());
item.setTitle(textsUtil.getText(applicationType.titleKey()));
item.setDescription(textsUtil.getText(applicationType.descKey()));
item.setSingleton(applicationType.singleton());
item.setNumberOfInstances(
appRepository.findByType(applicationType.name()).size()
);
final Class<? extends ApplicationController> controllerClass
= applicationType.applicationController();
if (!DefaultApplicationController.class.isAssignableFrom(
controllerClass
)) {
item.setControllerLink(
String.format(
"%s#getApplication",
controllerClass.getSimpleName())
);
}
return item;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.applications;
import org.libreccm.ui.admin.AdminConstants;
import org.libreccm.ui.admin.AdminPage;
import org.libreccm.web.ApplicationManager;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
/**
* {@link AdminPage} for managing applications.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationScoped
public class ApplicationsPage implements AdminPage {
@Inject
private ApplicationManager applicationManager;
@Override
public Set<Class<?>> getControllerClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(ApplicationsController.class);
classes.addAll(
applicationManager
.getApplicationTypes()
.entrySet()
.stream()
.map(type -> type.getValue().applicationController())
.collect(Collectors.toSet())
);
return classes;
}
@Override
public String getUriIdentifier() {
return String.format(
"%s#getApplicationTypes",
ApplicationsController.class.getSimpleName()
);
}
@Override
public String getLabelBundle() {
return AdminConstants.ADMIN_BUNDLE;
}
@Override
public String getLabelKey() {
return "applications.label";
}
@Override
public String getDescriptionBundle() {
return AdminConstants.ADMIN_BUNDLE;
}
@Override
public String getDescriptionKey() {
return "applications.description";
}
@Override
public String getIcon() {
return "puzzle";
}
@Override
public int getPosition() {
return 40;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.applications;
import javax.enterprise.context.RequestScoped;
import javax.mvc.Controller;
import javax.ws.rs.Path;
/**
* A default implementation of the {@link ApplicationController} used if there
* is not implementation of the {@link ApplicationController} interface for an
* application.
*
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/application")
public class DefaultApplicationController implements ApplicationController {
@Override
public String getApplication() {
return "";
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2020 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
*/
/**
* UI for managing application instances.
*/
package org.libreccm.ui.admin.applications;

View File

@ -0,0 +1,851 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.CategoryRepository;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainRepository;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.ui.Message;
import org.libreccm.ui.MessageType;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
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.MediaType;
/**
* Primary controller for the UI for managing category systems and categories.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/categorymanager/categories")
public class CategoriesController {
@Inject
private AdminMessages adminMessages;
@Inject
private CategoryDetailsModel categoryDetailsModel;
@Inject
private CategoryManager categoryManager;
@Inject
private CategoryRepository categoryRepository;
@Inject
private DomainRepository domainRepository;
@Inject
private IdentifierParser identifierParser;
@Inject
private Models models;
/**
* Show details about a category.
*
* @param categoryIdentifier Identifier of the category to show.
*
* @return The template to render.
*/
@GET
@Path("/{categoryIdentifier}")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String getCategory(
@PathParam("categoryIdentifier") final String categoryIdentifier
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifier
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
categoryDetailsModel.setCategory(result.get());
return "org/libreccm/ui/admin/categories/category-details.xhtml";
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifier)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Show the edit form for a category.
*
* @param categoryIdentifier Identifier of the category to edit.
*
* @return The template to render.
*/
@GET
@Path("/{categoryIdentifier}/edit")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String editCategory(
@PathParam("categoryIdentifier") final String categoryIdentifier
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifier
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
categoryDetailsModel.setCategory(result.get());
return "org/libreccm/ui/admin/categories/category-form.xhtml";
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifier)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Displays the form for creating a new subcategory.
*
* @param categoryIdentifier The identifier of the parent category.
*
* @return The template to render.
*/
@GET
@Path("/{categoryIdentifier}/subcategories/new")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String newSubCategory(
@PathParam("categoryIdentifier") final String categoryIdentifier
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifier
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
categoryDetailsModel.setParentCategory(result.get());
return "org/libreccm/ui/admin/categories/category-form.xhtml";
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifier)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Moves a category from one parent category to another. The target is
* provided
*
* @param categoryIdentifierParam Identifier of the category to move.
* @param targetIdentifierParam Identifier of the target category.
*
* @return Redirect to the detail page of the target category.
*/
@POST
@Path("/{categoryIdentifier}/subcategories/move")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String moveSubCategory(
@PathParam("categoryIdentifier") final String categoryIdentifierParam,
@FormParam("targetIdentifier") final String targetIdentifierParam
) {
final Identifier categoryIdentifier = identifierParser.parseIdentifier(
categoryIdentifierParam
);
final Optional<Category> categoryResult;
switch (categoryIdentifier.getType()) {
case ID:
categoryResult = categoryRepository.findById(
Long.parseLong(categoryIdentifier.getIdentifier())
);
break;
default:
categoryResult = categoryRepository.findByUuid(
categoryIdentifier.getIdentifier()
);
break;
}
if (!categoryResult.isPresent()) {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
final Identifier targetIdentifier = identifierParser.parseIdentifier(
targetIdentifierParam
);
final Optional<Category> targetResult;
switch (targetIdentifier.getType()) {
case ID:
targetResult = categoryRepository.findById(
Long.parseLong(targetIdentifier.getIdentifier())
);
break;
default:
targetResult = categoryRepository.findByUuid(
targetIdentifier.getIdentifier()
);
break;
}
if (!categoryResult.isPresent()) {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(targetIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
final Category category = categoryResult.get();
final Category oldParent = category.getParentCategory();
if (oldParent == null) {
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
}
final Category target = targetResult.get();
categoryManager.removeSubCategoryFromCategory(category, oldParent);
categoryManager.addSubCategoryToCategory(category, target);
return String.format(
"redirect:categorymanager/categories/ID-%d", target.getObjectId()
);
}
/**
* Deletes a category.
*
* @param categoryIdentifier Identifier of the category to remove.
*
* @return Redirect to the details page of the parent category of the
* removed category.
*/
@POST
@Path("/{categoryIdentifier}/subcategories/remove")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String removeSubCategory(
@PathParam("categoryIdentifier") final String categoryIdentifier
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifier
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Category parentCategory = category.getParentCategory();
if (parentCategory == null) {
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
}
categoryManager.removeSubCategoryFromCategory(category,
parentCategory
);
categoryRepository.delete(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
parentCategory.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifier)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Adds a localized title the a category.
*
* @param identifierParam Identifier of the category.
* @param localeParam The locale of the title.
* @param value The localized title.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}/title/add")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String addTitle(
@PathParam("identifier") final String identifierParam,
@FormParam("locale") final String localeParam,
@FormParam("value") final String value
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getTitle().addValue(locale, value);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(identifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Updates the localized title of a category.
*
* @param identifierParam Identifier of the category.
* @param localeParam The locale of the title.
* @param value The localized title.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}/title/{locale}/edit")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String editTitle(
@PathParam("identifier") final String identifierParam,
@PathParam("locale") final String localeParam,
@FormParam("value") final String value
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getTitle().addValue(locale, value);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(identifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Removes the localized title of a category.
*
* @param categoryIdentifierParam Identifier of the category.
* @param localeParam The locale of the title.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}/title/{locale}/remove")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeTitle(
@PathParam("identifier")
final String categoryIdentifierParam,
@PathParam("locale") final String localeParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getTitle().removeValue(locale);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Adds a localized description the a category.
*
* @param identifierParam Identifier of the category.
* @param localeParam The locale of the description
* @param value The localized description.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}decsription/add")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String addDescription(
@PathParam("identifier") final String identifierParam,
@FormParam("locale") final String localeParam,
@FormParam("value") final String value
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getDescription().addValue(locale, value);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(identifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Updates the localized description the a category.
*
* @param identifierParam Identifier of the category.
* @param localeParam The locale of the description
* @param value The localized description.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}/description/{locale}/edit")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String editDescription(
@PathParam("identifier") final String identifierParam,
@PathParam("locale") final String localeParam,
@FormParam("value") final String value
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getDescription().addValue(locale, value);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(identifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Removes a localized description the a category.
*
* @param identifierParam Identifier of the category.
* @param localeParam The locale of the description
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{identifier}/description/{locale}/remove")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeDescription(
@PathParam("identifier") final String identifierParam,
@PathParam("locale") final String localeParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
final Locale locale = new Locale(localeParam);
category.getDescription().removeValue(locale);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(identifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Changes the order of the subcategories of a category.
*
* @param categoryIdentifierParam Identifier of the category.
* @param subCategoryIdentifierParam Identifier of the sub category to move.
* @param direction The direction, either
* {@code INCREASE or DECREASE}.
*
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{categoryIdentifier}/subcategories/{subCategoryIdentifier}/reorder")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String reorderSubCategory(
@PathParam("categoryIdentifier") final String categoryIdentifierParam,
@PathParam("subCategoryIdentifier") final String subCategoryIdentifierParam,
@FormParam("direction") final String direction
) {
final Identifier categoryIdentifier = identifierParser.parseIdentifier(
categoryIdentifierParam
);
final Identifier subCategoryIdentifier = identifierParser
.parseIdentifier(subCategoryIdentifierParam);
final Optional<Category> categoryResult;
switch (categoryIdentifier.getType()) {
case ID:
categoryResult = categoryRepository.findById(
Long.parseLong(categoryIdentifier.getIdentifier())
);
break;
default:
categoryResult = categoryRepository.findByUuid(
categoryIdentifier.getIdentifier()
);
break;
}
final Category category;
if (categoryResult.isPresent()) {
category = categoryResult.get();
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
final Optional<Category> subCategoryResult;
switch (subCategoryIdentifier.getType()) {
case ID:
subCategoryResult = categoryRepository.findById(
Long.parseLong(subCategoryIdentifier.getIdentifier())
);
break;
default:
subCategoryResult = categoryRepository.findByUuid(
subCategoryIdentifier.getIdentifier()
);
break;
}
final Category subCategory;
if (subCategoryResult.isPresent()) {
subCategory = subCategoryResult.get();
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(subCategoryIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
switch (direction) {
case "DECREASE":
categoryManager.decreaseCategoryOrder(subCategory, category);
break;
case "INCREASE":
categoryManager.increaseCategoryOrder(subCategory, category);
break;
default:
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.invalid_direction.message",
Arrays.asList(direction)),
MessageType.WARNING
)
);
}
if (category.getParentCategory() == null) {
final Optional<Domain> categorySystem = domainRepository
.findByRootCategory(category);
if (categorySystem.isPresent()) {
return String.format(
"redirect:categorymanager/categorysystems/ID-%d/details",
categorySystem.get().getObjectId()
);
} else {
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
}
} else {
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.ui.admin.AdminConstants;
import org.libreccm.ui.admin.AdminPage;
import java.util.HashSet;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
/**
* {@link AdminPage} implementation for the UI for managing categories.
*
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationScoped
public class CategoriesPage implements AdminPage {
@Override
public Set<Class<?>> getControllerClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(CategorySystemsController.class);
classes.add(CategorySystemFormController.class);
classes.add(CategoriesController.class);
classes.add(CategoryFormController.class);
return classes;
}
@Override
public String getUriIdentifier() {
return String.format(
"%s#getCategorySystems",
CategorySystemsController.class.getSimpleName()
);
}
@Override
public String getLabelBundle() {
return AdminConstants.ADMIN_BUNDLE;
}
@Override
public String getLabelKey() {
return "categories.label";
}
@Override
public String getDescriptionBundle() {
return AdminConstants.ADMIN_BUNDLE;
}
@Override
public String getDescriptionKey() {
return "categories.description";
}
@Override
public String getIcon() {
return "diagram-3-fill";
}
@Override
public int getPosition() {
return 20;
}
}

View File

@ -0,0 +1,327 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainRepository;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.ui.Message;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
* Model for the details of a category.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CategoryDetailsModel")
public class CategoryDetailsModel {
@Inject
private CategoryManager categoryManager;
@Inject
private DomainRepository domainRepository;
@Inject
private GlobalizationHelper globalizationHelper;
private long categoryId;
private String uuid;
private String uniqueId;
private String name;
private String path;
private Map<String, String> title;
private List<String> unusedTitleLocales;
private Map<String, String> description;
private List<String> unusedDescriptionLocales;
private boolean enabled;
private boolean visible;
private boolean abstractCategory;
private List<CategoryNodeModel> subCategories;
private CategoryNodeModel parentCategory;
private CategoryPathModel categoryPath;
private long categoryOrder;
private final List<Message> messages;
private Set<String> invalidFields;
public CategoryDetailsModel() {
this.messages = new ArrayList<>();
}
public long getCategoryId() {
return categoryId;
}
public String getIdentifier() {
return String.format("ID-%d", categoryId);
}
public String getUuid() {
return uuid;
}
public String getUniqueId() {
return uniqueId;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
public Map<String, String> getTitle() {
return Collections.unmodifiableMap(title);
}
public List<String> getUnusedTitleLocales() {
return Collections.unmodifiableList(unusedTitleLocales);
}
public boolean hasUnusedTitleLocales() {
return !unusedTitleLocales.isEmpty();
}
public Map<String, String> getDescription() {
return Collections.unmodifiableMap(description);
}
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
public boolean hasUnusedDescriptionLocales() {
return !unusedDescriptionLocales.isEmpty();
}
public boolean isEnabled() {
return enabled;
}
public boolean isVisible() {
return visible;
}
public boolean isAbstractCategory() {
return abstractCategory;
}
public List<CategoryNodeModel> getSubCategories() {
return Collections.unmodifiableList(subCategories);
}
public CategoryNodeModel getParentCategory() {
return parentCategory;
}
protected void setParentCategory(final Category parent) {
parentCategory = buildCategoryNodeModel(parent);
}
public CategoryPathModel getCategoryPath() {
return categoryPath;
}
public long getCategoryOrder() {
return categoryOrder;
}
public boolean isNew() {
return categoryId == 0;
}
public List<Message> getMessages() {
return Collections.unmodifiableList(messages);
}
public void addMessage(final Message message) {
messages.add(message);
}
public Set<String> getInvalidFields() {
return Collections.unmodifiableSet(invalidFields);
}
protected void addInvalidField(final String invalidField) {
invalidFields.add(invalidField);
}
protected void setInvalidFields(final Set<String> invalidFields) {
this.invalidFields = new HashSet<>(invalidFields);
}
/**
* Sets the model to the properties of the provided category.
*
* @param category The category.
*/
@Transactional(Transactional.TxType.REQUIRED)
protected void setCategory(final Category category) {
Objects.requireNonNull(category);
categoryId = category.getObjectId();
uuid = category.getUuid();
uniqueId = category.getUniqueId();
name = category.getName();
path = categoryManager.getCategoryPath(category);
final List<Locale> availableLocales = globalizationHelper
.getAvailableLocales();
title = category
.getTitle()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
);
final Set<Locale> titleLocales = category
.getTitle()
.getAvailableLocales();
unusedTitleLocales = availableLocales
.stream()
.filter(locale -> !titleLocales.contains(locale))
.map(Locale::toString)
.sorted()
.collect(Collectors.toList());
description = category
.getDescription()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
);
final Set<Locale> descriptionLocales = category
.getDescription()
.getAvailableLocales();
unusedDescriptionLocales = availableLocales
.stream()
.filter(locale -> !descriptionLocales.contains(locale))
.map(Locale::toString)
.sorted()
.collect(Collectors.toList());
enabled = category.isEnabled();
visible = category.isVisible();
abstractCategory = category.isAbstractCategory();
subCategories = category
.getSubCategories()
.stream()
.map(this::buildCategoryNodeModel)
.sorted()
.collect(Collectors.toList());
if (category.getParentCategory() != null) {
parentCategory
= buildCategoryNodeModel(category.getParentCategory());
}
categoryPath = buildCategoryPathModel(category);
categoryOrder = category.getCategoryOrder();
}
private DomainNodeModel buildDomainNodeModel(final Domain domain) {
final DomainNodeModel model = new DomainNodeModel();
model.setDomainId(domain.getObjectId());
model.setUuid(domain.getUuid());
model.setDomainKey(domain.getDomainKey());
return model;
}
private CategoryNodeModel buildCategoryNodeModel(final Category category) {
final CategoryNodeModel model = new CategoryNodeModel();
model.setCategoryId(category.getObjectId());
model.setUuid(category.getUuid());
model.setUniqueId(category.getUniqueId());
model.setName(category.getName());
model.setPath(categoryManager.getCategoryPath(category));
model.setCategoryOrder(category.getCategoryOrder());
model.setEnabled(category.isEnabled());
model.setVisible(category.isVisible());
model.setAbstractCategory(category.isAbstractCategory());
return model;
}
private CategoryPathModel buildCategoryPathModel(final Category category) {
return buildCategoryPathModel(category, new CategoryPathModel());
}
private CategoryPathModel buildCategoryPathModel(
final Category category,
final CategoryPathModel categoryPathModel
) {
categoryPathModel.addCategoryAtBegin(buildCategoryNodeModel(category));
final Category parent = category.getParentCategory();
if (parent == null) {
final Optional<Domain> domain = domainRepository
.findByRootCategory(category);
if (domain.isPresent()) {
categoryPathModel.setDomain(buildDomainNodeModel(domain.get()));
}
return categoryPathModel;
} else {
return buildCategoryPathModel(parent, categoryPathModel);
}
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.CategoryRepository;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainRepository;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.ui.Message;
import org.libreccm.ui.MessageType;
import org.libreccm.ui.admin.AdminMessages;
import java.util.Arrays;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
* Controller processing the POST requests from the form for creating and
* editing categories.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/categorymanager/categories")
public class CategoryFormController {
@Inject
private AdminMessages adminMessages;
@Inject
private CategoryDetailsModel categoryDetailsModel;
@Inject
private CategoryManager categoryManager;
@Inject
private CategoryRepository categoryRepository;
@Inject
private DomainRepository domainRepository;
@Inject
private IdentifierParser identifierParser;
@FormParam("uniqueId")
private String uniqueId;
@FormParam("name")
private String name;
@FormParam("enabled")
private String enabled;
@FormParam("visible")
private String visible;
@FormParam("abstractCategory")
private String abstractCategory;
/**
* Create a new category.
*
* @param parentCategoryIdentifier Identifier of the parent category.
* @return Redirect to the details page of the parent category.
*/
@POST
@Path("/{parentCategoryIdentifier}/new")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String createCategory(
@PathParam("parentCategoryIdentifier") final String parentCategoryIdentifier
) {
final Identifier parentIdentifier = identifierParser.parseIdentifier(
parentCategoryIdentifier
);
final Optional<Category> parentResult;
switch (parentIdentifier.getType()) {
case ID:
parentResult = categoryRepository.findById(
Long.parseLong(
parentIdentifier.getIdentifier()
)
);
break;
default:
parentResult = categoryRepository.findByUuid(
parentIdentifier.getIdentifier()
);
break;
}
if (parentResult.isPresent()) {
final Category parentCategory = parentResult.get();
final Category category = new Category();
category.setUniqueId(uniqueId);
category.setName(name);
category.setEnabled(enabled != null);
category.setVisible(visible != null);
category.setAbstractCategory(abstractCategory != null);
categoryRepository.save(category);
categoryManager.addSubCategoryToCategory(category, parentCategory);
if (parentCategory.getParentCategory() == null) {
final Optional<Domain> categorySystem = domainRepository
.findByRootCategory(parentCategory);
if (categorySystem.isPresent()) {
return String.format(
"redirect:categorymanager/categorysystems/ID-%d/details",
categorySystem.get().getObjectId()
);
} else {
return String.format(
"redirect:categorymanager/categories/ID-%d",
parentCategory.getObjectId()
);
}
} else {
return String.format(
"redirect:categorymanager/categories/ID-%d",
parentCategory.getObjectId()
);
}
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(parentCategoryIdentifier)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
/**
* Updates a category with the data from the form.
*
* @param categoryIdentifierParam Identifier of the category to update.
* @return Redirect to the details page of the category.
*/
@POST
@Path("/{categoryIdentifier}/edit")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateCategory(
@PathParam("categoryIdentifier")
final String categoryIdentifierParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
categoryIdentifierParam
);
final Optional<Category> result;
switch (identifier.getType()) {
case ID:
result = categoryRepository.findById(
Long.parseLong(
identifier.getIdentifier()
)
);
break;
default:
result = categoryRepository.findByUuid(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
final Category category = result.get();
category.setUniqueId(uniqueId);
category.setName(name);
category.setEnabled(enabled != null);
category.setVisible(visible != null);
category.setAbstractCategory(abstractCategory != null);
categoryRepository.save(category);
return String.format(
"redirect:categorymanager/categories/ID-%d",
category.getObjectId()
);
} else {
categoryDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categories.not_found.message",
Arrays.asList(categoryIdentifierParam)
), MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/category-not-found.xhtml";
}
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import java.util.Objects;
/**
* A DTO with the of a category shown in the UI.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CategoryNodeModel implements Comparable<CategoryNodeModel> {
private long categoryId;
private String uuid;
private String uniqueId;
private String name;
private String path;
private boolean enabled;
private boolean visible;
private boolean abstractCategory;
private long categoryOrder;
public long getCategoryId() {
return categoryId;
}
protected void setCategoryId(final long categoryId) {
this.categoryId = categoryId;
}
public String getIdentifier() {
return String.format("ID-%d", categoryId);
}
public String getUuid() {
return uuid;
}
protected void setUuid(final String uuid) {
this.uuid = uuid;
}
public String getUniqueId() {
return uniqueId;
}
protected void setUniqueId(final String uniqueId) {
this.uniqueId = uniqueId;
}
public String getName() {
return name;
}
protected void setName(final String name) {
this.name = name;
}
public String getPath() {
return path;
}
protected void setPath(final String path) {
this.path = path;
}
public long getCategoryOrder() {
return categoryOrder;
}
protected void setCategoryOrder(final long categoryOrder) {
this.categoryOrder = categoryOrder;
}
public boolean isEnabled() {
return enabled;
}
protected void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public boolean isVisible() {
return visible;
}
protected void setVisible(final boolean visible) {
this.visible = visible;
}
public boolean isAbstractCategory() {
return abstractCategory;
}
protected void setAbstractCategory(final boolean abstractCategory) {
this.abstractCategory = abstractCategory;
}
@Override
public int compareTo(final CategoryNodeModel other) {
int result = Long.compare(
categoryOrder,
Objects.requireNonNull(other).getCategoryOrder()
);
if (result == 0) {
result = Objects.compare(
name,
Objects.requireNonNull(other).getName(),
String::compareTo
);
}
return result;
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Model for displaying the path of category in the UI.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CategoryPathModel {
private DomainNodeModel domain;
private List<CategoryNodeModel> categories;
public CategoryPathModel() {
categories = new ArrayList<>();
}
public DomainNodeModel getDomain() {
return domain;
}
protected void setDomain(final DomainNodeModel domain) {
this.domain = domain;
}
public List<CategoryNodeModel> getCategories() {
return Collections.unmodifiableList(categories);
}
protected void addCategory(final CategoryNodeModel category) {
categories.add(category);
}
protected void addCategoryAtBegin(final CategoryNodeModel category) {
categories.add(0, category);
}
protected void setCategories(final List<CategoryNodeModel> categories) {
this.categories = new ArrayList<>(categories);
}
}

View File

@ -0,0 +1,362 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainOwnership;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.ui.Message;
import org.libreccm.web.ApplicationRepository;
import org.libreccm.web.CcmApplication;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
* Model for the details of a category system (Domain)
*
* @see org.libreccm.categorization.Domain
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CategorySystemDetailsModel")
public class CategorySystemDetailsModel {
@Inject
private ApplicationRepository applicationRepository;
@Inject
private CategoryManager categoryManager;
@Inject
private GlobalizationHelper globalizationHelper;
private long categorySystemId;
private String uuid;
private String domainKey;
private String uri;
private String version;
private String released;
private Map<String, String> title;
private List<String> unusedTitleLocales;
private Map<String, String> description;
private List<String> unusedDescriptionLocales;
private List<CategorySystemOwnerRow> owners;
private List<CategorySystemOwnerOption> ownerOptions;
private String rootIdentifier;
private List<CategoryNodeModel> categories;
private final List<Message> messages;
private Set<String> invalidFields;
public CategorySystemDetailsModel() {
messages = new ArrayList<>();
invalidFields = new HashSet<>();
}
public long getCategorySystemId() {
return categorySystemId;
}
protected void setCategorySystemId(final long categorySystemId) {
this.categorySystemId = categorySystemId;
}
public String getIdentifier() {
return String.format("ID-%d", categorySystemId);
}
public String getRootIdentifier() {
return rootIdentifier;
}
protected void setRootIdentifier(final String rootIdentifier) {
this.rootIdentifier = rootIdentifier;
}
public String getUuid() {
return uuid;
}
protected void setUuid(final String uuid) {
this.uuid = uuid;
}
public String getDomainKey() {
return domainKey;
}
protected void setDomainKey(final String domainKey) {
this.domainKey = domainKey;
}
public String getUri() {
return uri;
}
protected void setUri(final String uri) {
this.uri = uri;
}
public String getVersion() {
return version;
}
protected void setVersion(final String version) {
this.version = version;
}
public String getReleased() {
return released;
}
protected void setReleased(final String released) {
this.released = released;
}
protected void setReleased(final LocalDate released) {
if (released == null) {
this.released = "";
} else {
this.released = DateTimeFormatter.ISO_DATE.format(released);
}
}
public Map<String, String> getTitle() {
return Collections.unmodifiableMap(title);
}
public List<String> getUnusedTitleLocales() {
return Collections.unmodifiableList(unusedTitleLocales);
}
public boolean hasUnusedTitleLocales() {
return !unusedTitleLocales.isEmpty();
}
public Map<String, String> getDescription() {
return Collections.unmodifiableMap(description);
}
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
public boolean hasUnusedDescriptionLocales() {
return !unusedDescriptionLocales.isEmpty();
}
public List<CategorySystemOwnerRow> getOwners() {
return Collections.unmodifiableList(owners);
}
public List<CategorySystemOwnerOption> getOwnerOptions() {
return Collections.unmodifiableList(ownerOptions);
}
public List<CategoryNodeModel> getCategories() {
return Collections.unmodifiableList(categories);
}
public boolean isNew() {
return categorySystemId == 0;
}
public List<Message> getMessages() {
return Collections.unmodifiableList(messages);
}
public void addMessage(final Message message) {
messages.add(message);
}
public Set<String> getInvalidFields() {
return Collections.unmodifiableSet(invalidFields);
}
protected void addInvalidField(final String invalidField) {
invalidFields.add(invalidField);
}
protected void setInvalidFields(final Set<String> invalidFields) {
this.invalidFields = new HashSet<>(invalidFields);
}
/**
* Sets the properties of this model using the provided {@link Domain}.
* @param domain The domain to display.
*/
@Transactional(Transactional.TxType.REQUIRED)
protected void setCategorySystem(final Domain domain) {
Objects.requireNonNull(domain);
categorySystemId = domain.getObjectId();
uuid = domain.getUuid();
domainKey = domain.getDomainKey();
uri = domain.getUri();
version = domain.getVersion();
if (domain.getReleased() == null) {
released = "";
} else {
released = DateTimeFormatter.ISO_DATE
.withZone(ZoneOffset.systemDefault())
.format(domain.getReleased());
}
final List<Locale> availableLocales = globalizationHelper
.getAvailableLocales();
title = domain
.getTitle()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
);
final Set<Locale> titleLocales = domain
.getTitle()
.getAvailableLocales();
unusedTitleLocales = availableLocales
.stream()
.filter(locale -> !titleLocales.contains(locale))
.map(Locale::toString)
.sorted()
.collect(Collectors.toList());
description = domain
.getDescription()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
);
final Set<Locale> descriptionLocales = domain
.getDescription()
.getAvailableLocales();
unusedDescriptionLocales = availableLocales
.stream()
.filter(locale -> !descriptionLocales.contains(locale))
.map(Locale::toString)
.sorted()
.collect(Collectors.toList());
owners = domain
.getOwners()
.stream()
.map(this::buildOwnerRow)
.sorted()
.collect(Collectors.toList());
final List<CcmApplication> ownerApplications = domain
.getOwners()
.stream()
.map(DomainOwnership::getOwner)
.collect(Collectors.toList());
ownerOptions = applicationRepository
.findAll()
.stream()
.filter(application -> !ownerApplications.contains(application))
.map(CategorySystemOwnerOption::new)
.sorted()
.collect(Collectors.toList());
rootIdentifier = String.format("UUID-%s", domain.getRoot().getUuid());
categories = domain
.getRoot()
.getSubCategories()
.stream()
.map(this::buildCategoryTableRow)
.sorted()
.collect(Collectors.toList());
}
private CategorySystemOwnerRow buildOwnerRow(
final DomainOwnership ownership
) {
final CategorySystemOwnerRow ownerRow = new CategorySystemOwnerRow();
ownerRow.setOwnershipId(ownership.getOwnershipId());
ownerRow.setUuid(ownership.getOwner().getUuid());
ownerRow.setContext(ownership.getContext());
ownerRow.setOwnerOrder(ownership.getOwnerOrder());
if (ownership.getOwner().getDisplayName() == null) {
ownerRow.setOwnerAppName(ownership.getOwner().getApplicationType());
} else {
ownerRow.setOwnerAppName(ownership.getOwner().getDisplayName());
}
return ownerRow;
}
private CategoryNodeModel buildCategoryTableRow(final Category category) {
final CategoryNodeModel row = new CategoryNodeModel();
row.setCategoryId(category.getObjectId());
row.setUuid(category.getUuid());
row.setUniqueId(category.getUniqueId());
row.setName(category.getName());
row.setPath(categoryManager.getCategoryPath(category));
row.setEnabled(category.isEnabled());
row.setVisible(category.isVisible());
row.setAbstractCategory(category.isAbstractCategory());
row.setCategoryOrder(category.getCategoryOrder());
return row;
}
}

View File

@ -0,0 +1,231 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.apache.commons.validator.routines.UrlValidator;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.categorization.Domain;
import org.libreccm.categorization.DomainManager;
import org.libreccm.categorization.DomainRepository;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.ui.Message;
import org.libreccm.ui.MessageType;
import org.libreccm.ui.admin.AdminMessages;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
* Controller for processing the {@code POST} requests from the form for
* creating and editing category systems.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Controller
@Path("/categorymanager/categorysystems")
@RequestScoped
public class CategorySystemFormController {
@Inject
private AdminMessages adminMessages;
@Inject
private CategorySystemDetailsModel categorySystemDetailsModel;
@Inject
private DomainManager domainManager;
@Inject
private DomainRepository domainRepository;
@Inject
private IdentifierParser identifierParser;
@FormParam("domainKey")
private String domainKey;
@FormParam("uri")
private String uri;
@FormParam("version")
private String version;
@FormParam("released")
private String released;
/**
* Creates a new category system (domain).
*
* @return Redirect to the list of category systems.
*/
@POST
@Path("/new")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String createCategorySystem() {
if (!isValidUri()) {
categorySystemDetailsModel.setDomainKey(domainKey);
categorySystemDetailsModel.setUri(uri);
categorySystemDetailsModel.setVersion(version);
categorySystemDetailsModel.setReleased(released);
categorySystemDetailsModel.addMessage(
new Message(
adminMessages.get("categorysystems.form.errors.uri_invalid"),
MessageType.DANGER)
);
categorySystemDetailsModel.addInvalidField("uri");
return "org/libreccm/ui/admin/categories/categorysystem-form.xhtml";
}
final Domain domain = domainManager.createDomain(domainKey, domainKey);
domain.setUri(uri);
domain.setVersion(version);
if (released == null || released.isEmpty()) {
domain.setReleased(null);
} else {
domain.setReleased(convertReleased());
}
domainRepository.save(domain);
return "redirect:/categorymanager/categorysystems";
}
/**
* Update a category with the data from the form.
*
* @param identifierParam Identifier of the category system to update.
*
* @return Redirect to the details page of the category system.
*/
@POST
@Path("{categorySystemIdentifier}/edit")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateCategorySystem(
@PathParam("categorySystemIdentifier")
final String identifierParam
) {
final Identifier identifier = identifierParser.parseIdentifier(
identifierParam
);
final Optional<Domain> result;
switch (identifier.getType()) {
case ID:
result = domainRepository.findById(
Long.parseLong(identifier.getIdentifier())
);
break;
case UUID:
result = domainRepository.findByUuid(identifier.getIdentifier());
break;
default:
result = domainRepository.findByDomainKey(
identifier.getIdentifier()
);
break;
}
if (result.isPresent()) {
if (!isValidUri()) {
categorySystemDetailsModel.setDomainKey(domainKey);
categorySystemDetailsModel.setUri(uri);
categorySystemDetailsModel.setVersion(version);
categorySystemDetailsModel.setReleased(released);
categorySystemDetailsModel.addMessage(
new Message(
adminMessages.get(
"categorysystems.form.errors.uri_invalid"),
MessageType.DANGER)
);
categorySystemDetailsModel.addInvalidField("uri");
return "org/libreccm/ui/admin/categories/categorysystem-form.xhtml";
}
final Domain domain = result.get();
domain.setDomainKey(domainKey);
domain.setUri(uri);
domain.setVersion(version);
if (released == null || released.isEmpty()) {
domain.setReleased(null);
} else {
domain.setReleased(convertReleased());
}
domainRepository.save(domain);
return String.format(
"redirect:/categorymanager/categorysystems/ID-%d/details",
domain.getObjectId()
);
} else {
categorySystemDetailsModel.addMessage(
new Message(
adminMessages.getMessage(
"categorysystems.not_found.message",
Arrays.asList(identifierParam)
),
MessageType.WARNING
)
);
return "org/libreccm/ui/admin/categories/categorysystem-not-found.xhtml";
}
}
/**
* Helper method for converting the {@link #released} date to an ISO 8601
* formatted string.
*
* @return The released date in ISO 8601 format.
*/
private LocalDate convertReleased() {
return LocalDate.parse(
released,
DateTimeFormatter.ISO_DATE.withZone(ZoneOffset.systemDefault())
);
}
/**
* Helper method for validating a URI.
*
* @return {@code true} if the URI is valid, {@code false} otherwise.
*/
private boolean isValidUri() {
final UrlValidator urlValidator = new UrlValidator();
return urlValidator.isValid(uri);
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import org.libreccm.web.CcmApplication;
import java.util.Objects;
/**
* DTO for the options for selecting the owner applications of a category system.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CategorySystemOwnerOption
implements Comparable<CategorySystemOwnerOption> {
private final long applicationId;
private final String applicationUuid;
private final String applicationName;
public CategorySystemOwnerOption(final CcmApplication application) {
applicationId = application.getObjectId();
applicationUuid = application.getUuid();
if (application.getDisplayName() == null) {
applicationName = application.getApplicationType();
} else {
applicationName = application.getDisplayName();
}
}
public long getApplicationId() {
return applicationId;
}
public String getApplicationUuid() {
return applicationUuid;
}
public String getApplicationName() {
return applicationName;
}
@Override
public int compareTo(final CategorySystemOwnerOption other) {
return Objects.compare(
applicationName,
Objects.requireNonNull(other).getApplicationName(),
String::compareTo
);
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
/**
* Data for a row in the table of owner applications of a category system.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CategorySystemOwnerRow
implements Comparable<CategorySystemOwnerRow>{
private long ownershipId;
private String uuid;
private String ownerAppName;
private String context;
private long ownerOrder;
public long getOwnershipId() {
return ownershipId;
}
void setOwnershipId(final long ownershipId) {
this.ownershipId = ownershipId;
}
public String getUuid() {
return uuid;
}
void setUuid(final String uuid) {
this.uuid = uuid;
}
public String getOwnerAppName() {
return ownerAppName;
}
void setOwnerAppName(final String ownerAppName) {
this.ownerAppName = ownerAppName;
}
public String getContext() {
return context;
}
void setContext(final String context) {
this.context = context;
}
public long getOwnerOrder() {
return ownerOrder;
}
void setOwnerOrder(final long ownerOrder) {
this.ownerOrder = ownerOrder;
}
@Override
public int compareTo(final CategorySystemOwnerRow other) {
return Long.compare(ownerOrder, other.getOwnerOrder());
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2020 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.ui.admin.categories;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Data for a row in the table of category systems.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class CategorySystemTableRow implements
Comparable<CategorySystemTableRow> {
private long domainId;
private String domainKey;
private String uri;
private Map<String, String> title;
private String version;
private String released;
public long getDomainId() {
return domainId;
}
void setDomainId(final long domainId) {
this.domainId = domainId;
}
public String getIdentifier() {
return String.format("ID-%d", domainId);
}
public String getDomainKey() {
return domainKey;
}
void setDomainKey(final String domainKey) {
this.domainKey = domainKey;
}
public String getUri() {
return uri;
}
void setUri(final String uri) {
this.uri = uri;
}
public Map<String, String> getTitle() {
return Collections.unmodifiableMap(title);
}
void setTitle(final Map<String, String> title) {
this.title = new HashMap<>(title);
}
public String getVersion() {
return version;
}
void setVersion(final String version) {
this.version = version;
}
public String getReleased() {
return released;
}
void setReleased(final String released) {
this.released = released;
}
@Override
public int compareTo(final CategorySystemTableRow other) {
int result;
result = Objects.compare(
domainKey, other.getDomainKey(), String::compareTo
);
if (result == 0) {
result = Objects.compare(uri, uri, String::compareTo);
}
if (result == 0) {
result = Objects.compare(
domainId, other.getDomainId(), Long::compare
);
}
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More