/*************************************************************************
* *
* CESeCore: CE Security Core *
* *
* This software 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 any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.cesecore.internal;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Locale;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.cesecore.config.CesecoreConfiguration;
/**
* Class managing internal localization of texts such as notification messages
* and log comments.
*
* If fetched the resource files from the src/intresources directory and is
* included in the file cesecore-ejb.jar
*
* @version $Id: InternalResources.java 26298 2017-08-14 13:49:21Z anatom $
*/
public class InternalResources implements Serializable {
private static final Logger log = Logger.getLogger(InternalResources.class);
/**
* Determines if a de-serialized file is compatible with this class.
*
* Maintainers must change this value if and only if the new version of this
* class is not compatible with old versions. See Sun docs for details.
*
*/
private static final long serialVersionUID = -1003L;
protected static InternalResources instance = null;
protected Properties primaryResource = new Properties();
protected Properties secondaryResource = new Properties();
private static String[] placeHolders = null;
private static final String RESOURCE_PATH = "/intresources";
private static final String RESOURCE_NAME = "/intresources.";
private static final String RESOURCE_LOCATION = RESOURCE_PATH+RESOURCE_NAME;
/**
* Method used to setup the Internal Resource management.
*
* @param globalConfiguration
* used to retrieve the internal language of the application,
* configured in the System Configuration.
* @throws IOException
*/
protected InternalResources() {
setupResources(RESOURCE_LOCATION);
}
protected InternalResources(String resPath) {
setupResources(resPath+RESOURCE_NAME);
}
private void setupResources(String resLocation) {
final String primaryLanguage = CesecoreConfiguration.getInternalResourcesPreferredLanguage().toLowerCase(Locale.ENGLISH);
final String secondaryLanguage = CesecoreConfiguration.getInternalResourcesSecondaryLanguage().toLowerCase(Locale.ENGLISH);
// The test flag is defined when called from test code (junit)
InputStream primaryStream = null;
InputStream secondaryStream = null;
try {
primaryStream = InternalResources.class.getResourceAsStream(resLocation + primaryLanguage + ".properties");
if (primaryStream == null) {
try {
primaryStream = new FileInputStream(resLocation + primaryLanguage + ".properties");
} catch (FileNotFoundException e) {
log.error("Localization files not found: "+e.getMessage());
}
}
secondaryStream = InternalResources.class.getResourceAsStream(resLocation + secondaryLanguage + ".properties");
if (secondaryStream == null) {
try {
secondaryStream = new FileInputStream(resLocation + secondaryLanguage + ".properties");
} catch (FileNotFoundException e) {
log.error("Localization files not found: "+e.getMessage());
}
}
try {
if (primaryStream != null) {
primaryResource.load(primaryStream);
} else {
log.warn("primaryResourse == null");
}
if (secondaryStream != null) {
secondaryResource.load(secondaryStream);
} else {
log.warn("secondaryResource == null");
}
} catch (IOException e) {
log.error("Error reading internal resourcefile", e);
}
} finally {
try {
if (primaryStream != null) {
primaryStream.close();
}
if (secondaryStream != null) {
secondaryStream.close();
}
} catch (IOException e) {
log.error("Error closing internal resources language streams: ", e);
}
}
}
/** @return an instance of the InternalResources. */
public static synchronized InternalResources getInstance() {
if (instance == null) {
instance = new InternalResources();
}
return instance;
}
/**
* Method returning the localized message for the given resource key.
*
* It first looks up in the primary language then in the secondary If not
* found in any of the resource file "no text" is returned.
*
* NOTE: String is immutable and you will get a copy of the String instead
* of a reference to it. This is more memory consuming than using
* getLocalizedMessageCs(..) if you pass on the result to Log4J.
* @see #getLocalizedMessageCs(String, Object...)
*
* @param key
* is the key searched for in the resource files
* @param params
* indicates the parameter that will be replaced by {X} in the
* language resource, a maximum of 10 parameters can be given.
*
* Ex Calling the method with key = TEST and param0 set to "hi"
* and the resource file have "TEST = messages is {0}" will
* result in the string "message is hi".
*
* @return The message as a String, not trimmed for whitespace
*/
public String getLocalizedMessage(final String key, final Object... params) {
return getLocalizedMessageCs(key, params).toString();
}
/**
* Method returning the localized message for the given resource key.
*
* It first looks up in the primary language then in the secondary If not
* found in any of the resource file "no text" is returned.
*
* @param key
* is the key searched for in the resource files
* @param params
* indicates the parameter that will be replaced by {X} in the
* language resource, a maximum of 10 parameters can be given.
*
* Ex Calling the method with key = TEST and param0 set to "hi"
* and the resource file have "TEST = messages is {0}" will
* result in the string "message is hi".
*
* @return The message as a CharSequence, the return value is not trimmed for whitespace.
*/
protected CharSequence getLocalizedMessageCs(final String key, final Object... params) {
final StringBuilder sb = new StringBuilder();
return getLocalizedMessageInternal(sb, key, params);
}
/** Lookup the default string if the StringBuilder is empty. Perform place holder replacement processing. */
protected CharSequence getLocalizedMessageInternal(final StringBuilder sb, final String key, final Object... params) {
if (sb.length()==0) {
if (primaryResource.containsKey(key)) {
sb.append(primaryResource.getProperty(key));
} else if (secondaryResource.containsKey(key)) {
sb.append(secondaryResource.getProperty(key));
} else {
sb.append(key);
for(Object param : params) {
if (param != null) {
sb.append(", ").append(param.toString());
}
}
}
}
replacePlaceholders(sb, params);
if (log.isTraceEnabled()) {
log.trace(key + "=" + sb.toString());
}
return sb;
}
public static void replacePlaceholders(final StringBuilder sb, final Object... params) {
for (int i=0; i(placeHolders.length-1)) {
log.error("Place holder index out of range. Unable to create localized message.");
return;
}
final String placeHolder = placeHolders[placeHolderIndex];
final int placeHolderLength = placeHolder.length();
int currentIndex = -placeHolderLength;
int bar = 20; // never allow more than 20 placeholders to avoid recursion
if (replacementObject==null) {
while ((currentIndex=sb.indexOf(placeHolder, currentIndex+placeHolderLength))!=-1 && bar > 0) {
sb.delete(currentIndex-1, currentIndex+placeHolderLength);
bar--;
}
} else {
final String replacement = replacementObject.toString();
while ((currentIndex=sb.indexOf(placeHolder, currentIndex+placeHolderLength))!=-1 && bar > 0) {
sb.replace(currentIndex-1, currentIndex+placeHolderLength, replacement);
bar--;
}
}
}
/** Remove any "{number}" string that is still present in the StringBuilder where number starts with 'startPlaceHolderIndex'. */
private static void removeUnusedPlaceHolders(final StringBuilder sb, final int startPlaceHolderIndex) {
final String[] placeHolders = getPlaceHolders();
if (startPlaceHolderIndex<0 || startPlaceHolderIndex>(placeHolders.length-1)) {
log.error("Place holder index out of range. Unable to create localized message.");
return;
}
for (int i=startPlaceHolderIndex; i