CCM NG: First part of Theme Utility functions
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5210 8810af33-2d31-482b-a856-94f89814c4df
Former-commit-id: 6d05383adf
pull/2/head
parent
3bddb3da66
commit
46aa35b8cf
|
|
@ -202,6 +202,16 @@
|
|||
<version>0.9.11</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.primefaces</groupId>
|
||||
<artifactId>primefaces</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.libreccm.theming.ThemeInfo;
|
||||
import org.libreccm.theming.ThemeProvider;
|
||||
import org.libreccm.theming.Themes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.enterprise.context.RequestScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
||||
/**
|
||||
* Methods for reading configuration options from the theme. Most themes have
|
||||
* some configuration options. The class provides several methods for reading
|
||||
* settings from different file formats. The following formats are currently
|
||||
* supported:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Java Properties files</li>
|
||||
* <li>JSON files</li>
|
||||
* <li>XML files</li>
|
||||
* </ul>
|
||||
*
|
||||
* For property files and JSON files it is expected that the name of the setting
|
||||
* is the key. For XML files the following simple format is expected:
|
||||
*
|
||||
* <pre>
|
||||
* <settings>
|
||||
* <setting key="nameOfSetting">value of setting</setting>
|
||||
* <setting key="anotherSetting">value of setting</setting>
|
||||
* ...
|
||||
* </settings/>
|
||||
* </pre>
|
||||
*
|
||||
* The file type is determined by the file extension of the file to read.
|
||||
*
|
||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||
*/
|
||||
@RequestScoped
|
||||
public class SettingsUtils {
|
||||
|
||||
private static final Logger LOGGER = LogManager
|
||||
.getLogger(SettingsUtils.class);
|
||||
|
||||
/**
|
||||
* Retrieve the value of a setting.
|
||||
*
|
||||
* @param fromTheme The theme from which the setting is read.
|
||||
* @param themeProvider The provider of the the theme.
|
||||
* @param filePath The path of the settings file relative to the root
|
||||
* directory of the theme.
|
||||
* @param settingName The name of the setting.
|
||||
*
|
||||
* @return The value of the setting as String.
|
||||
*/
|
||||
public String getSetting(final ThemeInfo fromTheme,
|
||||
final ThemeProvider themeProvider,
|
||||
final String filePath,
|
||||
final String settingName) {
|
||||
|
||||
return getSetting(fromTheme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of a setting. If the provided file does not have a
|
||||
* value for the setting the provided default value is used.
|
||||
*
|
||||
* @param fromTheme The theme from which the setting is read.
|
||||
* @param themeProvider The provider of the theme.
|
||||
* @param filePath The path of the settings file relative to the root
|
||||
* directory of the theme.
|
||||
* @param settingName The name of the setting.
|
||||
* @param defaultValue The default value for the setting.
|
||||
*
|
||||
* @return The value of the setting as String. If the provided file has no
|
||||
* value for the setting the provided default value is used.
|
||||
*/
|
||||
public String getSetting(final ThemeInfo fromTheme,
|
||||
final ThemeProvider themeProvider,
|
||||
final String filePath,
|
||||
final String settingName,
|
||||
final String defaultValue) {
|
||||
|
||||
Objects.requireNonNull(fromTheme);
|
||||
Objects.requireNonNull(themeProvider);
|
||||
Objects.requireNonNull(filePath);
|
||||
Objects.requireNonNull(settingName);
|
||||
|
||||
if (filePath.isEmpty() || filePath.matches("\\s*")) {
|
||||
throw new IllegalArgumentException(
|
||||
"The path of the settings file can't be empty.");
|
||||
}
|
||||
|
||||
if (settingName.isEmpty() || settingName.matches("\\s*")) {
|
||||
throw new IllegalArgumentException(
|
||||
"The name of the settings file can't be empty.");
|
||||
}
|
||||
|
||||
final Optional<InputStream> fileInputStream = themeProvider
|
||||
.getThemeFileAsStream(fromTheme.getName(),
|
||||
fromTheme.getVersion(),
|
||||
filePath);
|
||||
if (!fileInputStream.isPresent()) {
|
||||
LOGGER.warn(
|
||||
"Configuration file \"{}\" was not found in theme \"{}\".",
|
||||
filePath,
|
||||
fromTheme.getName());
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (filePath.toLowerCase(Locale.ROOT).endsWith(".properties")) {
|
||||
return getSettingFromPropertiesFile(fileInputStream.get(),
|
||||
settingName)
|
||||
.orElse(defaultValue);
|
||||
} else if (filePath.toLowerCase(Locale.ROOT).endsWith(".xml")) {
|
||||
return getSettingFromXmlFile(fileInputStream.get(), settingName)
|
||||
.orElse(defaultValue);
|
||||
} else if (filePath.toLowerCase(Locale.ROOT).endsWith(".json")) {
|
||||
return getSettingFromJsonFile(fileInputStream.get(), settingName)
|
||||
.orElse(defaultValue);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"The file path must point to file in a supported format. "
|
||||
+ "Supported formats are \".properties\", \".xml\" and \".json\".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the boolean value of a setting. This method reads the value of a
|
||||
* setting using null null null null null null null null null null null null
|
||||
* null null null null null null null null null null {@link #getSetting(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
|
||||
* and converts it into a {@code boolean} value using
|
||||
* {@link Boolean#parseBoolean(java.lang.String)}.
|
||||
*
|
||||
* @param fromTheme The name of the theme from which the setting is
|
||||
* read.
|
||||
* @param themeProvider The provider of the theme.
|
||||
* @param filePath The path of the settings file relative to the root
|
||||
* directory of the theme.
|
||||
* @param settingName The name of the setting.
|
||||
* @param defaultValue The default value.
|
||||
*
|
||||
* @return The value of the setting as {@code boolean} or the default value
|
||||
* if the configuration file has no value for the setting.
|
||||
*/
|
||||
public boolean getSettingAsBoolean(final ThemeInfo fromTheme,
|
||||
final ThemeProvider themeProvider,
|
||||
final String filePath,
|
||||
final String settingName,
|
||||
final boolean defaultValue) {
|
||||
|
||||
final String result = getSetting(fromTheme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName,
|
||||
Boolean.toString(defaultValue));
|
||||
return Boolean.parseBoolean(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of setting and convert it to a {@code long} value.
|
||||
* This method reads the value using
|
||||
* {@link #getSetting(java.lang.String, java.lang.String, java.lang.String, java.lang.String)}
|
||||
* and converts the value to a value of type {@code long} using
|
||||
* {@link Long#parseLong(java.lang.String)}.
|
||||
*
|
||||
*
|
||||
* @param fromTheme The name of the theme from which the setting is
|
||||
* read.
|
||||
* @param themeProvider The provider of the theme.
|
||||
* @param filePath The path of the settings file relative to the root
|
||||
* directory of the theme.
|
||||
* @param settingName The name of the setting.
|
||||
* @param defaultValue The default value.
|
||||
*
|
||||
* @return The value of the setting as {@code boolean} or the default value
|
||||
* if the configuration file has no value for the setting.
|
||||
*
|
||||
* @throws NumberFormatException If the the value can't be converted into a
|
||||
* {@code long} value.
|
||||
*/
|
||||
public long getSettingAsLong(final ThemeInfo fromTheme,
|
||||
final ThemeProvider themeProvider,
|
||||
final String filePath,
|
||||
final String settingName,
|
||||
final long defaultValue) {
|
||||
|
||||
final String result = getSetting(fromTheme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName,
|
||||
Long.toString(defaultValue));
|
||||
return Long.parseLong(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of setting and convert it to a {@code double} value.
|
||||
* This method reads the value using
|
||||
* {@link #getSetting(java.lang.String, java.lang.String, java.lang.String, java.lang.String)}
|
||||
* and converts the value to a value of type {@code double} using
|
||||
* {@link Long#parseLong(java.lang.String)}.
|
||||
*
|
||||
*
|
||||
* @param fromTheme The name of the theme from which the setting is
|
||||
* read.
|
||||
* @param themeProvider The provider of the theme.
|
||||
* @param filePath The path of the settings file relative to the root
|
||||
* directory of the theme.
|
||||
* @param settingName The name of the setting.
|
||||
* @param defaultValue The default value.
|
||||
*
|
||||
* @return The value of the setting as {@code boolean} or the default value
|
||||
* if the configuration file has no value for the setting.
|
||||
*
|
||||
* @throws NumberFormatException If the the value can't be converted into a
|
||||
* {@code double} value.
|
||||
*/
|
||||
public double getSettingAsDouble(final ThemeInfo fromTheme,
|
||||
final ThemeProvider themeProvider,
|
||||
final String filePath,
|
||||
final String settingName,
|
||||
final double defaultValue) {
|
||||
|
||||
final String result = getSetting(fromTheme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName,
|
||||
settingName);
|
||||
return Double.parseDouble(result);
|
||||
}
|
||||
|
||||
private Optional<String> getSettingFromJsonFile(
|
||||
final InputStream fileInputStream,
|
||||
final String settingName) {
|
||||
|
||||
final JsonReader jsonReader = Json.createReader(fileInputStream);
|
||||
final JsonObject settings = jsonReader.readObject();
|
||||
final String result = settings.getString(settingName);
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
private Optional<String> getSettingFromPropertiesFile(
|
||||
final InputStream fileInputStream,
|
||||
final String settingName) {
|
||||
|
||||
final Properties settings = new Properties();
|
||||
try {
|
||||
settings.load(fileInputStream);
|
||||
} catch (IOException ex) {
|
||||
LOGGER.warn("Failed to load setting file.");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (settings.containsKey(settingName)) {
|
||||
return Optional.of(settings.getProperty(settingName));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSettingFromXmlFile(
|
||||
final InputStream fileInputStream,
|
||||
final String settingName) {
|
||||
|
||||
final Map<String, String> settings;
|
||||
// = new HashMap<>();
|
||||
// final XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
|
||||
// final XMLStreamReader xmlStreamReader;
|
||||
// try {
|
||||
// xmlStreamReader = xmlInputFactory
|
||||
// .createXMLStreamReader(fileInputStream);
|
||||
// } catch (XMLStreamException ex) {
|
||||
// LOGGER.error("Failed to read XML settings file.");
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// while
|
||||
|
||||
final JacksonXmlModule xmlModule = new JacksonXmlModule();
|
||||
final ObjectMapper mapper = new XmlMapper(xmlModule);
|
||||
try {
|
||||
settings = mapper
|
||||
.reader()
|
||||
.withRootName("settings")
|
||||
.readValue(fileInputStream);
|
||||
} catch (IOException ex) {
|
||||
LOGGER.error("Failed to read Xfinal XML settings file.");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (settings.containsKey(settingName)) {
|
||||
return Optional.of(settings.get(settingName));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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
|
||||
*/
|
||||
/**
|
||||
* This package provides several utility methods/functions for themes.
|
||||
* They are implemented as static methods. The implementations of the
|
||||
* {@link org.libreccm.theming.ThemeProcessor} interface are responsible for
|
||||
* making the functions available in the the templates they process.
|
||||
*/
|
||||
package org.libreccm.theming.utils;
|
||||
|
|
@ -40,10 +40,22 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
import static org.libreccm.theming.ThemeConstants.*;
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import net.sf.saxon.Configuration;
|
||||
import net.sf.saxon.TransformerFactoryImpl;
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
import net.sf.saxon.lib.ExtensionFunctionCall;
|
||||
import net.sf.saxon.lib.ExtensionFunctionDefinition;
|
||||
import net.sf.saxon.om.Item;
|
||||
import net.sf.saxon.om.Sequence;
|
||||
import net.sf.saxon.om.StructuredQName;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
import net.sf.saxon.value.SequenceType;
|
||||
import net.sf.saxon.value.StringValue;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.libreccm.theming.ProcessesThemes;
|
||||
import org.libreccm.theming.manifest.ThemeTemplate;
|
||||
import org.libreccm.theming.utils.SettingsUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -54,6 +66,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.transform.ErrorListener;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Transformer;
|
||||
|
|
@ -78,6 +91,9 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
private static final Logger LOGGER = LogManager
|
||||
.getLogger(XsltThemeProcessor.class);
|
||||
|
||||
@Inject
|
||||
private SettingsUtils settingsUtils;
|
||||
|
||||
@Override
|
||||
public String process(final Map<String, Object> page,
|
||||
final ThemeInfo theme,
|
||||
|
|
@ -160,7 +176,16 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
|
||||
final StreamSource xslFileStreamSource = new StreamSource(reader);
|
||||
final TransformerFactory transformerFactory = TransformerFactory
|
||||
.newInstance();
|
||||
.newInstance(TransformerFactoryImpl.class.getName(),
|
||||
getClass().getClassLoader());
|
||||
final TransformerFactoryImpl transformerFactoryImpl
|
||||
= (TransformerFactoryImpl) transformerFactory;
|
||||
final Configuration configuration = transformerFactoryImpl
|
||||
.getConfiguration();
|
||||
configuration.registerExtensionFunction(new Greeter());
|
||||
configuration
|
||||
.registerExtensionFunction(
|
||||
new GetSettingFunctionDefinition(theme, themeProvider));
|
||||
final Transformer transformer;
|
||||
try {
|
||||
transformer = transformerFactory.newTransformer(xslFileStreamSource);
|
||||
|
|
@ -212,4 +237,126 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
return resultWriter.toString();
|
||||
}
|
||||
|
||||
private class GetSettingFunctionDefinition extends ExtensionFunctionDefinition {
|
||||
|
||||
private final ThemeInfo theme;
|
||||
private final ThemeProvider themeProvider;
|
||||
|
||||
public GetSettingFunctionDefinition(final ThemeInfo themeInfo,
|
||||
final ThemeProvider themeProvider) {
|
||||
this.theme = themeInfo;
|
||||
this.themeProvider = themeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructuredQName getFunctionQName() {
|
||||
return new StructuredQName("ccm",
|
||||
"http://xmlns.libreccm.org",
|
||||
"getSetting");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
return new SequenceType[]{SequenceType.SINGLE_STRING,
|
||||
SequenceType.SINGLE_STRING,
|
||||
SequenceType.SINGLE_STRING};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumNumberOfArguments() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumNumberOfArguments() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(final SequenceType[] sts) {
|
||||
return SequenceType.SINGLE_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
|
||||
return new ExtensionFunctionCall() {
|
||||
|
||||
@Override
|
||||
public Sequence call(final XPathContext xPathContext,
|
||||
final Sequence[] arguments)
|
||||
throws XPathException {
|
||||
|
||||
final String result;
|
||||
final String filePath = ((Item) arguments[0])
|
||||
.getStringValue();
|
||||
final String settingName = ((Item) arguments[1])
|
||||
.getStringValue();
|
||||
switch (arguments.length) {
|
||||
case 2:
|
||||
result = settingsUtils.getSetting(theme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName);
|
||||
break;
|
||||
case 3:
|
||||
final String defaultValue = ((Item) arguments[2])
|
||||
.getStringValue();
|
||||
result = settingsUtils.getSetting(theme,
|
||||
themeProvider,
|
||||
filePath,
|
||||
settingName,
|
||||
defaultValue);
|
||||
break;
|
||||
default:
|
||||
throw new UnexpectedErrorException(
|
||||
"Illegal number of arguments.");
|
||||
}
|
||||
|
||||
return StringValue.makeStringValue(result);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Greeter extends ExtensionFunctionDefinition {
|
||||
|
||||
@Override
|
||||
public StructuredQName getFunctionQName() {
|
||||
return new StructuredQName("ccm",
|
||||
"http://xmlns.libreccm.org",
|
||||
"greet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
return new SequenceType[]{SequenceType.SINGLE_STRING};
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(final SequenceType[] sts) {
|
||||
return SequenceType.SINGLE_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
return new ExtensionFunctionCall() {
|
||||
|
||||
@Override
|
||||
public Sequence call(final XPathContext xPathContext,
|
||||
final Sequence[] arguments)
|
||||
throws XPathException {
|
||||
|
||||
final String name = arguments[0].toString();
|
||||
return StringValue
|
||||
.makeStringValue(String.format("Hello %s.", name));
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,23 @@
|
|||
</p>
|
||||
<xsl:value-of disable-output-escaping="true" select="./text" />
|
||||
|
||||
<h2>Example of Saxon Extension Function</h2>
|
||||
<pre>
|
||||
<xsl:value-of select="ccm:greet('John')" />
|
||||
</pre>
|
||||
|
||||
<h2>Example of Theme Utils</h2>
|
||||
<dl>
|
||||
<dt>
|
||||
<code>getSetting</code>
|
||||
</dt>
|
||||
<dd>
|
||||
<code>
|
||||
<xsl:value-of select="ccm:getSetting('settings.properties', 'example.setting', 'n/a')" />
|
||||
</code>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
example.setting=Properties from the theme.
|
||||
Loading…
Reference in New Issue