diff --git a/ccm-pages/lib/freemarker.jar b/ccm-pages/lib/freemarker.jar new file mode 100644 index 000000000..75edf6c03 Binary files /dev/null and b/ccm-pages/lib/freemarker.jar differ diff --git a/ccm-pages/src/org/libreccm/l10n/L10NConstants.java b/ccm-pages/src/org/libreccm/l10n/L10NConstants.java new file mode 100644 index 000000000..73f3b20a0 --- /dev/null +++ b/ccm-pages/src/org/libreccm/l10n/L10NConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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.l10n; + +/** + * + * @author Jens Pelzetter + */ +public final class L10NConstants { + + public static final String L10N_XML_NS = "http://l10n.libreccm.org"; + + private L10NConstants() { + //Nothing + } + +} diff --git a/ccm-pages/src/org/libreccm/l10n/LocalizedString.java b/ccm-pages/src/org/libreccm/l10n/LocalizedString.java new file mode 100644 index 000000000..fb21d3732 --- /dev/null +++ b/ccm-pages/src/org/libreccm/l10n/LocalizedString.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 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.l10n; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.libreccm.l10n.jaxb.LocalizedStringValuesAdapter; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "localized-string", + namespace = L10NConstants.L10N_XML_NS) +@XmlAccessorType(XmlAccessType.FIELD) +public class LocalizedString implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * The localised values of the string. + */ + @XmlElement(name = "values", namespace = L10NConstants.L10N_XML_NS) + @XmlJavaTypeAdapter(LocalizedStringValuesAdapter.class) + private Map values; + + /** + * Constructor. Only creates the initial, empty map for new instances. + */ + public LocalizedString() { + values = new HashMap<>(); + } + + /** + * Get all localised values. + * + * @return A unmodifiable {@code Map} containing all localised values of + * this localised string. + */ + public Map getValues() { + if (values == null) { + return null; + } else { + return Collections.unmodifiableMap(values); + } + } + + /** + * Setter for replacing the complete {@code Map} of values. Only to be used + * by JPA and the Repository classes in the package. + * + * @param values The new map of values. + */ + protected void setValues(final Map values) { + if (values == null) { + this.values = new HashMap<>(); + } else { + this.values = new HashMap<>(values); + } + } + + /** + * Retrieves the values for the default locale. + * + * @return The localised value for the default locale of the system the + * application is running on. In most cases this is not what you + * want. Use {@link #getValue(java.util.Locale)} instead. + */ + public String getValue() { + return getValue(Locale.getDefault()); + } + + /** + * Retrieves the localised value of a locale. + * + * @param locale The locale for which the value shall be retrieved. + * + * @return The localised for the {@code locale} or {@code null} if there is + * no value for the provided locale. + */ + public String getValue(final Locale locale) { + return values.get(locale); + } + + /** + * Add a new localised value for a locale. If there is already a value for + * the provided locale the value is replaced with the new value. + * + * @param locale The locale of the provided value. + * @param value The localised value for the provided locale. + */ + public void addValue(final Locale locale, final String value) { + values.put(locale, value); + } + + /** + * Removes the value for the provided locale. + * + * @param locale The locale for which the value shall be removed. + */ + public void removeValue(final Locale locale) { + values.remove(locale); + } + + /** + * Checks if a localised string instance has a value for a locale. + * + * @param locale The locale. + * + * @return {@code true} if this localised string has a value for the + * provided locale, {@code false} if not. + */ + public boolean hasValue(final Locale locale) { + return values.containsKey(locale); + } + + /** + * Retrieves all present locales. + * + * @return A {@link Set} containing all locales for which this localised + * string has values. + */ + @JsonIgnore + public Set getAvailableLocales() { + return values.keySet(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + Objects.hashCode(this.values); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof LocalizedString)) { + return false; + } + final LocalizedString other = (LocalizedString) obj; + if (!other.canEqual(this)) { + return false; + } + + return Objects.equals(values, other.getValues()); + } + + public boolean canEqual(final Object obj) { + return obj instanceof LocalizedString; + } + + @Override + public String toString() { + return String.format( + "%s{ " + + "%s" + + " }", + super.toString(), + Objects.toString(values)); + } + +} diff --git a/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValue.java b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValue.java new file mode 100644 index 000000000..260a45dd6 --- /dev/null +++ b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValue.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 org.libreccm.l10n.jaxb; + +import static org.libreccm.l10n.L10NConstants.*; + +import java.io.Serializable; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlValue; + +/** + * + * @author Jens Pelzetter + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class LocalizedStringValue implements Serializable { + + private static final long serialVersionUID = 8435485565736441379L; + + @XmlAttribute(name = "lang", namespace = L10N_XML_NS) + private String locale; + + @XmlValue + private String value; + + public String getLocale() { + return locale; + } + + public void setLocale(final String locale) { + this.locale = locale; + } + + public String getValue() { + return value; + } + + public void setValue(final String value) { + this.value = value; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(locale); + hash = 97 * hash + Objects.hashCode(value); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LocalizedStringValue)) { + return false; + } + final LocalizedStringValue other = (LocalizedStringValue) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(locale, other.getLocale())) { + return false; + } + return Objects.equals(value, other.getValue()); + } + + public boolean canEqual(final Object obj) { + return obj instanceof LocalizedStringValue; + } + + @Override + public final String toString() { + return toString(""); + } + + public String toString(final String data) { + + return String.format("%s{ " + + "locale = %s, " + + "value = \"%s\"%s" + + " }", + super.toString(), + Objects.toString(locale), + value, + data); + } + +} diff --git a/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValues.java b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValues.java new file mode 100644 index 000000000..ee74ccc20 --- /dev/null +++ b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValues.java @@ -0,0 +1,98 @@ +/* + * 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.l10n.jaxb; + +import static org.libreccm.l10n.L10NConstants.*; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +/** + * + * @author Jens Pelzetter + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class LocalizedStringValues implements Serializable { + + private static final long serialVersionUID = 1L; + + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "value", namespace = L10N_XML_NS) + private List values; + + public List getValues() { + return new ArrayList<>(values); + } + + public void setValues(final List values) { + this.values = new ArrayList<>(values); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 41 * hash + Objects.hashCode(values); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LocalizedStringValues)) { + return false; + } + final LocalizedStringValues other = (LocalizedStringValues) obj; + if (!other.canEqual(this)) { + return false; + } + return Objects.equals(values, other.getValues()); + } + + public boolean canEqual(final Object obj) { + return obj instanceof LocalizedStringValues; + } + + @Override + public final String toString() { + return toString(""); + } + + public String toString(final String data) { + return String.format("%s{ " + + "values = %s%s" + + " }", + super.toString(), + Objects.toString(values), + data); + } + +} diff --git a/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValuesAdapter.java b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValuesAdapter.java new file mode 100644 index 000000000..bb472b1af --- /dev/null +++ b/ccm-pages/src/org/libreccm/l10n/jaxb/LocalizedStringValuesAdapter.java @@ -0,0 +1,77 @@ +/* + * 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.l10n.jaxb; + +import org.libreccm.l10n.LocalizedString; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +/** + * JAXB adapter for {@link LocalizedString#values} to produce a more compact XML + * for the values map. + * + * @author Jens Pelzetter + */ +public class LocalizedStringValuesAdapter + extends XmlAdapter> { + + @Override + public Map unmarshal(final LocalizedStringValues values) + throws Exception { + + return values + .getValues() + .stream() + .collect(Collectors.toMap(value -> new Locale(value.getLocale()), + value -> value.getValue())); + + } + + @Override + public LocalizedStringValues marshal(final Map values) + throws Exception { + + final List list = values + .entrySet() + .stream() + .map(this::generateValue) + .collect(Collectors.toList()); + + final LocalizedStringValues result = new LocalizedStringValues(); + result.setValues(list); + + return result; + } + + private LocalizedStringValue generateValue( + final Map.Entry entry) { + + final LocalizedStringValue value = new LocalizedStringValue(); + value.setLocale(entry.getKey().toString()); + value.setValue(entry.getValue()); + + return value; + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/ApplicationTemplate.java b/ccm-pages/src/org/libreccm/theming/ApplicationTemplate.java new file mode 100644 index 000000000..5df031e09 --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/ApplicationTemplate.java @@ -0,0 +1,117 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.libreccm.theming; + +import static org.libreccm.theming.ThemeConstants.*; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "application-template", namespace = THEMES_XML_NS) +@XmlAccessorType(XmlAccessType.FIELD) +public class ApplicationTemplate { + + @XmlElement(name = "application-name", namespace = THEMES_XML_NS) + private String applicationName; + + @XmlElement(name = "application-class", namespace = THEMES_XML_NS) + private String applicationClass; + + @XmlElement(name = "template", namespace = THEMES_XML_NS) + private String template; + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(final String applicationName) { + this.applicationName = applicationName; + } + + public String getApplicationClass() { + return applicationClass; + } + + public void setApplicationClass(final String applicationClass) { + this.applicationClass = applicationClass; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(final String template) { + this.template = template; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + Objects.hashCode(applicationName); + hash = 79 * hash + Objects.hashCode(applicationClass); + hash = 79 * hash + Objects.hashCode(template); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ApplicationTemplate)) { + return false; + } + final ApplicationTemplate other = (ApplicationTemplate) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(applicationName, other.getApplicationName())) { + return false; + } + if (!Objects.equals(applicationClass, other.getApplicationClass())) { + return false; + } + + return Objects.equals(template, other.getTemplate()); + } + + public boolean canEqual(final Object obj) { + + return obj instanceof ApplicationTemplate; + } + + @Override + public final String toString() { + return toString(""); + } + + public String toString(final String data) { + + return String.format("%s{ " + + "applicationName = \"%s\", " + + "applicationClass = \"%s\", " + + "template = \"%s\"%s" + + " }", + super.toString(), + applicationName, + applicationClass, + template, + data + ); + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/ContentItemTemplate.java b/ccm-pages/src/org/libreccm/theming/ContentItemTemplate.java new file mode 100644 index 000000000..bf235be16 --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/ContentItemTemplate.java @@ -0,0 +1,165 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.libreccm.theming; + +import static org.libreccm.theming.ThemeConstants.*; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "contentitem-template", namespace = THEMES_XML_NS) +@XmlAccessorType(XmlAccessType.FIELD) +public class ContentItemTemplate { + + @XmlElement(name = "view", namespace = THEMES_XML_NS) + private ContentItemViews view; + + @XmlElement(name = "contenttype", namespace = THEMES_XML_NS) + private String contentType; + + @XmlElement(name = "style", namespace = THEMES_XML_NS) + private String style; + + @XmlElement(name = "contentsection", namespace = THEMES_XML_NS) + private String contentSection; + + @XmlElement(name = "category", namespace = THEMES_XML_NS) + private String category; + + @XmlElement(name = "template", namespace = THEMES_XML_NS) + private String template; + + public ContentItemViews getView() { + return view; + } + + public void setView(final ContentItemViews view) { + this.view = view; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(final String contentType) { + this.contentType = contentType; + } + + public String getStyle() { + return style; + } + + public void setStyle(final String style) { + this.style = style; + } + + public String getContentSection() { + return contentSection; + } + + public void setContentSection(final String contentSection) { + this.contentSection = contentSection; + } + + public String getCategory() { + return category; + } + + public void setCategory(final String category) { + this.category = category; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(final String template) { + this.template = template; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 73 * hash + Objects.hashCode(view); + hash = 73 * hash + Objects.hashCode(contentType); + hash = 73 * hash + Objects.hashCode(style); + hash = 73 * hash + Objects.hashCode(contentSection); + hash = 73 * hash + Objects.hashCode(category); + hash = 73 * hash + Objects.hashCode(template); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ContentItemTemplate)) { + return false; + } + final ContentItemTemplate other = (ContentItemTemplate) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(contentType, other.getContentType())) { + return false; + } + if (!Objects.equals(style, other.getStyle())) { + return false; + } + if (!Objects.equals(contentSection, other.getContentSection())) { + return false; + } + if (!Objects.equals(category, other.getCategory())) { + return false; + } + if (view != other.getView()) { + return false; + } + return Objects.equals(template, other.getTemplate()); + } + + public boolean canEqual(final Object obj) { + + return obj instanceof ContentItemTemplate; + } + + @Override + public final String toString() { + + return toString(""); + } + + public String toString(final String data) { + + return String.format("%s{ " + + "contentType = \"%s\", " + + "style = \"%s\", " + + "contentSection = \"%s\", " + + "category = \"%s\"" + + "template = \"%s\"%s" + + " }", + super.toString(), + contentType, + style, + contentSection, + category, + template, + data); + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/ContentItemViews.java b/ccm-pages/src/org/libreccm/theming/ContentItemViews.java new file mode 100644 index 000000000..e62829390 --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/ContentItemViews.java @@ -0,0 +1,18 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.libreccm.theming; + +/** + * + * @author Jens Pelzetter + */ +public enum ContentItemViews { + + DETAIL, + GREETING_ITEM, + LIST, + PORTLET_ITEM, +} diff --git a/ccm-pages/src/org/libreccm/theming/FreeMarkerPresentationManager.java b/ccm-pages/src/org/libreccm/theming/FreeMarkerPresentationManager.java index 6575203dc..d06c0e459 100644 --- a/ccm-pages/src/org/libreccm/theming/FreeMarkerPresentationManager.java +++ b/ccm-pages/src/org/libreccm/theming/FreeMarkerPresentationManager.java @@ -8,17 +8,16 @@ import com.arsdigita.util.UncheckedWrapperException; import com.arsdigita.web.Web; import com.arsdigita.xml.Document; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; +import org.libreccm.theming.manifest.ThemeManifest; +import org.libreccm.theming.manifest.ThemeManifestUtil; import org.w3c.dom.Node; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -50,7 +49,9 @@ public class FreeMarkerPresentationManager implements PresentationManager { final String defaultTheme; if (subSite == null) { - defaultTheme = ThemeDirector.getThemeDirector().getDefaultTheme() + defaultTheme = ThemeDirector + .getThemeDirector() + .getDefaultTheme() .getURL(); } else { defaultTheme = subSite.getStyleDirectory(); @@ -77,46 +78,65 @@ public class FreeMarkerPresentationManager implements PresentationManager { } themePathBuilder.append(selectedTheme).append("/"); final String themePath = themePathBuilder.toString(); - final String themeManifestPath = String.format("%stheme-manifest.json", - themePath); + final String themeManifestPath = String.format( + "%s" + ThemeConstants.THEME_MANIFEST_JSON, themePath); final ServletContext servletContext = Web.getServletContext(); // final String themeManifest = ""; - final String themeManifest = new BufferedReader( - new InputStreamReader( - servletContext.getResourceAsStream(themeManifestPath), - StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.joining(System.lineSeparator())); +// final String themeManifest = new BufferedReader( +// new InputStreamReader( +// servletContext.getResourceAsStream(themeManifestPath), +// StandardCharsets.UTF_8)) +// .lines() +// .collect(Collectors.joining(System.lineSeparator())); +// +// String name = "???"; +// final JsonFactory jsonFactory = new JsonFactory(); +// try { +// final JsonParser parser = jsonFactory.createParser(servletContext +// .getResourceAsStream(themeManifestPath)); +// +// while (!parser.isClosed()) { +// +// final JsonToken token = parser.nextToken(); +// if (JsonToken.FIELD_NAME.equals(token)) { +// final String fieldName = parser.getCurrentName(); +// +// if ("name".equals(fieldName)) { +// +// final JsonToken valueToken = parser.nextToken(); +// final String value = parser.getValueAsString(); +// name = value; +// } +// } +// +// } +// +// } catch (IOException ex) { +// throw new UncheckedWrapperException(ex); +// } + final InputStream manifestInputStream = servletContext + .getResourceAsStream(themeManifestPath); + final ThemeManifestUtil manifestUtil = ThemeManifestUtil.getInstance(); - String name = "???"; - final JsonFactory jsonFactory = new JsonFactory(); + final ThemeManifest manifest = manifestUtil + .loadManifest(manifestInputStream, + themeManifestPath); + + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JaxbAnnotationModule()); + final Templates templates; try { - final JsonParser parser = jsonFactory.createParser(servletContext - .getResourceAsStream(themeManifestPath)); - - while (!parser.isClosed()) { - - final JsonToken token = parser.nextToken(); - if (JsonToken.FIELD_NAME.equals(token)) { - final String fieldName = parser.getCurrentName(); - - if ("name".equals(fieldName)) { - - final JsonToken valueToken = parser.nextToken(); - final String value = parser.getValueAsString(); - name = value; - } - } - - } - + templates = objectMapper.readValue( + servletContext.getResourceAsStream( + String.format("%stemplates.json", themePath)), + Templates.class); } catch (IOException ex) { throw new UncheckedWrapperException(ex); } + // ToDo - // Parse theme manifest // Get Freemarker templates by File API or by HTTP? // Or via getResourceAsStream? response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); @@ -152,12 +172,21 @@ public class FreeMarkerPresentationManager implements PresentationManager { .append("\n"); writer .append("themeManifest: ") - .append(themeManifest) + .append(manifest.toString()) .append("\n"); writer .append("theme name: ") - .append(name) + .append(manifest.getName()) .append("\n"); + writer + .append("Application templates:\n"); + for (final ApplicationTemplate template : templates + .getApplications()) { + writer + .append("\t") + .append(template.toString()) + .append("\n"); + } } catch (IOException ex) { throw new UncheckedWrapperException(ex); } diff --git a/ccm-pages/src/org/libreccm/theming/Templates.java b/ccm-pages/src/org/libreccm/theming/Templates.java new file mode 100644 index 000000000..07169fc27 --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/Templates.java @@ -0,0 +1,83 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.libreccm.theming; + + +import static org.libreccm.theming.ThemeConstants.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "templates", namespace = THEMES_XML_NS) +@XmlAccessorType(XmlAccessType.FIELD) +public class Templates { + + @XmlElementWrapper(name = "applications", namespace = THEMES_XML_NS) + @XmlElement(name = "applications", namespace = THEMES_XML_NS) + private List applications; + + @XmlElementWrapper(name = "contentitems", namespace = THEMES_XML_NS) + @XmlElement(name = "contentitems", namespace = THEMES_XML_NS) + private List contentItems; + + public Templates() { + + applications = new ArrayList<>(); + contentItems= new ArrayList<>(); + } + + public List getApplications() { + + return Collections.unmodifiableList(applications); + } + + public void addApplication(final ApplicationTemplate template) { + + applications.add(template); + } + + public void removeApplication(final ApplicationTemplate template) { + + applications.remove(template); + } + + public void setApplications(final List applications) { + + this.applications = new ArrayList<>(applications); + } + + public List getContentItems() { + + return Collections.unmodifiableList(contentItems); + } + + public void addContentItem(final ContentItemTemplate template) { + + contentItems.add(template); + } + + public void removeContentItem(final ContentItemTemplate template) { + + contentItems.remove(template); + } + + public void setContentItems(final List contentItems) { + + this.contentItems = new ArrayList<>(contentItems); + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/ThemeConstants.java b/ccm-pages/src/org/libreccm/theming/ThemeConstants.java new file mode 100644 index 000000000..3ed285aca --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/ThemeConstants.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * + * @author Jens Pelzetter + */ +public final class ThemeConstants { + + public static final String PAGE_PARAMETER_TEMPLATE = "template"; + + public final static String THEME_MANIFEST_JSON = "theme.json"; + public final static String THEME_MANIFEST_XML = "theme.xml"; + + public final static String THEMES_XML_NS = "http://themes.libreccm.org"; + + private ThemeConstants() { + //Nothing + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifest.java b/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifest.java new file mode 100644 index 000000000..76133ed7e --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifest.java @@ -0,0 +1,240 @@ +/* + * 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.manifest; + +import org.libreccm.l10n.LocalizedString; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import static org.libreccm.theming.ThemeConstants.*; + +import java.io.Serializable; + +/** + * Each theme contains a Manifest (either in XML or JSON format) which provides + * informations about the theme. + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "theme", namespace = THEMES_XML_NS) +@XmlAccessorType(XmlAccessType.FIELD) +public class ThemeManifest implements Serializable { + + private static final long serialVersionUID = 699497658459398231L; + + /** + * The name of the theme. Usually the same as the name of directory which + * contains the theme. + */ + @XmlElement(name = "name", namespace = THEMES_XML_NS) + private String name; + + /** + * The type of the theme, for example XSLT. + */ + @XmlElement(name = "type", namespace = THEMES_XML_NS) + private String type; + + @XmlElement(name = "master-theme", namespace = THEMES_XML_NS) + private String masterTheme; + + /** + * The (localised) title of the theme. + */ + @XmlElement(name = "title", namespace = THEMES_XML_NS) + private LocalizedString title; + + /** + * A (localised) description of the theme. + */ + @XmlElement(name = "description", namespace = THEMES_XML_NS) + private LocalizedString description; + + /** + * The templates provided by the theme. + */ + @XmlElementWrapper(name = "templates", namespace = THEMES_XML_NS) + @XmlElement(name = "template", namespace = THEMES_XML_NS) + private List templates; + + /** + * Path of the default template. + */ + @XmlElement(name = "default-template", namespace = THEMES_XML_NS) + private String defaultTemplate; + + public ThemeManifest() { + templates = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public String getMasterTheme() { + return masterTheme; + } + + public void setMasterTheme(final String masterTheme) { + this.masterTheme = masterTheme; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(final LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(final LocalizedString description) { + this.description = description; + } + + public List getTemplates() { + return Collections.unmodifiableList(templates); + } + + public void setTemplates(final List templates) { + this.templates = new ArrayList<>(templates); + } + + public void addThemeTemplate(final ThemeTemplate template) { + templates.add(template); + } + + public void removeThemeTemplate(final ThemeTemplate template) { + templates.remove(template); + } + + public String getDefaultTemplate() { + return defaultTemplate; + } + + public void setDefaultTemplate(final String defaultTemplate) { + this.defaultTemplate = defaultTemplate; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(name); + hash = 83 * hash + Objects.hashCode(type); + hash = 83 * hash + Objects.hashCode(masterTheme); + hash = 83 * hash + Objects.hashCode(title); + hash = 83 * hash + Objects.hashCode(description); + hash = 83 * hash + Objects.hashCode(templates); + hash = 83 * hash + Objects.hashCode(defaultTemplate); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ThemeManifest)) { + return false; + } + final ThemeManifest other = (ThemeManifest) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(name, other.getName())) { + return false; + } + if (!Objects.equals(type, other.getType())) { + return false; + } + if (!Objects.equals(masterTheme, other.getMasterTheme())) { + return false; + } + if (!Objects.equals(title, other.getTitle())) { + return false; + } + if (!Objects.equals(description, other.getDescription())) { + return false; + } + if (!Objects.equals(templates, other.getTemplates())) { + return false; + } + return Objects.equals(defaultTemplate, other.getDefaultTemplate()); + } + + public boolean canEqual(final Object obj) { + return obj instanceof ThemeManifest; + } + + @Override + public String toString() { + return toString(""); + } + + public String toString(final String data) { + + return String.format("%s{ " + + "name = \"%s\", " + + "type = \"%s\", " + + "masterTheme = \"%s\", " + + "title = \"%s\", " + + "description = \"%s\", " + + "templates = %s, " + + "defaultTemplate%s" + + " }", + super.toString(), + name, + type, + masterTheme, + Objects.toString(title), + Objects.toString(description), + Objects.toString(templates), + defaultTemplate, + data); + + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifestUtil.java b/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifestUtil.java new file mode 100644 index 000000000..5841515bd --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/manifest/ThemeManifestUtil.java @@ -0,0 +1,202 @@ +/* + * 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.manifest; + +import com.arsdigita.util.UncheckedWrapperException; + +import static org.libreccm.theming.ThemeConstants.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Serializable; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; + + +/** + * A Utility class for loading them manifest file of a theme. + * + * @author Jens Pelzetter + */ +public class ThemeManifestUtil implements Serializable { + + private static final long serialVersionUID = -7650437144515619682L; + + private static final ThemeManifestUtil INSTANCE = new ThemeManifestUtil(); + + private ThemeManifestUtil() {}; + + public static final ThemeManifestUtil getInstance() { + + return INSTANCE; + } + + /** + * Reads the manifest file at {@code path}. + * + * @param path The path of the manifest file. + * + * @return The parsed manifest file. + */ + public ThemeManifest loadManifest(final Path path) { + +// final String pathStr = path.toString().toLowerCase(Locale.ROOT); + final BufferedReader reader; + try { + reader = Files.newBufferedReader(path, Charset.forName("UTF-8")); + } catch (IOException ex) { + throw new UncheckedWrapperException(ex); + } + + return parseManifest(reader, path.toString()); + +// final ObjectMapper mapper; +// if (pathStr.endsWith(THEME_MANIFEST_JSON)) { +// mapper = new ObjectMapper(); +// } else if (pathStr.endsWith(THEME_MANIFEST_XML)) { +// final JacksonXmlModule xmlModule = new JacksonXmlModule(); +// mapper = new XmlMapper(xmlModule); +// } else { +// throw new IllegalArgumentException(String +// .format("The provided path \"%s\" does not point to a theme " +// + "manifest file.", +// path.toString())); +// } +// +// mapper.registerModule(new JaxbAnnotationModule()); +// +// final ThemeManifest manifest; +// try { +// manifest = mapper.readValue(reader, ThemeManifest.class); +// } catch (IOException ex) { +// throw new UnexpectedErrorException(ex); +// } +// return manifest; + } + + public ThemeManifest loadManifest(final InputStream inputStream, + final String fileName) { + + final InputStreamReader reader; + try { + reader = new InputStreamReader(inputStream, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new UncheckedWrapperException(ex); + } + + return parseManifest(reader, fileName); + +// final ObjectMapper mapper; +// if (fileName.endsWith(THEME_MANIFEST_JSON)) { +// mapper = new ObjectMapper(); +// } else if (fileName.endsWith(THEME_MANIFEST_XML)) { +// final JacksonXmlModule xmlModule = new JacksonXmlModule(); +// mapper = new XmlMapper(xmlModule); +// } else { +// throw new IllegalArgumentException(String +// .format("The provided path \"%s\" does not point to a theme " +// + "manifest file.", +// fileName)); +// } +// +// mapper.registerModule(new JaxbAnnotationModule()); +// +// final ThemeManifest manifest; +// try { +// manifest = mapper.readValue(reader, ThemeManifest.class); +// } catch (IOException ex) { +// throw new UnexpectedErrorException(ex); +// } +// return manifest; + } + + public String serializeManifest(final ThemeManifest manifest, + final String format) { + + final ObjectMapper mapper; + + switch (format) { + case THEME_MANIFEST_JSON: + mapper = new ObjectMapper(); + break; + case THEME_MANIFEST_XML: + final JacksonXmlModule xmlModule = new JacksonXmlModule(); + mapper = new XmlMapper(xmlModule); + break; + default: + throw new IllegalArgumentException( + "Unsupported format for ThemeManifest"); + } + + mapper.registerModule(new JaxbAnnotationModule()); + mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + + final StringWriter writer = new StringWriter(); + try { + mapper.writeValue(writer, manifest); + } catch (IOException ex) { + throw new UncheckedWrapperException(ex); + } + + return writer.toString(); + } + + private ThemeManifest parseManifest(final Reader reader, + final String path) { + + final String pathStr = path.toLowerCase(Locale.ROOT); + + final ObjectMapper mapper; + if (pathStr.endsWith(THEME_MANIFEST_JSON)) { + mapper = new ObjectMapper(); + } else if (pathStr.endsWith(THEME_MANIFEST_XML)) { + final JacksonXmlModule xmlModule = new JacksonXmlModule(); + mapper = new XmlMapper(xmlModule); + } else { + throw new IllegalArgumentException(String + .format("The provided path \"%s\" does not point to a theme " + + "manifest file.", + path)); + } + + mapper.registerModule(new JaxbAnnotationModule()); + + final ThemeManifest manifest; + try { + manifest = mapper.readValue(reader, ThemeManifest.class); + } catch (IOException ex) { + throw new UncheckedWrapperException(ex); + } + return manifest; + } + +} diff --git a/ccm-pages/src/org/libreccm/theming/manifest/ThemeTemplate.java b/ccm-pages/src/org/libreccm/theming/manifest/ThemeTemplate.java new file mode 100644 index 000000000..236862e77 --- /dev/null +++ b/ccm-pages/src/org/libreccm/theming/manifest/ThemeTemplate.java @@ -0,0 +1,160 @@ +/* + * 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.manifest; + +import org.libreccm.l10n.LocalizedString; + +import java.io.Serializable; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Informations about a template provided by a theme. + * + * @author Jens Pelzetter + */ +@XmlRootElement(name = "template", namespace = "http://themes.libreccm.org") +@XmlAccessorType(XmlAccessType.FIELD) +public class ThemeTemplate implements Serializable { + + private static final long serialVersionUID = -9034588759798295569L; + + /** + * The name of the template (usually the filename). + */ + @XmlElement(name = "name", namespace = "http://themes.libreccm.org") + private String name; + + /** + * The (localised) title of the template. + */ + @XmlElement(name = "title", namespace = "http://themes.libreccm.org") + private LocalizedString title; + + /** + * A (localised) description of the template. + */ + @XmlElement(name = "description", namespace = "http://themes.libreccm.org") + private LocalizedString description; + + /** + * Path of template relative to the directory of the theme. + */ + @XmlElement(name = "path", namespace = "http://themes.libreccm.org") + private String path; + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(final LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(final LocalizedString description) { + this.description = description; + } + + public String getPath() { + return path; + } + + public void setPath(final String path) { + this.path = path; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Objects.hashCode(name); + hash = 67 * hash + Objects.hashCode(title); + hash = 67 * hash + Objects.hashCode(description); + hash = 67 * hash + Objects.hashCode(path); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ThemeTemplate)) { + return false; + } + final ThemeTemplate other = (ThemeTemplate) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(name, other.getName())) { + return false; + } + if (!Objects.equals(path, other.getPath())) { + return false; + } + if (!Objects.equals(title, other.getTitle())) { + return false; + } + return Objects.equals(description, other.getDescription()); + } + + public boolean canEqual(final Object obj) { + return obj instanceof ThemeTemplate; + } + + @Override + public String toString() { + return toString(""); + } + + public String toString(final String data) { + + return String.format("%s{ " + + "name = \"%s\", " + + "title = %s, " + + "description = %s, " + + "path = \"%s\"%s" + + " }", + super.toString(), + name, + Objects.toString(title), + Objects.toString(description), + path, + data); + } + +}