- Updated dependency for Apache Shiro to 1.2.5 (bugfix release of Shiro)
- More work on the application UI
- Description of an application type is now localised (using the same approach as for the configuration classes)


git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4120 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-05-27 18:00:44 +00:00
parent 10ec4575d2
commit 7bc7551413
21 changed files with 342 additions and 53 deletions

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.admin.applications;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.Table;
import com.arsdigita.bebop.table.TableColumn;
import com.arsdigita.bebop.table.TableColumnModel;
import com.arsdigita.globalization.GlobalizedMessage;
import static com.arsdigita.ui.admin.AdminUiConstants.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public abstract class AbstractApplicationTypeInstancesPane extends BoxPanel {
private final ParameterSingleSelectionModel<String> selectedAppType;
private final Form instanceForm;
public AbstractApplicationTypeInstancesPane(
final ParameterSingleSelectionModel<String> selectedAppType) {
super(BoxPanel.VERTICAL);
this.selectedAppType = selectedAppType;
instanceForm = createInstanceForm();
add(instanceForm);
}
protected abstract Form createInstanceForm();
@Override
public void register(final Page page) {
page.setVisibleDefault(instanceForm, false);
}
private class InstanceTable extends Table {
private static final int COL_TITLE = 0;
private static final int COL_URL = 1;
private static final int COL_EDIT = 2;
private final AbstractApplicationTypeInstancesPane instancesPane;
private final ParameterSingleSelectionModel<String> selectedAppType;
public InstanceTable(
final AbstractApplicationTypeInstancesPane instancesPane,
final ParameterSingleSelectionModel<String> selectedAppType) {
super();
this.instancesPane = instancesPane;
this.selectedAppType = selectedAppType;
final TableColumnModel columnModel = getColumnModel();
columnModel.add(new TableColumn(
COL_TITLE,
new Label(new GlobalizedMessage(
"ui.admin.applications.instances.col_title",
ADMIN_BUNDLE))));
columnModel.add(new TableColumn(
COL_URL,
new Label(new GlobalizedMessage(
"ui.admin.applications.instances.col_url",
ADMIN_BUNDLE))));
columnModel.add(new TableColumn(
COL_EDIT,
new Label(new GlobalizedMessage(
"ui.admin.applications.instances.col_edit",
ADMIN_BUNDLE))));
}
}
}

View File

