/** * Created on Jan 9, 2006 * * $Id: JcrUtils.java,v 1.2 2006/01/09 15:45:56 costin Exp $ * $Revision: 1.2 $ */ package org.springmodules.jcr; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.jcr.Repository; import org.apache.xerces.util.XMLChar; /** * Utility class for Java Content Repository. The hex escaping/unescaping is based on Brian Moseley <bcm@osafoundation.org> work. * * @author Costin Leau * */ public abstract class JcrUtils { /** * Class used for escaping XML names which contain restricted character as defined in the * JCR spec version 1.0 Section 6.2.5.2. (i.e. "/", ":", "[", "]", "*", "'", """, * "|", and all whitespace characters other than " "). The escaping schema used is * described in the same document in paragraphs 6.4.3 and 6.4.4. * * The class was initially imported from the <a href="http://svn.apache.org/viewcvs.cgi/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/util/ISO9075.java?view=markup">Jackrabbit project</a>. * * Implements the encode and decode routines as specified for XML name to SQL * identifier conversion in ISO 9075-14:2003.<br/> * If a character <code>c</code> is not valid at a certain position in an XML 1.0 * NCName it is encoded in the form: '_x' + hexValueOf(c) + '_' * <p/> */ protected static class ISO9075 { /** Pattern on an encoded character */ private final Pattern ENCODE_PATTERN = Pattern.compile("_x\\p{XDigit}{4}_"); /** Padding characters */ private final char[] PADDING = new char[] { '0', '0', '0' }; /** All the possible hex digits */ private final String HEX_DIGITS = "0123456789abcdefABCDEF"; /** * Encodes <code>name</code> as specified in ISO 9075. * @param name the <code>String</code> to encode. * @return the encoded <code>String</code> or <code>name</code> if it does * not need encoding. */ public String encode(String name) { // quick check for root node name if (name.length() == 0) { return name; } if (XMLChar.isValidName(name) && name.indexOf("_x") < 0) { // already valid return name; } // encode StringBuffer encoded = new StringBuffer(); for (int i = 0; i < name.length(); i++) { if (i == 0) { // first character of name if (XMLChar.isNameStart(name.charAt(i))) { if (needsEscaping(name, i)) { // '_x' must be encoded encode('_', encoded); } else { encoded.append(name.charAt(i)); } } else { // not valid as first character -> encode encode(name.charAt(i), encoded); } } else if (!XMLChar.isName(name.charAt(i))) { encode(name.charAt(i), encoded); } else { if (needsEscaping(name, i)) { // '_x' must be encoded encode('_', encoded); } else { encoded.append(name.charAt(i)); } } } return encoded.toString(); } /** * Decodes the <code>name</code>. * @param name the <code>String</code> to decode. * @return the decoded <code>String</code>. */ public String decode(String name) { // quick check if (name.indexOf("_x") < 0) { // not encoded return name; } StringBuffer decoded = new StringBuffer(); Matcher m = ENCODE_PATTERN.matcher(name); while (m.find()) { m.appendReplacement(decoded, Character.toString((char) Integer.parseInt(m.group().substring( 2, 6), 16))); } m.appendTail(decoded); return decoded.toString(); } //-------------------------< internal >------------------------------------- /** * Encodes the character <code>c</code> as a String in the following form: * <code>"_x" + hex value of c + "_"</code>. Where the hex value has * four digits if the character with possibly leading zeros. * <p/> * Example: ' ' (the space character) is encoded to: _x0020_ * @param c the character to encode * @param b the encoded character is appended to <code>StringBuffer</code> * <code>b</code>. */ private void encode(char c, StringBuffer b) { b.append("_x"); String hex = Integer.toHexString(c); b.append(PADDING, 0, 4 - hex.length()); b.append(hex); b.append("_"); } /** * Returns true if <code>name.charAt(location)</code> is the underscore * character and the following character sequence is 'xHHHH_' where H * is a hex digit. * @param name the name to check. * @param location the location to look at. * @throws ArrayIndexOutOfBoundsException if location > name.length() */ private boolean needsEscaping(String name, int location) throws ArrayIndexOutOfBoundsException { if (name.charAt(location) == '_' && name.length() >= location + 6) { return name.charAt(location + 1) == 'x' && HEX_DIGITS.indexOf(name.charAt(location + 2)) != -1 && HEX_DIGITS.indexOf(name.charAt(location + 3)) != -1 && HEX_DIGITS.indexOf(name.charAt(location + 4)) != -1 && HEX_DIGITS.indexOf(name.charAt(location + 5)) != -1; } return false; } } private static ISO9075 escaper = new ISO9075(); public static boolean supportsLevel2(Repository repository) { return "true".equals(repository.getDescriptor(Repository.LEVEL_2_SUPPORTED)); } public static boolean supportsTransactions(Repository repository) { return "true".equals(repository.getDescriptor(Repository.OPTION_TRANSACTIONS_SUPPORTED)); } public static boolean supportsVersioning(Repository repository) { return "true".equals(repository.getDescriptor(Repository.OPTION_VERSIONING_SUPPORTED)); } public static boolean supportsObservation(Repository repository) { return "true".equals(repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED)); } public static boolean supportsLocking(Repository repository) { return "true".equals(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED)); } public static boolean supportsSQLQuery(Repository repository) { return "true".equals(repository.getDescriptor(Repository.OPTION_QUERY_SQL_SUPPORTED)); } public static boolean supportsXPathPosIndex(Repository repository) { return "true".equals(repository.getDescriptor(Repository.QUERY_XPATH_POS_INDEX)); } public static boolean supportsXPathDocOrder(Repository repository) { return "true".equals(repository.getDescriptor(Repository.QUERY_XPATH_DOC_ORDER)); } /** * Escapes the Jcr names using ISO 9075 encoding. * * @param unescaped * @return */ public static String encode(String decoded) { return escaper.encode(decoded); } /** * Decodes the Jcr names using ISO 9075 decoding. * * @param escaped * @return */ public static String decode(String encoded) { return escaper.decode(encoded); } }