0001: /***************************************************************
0002: * This file is part of the [fleXive](R) project.
0003: *
0004: * Copyright (c) 1999-2008
0005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
0006: * All rights reserved
0007: *
0008: * The [fleXive](R) project is free software; you can redistribute
0009: * it and/or modify it under the terms of the GNU General Public
0010: * License as published by the Free Software Foundation;
0011: * either version 2 of the License, or (at your option) any
0012: * later version.
0013: *
0014: * The GNU General Public License can be found at
0015: * http://www.gnu.org/copyleft/gpl.html.
0016: * A copy is found in the textfile GPL.txt and important notices to the
0017: * license from the author are found in LICENSE.txt distributed with
0018: * these libraries.
0019: *
0020: * This library is distributed in the hope that it will be useful,
0021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0023: * GNU General Public License for more details.
0024: *
0025: * For further information about UCS - unique computing solutions gmbh,
0026: * please see the company website: http://www.ucs.at
0027: *
0028: * For further information about [fleXive](R), please see the
0029: * project website: http://www.flexive.org
0030: *
0031: *
0032: * This copyright notice MUST APPEAR in all copies of the file!
0033: ***************************************************************/package com.flexive.shared;
0034:
0035: import com.flexive.shared.content.FxPK;
0036: import com.flexive.shared.exceptions.FxCreateException;
0037: import com.flexive.shared.exceptions.FxInvalidParameterException;
0038: import com.flexive.shared.search.FxPaths;
0039: import com.flexive.shared.structure.FxAssignment;
0040: import com.flexive.shared.value.FxString;
0041: import com.flexive.shared.value.FxValue;
0042: import com.flexive.shared.value.renderer.FxValueRendererFactory;
0043: import com.flexive.shared.workflow.Step;
0044: import com.flexive.shared.workflow.StepDefinition;
0045: import org.apache.commons.lang.ArrayUtils;
0046: import org.apache.commons.lang.StringUtils;
0047: import org.apache.commons.logging.Log;
0048: import org.apache.commons.logging.LogFactory;
0049:
0050: import java.io.*;
0051: import java.security.MessageDigest;
0052: import java.security.NoSuchAlgorithmException;
0053: import java.util.*;
0054: import java.text.Collator;
0055:
0056: /**
0057: * Flexive shared utility functions.
0058: *
0059: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
0060: */
0061: public final class FxSharedUtils {
0062: private static final Log LOG = LogFactory
0063: .getLog(FxSharedUtils.class);
0064:
0065: /**
0066: * Shared message resources bundle
0067: */
0068: public static final String SHARED_BUNDLE = "FxSharedMessages";
0069:
0070: private static String fxVersion = "3.0";
0071: private static String fxEdition = "framework";
0072: private static String fxProduct = "[fleXive]";
0073: private static String fxBuild = "unknown";
0074: private static String fxBuildDate = "unknown";
0075: private static String fxBuildUser = "unknown";
0076: private static String fxHeader = "[fleXive]";
0077: private static String bundledGroovyVersion = "unknown";
0078: private static List<String> translatedLocales = Arrays.asList("en");
0079:
0080: /**
0081: * The character(s) representing a "xpath slash" (/) in a public URL.
0082: */
0083: public static final String XPATH_ENCODEDSLASH = "#";
0084: /**
0085: * Browser tests set this cookie to force using the test division instead of the actual division
0086: * defined by the URL domain.
0087: * TODO: security?
0088: */
0089: public static final String COOKIE_FORCE_TEST_DIVISION = "ForceTestDivision";
0090:
0091: private static List<String> drops = null;
0092: public static MessageDigest digest = null;
0093:
0094: /**
0095: * Are JDK 6+ extensions allowed to be run on the current VM?
0096: */
0097: public final static boolean USE_JDK6_EXTENSION;
0098:
0099: static {
0100: int major = -1, minor = -1;
0101: try {
0102: String[] ver = System.getProperty(
0103: "java.specification.version").split("\\.");
0104: if (ver.length >= 2) {
0105: major = Integer.valueOf(ver[0]);
0106: minor = Integer.valueOf(ver[1]);
0107: }
0108: } catch (Exception e) {
0109: LOG.error(e);
0110: }
0111: USE_JDK6_EXTENSION = major > 1 || (major == 1 && minor >= 6);
0112: }
0113:
0114: /**
0115: * Get a list of all installed and deployed drops
0116: *
0117: * @return list of all installed and deployed drops
0118: */
0119: public static synchronized List<String> getDrops() {
0120: if (drops != null)
0121: return drops;
0122: String dropsList;
0123: try {
0124: dropsList = loadFromInputStream(Thread.currentThread()
0125: .getContextClassLoader().getResourceAsStream(
0126: "drops.archives"), -1);
0127: } catch (Exception e) {
0128: drops = new ArrayList<String>(0);
0129: return drops;
0130: }
0131: String[] d = dropsList.split(",");
0132: drops = new ArrayList<String>(d.length);
0133: drops.addAll(Arrays.asList(d));
0134: return drops;
0135: }
0136:
0137: /**
0138: * Return the index of the given column name. If <code>name</code> has no
0139: * prefix (e.g. "co."), then only a suffix match is performed (e.g.
0140: * "name" matches "co.name" or "abc.name", whichever comes first.)
0141: *
0142: * @param columnNames all column names to be searched
0143: * @param name the requested column name
0144: * @return the 1-based index of the given column, or -1 if it does not exist
0145: */
0146: public static int getColumnIndex(String[] columnNames, String name) {
0147: final String upperName = name.toUpperCase();
0148: for (int i = 0; i < columnNames.length; i++) {
0149: final String columnName = columnNames[i];
0150: final String upperColumn = columnName.toUpperCase();
0151: if (upperColumn.equals(upperName)
0152: || upperColumn.endsWith("." + upperName)) {
0153: return i + 1;
0154: }
0155: }
0156: return -1;
0157: }
0158:
0159: /**
0160: * Return the index of the given column name. If <code>name</code> has no
0161: * prefix (e.g. "co."), then only a suffix match is performed (e.g.
0162: * "name" matches "co.name" or "abc.name", whichever comes first.)
0163: *
0164: * @param columnNames all column names to be searched
0165: * @param name the requested column name
0166: * @return the 1-based index of the given column, or -1 if it does not exist
0167: */
0168: public static int getColumnIndex(List<String> columnNames,
0169: String name) {
0170: return getColumnIndex(columnNames
0171: .toArray(new String[columnNames.size()]), name);
0172: }
0173:
0174: /**
0175: * Compute the hash of the given flexive password.
0176: *
0177: * @param accountId the user account ID
0178: * @param password the cleartext password
0179: * @return a hashed password
0180: */
0181: public synchronized static String hashPassword(long accountId,
0182: String password) {
0183: try {
0184: return sha1Hash(getBytes("FX-SALT" + accountId + password));
0185: } catch (NoSuchAlgorithmException e) {
0186: throw new FxCreateException(
0187: "Failed to load the SHA1 algorithm.")
0188: .asRuntimeException();
0189: }
0190: }
0191:
0192: /**
0193: * Returns a collator for the calling user's locale.
0194: *
0195: * @return a collator for the calling user's locale.
0196: */
0197: public static Collator getCollator() {
0198: return Collator.getInstance(FxContext.get().getTicket()
0199: .getLanguage().getLocale());
0200: }
0201:
0202: /**
0203: * Is the script (most likely) a groovy script?
0204: *
0205: * @param name script name to check
0206: * @return if this script could be a groovy script
0207: */
0208: public static boolean isGroovyScript(String name) {
0209: return name.toLowerCase().endsWith(".gy")
0210: || name.toLowerCase().endsWith(".groovy");
0211: }
0212:
0213: /**
0214: * Maps keys to values. Used for constructing JSF-EL parameter
0215: * mapper objects for assicative lookups.
0216: */
0217: public static interface ParameterMapper<K, V> extends Serializable {
0218: V get(Object key);
0219: }
0220:
0221: public final static boolean WINDOWS = System.getProperty("os.name")
0222: .indexOf("Windows") >= 0;
0223:
0224: static {
0225: try {
0226: PropertyResourceBundle bundle = (PropertyResourceBundle) PropertyResourceBundle
0227: .getBundle("flexive");
0228: fxVersion = bundle.getString("flexive.version");
0229: fxEdition = bundle.getString("flexive.edition");
0230: fxProduct = bundle.getString("flexive.product");
0231: fxBuild = bundle.getString("flexive.buildnumber");
0232: fxBuildDate = bundle.getString("flexive.builddate");
0233: fxBuildUser = bundle.getString("flexive.builduser");
0234: fxHeader = bundle.getString("flexive.header");
0235: bundledGroovyVersion = bundle
0236: .getString("flexive.bundledGroovyVersion");
0237: final String languagesValue = bundle
0238: .getString("flexive.translatedLocales");
0239: if (StringUtils.isNotBlank(languagesValue)) {
0240: final String[] languages = StringUtils.split(
0241: languagesValue, ",");
0242: for (int i = 0; i < languages.length; i++) {
0243: languages[i] = languages[i].trim();
0244: }
0245: translatedLocales = Arrays.asList(languages);
0246: }
0247: } catch (Exception e) {
0248: //ignore
0249: }
0250: }
0251:
0252: /**
0253: * Private constructor
0254: */
0255: private FxSharedUtils() {
0256: }
0257:
0258: /**
0259: * Creates a SHA-1 hash for the given data and returns it
0260: * in hexadecimal string encoding.
0261: *
0262: * @param bytes data to be hashed
0263: * @return hex-encoded hash
0264: * @throws java.security.NoSuchAlgorithmException
0265: * if the SHA-1 provider does not exist
0266: */
0267: public static String sha1Hash(byte[] bytes)
0268: throws NoSuchAlgorithmException {
0269: MessageDigest md = MessageDigest.getInstance("SHA-1");
0270: md.update(bytes);
0271: return FxFormatUtils.encodeHex(md.digest());
0272: }
0273:
0274: /**
0275: * Helperclass holding the result of the <code>executeCommand</code> method
0276: *
0277: * @see FxSharedUtils#executeCommand(String,String...)
0278: */
0279: public static final class ProcessResult {
0280: private String commandLine;
0281: private int exitCode;
0282: private String stdOut, stdErr;
0283:
0284: /**
0285: * Constructor
0286: *
0287: * @param commandLine the commandline executed
0288: * @param exitCode exit code
0289: * @param stdOut result from stdOut
0290: * @param stdErr result from stdErr
0291: */
0292: public ProcessResult(String commandLine, int exitCode,
0293: String stdOut, String stdErr) {
0294: this .commandLine = commandLine;
0295: this .exitCode = exitCode;
0296: this .stdOut = stdOut;
0297: this .stdErr = stdErr;
0298: }
0299:
0300: /**
0301: * Getter for the commandline
0302: *
0303: * @return commandline
0304: */
0305: public String getCommandLine() {
0306: return commandLine;
0307: }
0308:
0309: /**
0310: * Getter for the exit code
0311: *
0312: * @return exit code
0313: */
0314: public int getExitCode() {
0315: return exitCode;
0316: }
0317:
0318: /**
0319: * Getter for stdOut
0320: *
0321: * @return stdOut
0322: */
0323: public String getStdOut() {
0324: return stdOut;
0325: }
0326:
0327: /**
0328: * Getter for stdErr
0329: *
0330: * @return stdErr
0331: */
0332: public String getStdErr() {
0333: return stdErr;
0334: }
0335: }
0336:
0337: /**
0338: * Helper thread to asynchronously read and buffer an InputStream
0339: */
0340: static final class AsyncStreamBuffer extends Thread {
0341: protected InputStream in;
0342: protected StringBuffer sb = new StringBuffer();
0343:
0344: /**
0345: * Constructor
0346: *
0347: * @param in the InputStream to buffer
0348: */
0349: AsyncStreamBuffer(InputStream in) {
0350: this .in = in;
0351: }
0352:
0353: /**
0354: * Getter for the buffered result
0355: *
0356: * @return buffered result
0357: */
0358: public String getResult() {
0359: return sb.toString();
0360: }
0361:
0362: /**
0363: * {@inheritDoc}
0364: */
0365: @Override
0366: public void run() {
0367: try {
0368: BufferedReader br = new BufferedReader(
0369: new InputStreamReader(in));
0370: String line;
0371: while ((line = br.readLine()) != null)
0372: sb.append(line).append('\n');
0373: } catch (IOException e) {
0374: sb.append("[Error: ").append(e.getMessage())
0375: .append("]");
0376: }
0377: }
0378: }
0379:
0380: /**
0381: * Execute a command on the operating system
0382: *
0383: * @param command name of the command
0384: * @param arguments arguments to pass to the command (one argument per String!)
0385: * @return result
0386: */
0387: public static ProcessResult executeCommand(String command,
0388: String... arguments) {
0389: Runtime r = Runtime.getRuntime();
0390: String[] cmd = new String[arguments.length + (WINDOWS ? 3 : 1)];
0391: if (WINDOWS) {
0392: //have to run a shell on windows
0393: cmd[0] = "cmd";
0394: cmd[1] = "/c";
0395: }
0396:
0397: cmd[WINDOWS ? 2 : 0] = command;
0398: System.arraycopy(arguments, 0, cmd, (WINDOWS ? 3 : 1),
0399: arguments.length);
0400: StringBuilder cmdline = new StringBuilder(200);
0401: cmdline.append(command);
0402: for (String argument : arguments)
0403: cmdline.append(" ").append(argument);
0404: Process p = null;
0405: AsyncStreamBuffer out = null;
0406: AsyncStreamBuffer err = null;
0407: try {
0408: p = r.exec(cmd);
0409: // p = r.exec(cmdline);
0410: out = new AsyncStreamBuffer(p.getInputStream());
0411: err = new AsyncStreamBuffer(p.getErrorStream());
0412: out.start();
0413: err.start();
0414: p.waitFor();
0415: while (out.isAlive())
0416: Thread.sleep(10);
0417: while (err.isAlive())
0418: Thread.sleep(10);
0419: } catch (Exception e) {
0420: String error = e.getMessage();
0421: if (err != null && err.getResult() != null
0422: && err.getResult().trim().length() > 0)
0423: error = error + "(" + err.getResult() + ")";
0424: return new ProcessResult(cmdline.toString(),
0425: (p == null ? -1 : p.exitValue()), (out == null ? ""
0426: : out.getResult()), error);
0427: } finally {
0428: try {
0429: p.getInputStream().close();
0430: } catch (Exception e1) {
0431: //bad luck
0432: }
0433: try {
0434: p.getErrorStream().close();
0435: } catch (Exception e1) {
0436: //bad luck
0437: }
0438: try {
0439: p.getOutputStream().close();
0440: } catch (Exception e1) {
0441: //bad luck
0442: }
0443: }
0444: return new ProcessResult(cmdline.toString(), p.exitValue(), out
0445: .getResult(), err.getResult());
0446: }
0447:
0448: /**
0449: * Load the contents of a file, returning it as a String.
0450: * This method should only be used when really necessary since no real error handling is performed!!!
0451: *
0452: * @param file the File to load
0453: * @return file contents
0454: */
0455: public static String loadFile(File file) {
0456: try {
0457: return loadFromInputStream(new FileInputStream(file),
0458: (int) file.length());
0459: } catch (FileNotFoundException e) {
0460: LOG.error(e);
0461: return "";
0462: }
0463: }
0464:
0465: /**
0466: * Load a String from an InputStream (until end of stream)
0467: *
0468: * @param in InputStream
0469: * @param length length of the string if > -1 (NOT number of bytes to read!)
0470: * @return String
0471: */
0472: public static String loadFromInputStream(InputStream in, int length) {
0473: StringBuilder sb = new StringBuilder(length > 0 ? length : 5000);
0474: try {
0475: int read;
0476: byte[] buffer = new byte[1024];
0477: while ((read = in.read(buffer)) != -1) {
0478: sb.append(new String(buffer, 0, read, "UTF-8"));
0479: }
0480: } catch (IOException e) {
0481: LOG.error(e.getMessage(), e);
0482: } finally {
0483: if (in != null) {
0484: try {
0485: in.close();
0486: } catch (IOException e) {
0487: // ignore
0488: }
0489: }
0490: }
0491: return sb.toString();
0492: }
0493:
0494: /**
0495: * Rather primitive "write String to file" helper, returns <code>false</code> if failed
0496: *
0497: * @param contents the String to store
0498: * @param file the file, if existing it will be overwritten
0499: * @return if successful
0500: */
0501: public static boolean storeFile(String contents, File file) {
0502: if (file.exists()) {
0503: LOG.warn("Warning: " + file.getName()
0504: + " already exists! Overwriting!");
0505: }
0506: FileOutputStream out = null;
0507: try {
0508: out = new FileOutputStream(file);
0509: out.write(FxSharedUtils.getBytes(contents));
0510: out.flush();
0511: out.close();
0512: return true;
0513: } catch (IOException e) {
0514: LOG.error("Failed to store " + file.getAbsolutePath()
0515: + ": " + e.getMessage());
0516: return false;
0517: } finally {
0518: if (out != null) {
0519: try {
0520: out.close();
0521: } catch (IOException e) {
0522: //ignore
0523: }
0524: }
0525: }
0526: }
0527:
0528: /**
0529: * Get the flexive version
0530: *
0531: * @return flexive version
0532: */
0533: public static String getFlexiveVersion() {
0534: return fxVersion;
0535: }
0536:
0537: /**
0538: * Get the subversion build number
0539: *
0540: * @return subversion build number
0541: */
0542: public static String getBuildNumber() {
0543: return fxBuild;
0544: }
0545:
0546: /**
0547: * Get the date flexive was compiled
0548: *
0549: * @return compile date
0550: */
0551: public static String getBuildDate() {
0552: return fxBuildDate;
0553: }
0554:
0555: /**
0556: * Get the name of this flexive edition
0557: *
0558: * @return flexive edition
0559: */
0560: public static String getFlexiveEdition() {
0561: return fxEdition;
0562: }
0563:
0564: /**
0565: * Get the name of this flexive edition with the product name
0566: *
0567: * @return flexive edition with product name
0568: */
0569: public static String getFlexiveEditionFull() {
0570: return fxEdition + "." + fxProduct;
0571: }
0572:
0573: /**
0574: * Get the name of the user that built flexive
0575: *
0576: * @return build user
0577: */
0578: public static String getBuildUser() {
0579: return fxBuildUser;
0580: }
0581:
0582: /**
0583: * Get the default html header title
0584: *
0585: * @return html header title
0586: */
0587: public static String getHeader() {
0588: return fxHeader;
0589: }
0590:
0591: /**
0592: * Get the version of the bundled groovy runtime
0593: *
0594: * @return version of the bundled groovy runtime
0595: */
0596: public static String getBundledGroovyVersion() {
0597: return bundledGroovyVersion;
0598: }
0599:
0600: /**
0601: * Renders the value as returned from a flexive search query to the
0602: * given output writer.
0603: *
0604: * @param out the output writer
0605: * @param value the value to be formatted
0606: * @throws IOException if the value could not be written
0607: */
0608: public static void writeResultValue(Writer out, Object value,
0609: ContentLinkFormatter linkFormatter, String linkFormat,
0610: String itemLinkFormat) throws IOException {
0611: out.write(formatResultValue(value, linkFormatter, linkFormat,
0612: itemLinkFormat));
0613: }
0614:
0615: /**
0616: * Formats the value as returned from a flexive search query.
0617: *
0618: * @param value the value to be formatted
0619: * @return the formatted string value
0620: */
0621: public static String formatResultValue(Object value,
0622: ContentLinkFormatter linkFormatter, String linkFormat,
0623: String itemLinkFormat) {
0624: linkFormatter = linkFormatter != null ? linkFormatter
0625: : ContentLinkFormatter.getInstance();
0626: if (value == null
0627: || (value instanceof FxValue && ((FxValue) value)
0628: .isEmpty())) {
0629: return "<i>" + getEmptyResultMessage() + "</i>";
0630: } else if (value instanceof FxValue) {
0631: //noinspection unchecked
0632: return FxValueRendererFactory.getInstance().format(
0633: (FxValue) value);
0634: } else if (value instanceof FxPK) {
0635: return linkFormatter.format(linkFormat, (FxPK) value);
0636: } else if (value instanceof FxPaths) {
0637: return linkFormatter
0638: .format(itemLinkFormat, (FxPaths) value);
0639: } else {
0640: return value.toString(); // unsupported type
0641: }
0642: }
0643:
0644: /**
0645: * Returns the localized "empty" message for empty result fields
0646: *
0647: * @return the localized "empty" message for empty result fields
0648: */
0649: public static String getEmptyResultMessage() {
0650: final FxLanguage language = FxContext.get().getTicket()
0651: .getLanguage();
0652: return getLocalizedMessage(SHARED_BUNDLE, language.getId(),
0653: language.getIso2digit(), "shared.result.emptyValue");
0654: }
0655:
0656: /**
0657: * Check if the given value is empty (empty string or null for String objects, empty collection,
0658: * null for other objects) and throw an exception if empty.
0659: *
0660: * @param value value to check
0661: * @param parameterName name of the value (for the exception)
0662: */
0663: public static void checkParameterNull(Object value,
0664: String parameterName) {
0665: if (value == null) {
0666: throw new FxInvalidParameterException(parameterName,
0667: "ex.general.parameter.null", parameterName)
0668: .asRuntimeException();
0669: }
0670: }
0671:
0672: /**
0673: * Check if the given value is empty (empty string or null for String objects, empty collection,
0674: * null for other objects) and throw an exception if empty.
0675: *
0676: * @param value value to check
0677: * @param parameterName name of the value (for the exception)
0678: */
0679: public static void checkParameterEmpty(Object value,
0680: String parameterName) {
0681: if (value == null
0682: || (value instanceof String && StringUtils
0683: .isBlank((String) value))
0684: || (value instanceof Collection && ((Collection) value)
0685: .isEmpty())) {
0686: throw new FxInvalidParameterException(parameterName,
0687: "ex.general.parameter.empty", parameterName)
0688: .asRuntimeException();
0689: }
0690: }
0691:
0692: /**
0693: * Try to find a localized resource messages
0694: *
0695: * @param resourceBundle the name of the resource bundle to retrieve the message from
0696: * @param key resource key
0697: * @param localeIso locale of the resource bundle
0698: * @return resource from a localized bundle
0699: */
0700: public static String lookupResource(String resourceBundle,
0701: String key, String localeIso) {
0702: String result = _lookupResource(resourceBundle, key, localeIso);
0703: if (result == null) {
0704: for (String drop : getDrops()) {
0705: result = _lookupResource(drop + "Resources/"
0706: + resourceBundle, key, localeIso);
0707: if (result != null)
0708: return result;
0709: }
0710: }
0711: return result;
0712: }
0713:
0714: private static String _lookupResource(String resourceBundle,
0715: String key, String localeIso) {
0716: try {
0717: String isoCode = localeIso != null ? localeIso : Locale
0718: .getDefault().getLanguage();
0719: PropertyResourceBundle bundle = (PropertyResourceBundle) PropertyResourceBundle
0720: .getBundle(resourceBundle, new Locale(isoCode));
0721: return bundle.getString(key);
0722: } catch (MissingResourceException e) {
0723: //try default (english) locale
0724: try {
0725: PropertyResourceBundle bundle = (PropertyResourceBundle) PropertyResourceBundle
0726: .getBundle(resourceBundle, Locale.ENGLISH);
0727: return bundle.getString(key);
0728: } catch (MissingResourceException e1) {
0729: return null;
0730: }
0731: }
0732: }
0733:
0734: /**
0735: * Get the localized message for a given language code and ISO
0736: *
0737: * @param resourceBundle the resource bundle to use
0738: * @param localeId used locale if args contain FxString instances
0739: * @param localeIso ISO code of the requested locale
0740: * @param key the key in the resource bundle
0741: * @param args arguments to replace in the message ({n})
0742: * @return localized message
0743: */
0744: public static String getLocalizedMessage(String resourceBundle,
0745: long localeId, String localeIso, String key, Object... args) {
0746: if (key == null) {
0747: LOG.error("No key given!", new Throwable());
0748: return "##NO_KEY_GIVEN";
0749: }
0750: String resource = lookupResource(resourceBundle, key, localeIso);
0751: if (resource == null) {
0752: LOG.warn("Called with unlocalized Message [" + key
0753: + "]. See StackTrace for origin!", new Throwable());
0754: return key;
0755: }
0756:
0757: //lookup possible resource keys in values (they may not have placeholders like {n} though)
0758: String tmp;
0759: for (int i = 0; i < args.length; i++) {
0760: Object o = args[i];
0761: if (o instanceof String && ((String) o).indexOf(' ') == -1
0762: && ((String) o).indexOf('.') > 0)
0763: if ((tmp = lookupResource(resourceBundle, (String) o,
0764: localeIso)) != null)
0765: args[i] = tmp;
0766: }
0767: return FxFormatUtils.formatResource(resource, localeId, args);
0768: }
0769:
0770: /**
0771: * Returns a multilingual FxString with all translations for the given property key.
0772: *
0773: * @param resourceBundle the resource bundle to be used
0774: * @param key the message key
0775: * @param args optional parameters to be replaced in the property translations
0776: * @return a multilingual FxString with all translations for the given property key.
0777: */
0778: public static FxString getMessage(String resourceBundle,
0779: String key, Object... args) {
0780: Map<Long, String> translations = new HashMap<Long, String>();
0781: for (String localeIso : translatedLocales) {
0782: final long localeId = new FxLanguage(localeIso).getId();
0783: translations.put(localeId, getLocalizedMessage(
0784: resourceBundle, localeId, localeIso, key, args));
0785: }
0786: return new FxString(translations);
0787: }
0788:
0789: /**
0790: * Returns the localized label for the given enum value. The enum translations are
0791: * stored in FxSharedMessages.properties and are standardized as
0792: * {@code FQCN.value},
0793: * e.g. {@code com.flexive.shared.search.query.ValueComparator.LIKE}.
0794: *
0795: * @param value the enum value to be translated
0796: * @param args optional arguments to be replaced in the localized messages
0797: * @return the localized label for the given enum value
0798: */
0799: public static FxString getEnumLabel(Enum<?> value, Object... args) {
0800: final Class<? extends Enum> valueClass = value.getClass();
0801: final String clsName;
0802: if (valueClass.getEnclosingClass() != null
0803: && Enum.class.isAssignableFrom(valueClass
0804: .getEnclosingClass())) {
0805: // don't include anonymous inner class definitions often used by enums in class name
0806: clsName = valueClass.getEnclosingClass().getName();
0807: } else {
0808: clsName = valueClass.getName();
0809: }
0810: return getMessage(SHARED_BUNDLE, clsName + "." + value.name(),
0811: args);
0812: }
0813:
0814: /**
0815: * Returns a list of all used step definitions for the given steps
0816: *
0817: * @param steps list of steps to be examined
0818: * @param stepDefinitions all defined step definitions
0819: * @return a list of all used step definitions for this workflow
0820: */
0821: public static List<StepDefinition> getUsedStepDefinitions(
0822: List<? extends Step> steps,
0823: List<? extends StepDefinition> stepDefinitions) {
0824: List<StepDefinition> result = new ArrayList<StepDefinition>(
0825: steps.size());
0826: for (Step step : steps) {
0827: for (StepDefinition stepDefinition : stepDefinitions) {
0828: if (step.getStepDefinitionId() == stepDefinition
0829: .getId()) {
0830: result.add(stepDefinition);
0831: break;
0832: }
0833: }
0834: }
0835: return result;
0836: }
0837:
0838: /**
0839: * Splits the given text using separator. String literals are supported, e.g.
0840: * abc,def yields two elements, but 'abc,def' yields one (stringDelims = ['\''], separator = ',').
0841: *
0842: * @param text the text to be splitted
0843: * @param stringDelims delimiters for literal string values, usually ' and "
0844: * @param separator separator between tokens
0845: * @return split string
0846: */
0847: public static String[] splitLiterals(String text,
0848: char[] stringDelims, char separator) {
0849: if (text == null) {
0850: return new String[0];
0851: }
0852: List<String> result = new ArrayList<String>();
0853: Character currentStringDelim = null;
0854: int startIndex = 0;
0855: for (int i = 0; i < text.length(); i++) {
0856: char character = text.charAt(i);
0857: if (character == separator && currentStringDelim == null) {
0858: // not in string
0859: if (startIndex != -1) {
0860: result.add(text.substring(startIndex, i).trim());
0861: }
0862: startIndex = i + 1;
0863: } else if (currentStringDelim != null
0864: && currentStringDelim == character) {
0865: // end string
0866: result.add(text.substring(startIndex, i).trim());
0867: currentStringDelim = null;
0868: startIndex = -1;
0869: } else if (currentStringDelim != null) {
0870: // continue in string literal
0871: } else if (ArrayUtils.contains(stringDelims, character)) {
0872: // begin string literal
0873: currentStringDelim = character;
0874: // skip string delim
0875: startIndex = i + 1;
0876: }
0877: }
0878: if (startIndex != -1 && startIndex <= text.length()) {
0879: // add last parameter
0880: result
0881: .add(text.substring(startIndex, text.length())
0882: .trim());
0883: }
0884: return result.toArray(new String[result.size()]);
0885: }
0886:
0887: /**
0888: * Splits the given comma-separated text. String literals are supported, e.g.
0889: * abc,def yields two elements, but 'abc,def' yields one.
0890: *
0891: * @param text the text to be splitted
0892: * @return split string
0893: */
0894: public static String[] splitLiterals(String text) {
0895: return splitLiterals(text, new char[] { '\'', '"' }, ',');
0896: }
0897:
0898: /**
0899: * Projects a single-parameter function on a hashmap.
0900: * Useful for calling parameterized functions from JSF-EL.
0901: *
0902: * @param mapper the parameter mapper wrapping the function to be called
0903: * @return a hashmap projected on the given parameter mapper
0904: */
0905: public static <K, V> Map<K, V> getMappedFunction(
0906: final ParameterMapper<K, V> mapper) {
0907: return new HashMap<K, V>() {
0908: @Override
0909: public V get(Object key) {
0910: return mapper.get(key);
0911: }
0912: };
0913: }
0914:
0915: /**
0916: * Escape a path for usage on the current operating systems shell
0917: *
0918: * @param path path to escape
0919: * @return escaped path
0920: */
0921: public static String escapePath(String path) {
0922: if (WINDOWS)
0923: return "\"" + path + "\"";
0924: else
0925: return path.replace(" ", "\\ ");
0926:
0927: }
0928:
0929: /**
0930: * Escapes the given XPath for use in a public URI.
0931: *
0932: * @param xpath the xpath to be escaped
0933: * @return the given XPath for use in a public URI.
0934: * @see #decodeXPath(String)
0935: */
0936: public static String escapeXPath(String xpath) {
0937: return StringUtils.replace(xpath, "/", XPATH_ENCODEDSLASH);
0938: }
0939:
0940: /**
0941: * Decodes a previously escaped XPath.
0942: *
0943: * @param escapedXPath the escaped XPath
0944: * @return the decoded XPath
0945: * @see #escapeXPath(String)
0946: */
0947: public static String decodeXPath(String escapedXPath) {
0948: return StringUtils.replace(escapedXPath, XPATH_ENCODEDSLASH,
0949: "/");
0950: }
0951:
0952: /**
0953: * Returns <code>map.get(key)</code> if <code>key</code> exists, <code>defaultValue</code> otherwise.
0954: *
0955: * @param map a map
0956: * @param key the required key
0957: * @param defaultValue the default value to be returned if <code>key</code> does not exist in <code>map</code>
0958: * @return <code>map.get(key)</code> if <code>key</code> exists, <code>defaultValue</code> otherwise.
0959: */
0960: public static <K, V> V get(Map<K, V> map, K key, V defaultValue) {
0961: return map.containsKey(key) ? map.get(key) : defaultValue;
0962: }
0963:
0964: /**
0965: * Returns true if the given string value is quoted with the given character (e.g. 'value').
0966: *
0967: * @param value the string value to be checked
0968: * @param quoteChar the quote character, for example '
0969: * @return true if the given string value is quoted with the given character (e.g. 'value').
0970: */
0971: public static boolean isQuoted(String value, char quoteChar) {
0972: return value != null && value.length() >= 2
0973: && value.charAt(0) == quoteChar
0974: && value.charAt(value.length() - 1) == quoteChar;
0975: }
0976:
0977: /**
0978: * Strips the quotes from the given string if it is quoted, otherwise it returns
0979: * the input value itself.
0980: *
0981: * @param value the value to be "unquoted"
0982: * @param quoteChar the quote character, for example '
0983: * @return the unquoted string, or <code>value</code>, if it was not quoted
0984: */
0985: public static String stripQuotes(String value, char quoteChar) {
0986: if (isQuoted(value, quoteChar)) {
0987: return value.substring(1, value.length() - 1);
0988: }
0989: return value;
0990: }
0991:
0992: /**
0993: * Returns the UTF-8 byte representation of the given string. Use this instead of
0994: * {@link String#getBytes()}, since the latter will fail if the system locale is not UTF-8.
0995: *
0996: * @param s the string to be processed
0997: * @return the UTF-8 byte representation of the given string
0998: */
0999: public static byte[] getBytes(String s) {
1000: try {
1001: return s.getBytes("UTF-8");
1002: } catch (UnsupportedEncodingException e) {
1003: if (LOG.isWarnEnabled()) {
1004: LOG.warn("Failed to decode UTF-8 string: "
1005: + e.getMessage(), e);
1006: }
1007: return s.getBytes();
1008: }
1009: }
1010:
1011: /**
1012: * Comparator for sorting Assignments according to their position.
1013: */
1014: public static class AssignmentPositionSorter implements
1015: Comparator<FxAssignment>, Serializable {
1016: private static final long serialVersionUID = 9197582519027523108L;
1017:
1018: public int compare(FxAssignment o1, FxAssignment o2) {
1019: if (o1.getPosition() < o2.getPosition())
1020: return -1;
1021: else if (o1.getPosition() == o2.getPosition())
1022: return 0;
1023: else
1024: return 1;
1025: }
1026: }
1027:
1028: /**
1029: * Comparator for sorting {@link SelectableObjectWithName} instances by ID.
1030: */
1031: public static class SelectableObjectSorter implements
1032: Comparator<SelectableObject>, Serializable {
1033: private static final long serialVersionUID = -1786371691872260074L;
1034:
1035: public int compare(SelectableObject o1, SelectableObject o2) {
1036: return o1.getId() > o2.getId() ? 1 : o1.getId() < o2
1037: .getId() ? -1 : 0;
1038: }
1039: }
1040: }
|