diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemCreateForm.java b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemCreateForm.java
new file mode 100644
index 000000000..938efc4da
--- /dev/null
+++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemCreateForm.java
@@ -0,0 +1,29 @@
+/*
+ * 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.cms.ui.item;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class ItemCreateForm {
+
+ //Placeholder
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKit.java b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKit.java
new file mode 100644
index 000000000..e1be1afbf
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKit.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import com.arsdigita.cms.ui.item.ItemCreateForm;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AuthoringKit {
+
+ Class extends ItemCreateForm> createComponent();
+
+ AuthoringStep[] steps();
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKitInfo.java b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKitInfo.java
new file mode 100644
index 000000000..24f6bbfd3
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringKitInfo.java
@@ -0,0 +1,126 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import com.arsdigita.cms.ui.item.ItemCreateForm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Encapsulates the informations about an authoring kit.
+ *
+ * @author Jens Pelzetter
+ */
+public class AuthoringKitInfo {
+
+ /**
+ * The create component (the form used to collect the mandatory data for
+ * the content type).
+ */
+ private Class extends ItemCreateForm> createComponent;
+
+ /**
+ * The authoring steps of the authoring kit.
+ */
+ private List authoringSteps;
+
+ protected AuthoringKitInfo() {
+ authoringSteps = new ArrayList<>();
+ }
+
+ public Class extends ItemCreateForm> getCreateComponent() {
+ return createComponent;
+ }
+
+ public void setCreateComponent(
+ final Class extends ItemCreateForm> createComponent) {
+
+ this.createComponent = createComponent;
+ }
+
+ public List getAuthoringSteps() {
+ return Collections.unmodifiableList(authoringSteps);
+ }
+
+ protected void setAuthoringSteps(
+ final List authoringSteps) {
+ this.authoringSteps = authoringSteps;
+ }
+
+ protected void addAuthoringStep(final AuthoringStepInfo authoringStep) {
+ authoringSteps.add(authoringStep);
+ }
+
+ protected void removeAuthoringStep(final AuthoringStepInfo authoringStep) {
+ authoringSteps.remove(authoringStep);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 59 * hash + Objects.hashCode(createComponent);
+ hash = 59 * hash + Objects.hashCode(authoringSteps);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof AuthoringKitInfo)) {
+ return false;
+ }
+ final AuthoringKitInfo other = (AuthoringKitInfo) obj;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ if (!Objects.equals(this.createComponent, other.getCreateComponent())) {
+ return false;
+ }
+ return Objects.equals(this.authoringSteps, other.getAuthoringSteps());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof AuthoringKitInfo;
+ }
+
+ @Override
+ public final String toString() {
+ return toString("");
+ }
+
+ public String toString(final String data) {
+ return String.format("%s{ "
+ + "createComponent = \"%s\", "
+ + "authoringSteps = { %s }%s"
+ + " }",
+ super.toString(),
+ Objects.toString(createComponent),
+ Objects.toString(authoringSteps),
+ data);
+ }
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStep.java b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStep.java
new file mode 100644
index 000000000..ac25ed6df
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStep.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import com.arsdigita.bebop.Component;
+import com.arsdigita.bebop.Form;
+
+/**
+ * Annotation used inside the {@link AuthoringKit} annotation to describe the
+ * authoring steps belonging to an authoring kit.
+ *
+ * @author Jens Pelzetter
+ */
+public @interface AuthoringStep {
+
+ /**
+ * Key of the label in the {@link #labelBundle()}. If blank (default) the
+ * simple name of the annotated class with the suffix {@code .label} is
+ * used.
+ *
+ * @return The label key of the authoring step.
+ */
+ String labelKey() default "";
+
+ /**
+ * Bundle providing the localised label for the authoring step. If omitted
+ * the default bundle for the content type will be used. The default bundle
+ * is the fully qualified name of the content type class with the suffix
+ * {@code Bundle}.
+ *
+ * @return The bundle providing the label for the authoring step.
+ */
+ String labelBundle() default "";
+
+ /**
+ * Key of the description in the {@link #descriptionBundle()}. If blank
+ * (default) the simple name of the annotated class with the suffix
+ * {@code .description} is used.
+ *
+ * @return The description key of the authoring step.
+ */
+ String descriptionKey() default "";
+
+ /**
+ * Bundle providing the localised description for the authoring step. If
+ * omitted the default bundle for the content type will be used. The default
+ * bundle is the fully qualified name of the content type class with the
+ * suffix {@code Bundle}.
+ *
+ * @return The bundle providing the description for the authoring step.
+ */
+ String descriptionBundle() default "";
+
+ /**
+ * The position of the authoring step.
+ *
+ * @return The position of the authoring step.
+ */
+ int order();
+
+ /**
+ * The component (usually a {@link Form} providing the UI for the authoring
+ * step.
+ *
+ * @return The class providing the UI for the authoring step.
+ */
+ Class extends Component> component();
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStepInfo.java b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStepInfo.java
new file mode 100644
index 000000000..97bdbc850
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/AuthoringStepInfo.java
@@ -0,0 +1,182 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import com.arsdigita.bebop.Component;
+
+import java.util.Objects;
+
+/**
+ * Encapsulates the information about an authoring step.
+ *
+ * @see AuthoringStep
+ *
+ * @author Jens Pelzetter
+ */
+public class AuthoringStepInfo {
+
+ /**
+ * The bundle which provides the label for the authoring step.
+ */
+ private String labelBundle;
+ /**
+ * The key of label for the authoring step in the {@link #labelBundle}
+ */
+ private String labelKey;
+ /**
+ * The bundle which provides the description for the authoring step.
+ */
+ private String descriptionBundle;
+ /**
+ * The key of the description for the authoring step in the
+ * {@link #descriptionBundle}.
+ */
+ private String descriptionKey;
+
+ private int order;
+ private Class extends Component> component;
+
+ protected AuthoringStepInfo() {
+ super();
+ }
+
+ public String getLabelBundle() {
+ return labelBundle;
+ }
+
+ public void setLabelBundle(final String labelBundle) {
+ this.labelBundle = labelBundle;
+ }
+
+ public String getLabelKey() {
+ return labelKey;
+ }
+
+ public void setLabelKey(final String labelKey) {
+ this.labelKey = labelKey;
+ }
+
+ public String getDescriptionBundle() {
+ return descriptionBundle;
+ }
+
+ public void setDescriptionBundle(final String descriptionBundle) {
+ this.descriptionBundle = descriptionBundle;
+ }
+
+ public String getDescriptionKey() {
+ return descriptionKey;
+ }
+
+ public void setDescriptionKey(final String descriptionKey) {
+ this.descriptionKey = descriptionKey;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(final int order) {
+ this.order = order;
+ }
+
+ public Class extends Component> getComponent() {
+ return component;
+ }
+
+ public void setComponent(final Class extends Component> component) {
+ this.component = component;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 53 * hash + Objects.hashCode(labelBundle);
+ hash = 53 * hash + Objects.hashCode(labelKey);
+ hash = 53 * hash + Objects.hashCode(descriptionBundle);
+ hash = 53 * hash + Objects.hashCode(descriptionKey);
+ hash = 53 * hash + Objects.hashCode(order);
+ hash = 53 * hash + Objects.hashCode(component);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof AuthoringStepInfo)) {
+ return false;
+ }
+ final AuthoringStepInfo other = (AuthoringStepInfo) obj;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ if (!Objects.equals(this.labelBundle, other.getLabelBundle())) {
+ return false;
+ }
+ if (!Objects.equals(this.labelKey, other.getLabelKey())) {
+ return false;
+ }
+ if (!Objects.equals(this.descriptionBundle,
+ other.getDescriptionBundle())) {
+ return false;
+ }
+ if (!Objects.equals(this.descriptionKey, other.getDescriptionKey())) {
+ return false;
+ }
+
+ if (order != other.getOrder()) {
+ return false;
+ }
+
+ return Objects.equals(this.component, other.getComponent());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof AuthoringStepInfo;
+ }
+
+ @Override
+ public final String toString() {
+ return toString("");
+ }
+
+ public String toString(final String data) {
+ return String.format("%s{ "
+ + "labelBundle = \"%s\","
+ + "labelKey = \"%s\", "
+ + "descriptionBundle = \"%s\","
+ + "descriptionKey = \"%s\","
+ + "order = %d, "
+ + "component = \"%s\"%s }",
+ super.toString(),
+ labelBundle,
+ labelKey,
+ descriptionKey,
+ descriptionBundle,
+ order,
+ Objects.toString(component),
+ data);
+ }
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeDescription.java b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeDescription.java
new file mode 100644
index 000000000..9c4ee990b
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeDescription.java
@@ -0,0 +1,81 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to customise the keys and bundles used to
+ * retrieve the label and the description of a content type.
+ *
+ * If the annotation is not present or if one of the values in this annotation
+ * is omitted the default values are used.
+ *
+ * @author Jens Pelzetter
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ContentTypeDescription {
+
+ /**
+ * The key of the label for the content type in the label bundle. Default
+ * value is {@code label}.
+ *
+ * @return The key for label of the content type.
+ */
+ String labelKey() default "";
+
+ /**
+ * The bundle which provides the label for the content type. Default is the
+ * fully qualified class name of the annotated content item class with the
+ * suffix {@code Bundle}. For example the default bundle for the content
+ * type {@code org.librecms.contenttypes.Article} is
+ * {@code org.librecms.contenttypes.ArticleBundle}.
+ *
+ *
+ * @return The fully qualified name of the bundle providing the label for
+ * the content type.
+ */
+ String labelBundle() default "";
+
+ /**
+ * The key of the description for the content type in the label bundle.
+ * Default value is {@code descripion}.
+ *
+ * @return The key for description of the content type.
+ */
+ String descriptionKey() default "";
+
+ /**
+ * The bundle which provides the description for the content type. Default
+ * is the fully qualified class name of the annotated content item class
+ * with the suffix {@code Bundle}. For example the default bundle for the
+ * content type {@code org.librecms.contenttypes.Article} is
+ * {@code org.librecms.contenttypes.ArticleBundle}.
+ *
+ *
+ * @return The fully qualified name of the bundle providing the label for
+ * the content type.
+ */
+ String descriptionBundle() default "";
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeInfo.java b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeInfo.java
new file mode 100644
index 000000000..8659430fc
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeInfo.java
@@ -0,0 +1,177 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import org.librecms.contentsection.ContentItem;
+
+import java.util.Objects;
+
+/**
+ * A class encapsulating all informations about a available Content Type.
+ *
+ * @author Jens Pelzetter
+ */
+public class ContentTypeInfo {
+
+ /**
+ * The bundle which provides the localisable label for the content type.
+ */
+ private String labelBundle;
+ /**
+ * The key of the label in the {@link #labelBundle}.
+ */
+ private String labelKey;
+ /**
+ * The bundle which provides the description of the content type.
+ */
+ private String descriptionBundle;
+ /**
+ * The key of the description of the content type in the
+ * {@link #descriptionBundle}.
+ */
+ private String descriptionKey;
+
+ private Class extends ContentItem> contentItemClass;
+ private AuthoringKitInfo authoringKit;
+
+ public String getLabelBundle() {
+ return labelBundle;
+ }
+
+ public void setLabelBundle(final String labelBundle) {
+ this.labelBundle = labelBundle;
+ }
+
+ public String getLabelKey() {
+ return labelKey;
+ }
+
+ public void setLabelKey(final String labelKey) {
+ this.labelKey = labelKey;
+ }
+
+ public String getDescriptionBundle() {
+ return descriptionBundle;
+ }
+
+ public void setDescriptionBundle(final String descriptionBundle) {
+ this.descriptionBundle = descriptionBundle;
+ }
+
+ public String getDescriptionKey() {
+ return descriptionKey;
+ }
+
+ public void setDescriptionKey(final String descriptionKey) {
+ this.descriptionKey = descriptionKey;
+ }
+
+ public Class extends ContentItem> getContentItemClass() {
+ return contentItemClass;
+ }
+
+ public void setContentItemClass(
+ final Class extends ContentItem> contentItemClass) {
+ this.contentItemClass = contentItemClass;
+ }
+
+ public AuthoringKitInfo getAuthoringKit() {
+ return authoringKit;
+ }
+
+ public void setAuthoringKit(final AuthoringKitInfo authoringKit) {
+ this.authoringKit = authoringKit;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 97 * hash + Objects.hashCode(labelBundle);
+ hash = 97 * hash + Objects.hashCode(labelKey);
+ hash = 97 * hash + Objects.hashCode(descriptionBundle);
+ hash = 97 * hash + Objects.hashCode(descriptionKey);
+ hash = 97 * hash + Objects.hashCode(contentItemClass);
+ hash = 97 * hash + Objects.hashCode(authoringKit);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof ContentTypeInfo)) {
+ return false;
+ }
+ final ContentTypeInfo other = (ContentTypeInfo) obj;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ if (!Objects.equals(labelBundle, other.getLabelBundle())) {
+ return false;
+ }
+ if (!Objects.equals(labelKey, other.getLabelKey())) {
+ return false;
+ }
+ if (!Objects.equals(descriptionBundle, other.getDescriptionBundle())) {
+ return false;
+ }
+ if (!Objects.equals(descriptionKey, other.getDescriptionKey())) {
+ return false;
+ }
+
+ if (!Objects.equals(contentItemClass, other.getContentItemClass())) {
+ return false;
+ }
+
+ return Objects.equals(authoringKit, other.getAuthoringKit());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof ContentTypeInfo;
+ }
+
+ @Override
+ public final String toString() {
+ return toString("");
+ }
+
+ public String toString(final String data) {
+ return String.format("%s{ "
+ + "labelBundle = \"%s\", "
+ + "labelKey = \"%s\", "
+ + "descriptionBundle = \"%s\", "
+ + "descriptionKey = \"%s\","
+ + "contentItemClass = \"%s\", "
+ + "authoringKit = { %s }%s"
+ + " }",
+ super.toString(),
+ labelBundle,
+ labelKey,
+ descriptionBundle,
+ descriptionKey,
+ Objects.toString(contentItemClass),
+ Objects.toString(authoringKit),
+ data);
+ }
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeManager.java b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeManager.java
new file mode 100644
index 000000000..f196f4f27
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypeManager.java
@@ -0,0 +1,220 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import org.libreccm.modules.CcmModule;
+import org.librecms.contentsection.ContentItem;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.RequestScoped;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@RequestScoped
+public class ContentTypeManager {
+
+ private static final String DEFAULT_DESCRIPTION_KEY = "description";
+ private static final String DEFAULT_LABEL_KEY = "label";
+
+ /**
+ * A list of all content type currently available.
+ */
+ private List availableContentTypes;
+
+ /**
+ * Initialises the class (is called by CDI). This method is called by the
+ * CDI container after an instance of this class has been created by the CDI
+ * container. This method fills the list {@link #availableContentTypes}.
+ */
+ @PostConstruct
+ protected void initialize() {
+ final ServiceLoader modules = ServiceLoader.load(
+ CcmModule.class);
+
+ final SortedSet> types = new TreeSet<>(
+ (type1, type2) -> type1.getName().compareTo(type2.getName())
+ );
+
+ for (final CcmModule module : modules) {
+ final ContentTypes annotation = module.getClass().getAnnotation(
+ ContentTypes.class);
+
+ if (annotation == null) {
+ continue;
+ }
+
+ final List> moduleTypes = Arrays
+ .stream(annotation.value()).collect(Collectors.toList());
+
+ types.addAll(moduleTypes);
+ }
+
+ availableContentTypes = types.stream()
+ .filter(type -> type.getAnnotation(AuthoringKit.class) != null)
+ .map(contentTypeClass -> createContentTypeInfo(contentTypeClass))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Helper method for creating the info object for a content type.
+ *
+ * @param contentTypeClass The class which provides the implementation of
+ * the content type.
+ *
+ * @return A {@link ContentTypeInfo} object describing the content type.
+ */
+ private ContentTypeInfo createContentTypeInfo(
+ final Class extends ContentItem> contentTypeClass) {
+
+ final ContentTypeInfo contentTypeInfo = new ContentTypeInfo();
+ contentTypeInfo.setContentItemClass(contentTypeClass);
+
+ final String defaultBundleName = String.join(
+ "",
+ contentTypeClass.getClass().getName(),
+ "Bundle");
+ final ContentTypeDescription typeDesc = contentTypeClass.getAnnotation(
+ ContentTypeDescription.class);
+ if (typeDesc == null) {
+ contentTypeInfo.setLabelBundle(defaultBundleName);
+ contentTypeInfo.setDescriptionBundle(defaultBundleName);
+ contentTypeInfo.setLabelKey(DEFAULT_LABEL_KEY);
+ contentTypeInfo.setDescriptionKey(DEFAULT_DESCRIPTION_KEY);
+ } else {
+ if (typeDesc.labelBundle().isEmpty()) {
+ contentTypeInfo.setLabelBundle(defaultBundleName);
+ } else {
+ contentTypeInfo.setLabelBundle(typeDesc.labelBundle());
+ }
+
+ if (typeDesc.labelKey().isEmpty()) {
+ contentTypeInfo.setLabelKey(DEFAULT_LABEL_KEY);
+ } else {
+ contentTypeInfo.setLabelKey(typeDesc.labelKey());
+ }
+
+ if (typeDesc.descriptionBundle().isEmpty()) {
+ contentTypeInfo.setDescriptionBundle(defaultBundleName);
+ } else {
+ contentTypeInfo.setDescriptionBundle(typeDesc
+ .descriptionBundle());
+ }
+
+ if (typeDesc.descriptionKey().isEmpty()) {
+ contentTypeInfo.setDescriptionKey(DEFAULT_DESCRIPTION_KEY);
+ } else {
+ contentTypeInfo.setDescriptionKey(typeDesc.descriptionKey());
+ }
+ }
+
+ final AuthoringKit authoringKit = contentTypeClass.getAnnotation(
+ AuthoringKit.class);
+ final AuthoringKitInfo authoringKitInfo = new AuthoringKitInfo();
+ authoringKitInfo.setCreateComponent(authoringKit.createComponent());
+
+ final List steps = Arrays.stream(authoringKit
+ .steps())
+ .map(step -> createAuthoringStepInfo(contentTypeClass, step))
+ .collect(Collectors.toList());
+ authoringKitInfo.setAuthoringSteps(steps);
+ steps.sort((step1, step2) -> Integer.compare(step1.getOrder(),
+ step2.getOrder()));
+
+ return contentTypeInfo;
+ }
+
+ /**
+ * Helper method for creating an info object about an authoring step.
+ *
+ * @param contentTypeClass The class which provides the implementation of
+ * the content type.
+ * @param authoringStep The {@link AuthoringStep} annotation providing
+ * the information about the authoring step.
+ *
+ * @return An {@link AuthoringStepInfo} object describing the authoring
+ * step.
+ *
+ */
+ private AuthoringStepInfo createAuthoringStepInfo(
+ final Class extends ContentItem> contentTypeClass,
+ final AuthoringStep authoringStep) {
+
+ final AuthoringStepInfo stepInfo = new AuthoringStepInfo();
+
+ stepInfo.setComponent(authoringStep.component());
+ stepInfo.setOrder(authoringStep.order());
+
+ final String defaultBundleName = String.join(
+ "",
+ contentTypeClass.getClass().getName(),
+ "Bundle");
+
+ if (authoringStep.labelBundle().isEmpty()) {
+ stepInfo.setLabelBundle(defaultBundleName);
+ } else {
+ stepInfo.setLabelBundle(authoringStep.labelBundle());
+ }
+
+ if (authoringStep.labelKey().isEmpty()) {
+ stepInfo.setLabelKey(
+ String.join(".",
+ authoringStep.component().getSimpleName(),
+ DEFAULT_LABEL_KEY));
+ } else {
+ stepInfo.setLabelKey(authoringStep.labelKey());
+ }
+
+ if (authoringStep.descriptionBundle().isEmpty()) {
+ stepInfo.setDescriptionBundle(defaultBundleName);
+ } else {
+ stepInfo.setDescriptionBundle(authoringStep.descriptionBundle());
+ }
+
+ if (authoringStep.descriptionKey().isEmpty()) {
+ stepInfo.setDescriptionKey(
+ String.join(".",
+ authoringStep.component().getSimpleName(),
+ DEFAULT_DESCRIPTION_KEY));
+ } else {
+ stepInfo.setDescriptionKey(authoringStep.descriptionKey());
+ }
+
+ return stepInfo;
+ }
+
+ /**
+ * Retrieves a list of all content types currently available on the system.
+ *
+ * @return A list of all available content types.
+ */
+ public List getAvailableContentTypes() {
+ return Collections.unmodifiableList(availableContentTypes);
+ }
+
+}
diff --git a/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypes.java b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypes.java
new file mode 100644
index 000000000..5d41b9be2
--- /dev/null
+++ b/ccm-cms/src/main/java/org/librecms/contenttypes/ContentTypes.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.librecms.contenttypes;
+
+import org.librecms.contentsection.ContentItem;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used on module classes like {@link org.librecms.Cms} to
+ * inform the system which content types are provided by the module.
+ *
+ * @author Jens Pelzetter
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ContentTypes {
+
+ Class extends ContentItem>[] value();
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java
index 36d67e916..2edbeba64 100644
--- a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java
+++ b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java
@@ -34,9 +34,11 @@ import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import java.util.Arrays;
+import java.util.List;
import java.util.ServiceLoader;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.stream.Collectors;
/**
* Maps between configuration classes and the settings stored in the database.
@@ -67,9 +69,8 @@ public class ConfigurationManager {
final ServiceLoader modules = ServiceLoader.load(
CcmModule.class);
- final SortedSet> configurations = new TreeSet<>((c1, c2) -> {
- return c1.getName().compareTo(c2.getName());
- });
+ final SortedSet> configurations = new TreeSet<>(
+ (conf1, conf2) -> conf1.getName().compareTo(conf2.getName()));
for (CcmModule module : modules) {
final Module annotation = module.getClass().getAnnotation(
@@ -79,9 +80,10 @@ public class ConfigurationManager {
continue;
}
- Arrays.stream(annotation.configurations()).forEach(c -> {
- configurations.add(c);
- });
+ final List> moduleConfs = Arrays.stream(
+ annotation.configurations()).collect(Collectors.toList());
+
+ configurations.addAll(moduleConfs);
}
return configurations;