@ -22,6 +22,7 @@ import com.arsdigita.bebop.ActionLink;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.PropertySheet;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.globalization.GlobalizedMessage;
@ -33,13 +34,19 @@ import static com.arsdigita.ui.admin.AdminUiConstants.*;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public abstract class AbstractApplicationTypePane extends BoxPanel {
private final ParameterSingleSelectionModel<String> selectedAppType;
private final SimpleContainer pane;
private final PropertySheet propertySheet;
public AbstractApplicationTypePane() {
public AbstractApplicationTypePane(
final ParameterSingleSelectionModel<String> selectedAppType) {
super(BoxPanel.VERTICAL);
this.selectedAppType = selectedAppType;
final BoxPanel links = new BoxPanel(BoxPanel.HORIZONTAL);
final ActionLink paneLink = new ActionLink(getPaneLabel());
paneLink.addActionListener(e -> {
@ -48,42 +55,43 @@ public abstract class AbstractApplicationTypePane extends BoxPanel {
});
links.add(paneLink);
final ActionLink propertySheetLink = new ActionLink(
new GlobalizedMessage(
"ui.admin.appliations.type_pane.info_sheet",
ADMIN_BUNDLE));
new GlobalizedMessage(
"ui.admin.appliations.type_pane.info_sheet",
ADMIN_BUNDLE));
propertySheetLink.addActionListener(e -> {
final PageState state = e.getPageState();
showPropertySheet(state);
});
links.add(propertySheetLink);
add(links);
pane = createPane();
add(pane);
propertySheet = new PropertySheet(
new ApplicationTypePropertySheetModelBuilder(selectedAppType));
}
protected abstract SimpleContainer createPane();
protected abstract GlobalizedMessage getPaneLabel();
@Override
public void register(final Page page) {
super.register(page);
page.setVisibleDefault(pane, true);
page.setVisibleDefault(propertySheet, false);
}
protected void showPane(final PageState state) {
pane.setVisible(state, true);
propertySheet.setVisible(state, false);
}
protected void showPropertySheet(final PageState state) {
pane.setVisible(state, false);
propertySheet.setVisible(state, false);
}
}

View File

@ -20,8 +20,16 @@ package com.arsdigita.ui.admin.applications;
import com.arsdigita.bebop.PropertySheetModel;
import com.arsdigita.globalization.GlobalizedMessage;
import com.fasterxml.jackson.databind.deser.CreatorProperty;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.web.ApplicationType;
import java.util.Arrays;
import java.util.Iterator;
import static com.arsdigita.ui.admin.AdminUiConstants.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -29,44 +37,87 @@ import org.libreccm.web.ApplicationType;
public class ApplicationTypePropertySheetModel implements PropertySheetModel {
private static enum AppTypeProperty {
TITLE,
NAME,
DESC,
APP_CLASS,
CREATOR,
SINGLETON,
SERVLET_CLASS,
SERVLET_PATH,
}
private final ApplicationType applicationType;
private final Iterator<AppTypeProperty> propertyIterator;
private AppTypeProperty currentProperty;
public ApplicationTypePropertySheetModel(
final ApplicationType applicationType) {
final ApplicationType applicationType) {
this.applicationType = applicationType;
propertyIterator = Arrays.asList(AppTypeProperty.values()).iterator();
}
@Override
public boolean nextRow() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
if (applicationType == null) {
return false;
}
if (propertyIterator.hasNext()) {
currentProperty = propertyIterator.next();
return true;
} else {
return false;
}
}
@Override
public String getLabel() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
return currentProperty.toString();
}
private GlobalizedMessage generateGlobalizedLabel(
final AppTypeProperty property) {
final String key = String.join(
"",
"ui.admin.applications.type.property_sheet.",
property.toString().toLowerCase());
return new GlobalizedMessage(key, ADMIN_BUNDLE);
}
@Override
public GlobalizedMessage getGlobalizedLabel() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
return generateGlobalizedLabel(currentProperty);
}
@Override
public String getValue() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
switch(currentProperty) {
case NAME:
return applicationType.name();
case DESC:
return getAppTypeDesc();
case APP_CLASS:
return applicationType.applicationClass().getName();
case CREATOR:
return applicationType.creator().getName();
case SINGLETON:
return Boolean.toString(applicationType.singleton());
case SERVLET_CLASS:
return applicationType.servlet().getName();
case SERVLET_PATH:
return applicationType.servletPath();
default:
return "";
}
}
private String getAppTypeDesc() {
final org.libreccm.web.ApplicationManager appManager = CdiUtil.createCdiUtil().findBean(
org.libreccm.web.ApplicationManager.class);
return appManager.getApplicationTypeDescription(applicationType);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.admin.applications;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.PropertySheet;
import com.arsdigita.bebop.PropertySheetModel;
import com.arsdigita.bebop.PropertySheetModelBuilder;
import com.arsdigita.util.LockableImpl;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.web.ApplicationType;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ApplicationTypePropertySheetModelBuilder
extends LockableImpl
implements PropertySheetModelBuilder {
private final ParameterSingleSelectionModel<String> selectedAppType;
public ApplicationTypePropertySheetModelBuilder(
final ParameterSingleSelectionModel<String> selectedAppType) {
this.selectedAppType = selectedAppType;
}
@Override
public PropertySheetModel makeModel(final PropertySheet sheet,
final PageState state) {
final String selectedAppTypeStr = selectedAppType.getSelectedKey(state);
final ApplicationType applicationType;
if (selectedAppTypeStr == null || selectedAppTypeStr.isEmpty()) {
applicationType = null;
} else {
final org.libreccm.web.ApplicationManager appManager = CdiUtil
.createCdiUtil().findBean(
org.libreccm.web.ApplicationManager.class);
applicationType = appManager.getApplicationTypes().get(
selectedAppTypeStr);
}
return new ApplicationTypePropertySheetModel(applicationType);
}
}

View File

@ -134,7 +134,7 @@ public class LegacyApplicationInfoPropertySheetModel implements PropertySheetMod
.localize();
}
case APP_DESC:
return applicationType.description();
return applicationType.descKey();
case SINGLETON_PATH:
final String path;
final ApplicationRepository appRepo = CdiUtil.createCdiUtil()

