diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/ComponentsTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/ComponentsTable.java
new file mode 100644
index 000000000..6eacc8dca
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/ComponentsTable.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.Component;
+import com.arsdigita.bebop.ControlLink;
+import com.arsdigita.bebop.FormProcessException;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.Table;
+import com.arsdigita.bebop.event.TableActionEvent;
+import com.arsdigita.bebop.event.TableActionListener;
+import com.arsdigita.bebop.table.TableCellRenderer;
+import com.arsdigita.bebop.table.TableColumn;
+import com.arsdigita.bebop.table.TableColumnModel;
+import com.arsdigita.bebop.table.TableModel;
+import com.arsdigita.bebop.table.TableModelBuilder;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.ui.admin.AdminUiConstants;
+import com.arsdigita.util.LockableImpl;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.pagemodel.ComponentModel;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class ComponentsTable extends Table {
+
+ protected static final int COL_COMPONENT_KEY = 0;
+ protected static final int COL_COMPONENT_TYPE = 1;
+ protected static final int COL_EDIT = 2;
+ protected static final int COL_DELETE = 3;
+
+ public ComponentsTable(
+ final PageModelTab pageModelTab,
+ final ParameterSingleSelectionModel selectedModelId,
+ final ParameterSingleSelectionModel selectedComponentId) {
+
+ super();
+ super.setIdAttr("pageModelComponentModelsTable");
+
+ super.setEmptyView(new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.none",
+ AdminUiConstants.ADMIN_BUNDLE)));
+
+ final TableColumnModel columnModel = getColumnModel();
+ columnModel.add(new TableColumn(
+ COL_COMPONENT_KEY,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.cols.key.heading",
+ AdminUiConstants.ADMIN_BUNDLE))));
+ columnModel.add(new TableColumn(
+ COL_COMPONENT_TYPE,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.cols.type.heading",
+ AdminUiConstants.ADMIN_BUNDLE))));
+ columnModel.add(new TableColumn(
+ COL_EDIT,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.cols.edit.heading",
+ AdminUiConstants.ADMIN_BUNDLE))));
+ columnModel.add(new TableColumn(
+ COL_DELETE,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.cols.delete.heading",
+ AdminUiConstants.ADMIN_BUNDLE))));
+
+ columnModel.get(COL_EDIT).setCellRenderer(
+ new TableCellRenderer() {
+
+ @Override
+ public Component getComponent(final Table table,
+ final PageState state,
+ final Object value,
+ final boolean isSelected,
+ final Object key,
+ final int row,
+ final int column) {
+
+ final ControlLink link = new ControlLink((Component) value);
+ return link;
+ }
+
+ });
+
+ columnModel.get(COL_DELETE).setCellRenderer(
+ new TableCellRenderer() {
+
+ @Override
+ public Component getComponent(final Table table,
+ final PageState state,
+ final Object value,
+ final boolean isSelected,
+ final Object key,
+ final int row,
+ final int column) {
+
+ final ControlLink link = new ControlLink((Component) value);
+ link.setConfirmation(new GlobalizedMessage(
+ "ui.admin.pagemodels.componentmodels.cols.delete.confirmation",
+ AdminUiConstants.ADMIN_BUNDLE));
+ return link;
+ }
+
+ });
+
+ super.addTableActionListener(new TableActionListener() {
+
+ @Override
+ public void cellSelected(final TableActionEvent event)
+ throws FormProcessException {
+
+ final PageState state = event.getPageState();
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+ final String key = (String) event.getRowKey();
+
+ switch (event.getColumn()) {
+ case COL_EDIT:
+ selectedComponentId.setSelectedKey(state, key);
+ pageModelTab.showComponentForm(state);
+ break;
+ case COL_DELETE:
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelsController controller = cdiUtil
+ .findBean(PageModelsController.class);
+ controller.removeComponentModel(
+ Long.parseLong(selectedModelIdStr),
+ Long.parseLong(key));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid value for column");
+ }
+ }
+
+ @Override
+ public void headSelected(final TableActionEvent event) {
+ //Nothing
+ }
+
+ });
+
+ super.setModelBuilder(new PageModelComponentsTableModelBuilder(
+ selectedModelId));
+ }
+
+ private class PageModelComponentsTableModelBuilder
+ extends LockableImpl
+ implements TableModelBuilder {
+
+ private final ParameterSingleSelectionModel selectedModelId;
+
+ public PageModelComponentsTableModelBuilder(
+ final ParameterSingleSelectionModel selectedModelId) {
+
+ this.selectedModelId = selectedModelId;
+ }
+
+ @Override
+ public TableModel makeModel(final Table table,
+ final PageState state) {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelsController controller = cdiUtil
+ .findBean(PageModelsController.class);
+
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+
+ final List components = controller
+ .retrieveComponents(Long.parseLong(selectedModelIdStr));
+
+ return new PageModelComponentsTableModel(components);
+ }
+
+ }
+
+ private class PageModelComponentsTableModel implements TableModel {
+
+ private final Iterator iterator;
+ private ComponentModel currentComponent;
+
+ public PageModelComponentsTableModel(
+ final List components) {
+
+ iterator = components.iterator();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public boolean nextRow() {
+
+ if (iterator.hasNext()) {
+ currentComponent = iterator.next();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object getElementAt(final int columnIndex) {
+
+ switch (columnIndex) {
+ case ComponentsTable.COL_COMPONENT_KEY:
+ return currentComponent.getKey();
+ case ComponentsTable.COL_COMPONENT_TYPE:
+ return currentComponent.getClass().getName();
+ case ComponentsTable.COL_EDIT:
+ return new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.components.edit",
+ AdminUiConstants.ADMIN_BUNDLE));
+ case ComponentsTable.COL_DELETE:
+ return new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.components.delete",
+ AdminUiConstants.ADMIN_BUNDLE));
+ default:
+ throw new IllegalArgumentException(
+ "Not a valid column index");
+ }
+ }
+
+ @Override
+ public Object getKeyAt(final int columnIndex) {
+
+ return currentComponent.getComponentModelId();
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelDetails.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelDetails.java
new file mode 100644
index 000000000..44359b592
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelDetails.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.ActionLink;
+import com.arsdigita.bebop.BoxPanel;
+import com.arsdigita.bebop.Form;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.PropertySheet;
+import com.arsdigita.bebop.form.SingleSelect;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.core.UnexpectedErrorException;
+import org.libreccm.modules.ModuleManager;
+import org.libreccm.pagemodel.ComponentModels;
+import org.libreccm.pagemodel.PageModel;
+import org.libreccm.pagemodel.PageModelRepository;
+
+import java.util.TooManyListenersException;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelDetails extends BoxPanel {
+
+ public PageModelDetails(
+ final PageModelTab pageModelTab,
+ final ParameterSingleSelectionModel selectedModelId,
+ final ParameterSingleSelectionModel selectedComponentId) {
+
+ super(BoxPanel.VERTICAL);
+
+ final ActionLink backLink = new ActionLink(new GlobalizedMessage(
+ "ui.admin.pagemodels.details.back",
+ AdminUiConstants.ADMIN_BUNDLE));
+ backLink.setClassAttr("back-link");
+ backLink.addActionListener(event -> {
+ selectedModelId.clearSelection(event.getPageState());
+ pageModelTab.showPageModelsTable(event.getPageState());
+ });
+ super.add(backLink);
+
+ final Label heading = new Label();
+ heading.setClassAttr("heading");
+ heading.addPrintListener(event -> {
+ final PageState state = event.getPageState();
+ final Label target = (Label) event.getTarget();
+ final PageModelRepository pageModelRepo = CdiUtil
+ .createCdiUtil()
+ .findBean(PageModelRepository.class);
+ final PageModel pageModel = pageModelRepo
+ .findById(Long.parseLong(selectedModelId.getSelectedKey(state)))
+ .get();
+ target.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.details.heading",
+ AdminUiConstants.ADMIN_BUNDLE,
+ new String[]{pageModel.getName()}));
+ });
+ super.add(heading);
+
+ final PropertySheet propertySheet = new PropertySheet(
+ new PageModelPropertySheetModelBuilder(selectedModelId));
+ super.add(propertySheet);
+
+ final ActionLink editProperties = new ActionLink(new GlobalizedMessage(
+ "ui.admin.pagemodels.details.edit_properties",
+ AdminUiConstants.ADMIN_BUNDLE));
+ editProperties.addActionListener(event -> {
+ pageModelTab.showPageModelForm(event.getPageState());
+ });
+ super.add(editProperties);
+
+ final ActionLink addComponent = new ActionLink(new GlobalizedMessage(
+ "ui.admin.pagemodels.details.add_component",
+ AdminUiConstants.ADMIN_BUNDLE));
+ addComponent.addActionListener(event -> {
+ //pageModelTab.showNewComponentForm(state, componentModelClass);
+ });
+ super.add(addComponent);
+
+ final ComponentsTable componentsTable
+ = new ComponentsTable(
+ pageModelTab, selectedModelId, selectedComponentId);
+ super.add(componentsTable);
+ }
+
+ private class AddComponentForm extends Form {
+
+ private final SingleSelect selectType;
+
+ public AddComponentForm() {
+ super("pagemodel_add_component_form");
+
+ selectType = new SingleSelect("select_component_type");
+ try {
+ selectType.addPrintListener(event -> {
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final ComponentModels componentModels = cdiUtil
+ .findBean(ComponentModels.class);
+
+
+
+ });
+ } catch (TooManyListenersException ex) {
+ throw new UnexpectedErrorException(ex);
+ }
+
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelForm.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelForm.java
new file mode 100644
index 000000000..1cd36d3c8
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelForm.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.Form;
+import com.arsdigita.bebop.FormData;
+import com.arsdigita.bebop.FormProcessException;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.SaveCancelSection;
+import com.arsdigita.bebop.Text;
+import com.arsdigita.bebop.event.FormInitListener;
+import com.arsdigita.bebop.event.FormProcessListener;
+import com.arsdigita.bebop.event.FormSectionEvent;
+import com.arsdigita.bebop.event.FormValidationListener;
+import com.arsdigita.bebop.form.Option;
+import com.arsdigita.bebop.form.SingleSelect;
+import com.arsdigita.bebop.form.TextArea;
+import com.arsdigita.bebop.form.TextField;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.kernel.KernelConfig;
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.configuration.ConfigurationManager;
+import org.libreccm.core.UnexpectedErrorException;
+import org.libreccm.pagemodel.PageModel;
+import org.libreccm.pagemodel.PageModelManager;
+import org.libreccm.pagemodel.PageModelRepository;
+import org.libreccm.web.ApplicationRepository;
+import org.libreccm.web.CcmApplication;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.TooManyListenersException;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelForm extends Form {
+
+ private static final String MODEL_APPLICATION = "application";
+ private static final String MODEL_NAME = "model_name";
+ private static final String MODEL_TITLE = "model_title";
+ private static final String MODEL_DESC = "model_desc";
+
+ private final PageModelTab pageModelTab;
+ private final ParameterSingleSelectionModel selectedModelId;
+
+ private final TextField nameField;
+ private final TextField titleField;
+ private final TextArea descArea;
+ private final SingleSelect applicationSelect;
+ private final SaveCancelSection saveCancelSection;
+
+ public PageModelForm(
+ final PageModelTab pageModelTab,
+ final ParameterSingleSelectionModel selectedModelId) {
+
+ super("pagemodelsform");
+
+ this.pageModelTab = pageModelTab;
+ this.selectedModelId = selectedModelId;
+
+ final Label heading = new Label(event -> {
+
+ final PageState state = event.getPageState();
+ final Label target = (Label) event.getTarget();
+
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+ if (selectedModelIdStr == null || selectedModelIdStr.isEmpty()) {
+ target.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.create_new",
+ AdminUiConstants.ADMIN_BUNDLE));
+ } else {
+ target.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.edit",
+ AdminUiConstants.ADMIN_BUNDLE));
+ }
+ });
+ heading.setClassAttr("heading");
+ super.add(heading);
+
+ nameField = new TextField(MODEL_NAME);
+ nameField.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.name",
+ AdminUiConstants.ADMIN_BUNDLE));
+ super.add(nameField);
+
+ titleField = new TextField(MODEL_TITLE);
+ titleField.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.title",
+ AdminUiConstants.ADMIN_BUNDLE));
+ super.add(titleField);
+
+ descArea = new TextArea(MODEL_DESC);
+ descArea.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.desc",
+ AdminUiConstants.ADMIN_BUNDLE));
+ super.add(descArea);
+
+ applicationSelect = new SingleSelect(MODEL_APPLICATION);
+ applicationSelect.setLabel(new GlobalizedMessage(
+ "ui.admin.pagemodels.application",
+ AdminUiConstants.ADMIN_BUNDLE));
+ super.add(applicationSelect);
+ try {
+ applicationSelect.addPrintListener(event -> {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final ApplicationRepository applicationRepo = cdiUtil
+ .findBean(ApplicationRepository.class);
+
+ final SingleSelect target = (SingleSelect) event.getTarget();
+ target.clearOptions();
+
+ final List applications = applicationRepo
+ .findAll();
+ applications.sort((app1, app2) -> {
+ return app1.getPrimaryUrl().compareTo(app2.getPrimaryUrl());
+ });
+ for (final CcmApplication app : applications) {
+ target.addOption(new Option(app.getPrimaryUrl(),
+ new Text(app.getPrimaryUrl())));
+ }
+ });
+
+ } catch (TooManyListenersException ex) {
+ throw new UnexpectedErrorException(ex);
+ }
+ super.add(applicationSelect);
+
+ saveCancelSection = new SaveCancelSection();
+ super.add(saveCancelSection);
+
+ super.addValidationListener(new ValidationListener());
+ super.addInitListener(new InitListener());
+ super.addProcessListener(new ProcessListener());
+ }
+
+ private class ValidationListener implements FormValidationListener {
+
+ @Override
+ public void validate(final FormSectionEvent event)
+ throws FormProcessException {
+
+ final PageState state = event.getPageState();
+
+ if (saveCancelSection.getSaveButton().isSelected(state)) {
+
+ final FormData data = event.getFormData();
+ final String nameValue = data.getString(MODEL_NAME);
+ final String titleValue = data.getString(MODEL_TITLE);
+ final String appValue = data.getString(MODEL_APPLICATION);
+
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+ final boolean modelEditedOrNew;
+
+ if (selectedModelIdStr == null || selectedModelIdStr.isEmpty()) {
+ modelEditedOrNew = true;
+ } else {
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final ConfigurationManager confManager = cdiUtil
+ .findBean(ConfigurationManager.class);
+ final PageModelRepository pageModelRepo = cdiUtil
+ .findBean(PageModelRepository.class);
+ final PageModel pageModel = pageModelRepo
+ .findById(Long.parseLong(selectedModelIdStr))
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %s in the database.",
+ selectedModelIdStr)));
+
+ final KernelConfig kernelConfig = confManager
+ .findConfiguration(KernelConfig.class);
+
+ final boolean nameEdited = !pageModel
+ .getName()
+ .equals(nameValue);
+ final boolean titleEdited = !pageModel
+ .getTitle()
+ .getValue(kernelConfig.getDefaultLocale())
+ .equals(titleValue);
+ final boolean appEdited = !pageModel
+ .getApplication()
+ .getPrimaryUrl()
+ .equals(appValue);
+
+ modelEditedOrNew = nameEdited || titleEdited || appEdited;
+ }
+
+ if (modelEditedOrNew) {
+ if (nameValue == null
+ || nameValue.isEmpty()
+ || nameValue.matches("\\s*")) {
+
+ data.addError(MODEL_NAME,
+ new GlobalizedMessage(
+ "ui.admin.pagemodels.name.error.empty",
+ AdminUiConstants.ADMIN_BUNDLE));
+ }
+
+ if (titleValue == null
+ || titleValue.isEmpty()
+ || titleValue.matches("\\s*")) {
+
+ data.addError(MODEL_TITLE,
+ new GlobalizedMessage(
+ "ui.admin.pagemodels.title.error.empty",
+ AdminUiConstants.ADMIN_BUNDLE));
+ }
+
+ if (appValue == null
+ || appValue.isEmpty()
+ || appValue.matches("\\s*")) {
+
+ data.addError(MODEL_TITLE,
+ new GlobalizedMessage(
+ "ui.admin.pagemodels.application.error.empty",
+ AdminUiConstants.ADMIN_BUNDLE));
+ } else {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final ApplicationRepository appRepo = cdiUtil
+ .findBean(ApplicationRepository.class);
+
+ final Optional application = appRepo
+ .retrieveApplicationForPath(appValue);
+
+ if (!application.isPresent()) {
+ data.addError(MODEL_TITLE,
+ new GlobalizedMessage(
+ "ui.admin.pagemodels.application.error.invalid",
+ AdminUiConstants.ADMIN_BUNDLE));
+ }
+
+ }
+ }
+
+ }
+ }
+
+ }
+
+ private class InitListener implements FormInitListener {
+
+ @Override
+ public void init(final FormSectionEvent event)
+ throws FormProcessException {
+
+ final PageState state = event.getPageState();
+
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+
+ if (selectedModelIdStr != null && !selectedModelIdStr.isEmpty()) {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelRepository pageModelRepo = cdiUtil
+ .findBean(PageModelRepository.class);
+ final ConfigurationManager confManager = cdiUtil
+ .findBean(ConfigurationManager.class);
+ final KernelConfig kernelConfig = confManager
+ .findConfiguration(KernelConfig.class);
+ final Locale defaultLocale = kernelConfig.getDefaultLocale();
+
+ final PageModel pageModel = pageModelRepo
+ .findById(Long.parseLong(selectedModelIdStr))
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %s in the database.",
+ selectedModelIdStr)));
+
+ nameField.setValue(state, pageModel.getName());
+ titleField.setValue(state,
+ pageModel.getTitle().getValue(defaultLocale));
+ descArea
+ .setValue(state,
+ pageModel.getDescription().getValue(defaultLocale));
+ applicationSelect
+ .setValue(state,
+ pageModel.getApplication().getPrimaryUrl());
+ }
+ }
+
+ }
+
+ private class ProcessListener implements FormProcessListener {
+
+ @Override
+ public void process(final FormSectionEvent event)
+ throws FormProcessException {
+
+ final PageState state = event.getPageState();
+
+ if (saveCancelSection.getSaveButton().isSelected(state)) {
+
+ final FormData data = event.getFormData();
+
+ final String nameValue = data.getString(MODEL_NAME);
+ final String titleValue = data.getString(MODEL_TITLE);
+ final String descValue = data.getString(MODEL_DESC);
+ final String appValue = data.getString(MODEL_APPLICATION);
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelRepository pageModelRepo = cdiUtil
+ .findBean(PageModelRepository.class);
+ final ConfigurationManager confManager = cdiUtil
+ .findBean(ConfigurationManager.class);
+ final ApplicationRepository appRepo = cdiUtil
+ .findBean(ApplicationRepository.class);
+ final KernelConfig kernelConfig = confManager
+ .findConfiguration(KernelConfig.class);
+ final Locale defaultLocale = kernelConfig.getDefaultLocale();
+
+ final String selectedModelIdStr = selectedModelId
+ .getSelectedKey(state);
+
+ final PageModel pageModel;
+ if (selectedModelIdStr == null || selectedModelIdStr.isEmpty()) {
+ pageModel = new PageModel();
+ } else {
+ pageModel = pageModelRepo
+ .findById(Long.parseLong(selectedModelIdStr))
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %s in the database.",
+ selectedModelIdStr)));
+ }
+
+ pageModel.setName(nameValue);
+
+ pageModel.getTitle().addValue(defaultLocale, titleValue);
+ pageModel.getDescription().addValue(defaultLocale, descValue);
+
+ final CcmApplication application = appRepo
+ .retrieveApplicationForPath(appValue)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No CcmApplication with primary URL \"%s\" in the "
+ + "database.",
+ appValue)));
+
+ pageModel.setApplication(application);
+
+ pageModelRepo.save(pageModel);
+ }
+
+ pageModelTab.showPageModelDetails(state);
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModel.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModel.java
new file mode 100644
index 000000000..5e1da4f37
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModel.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.PropertySheetModel;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.kernel.KernelConfig;
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.configuration.ConfigurationManager;
+import org.libreccm.pagemodel.PageModel;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelPropertySheetModel implements PropertySheetModel {
+
+ private static enum PageModelProperty {
+ MODEL_NAME,
+ MODEL_TITLE,
+ MODEL_APPLICATION,
+ MODEL_DESC
+ }
+
+ private final PageModel pageModel;
+ private final Iterator propertyIterator;
+ private PageModelProperty currentProperty;
+
+ public PageModelPropertySheetModel(final PageModel pageModel) {
+
+ this.pageModel = pageModel;
+ propertyIterator = Arrays
+ .asList(PageModelProperty.values())
+ .iterator();
+ }
+
+ @Override
+ public boolean nextRow() {
+ if (pageModel == null) {
+ return false;
+ }
+
+ if (propertyIterator.hasNext()) {
+ currentProperty = propertyIterator.next();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String getLabel() {
+ return currentProperty.toString();
+ }
+
+ @Override
+ public GlobalizedMessage getGlobalizedLabel() {
+
+ final String key = String
+ .join("",
+ "ui.admin.pagemodels.details.",
+ currentProperty.toString().toLowerCase());
+ return new GlobalizedMessage(key, AdminUiConstants.ADMIN_BUNDLE);
+ }
+
+ @Override
+ public String getValue() {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final ConfigurationManager confManager = cdiUtil
+ .findBean(ConfigurationManager.class);
+ final KernelConfig kernelConfig = confManager
+ .findConfiguration(KernelConfig.class);
+ final Locale defaultLocale = kernelConfig.getDefaultLocale();
+
+ switch (currentProperty) {
+ case MODEL_APPLICATION:
+ return pageModel.getApplication().getPrimaryUrl();
+ case MODEL_DESC:
+ return pageModel.getDescription().getValue(defaultLocale);
+ case MODEL_NAME:
+ return pageModel.getName();
+ case MODEL_TITLE:
+ return pageModel.getTitle().getValue(defaultLocale);
+ default:
+ return "";
+ }
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModelBuilder.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModelBuilder.java
new file mode 100644
index 000000000..6f6c48890
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelPropertySheetModelBuilder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.PropertySheet;
+import com.arsdigita.bebop.PropertySheetModel;
+import com.arsdigita.util.LockableImpl;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.pagemodel.PageModel;
+import org.libreccm.pagemodel.PageModelRepository;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelPropertySheetModelBuilder
+ extends LockableImpl
+ implements com.arsdigita.bebop.PropertySheetModelBuilder {
+
+ private final ParameterSingleSelectionModel selectedModelId;
+
+ public PageModelPropertySheetModelBuilder(
+ final ParameterSingleSelectionModel selectedModelId) {
+
+ this.selectedModelId = selectedModelId;
+ }
+
+ @Override
+ public PropertySheetModel makeModel(final PropertySheet sheet,
+ final PageState state) {
+
+ final String selectedModelIdStr = selectedModelId.getSelectedKey(
+ state);
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelRepository pageModelRepo = cdiUtil
+ .findBean(PageModelRepository.class);
+ final PageModel pageModel = pageModelRepo
+ .findById(Long.parseLong(selectedModelIdStr))
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %s in the database.",
+ selectedModelIdStr)));
+
+ return new PageModelPropertySheetModel(pageModel);
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelTab.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelTab.java
new file mode 100644
index 000000000..ae1a4d6bf
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelTab.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.ActionLink;
+import com.arsdigita.bebop.BoxPanel;
+import com.arsdigita.bebop.Form;
+import com.arsdigita.bebop.MetaForm;
+import com.arsdigita.bebop.Page;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.parameters.StringParameter;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.toolbox.ui.LayoutPanel;
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.core.UnexpectedErrorException;
+import org.libreccm.pagemodel.ComponentModel;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class PageModelTab extends LayoutPanel {
+
+ private final ParameterSingleSelectionModel selectedModelId;
+ private final ParameterSingleSelectionModel selectedComponentId;
+ private final ActionLink addNewModel;
+ private final PageModelsTable pageModelsTable;
+ private final PageModelDetails pageModelDetails;
+ private final PageModelForm pageModelForm;
+ private final MetaForm componentForm;
+
+ private Class extends ComponentModel> componentModelClass;
+
+ public PageModelTab() {
+
+ super();
+
+ super.setClassAttr("sidebarNavPanel");
+
+ final BoxPanel left = new BoxPanel(BoxPanel.VERTICAL);
+
+ selectedModelId = new ParameterSingleSelectionModel<>(
+ new StringParameter("selected_pagemodel_id"));
+ selectedComponentId = new ParameterSingleSelectionModel<>(
+ new StringParameter(("selected_pagemodel_component_id")));
+
+ pageModelsTable = new PageModelsTable(this, selectedModelId);
+ pageModelDetails = new PageModelDetails(this,
+ selectedModelId,
+ selectedComponentId);
+ pageModelForm = new PageModelForm(this, selectedModelId);
+
+ addNewModel = new ActionLink(new GlobalizedMessage(
+ "ui.admin.pagemodels.add_new_pagemodel_link",
+ AdminUiConstants.ADMIN_BUNDLE));
+ addNewModel.addActionListener(event -> {
+ showPageModelForm(event.getPageState());
+ });
+
+ componentForm = new MetaForm("componentsForm") {
+
+ @Override
+ public Form buildForm(final PageState state) {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelsController controller = cdiUtil
+ .findBean(PageModelsController.class);
+
+ try {
+ if (selectedComponentId.getSelectedKey(state) == null
+ || selectedComponentId.getSelectedKey(state)
+ .isEmpty()) {
+
+ final Class extends Form> formClass = controller
+ .getComponentModelForm(componentModelClass);
+ formClass
+ .getDeclaredConstructor()
+ .newInstance();
+ } else {
+
+ final Class extends Form> formClass = controller
+ .getComponentModelForm(Long
+ .parseLong(selectedComponentId
+ .getSelectedKey(state)));
+
+ return formClass
+ .getDeclaredConstructor(
+ ParameterSingleSelectionModel.class)
+ .newInstance(selectedComponentId);
+ }
+ } catch (InstantiationException
+ | InvocationTargetException
+ | IllegalAccessException
+ | NoSuchMethodException ex) {
+ throw new UnexpectedErrorException(ex);
+ }
+
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ };
+
+ final BoxPanel right = new BoxPanel(BoxPanel.VERTICAL);
+ right.add(addNewModel);
+ right.add(pageModelsTable);
+ right.add(pageModelDetails);
+ right.add(pageModelForm);
+ right.add(componentForm);
+
+ setLeft(left);
+ setRight(right);
+ }
+
+ @Override
+ public void register(final Page page) {
+
+ super.register(page);
+
+ page.addGlobalStateParam(selectedModelId.getStateParameter());
+
+ page.setVisibleDefault(addNewModel, true);
+ page.setVisibleDefault(pageModelsTable, true);
+ page.setVisibleDefault(pageModelDetails, false);
+ page.setVisibleDefault(pageModelForm, false);
+ page.setVisibleDefault(componentForm, false);
+ }
+
+ protected void showNewComponentForm(
+ final PageState state,
+ final Class extends ComponentModel> componentModelClass) {
+
+ this.componentModelClass = componentModelClass;
+ showComponentForm(state);
+
+ }
+
+ protected void showComponentForm(final PageState state) {
+ addNewModel.setVisible(state, false);
+ pageModelsTable.setVisible(state, false);
+ pageModelDetails.setVisible(state, true);
+ pageModelForm.setVisible(state, false);
+ componentForm.setVisible(state, false);
+ }
+
+ protected void showPageModelDetails(final PageState state) {
+ addNewModel.setVisible(state, false);
+ pageModelsTable.setVisible(state, false);
+ pageModelDetails.setVisible(state, true);
+ pageModelForm.setVisible(state, false);
+ componentForm.setVisible(state, false);
+ }
+
+ protected void showPageModelForm(final PageState state) {
+ addNewModel.setVisible(state, false);
+ pageModelsTable.setVisible(state, false);
+ pageModelDetails.setVisible(state, false);
+ pageModelForm.setVisible(state, true);
+ componentForm.setVisible(state, false);
+ }
+
+ protected void showPageModelsTable(final PageState state) {
+ addNewModel.setVisible(state, true);
+ pageModelsTable.setVisible(state, true);
+ pageModelDetails.setVisible(state, false);
+ pageModelForm.setVisible(state, false);
+ componentForm.setVisible(state, false);
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsController.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsController.java
new file mode 100644
index 000000000..f52a194b9
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsController.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.Form;
+
+import org.libreccm.l10n.GlobalizationHelper;
+import org.libreccm.pagemodel.ComponentModel;
+import org.libreccm.pagemodel.ComponentModelRepository;
+import org.libreccm.pagemodel.PageModel;
+import org.libreccm.pagemodel.PageModelComponentModel;
+import org.libreccm.pagemodel.PageModelManager;
+import org.libreccm.pagemodel.PageModelRepository;
+import org.libreccm.web.ApplicationRepository;
+import org.libreccm.web.CcmApplication;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.transaction.Transactional;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@RequestScoped
+class PageModelsController implements Serializable {
+
+ private static final long serialVersionUID = -5105462163244688201L;
+
+ @Inject
+ private ApplicationRepository applicationRepo;
+
+ @Inject
+ private ComponentModelRepository componentModelRepo;
+
+ @Inject
+ private GlobalizationHelper globalizationHelper;
+
+ @Inject
+ private PageModelManager pageModelManager;
+
+ @Inject
+ private PageModelRepository pageModelRepo;
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ protected List findPageModels() {
+
+ return pageModelRepo
+ .findAll()
+ .stream()
+ .map(this::buildRow)
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ protected boolean isUnique(final long applicationId,
+ final String name) {
+
+ final CcmApplication application = applicationRepo
+ .findById(applicationId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No CcmApplication with ID %d in the database.",
+ applicationId)));
+
+ return !pageModelRepo
+ .findByApplicationAndName(application, name)
+ .isPresent();
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ protected void deletePageModel(final long pageModelId) {
+
+ final PageModel model = pageModelRepo
+ .findById(pageModelId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %d in the database.",
+ pageModelId)));
+
+ pageModelRepo.delete(model);
+ }
+
+ private PageModelsTableRow buildRow(final PageModel model) {
+
+ final PageModelsTableRow row = new PageModelsTableRow();
+
+ row.setModelId(model.getPageModelId());
+ row.setName(model.getName());
+ row.setTitle(globalizationHelper
+ .getValueFromLocalizedString(model.getTitle()));
+ row.setDescription(globalizationHelper
+ .getValueFromLocalizedString(model.getDescription()));
+ row.setApplicationName(model.getApplication().getPrimaryUrl());
+ row.setLive(pageModelManager.isLive(model));
+
+ return row;
+ }
+
+ protected Class extends Form> getComponentModelForm(
+ final long componentModelId) {
+
+ final ComponentModel componentModel = componentModelRepo
+ .findById(componentModelId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No ComponentModel with ID %d in the database.",
+ componentModelId)));
+
+ final Class extends ComponentModel> clazz = componentModel
+ .getClass();
+
+ return getComponentModelForm(clazz);
+ }
+
+ protected Class extends Form> getComponentModelForm(
+ final Class extends ComponentModel> clazz) {
+
+ if (clazz.isAnnotationPresent(PageModelComponentModel.class)) {
+
+ final PageModelComponentModel annotation = clazz
+ .getAnnotation(PageModelComponentModel.class);
+
+ return annotation.editor();
+ } else {
+ return null;
+ }
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ protected List retrieveComponents(final long pageModelId) {
+
+ final PageModel model = pageModelRepo
+ .findById(pageModelId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %d in the database.",
+ pageModelId)));
+
+ return model.getComponents();
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ protected void removeComponentModel(final long pageModelId,
+ final long componentModelId) {
+
+ final PageModel model = pageModelRepo
+ .findById(pageModelId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No PageModel with ID %d in the database.",
+ pageModelId)));
+
+ final ComponentModel componentModel = componentModelRepo
+ .findById(componentModelId)
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No ComponentModel with ID %d in the database.",
+ componentModelId)));
+
+ pageModelManager.removeComponentModel(model, componentModel);
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTable.java
new file mode 100644
index 000000000..b035f2410
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTable.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import com.arsdigita.bebop.Component;
+import com.arsdigita.bebop.ControlLink;
+import com.arsdigita.bebop.FormProcessException;
+import com.arsdigita.bebop.Label;
+import com.arsdigita.bebop.PageState;
+import com.arsdigita.bebop.ParameterSingleSelectionModel;
+import com.arsdigita.bebop.Table;
+import com.arsdigita.bebop.Text;
+import com.arsdigita.bebop.event.TableActionEvent;
+import com.arsdigita.bebop.event.TableActionListener;
+import com.arsdigita.bebop.table.TableCellRenderer;
+import com.arsdigita.bebop.table.TableColumn;
+import com.arsdigita.bebop.table.TableColumnModel;
+import com.arsdigita.bebop.table.TableModel;
+import com.arsdigita.bebop.table.TableModelBuilder;
+import com.arsdigita.globalization.GlobalizedMessage;
+import com.arsdigita.ui.admin.AdminUiConstants;
+import com.arsdigita.util.LockableImpl;
+
+import org.libreccm.cdi.utils.CdiUtil;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelsTable extends Table {
+
+ public static final int COL_MODEL_APPLICATION = 0;
+ public static final int COL_MODEL_NAME = 1;
+ public static final int COL_MODEL_TITLE = 2;
+ public static final int COL_MODEL_DESC = 3;
+ public static final int COL_REMOVE = 4;
+
+ public PageModelsTable(
+ final PageModelTab parent,
+ final ParameterSingleSelectionModel selectedPageModelId) {
+
+ super();
+
+ super.setIdAttr("pageModelsTable");
+ super.setStyleAttr("wdith: 30em");
+
+ setEmptyView(new Label(
+ new GlobalizedMessage("ui.admin.pagemodels.table.empty_view",
+ AdminUiConstants.ADMIN_BUNDLE)));
+
+ final TableColumnModel columnModel = getColumnModel();
+ columnModel.add(new TableColumn(
+ COL_MODEL_APPLICATION,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.headers.application",
+ AdminUiConstants.ADMIN_BUNDLE))
+ ));
+ columnModel.add(new TableColumn(
+ COL_MODEL_NAME,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.headers.name",
+ AdminUiConstants.ADMIN_BUNDLE))
+ ));
+ columnModel.add(new TableColumn(
+ COL_MODEL_TITLE,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.headers.title",
+ AdminUiConstants.ADMIN_BUNDLE))
+ ));
+ columnModel.add(new TableColumn(
+ COL_MODEL_DESC,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.headers.desc",
+ AdminUiConstants.ADMIN_BUNDLE))
+ ));
+ columnModel.add(new TableColumn(
+ COL_REMOVE,
+ new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.headers.remove",
+ AdminUiConstants.ADMIN_BUNDLE))
+ ));
+
+ columnModel
+ .get(COL_MODEL_NAME)
+ .setCellRenderer(new TableCellRenderer() {
+
+ @Override
+ public Component getComponent(final Table table,
+ final PageState state,
+ final Object value,
+ final boolean isSelected,
+ final Object key,
+ final int row,
+ final int column) {
+
+ return new ControlLink((String) value);
+ }
+
+ });
+
+ columnModel
+ .get(COL_REMOVE)
+ .setCellRenderer(new TableCellRenderer() {
+
+ @Override
+ public Component getComponent(final Table table,
+ final PageState state,
+ final Object value,
+ final boolean isSelected,
+ final Object key,
+ final int row,
+ final int column) {
+
+ if (value == null) {
+ return new Text("");
+ } else {
+ final ControlLink link = new ControlLink(
+ (Component) value);
+ link.setConfirmation(new GlobalizedMessage(
+ "ui.admin.pagemodels.delete.confirm",
+ AdminUiConstants.ADMIN_BUNDLE));
+ return link;
+ }
+ }
+
+ });
+
+ super.addTableActionListener(new TableActionListener() {
+
+ @Override
+ public void cellSelected(final TableActionEvent event)
+ throws FormProcessException {
+
+ final PageState state = event.getPageState();
+ final String key = (String) event.getRowKey();
+
+ switch (event.getColumn()) {
+ case COL_MODEL_NAME:
+ selectedPageModelId.setSelectedKey(state, key);
+ parent.showPageModelDetails(state);
+ break;
+ case COL_REMOVE:
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelsController controller = cdiUtil
+ .findBean(PageModelsController.class);
+ controller.deletePageModel(Long.parseLong(key));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid value for column.");
+ }
+
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void headSelected(final TableActionEvent event) {
+
+ // Nothing
+ }
+
+ });
+
+ super.setModelBuilder(new PageModelsTableModelBuilder());
+ }
+
+ private class PageModelsTableModelBuilder
+ extends LockableImpl
+ implements TableModelBuilder {
+
+ @Override
+ public TableModel makeModel(final Table table,
+ final PageState state) {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final PageModelsController controller = cdiUtil
+ .findBean(PageModelsController.class);
+ return new PageModelsTableModel(controller.findPageModels());
+ }
+
+ }
+
+ private class PageModelsTableModel implements TableModel {
+
+ private final Iterator iterator;
+ private PageModelsTableRow currentRow;
+
+ public PageModelsTableModel(final List rows) {
+ iterator = rows.iterator();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 5;
+ }
+
+ @Override
+ public boolean nextRow() {
+
+ if (iterator.hasNext()) {
+ currentRow = iterator.next();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Object getElementAt(final int columnIndex) {
+
+ switch (columnIndex) {
+ case COL_MODEL_APPLICATION:
+ return currentRow.getApplicationName();
+ case COL_MODEL_DESC:
+ return currentRow.getDescription();
+ case COL_MODEL_NAME:
+ return currentRow.getName();
+ case COL_MODEL_TITLE:
+ return currentRow.getTitle();
+ case COL_REMOVE:
+ return new Label(new GlobalizedMessage(
+ "ui.admin.pagemodels.table.columns.remove.label",
+ AdminUiConstants.ADMIN_BUNDLE));
+ default:
+ throw new IllegalArgumentException("No a valid column index");
+ }
+ }
+
+ @Override
+ public Object getKeyAt(final int columnIndex) {
+
+ return currentRow.getModelId();
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTableRow.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTableRow.java
new file mode 100644
index 000000000..cb66163bb
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/pagemodels/PageModelsTableRow.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package com.arsdigita.ui.admin.pagemodels;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class PageModelsTableRow implements Comparable,
+ Serializable {
+
+ private static final long serialVersionUID = 7497498047332094014L;
+
+ private long modelId;
+
+ private String name;
+
+ private boolean live;
+
+ private String title;
+
+ private String description;
+
+ private String applicationName;
+
+ public long getModelId() {
+ return modelId;
+ }
+
+ public void setModelId(final long modelId) {
+ this.modelId = modelId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public boolean isLive() {
+ return live;
+ }
+
+ public void setLive(final boolean live) {
+ this.live = live;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public String getApplicationName() {
+ return applicationName;
+ }
+
+ public void setApplicationName(final String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ @Override
+ public int compareTo(final PageModelsTableRow other) {
+
+ int result;
+
+ result = applicationName.compareTo(other.getApplicationName());
+ if (result != 0) {
+ return result;
+ }
+
+ result = name.compareTo(other.getName());
+ if (result != 0) {
+ return result;
+ }
+
+ return title.compareTo(other.getTitle());
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesForm.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesForm.java
index 3e53aad4a..30c539761 100644
--- a/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesForm.java
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesForm.java
@@ -52,7 +52,7 @@ import static com.arsdigita.ui.admin.AdminUiConstants.*;
*
* @author Jens Pelzetter
*/
-public class SitesForm extends Form {
+class SitesForm extends Form {
private static final String DOMAIN_OF_SITE = "domainOfSite";
private static final String DEFAULT_SITE = "defaultSite";
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesTable.java
index d2777e578..419ee4bdb 100644
--- a/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesTable.java
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/sites/SitesTable.java
@@ -47,7 +47,7 @@ import static com.arsdigita.ui.admin.AdminUiConstants.*;
*
* @author Jens Pelzetter
*/
-public class SitesTable extends Table {
+class SitesTable extends Table {
public static final int COL_SITE_DOMAIN = 0;
public static final int COL_IS_DEFAULT_SITE = 1;
diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/roles/RoleDetails.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/roles/RoleDetails.java
index 2953661fb..4f5f723e4 100644
--- a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/roles/RoleDetails.java
+++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/roles/RoleDetails.java
@@ -48,56 +48,58 @@ class RoleDetails extends BoxPanel {
final ActionLink backLink = new ActionLink(new GlobalizedMessage(
"ui.admin.role_details.back", ADMIN_BUNDLE));
backLink.setClassAttr("back-link");
- backLink.addActionListener(e -> {
- roleAdmin.hideRoleDetails(e.getPageState());
+ backLink.addActionListener(event -> {
+ roleAdmin.hideRoleDetails(event.getPageState());
});
- add(backLink);
+ super.add(backLink);
final Label heading = new Label();
heading.setClassAttr("heading");
- heading.addPrintListener(e -> {
- final PageState state = e.getPageState();
- final Label target = (Label) e.getTarget();
- final RoleRepository roleRepository = CdiUtil.createCdiUtil()
+ heading.addPrintListener(event -> {
+ final PageState state = event.getPageState();
+ final Label target = (Label) event.getTarget();
+ final RoleRepository roleRepository = CdiUtil
+ .createCdiUtil()
.findBean(RoleRepository.class);
- final Role role = roleRepository.findById(Long.parseLong(
- selectedRoleId.getSelectedKey(state))).get();
+ final Role role = roleRepository
+ .findById(Long.parseLong(selectedRoleId.getSelectedKey(state)))
+ .get();
target.setLabel(new GlobalizedMessage(
"ui.admin.role_details.heading",
ADMIN_BUNDLE,
new String[]{role.getName()}));
});
- add(heading);
+ super.add(heading);
final PropertySheet propertySheet = new PropertySheet(
new RolePropertySheetModelBuilder(selectedRoleId));
- add(propertySheet);
+ super.add(propertySheet);
final BoxPanel links = new BoxPanel(BoxPanel.HORIZONTAL);
final ActionLink editProperties = new ActionLink(new GlobalizedMessage(
"ui.admin.role_details.edit_properties", ADMIN_BUNDLE));
- editProperties.addActionListener(e -> {
- roleAdmin.showRoleForm(e.getPageState());
+ editProperties.addActionListener(event -> {
+ roleAdmin.showRoleForm(event.getPageState());
});
links.add(editProperties);
final ActionLink manageMembers = new ActionLink(new GlobalizedMessage(
"ui.admin.role_details.manage_members", ADMIN_BUNDLE));
- manageMembers.addActionListener(e -> {
- roleAdmin.showRoleMembersPanel(e.getPageState());
+ manageMembers.addActionListener(event -> {
+ roleAdmin.showRoleMembersPanel(event.getPageState());
});
links.add(manageMembers);
final ActionLink managePermissions = new ActionLink(new GlobalizedMessage(
"ui.admin.role_details.manage_permissions", ADMIN_BUNDLE));
- managePermissions.addActionListener(e -> {
- roleAdmin.showRolePermissionsPanel(e.getPageState());
+ managePermissions.addActionListener(event -> {
+ roleAdmin.showRolePermissionsPanel(event.getPageState());
});
links.add(managePermissions);
- add(links);
+ super.add(links);
}
}
diff --git a/ccm-core/src/main/java/org/libreccm/pagemodel/ComponentModels.java b/ccm-core/src/main/java/org/libreccm/pagemodel/ComponentModels.java
new file mode 100644
index 000000000..193004aa8
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/pagemodel/ComponentModels.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.pagemodel;
+
+import org.libreccm.modules.CcmModule;
+import org.libreccm.modules.Module;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@ApplicationScoped
+public class ComponentModels {
+
+ private final List availableComponentModels = new ArrayList<>();
+
+ @PostConstruct
+ private void init() {
+
+ final ServiceLoader modules = ServiceLoader
+ .load(CcmModule.class);
+
+ for (final CcmModule module : modules) {
+
+ final Module moduleData = module
+ .getClass()
+ .getAnnotation(Module.class);
+
+ final PageModelComponentModel[] componentModels = moduleData
+ .pageModelComponentModels();
+
+ for(final PageModelComponentModel componentModel : componentModels) {
+ availableComponentModels.add(componentModel);
+ }
+ }
+ }
+
+ public List findAvailableComponentModels() {
+ return Collections.unmodifiableList(availableComponentModels);
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/pagemodel/PageModel.java b/ccm-core/src/main/java/org/libreccm/pagemodel/PageModel.java
index 0068b6485..38cea1aeb 100644
--- a/ccm-core/src/main/java/org/libreccm/pagemodel/PageModel.java
+++ b/ccm-core/src/main/java/org/libreccm/pagemodel/PageModel.java
@@ -270,7 +270,7 @@ public class PageModel implements Serializable {
return application;
}
- protected void setApplication(final CcmApplication application) {
+ public void setApplication(final CcmApplication application) {
this.application = application;
}
diff --git a/ccm-core/src/main/java/org/libreccm/theming/xslt/CcmUriResolver.java b/ccm-core/src/main/java/org/libreccm/theming/xslt/CcmUriResolver.java
new file mode 100644
index 000000000..23b388c65
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/theming/xslt/CcmUriResolver.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.theming.xslt;
+
+import org.libreccm.theming.ThemeProvider;
+import org.libreccm.theming.ThemeVersion;
+
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+class CcmUriResolver implements URIResolver {
+
+ private final String theme;
+ private final ThemeVersion version;
+ private final ThemeProvider themeProvider;
+
+ protected CcmUriResolver(final String theme,
+ final ThemeVersion version,
+ final ThemeProvider themeProvider) {
+
+ Objects.requireNonNull(theme);
+ Objects.requireNonNull(version);
+ Objects.requireNonNull(themeProvider);
+
+ this.theme = theme;
+ this.version = version;
+ this.themeProvider = themeProvider;
+ }
+
+ @Override
+ public Source resolve(final String href,
+ final String base) throws TransformerException {
+
+ final String path;
+ if (base == null) {
+ path = href;
+ } else {
+ path = String.join("/", href, base);
+ }
+
+ final InputStream inputStream = themeProvider
+ .getThemeFileAsStream(theme, version, path)
+ .orElseThrow(() -> new TransformerException(String
+ .format("Failed to resolve URI with href = \"%s\" and base = \"%s\" "
+ + "for theme \"%s\" (version = \"%s\" using "
+ + "ThemeProvider \"%s\".",
+ href,
+ base,
+ theme,
+ version,
+ themeProvider.getClass().getName())));
+
+ return new StreamSource(inputStream);
+ }
+
+}
+
diff --git a/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java b/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java
index 3a7f4d71d..b6d6a81c7 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java
@@ -39,6 +39,7 @@ import javax.xml.parsers.ParserConfigurationException;
import static org.libreccm.theming.ThemeConstants.*;
+import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.manifest.ThemeTemplate;
import java.io.InputStream;
@@ -49,6 +50,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Optional;
import javax.xml.transform.Result;
+import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
@@ -59,7 +61,7 @@ import javax.xml.transform.stream.StreamSource;
/**
* A {@link ThemeProcessor} implementation for XSLT based themes.
- *
+ *
* @author Jens Pelzetter
*/
@RequestScoped
@@ -147,10 +149,13 @@ public class XsltThemeProcessor implements ThemeProcessor {
final Transformer transformer;
try {
transformer = transformerFactory.newTransformer(xslFileStreamSource);
+ transformer.setURIResolver(new CcmUriResolver(theme.getName(),
+ theme.getVersion(),
+ themeProvider));
} catch (TransformerConfigurationException ex) {
throw new UnexpectedErrorException(ex);
}
-
+
final StringWriter resultWriter = new StringWriter();
final Result result = new StreamResult(resultWriter);
try {
@@ -158,7 +163,7 @@ public class XsltThemeProcessor implements ThemeProcessor {
} catch (TransformerException ex) {
throw new UnexpectedErrorException(ex);
}
-
+
return resultWriter.toString();
}
diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties
index e5cbca0eb..6d9151634 100644
--- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties
+++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties
@@ -641,3 +641,17 @@ ui.admin.sites.created_new_site=Create new site "{0}"
ui.admin.sites.saved_changes=Saved changes to site "{0}"
ui.admin.sites.site_deleted=Site "{0}" deleted
ui.admin.sites.site_delete.confirm=Are you sure to delete Site "{0}"?
+ui.admin.pagemodels.name.error.empty=The name of a Page Model can't be empty.
+ui.admin.pagemodels.title.error.empty=The title of a PageModel can't be empty.
+ui.admin.pagemodels.application.error.empty=A PageModel must be assigned to an application
+ui.admin.pagemodels.application.error.invalid=PageModel is not assigned to valid application.
+ui.admin.pagemodels.details.back=Back to list of page models.
+ui.admin.pagemodels.details.heading=Details of PageModel {0}
+ui.admin.pagemodels.componentmodels.none=No components have been added to this PageModel.
+ui.admin.pagemodels.componentmodels.cols.key.heading=Key
+ui.admin.pagemodels.componentmodels.cols.type.heading=Type
+ui.admin.pagemodels.componentmodels.cols.edit.heading=Edit
+ui.admin.pagemodels.componentmodels.cols.delete.heading=Delete
+ui.admin.pagemodels.components.edit=Edit
+ui.admin.pagemodels.components.delete=Delete
+ui.admin.pagemodels.details.add_component=Add Component
diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties
index 19518014f..1ed53072f 100644
--- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties
+++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties
@@ -645,3 +645,17 @@ ui.admin.sites.created_new_site=Neue Site "{0}" angelegt
ui.admin.sites.saved_changes=\u00c4nderungen an Seite "{0}" gespeichert
ui.admin.sites.site_deleted=Site "{0}" gel\u00f6scht
ui.admin.sites.site_delete.confirm=Sind Sie sicher, dass Sie die Site "{0}" l\u00f6schen wollen?
+ui.admin.pagemodels.name.error.empty=Der Name eines PageModels darf nicht leer sein.
+ui.admin.pagemodels.title.error.empty=Der Titel eines PageModels darf nicht leer sein.
+ui.admin.pagemodels.application.error.empty=Ein PageModel muss einer Applikation zugeordnet werden.
+ui.admin.pagemodels.application.error.invalid=Das PageModel ist nicht einer g\u00fcltigen Anwendung zugeordnet.
+ui.admin.pagemodels.details.back=Zur\u00fcck zur Liste der PageModels
+ui.admin.pagemodels.details.heading=Details PageModel {0}
+ui.admin.pagemodels.componentmodels.none=Diesem PageModel wurden noch keine Komponenten hinzugef\u00fcgt.
+ui.admin.pagemodels.componentmodels.cols.key.heading=Key
+ui.admin.pagemodels.componentmodels.cols.type.heading=Typ
+ui.admin.pagemodels.componentmodels.cols.edit.heading=Bearbeiten
+ui.admin.pagemodels.componentmodels.cols.delete.heading=Delete
+ui.admin.pagemodels.components.edit=Bearbeiten
+ui.admin.pagemodels.components.delete=L\u00f6schen
+ui.admin.pagemodels.details.add_component=Komponente hinzuf\u00fcgen
diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties
index ead9a7111..1995b1cf8 100755
--- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties
+++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties
@@ -638,3 +638,17 @@ ui.admin.sites.created_new_site=Create new site "{0}"
ui.admin.sites.saved_changes=Saved changes to site "{0}"
ui.admin.sites.site_deleted=Site "{0}" deleted
ui.admin.sites.site_delete.confirm=Are you sure to delete Site "{0}"?
+ui.admin.pagemodels.name.error.empty=The name of a Page Model can't be empty.
+ui.admin.pagemodels.title.error.empty=The title of a PageModel can't be empty.
+ui.admin.pagemodels.application.error.empty=A PageModel must be assigned to an application
+ui.admin.pagemodels.application.error.invalid=PageModel is not assigned to valid application.
+ui.admin.pagemodels.details.back=Back to list of page models.
+ui.admin.pagemodels.details.heading=Details of PageModel {0}
+ui.admin.pagemodels.componentmodels.none=No components have been added to this PageModel.
+ui.admin.pagemodels.componentmodels.cols.key.heading=Key
+ui.admin.pagemodels.componentmodels.cols.type.heading=Type
+ui.admin.pagemodels.componentmodels.cols.edit.heading=Edit
+ui.admin.pagemodels.componentmodels.cols.delete.heading=Delete
+ui.admin.pagemodels.components.edit=Edit
+ui.admin.pagemodels.components.delete=Delete
+ui.admin.pagemodels.details.add_component=Add Component
diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties
index a1aa11ce1..27aa0a203 100755
--- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties
+++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties
@@ -629,3 +629,17 @@ ui.admin.sites.created_new_site=Create new site "{0}"
ui.admin.sites.saved_changes=Saved changes to site "{0}"
ui.admin.sites.site_deleted=Site "{0}" deleted
ui.admin.sites.site_delete.confirm=Are you sure to delete Site "{0}"?
+ui.admin.pagemodels.name.error.empty=The name of a Page Model can't be empty.
+ui.admin.pagemodels.title.error.empty=The title of a PageModel can't be empty.
+ui.admin.pagemodels.application.error.empty=A PageModel must be assigned to an application
+ui.admin.pagemodels.application.error.invalid=PageModel is not assigned to valid application.
+ui.admin.pagemodels.details.back=Back to list of page models.
+ui.admin.pagemodels.details.heading=Details of PageModel {0}
+ui.admin.pagemodels.componentmodels.none=No components have been added to this PageModel.
+ui.admin.pagemodels.componentmodels.cols.key.heading=Key
+ui.admin.pagemodels.componentmodels.cols.type.heading=Type
+ui.admin.pagemodels.componentmodels.cols.edit.heading=Edit
+ui.admin.pagemodels.componentmodels.cols.delete.heading=Delete
+ui.admin.pagemodels.components.edit=Edit
+ui.admin.pagemodels.components.delete=Delete
+ui.admin.pagemodels.details.add_component=Add Component