/************************************************************************* * * * 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