View File

@ -43,7 +43,7 @@ public class LegacyApplicationTypeTreeNode implements TreeNode {
name = applicationType.name();
objectType = applicationType.name();
singleton = applicationType.singleton();
description = applicationType.description();
description = applicationType.descKey();
}
public String getName() {

View File

@ -24,7 +24,6 @@ import com.arsdigita.bebop.PropertySheet;
import com.arsdigita.bebop.PropertySheetModel;
import com.arsdigita.bebop.PropertySheetModelBuilder;
import com.arsdigita.util.LockableImpl;
import org.apache.logging.log4j.util.Strings;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryRepository;
import org.libreccm.cdi.utils.CdiUtil;
@ -52,7 +51,7 @@ public class CategoryPropertySheetModelBuilder
final String categoryIdStr = selectedCategoryId.getSelectedKey(state);
final Category selectedCategory;
if (Strings.isBlank(categoryIdStr)) {
if (categoryIdStr == null || categoryIdStr.isEmpty()) {
selectedCategory = null;
} else {
final CategoryRepository categoryRepository = CdiUtil.

View File

@ -43,12 +43,12 @@ import org.libreccm.web.ApplicationType;
*/
@Module(applicationTypes = {
@ApplicationType(name = LoginConstants.LOGIN_APP_TYPE,
description = "Login Application",
descBundle = "com.arsdigita.ui.login.LoginResources",
singleton = true,
creator = LoginApplicationCreator.class,
servlet = LoginServlet.class),
@ApplicationType(name = AdminUiConstants.ADMIN_APP_TYPE,
description = "Site-wide admin application",
descBundle = "com.arsdigita.ui.admin.AdminResources",
singleton = true,
creator = AdminApplicationCreator.class,
servlet = AdminServlet.class)},

View File

@ -18,6 +18,10 @@
*/
package org.libreccm.web;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.modules.CcmModule;
import org.libreccm.modules.Module;
@ -25,6 +29,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import javax.annotation.PostConstruct;
@ -42,9 +48,15 @@ import javax.servlet.http.HttpServlet;
@ApplicationScoped
public class ApplicationManager {
private static final Logger LOGGER = LogManager.getLogger(
ApplicationManager.class);
@Inject
private EntityManager entityManager;
@Inject
private GlobalizationHelper globalizationHelper;
private Map<String, ApplicationType> applicationTypes = new HashMap<>();
@PostConstruct
@ -138,4 +150,27 @@ public class ApplicationManager {
}
}
public String getApplicationTypeDescription(
final ApplicationType applicationType) {
final String descBundle;
if (Strings.isBlank(applicationType.descBundle())) {
descBundle = String.join("", applicationType.name(), "Description");
} else {
descBundle = applicationType.descBundle();
}
final ResourceBundle bundle;
try {
bundle = ResourceBundle.getBundle(
descBundle, globalizationHelper.getNegotiatedLocale());
} catch (MissingResourceException ex) {
LOGGER.warn("Failed to find resource bundle '{}'.", ex);
return "";
}
return bundle.getString(applicationType.descKey());
}
}

View File

@ -18,8 +18,10 @@
*/
package org.libreccm.web;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@ -39,15 +41,29 @@ public @interface ApplicationType {
String name();
/**
* A description of the application type.
* (Optional) Fully qualified name of a resource bundle containing a
* localised descKey of the application type. If not provided the
* {@link ApplicationManager} will use the default value which is the name
* of the descKey provided by {@link #name()} concatenated with
* {@code Description}. For example for an application with the name
* {@code org.example.ExampleApp} the default descKey bundle is
* {@code org.example.ExampleAppDescription}.
*
* @return
*/
String description();
String descBundle() default "";
/**
* The (optional) key for the description of the application in its resource
* bundle. Defaults to {@code application_title}
*
* @return
*/
String descKey() default "application_title";
/**
* The application type class. Default is {@link CcmApplication}. Most
* application types will no need to extend these class and can leave the
* application types will no need to extend these class and can leave the
* default has it is.
*
* @return
@ -56,34 +72,38 @@ public @interface ApplicationType {
/**
* Is the application type a singleton application?
*
* @return
*
* @return
*/
boolean singleton() default false;
/**
* Path to the primary Servlet of the application type. If the servlet class
* is provided and is annotated with the {@link WebServlet} annotation the
* path can be determined from the annotation.
*
* @return
* path can be determined from the annotation.
*
* @return
*/
String servletPath() default "";
/**
* The primary servlet class of the application type.
*
* @return
*
* @return
*/
Class<? extends HttpServlet> servlet() default HttpServlet.class;
/**
* The implementation of the {@link ApplicationCreator} interface for the
* The implementation of the {@link ApplicationCreator} interface for the
* application type which is used to create the objects representing the
* application instances.
*
* @return
*
* @return
*/
Class<? extends ApplicationCreator> creator();
//Class<? extends AbstractApplicationTypePane> appTypePane default com.arsdigita.ui.admin.applications.DefaultApplicationTypePane.class;
}

View File

@ -526,3 +526,4 @@ ui.admin.categories.category.name.enabled=Enabled?
ui.admin.categories.category.name.visible=Visible?
ui.admin.categories.category.name.abstract=Abstract?
ui.admin.appliations.type_pane.info_sheet=About
application_title=Administration

View File

@ -529,3 +529,4 @@ ui.admin.categories.category.name.enabled=Aktiviert?
ui.admin.categories.category.name.visible=Sichtbar?
ui.admin.categories.category.name.abstract=Abstrakt?
ui.admin.appliations.type_pane.info_sheet=\u00dcber
application_title=Administration

View File

@ -522,3 +522,4 @@ ui.admin.categories.category.name.enabled=Enabled?
ui.admin.categories.category.name.visible=Visible?
ui.admin.categories.category.name.abstract=Abstract?
ui.admin.appliations.type_pane.info_sheet=About
application_title=Administration

View File

@ -513,3 +513,4 @@ ui.admin.categories.category.name.enabled=Enabled?
ui.admin.categories.category.name.visible=Visible?
ui.admin.categories.category.name.abstract=Abstract?
ui.admin.appliations.type_pane.info_sheet=About
application_title=Administration

View File

@ -116,3 +116,4 @@ login.form.new_user.error.familyname.too_long=The family name can't be longer th
login.form.new_user.error.email.is_blank=The Email address can't be empty.
login.form.new_user.error.email.too_long=The Email address can't be longer than 256 characters.
login.form.new_user.error.password.is_blank=The password can't be blank.
application_title=Login

View File

@ -116,3 +116,4 @@ login.form.new_user.error.familyname.too_long=The family name can't be longer th
login.form.new_user.error.email.is_blank=The Email address can't be empty.
login.form.new_user.error.email.too_long=The Email address can't be longer than 256 characters.
login.form.new_user.error.password.is_blank=The password can't be blank.
application_title=Login

View File

@ -116,3 +116,4 @@ login.form.new_user.error.familyname.too_long=Der Familienname darf nicht l\u00e
login.form.new_user.error.email.is_blank=Die E-Mail-Adresse darf nicht leer sein.
login.form.new_user.error.email.too_long=Die E-Mail-Adresse darf nicht l\u00e4nger als 256 Zeichen sein.
login.form.new_user.error.password.is_blank=Das Passwort darf nicht leer sein.
application_title=Anmeldung

View File

@ -116,3 +116,4 @@ login.form.new_user.error.familyname.too_long=The family name can't be longer th
login.form.new_user.error.email.is_blank=The Email address can't be empty.
login.form.new_user.error.email.too_long=The Email address can't be longer than 256 characters.
login.form.new_user.error.password.is_blank=The password can't be blank.
application_title=Login

View File

@ -55,7 +55,7 @@ ccm_core.categories:
enabled: true
visible: true
abstract_category: false
category_order: 1
category_order: 2
ccm_core.category_domains:
- object_id: -1000

View File

@ -64,7 +64,7 @@ ccm_core.categories:
enabled: true
visible: true
abstract_category: false
category_order: 1
category_order: 2
- object_id: 2
unique_id: example
name: example

View File

@ -272,12 +272,12 @@
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
<version>1.2.5</version>
</dependency>