All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.plugins.document.util.Utils Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.plugins.document.util;

import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.mongodb.BasicDBObject;

import org.apache.commons.codec.binary.Hex;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.document.Revision;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Utility methods.
 */
public class Utils {
    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

    /**
     * Approximate length of a Revision string.
     */
    private static final int REVISION_LENGTH =
            new Revision(System.currentTimeMillis(), 0, 0).toString().length();

    /**
     * The length of path (in characters), whose UTF-8 representation can not
     * possibly be too large to be used for the primary key for the document
     * store.
     */
    static final int PATH_SHORT = Integer.getInteger("oak.pathShort", 165);

    /**
     * The maximum length of the parent path, in bytes. If the parent path is
     * longer, then the id of a document is no longer the path, but the hash of
     * the parent, and then the node name.
     */
    static final int PATH_LONG = Integer.getInteger("oak.pathLong", 350);

    /**
     * The maximum size a node name, in bytes. This is only a problem for long path.
     */
    private static final int NODE_NAME_LIMIT = Integer.getInteger("oak.nodeNameLimit", 150);

    private static final Charset UTF_8 = Charset.forName("UTF-8");

    /**
     * Make sure the name string does not contain unnecessary baggage (shared
     * strings).
     * 

* This is only needed for older versions of Java (before Java 7 update 6). * See also * http://mail.openjdk.java.net/pipermail/core-libs-dev/2012-May/010257.html * * @param x the string * @return the new string */ public static String unshareString(String x) { return new String(x); } public static int pathDepth(String path) { if (path.equals("/")) { return 0; } int depth = 0; for (int i = 0; i < path.length(); i++) { if (path.charAt(i) == '/') { depth++; } } return depth; } @SuppressWarnings("unchecked") public static int estimateMemoryUsage(Map map) { if (map == null) { return 0; } int size = 0; for (Entry e : map.entrySet()) { if (e.getKey() instanceof Revision) { size += 32; } else { size += 48 + e.getKey().toString().length() * 2; } Object o = e.getValue(); if (o instanceof String) { size += 48 + ((String) o).length() * 2; } else if (o instanceof Long) { size += 16; } else if (o instanceof Boolean) { size += 8; } else if (o instanceof Integer) { size += 8; } else if (o instanceof Map) { size += 8 + estimateMemoryUsage((Map) o); } else if (o == null) { // zero } else { throw new IllegalArgumentException("Can't estimate memory usage of " + o); } } if (map instanceof BasicDBObject) { // Based on empirical testing using JAMM size += 176; size += map.size() * 136; } else { // overhead for some other kind of map // TreeMap (80) + unmodifiable wrapper (32) size += 112; // 64 bytes per entry size += map.size() * 64; } return size; } /** * Generate a unique cluster id, similar to the machine id field in MongoDB ObjectId objects. * * @return the unique machine id */ public static int getUniqueClusterId() { ObjectId objId = new ObjectId(); return objId._machine(); } public static String escapePropertyName(String propertyName) { int len = propertyName.length(); if (len == 0) { return "_"; } // avoid creating a buffer if escaping is not needed StringBuilder buff = null; char c = propertyName.charAt(0); int i = 0; if (c == '_' || c == '$') { buff = new StringBuilder(len + 1); buff.append('_').append(c); i++; } for (; i < len; i++) { c = propertyName.charAt(i); char rep; switch (c) { case '.': rep = 'd'; break; case '\\': rep = '\\'; break; default: rep = 0; } if (rep != 0) { if (buff == null) { buff = new StringBuilder(propertyName.substring(0, i)); } buff.append('\\').append(rep); } else if (buff != null) { buff.append(c); } } return buff == null ? propertyName : buff.toString(); } public static String unescapePropertyName(String key) { int len = key.length(); if (key.startsWith("_") && (key.startsWith("__") || key.startsWith("_$") || len == 1)) { key = key.substring(1); len--; } // avoid creating a buffer if escaping is not needed StringBuilder buff = null; for (int i = 0; i < len; i++) { char c = key.charAt(i); if (c == '\\') { if (buff == null) { buff = new StringBuilder(key.substring(0, i)); } c = key.charAt(++i); if (c == '\\') { // ok } else if (c == 'd') { c = '.'; } buff.append(c); } else if (buff != null) { buff.append(c); } } return buff == null ? key : buff.toString(); } public static boolean isPropertyName(String key) { return !key.startsWith("_") || key.startsWith("__") || key.startsWith("_$"); } public static String getIdFromPath(String path) { if (isLongPath(path)) { MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } int depth = Utils.pathDepth(path); String parent = PathUtils.getParentPath(path); byte[] hash = digest.digest(parent.getBytes(UTF_8)); String name = PathUtils.getName(path); return depth + ":h" + Hex.encodeHexString(hash) + "/" + name; } int depth = Utils.pathDepth(path); return depth + ":" + path; } /** * Returns the parent id for given id if possible * *

It would return null in following cases *

    *
  • If id is from long path
  • *
  • If id is for root path
  • *
  • If id is for an invalid path
  • *
*

* @param id id for which parent id needs to be determined * @return parent id. null if parent id cannot be determined */ @CheckForNull public static String getParentId(String id){ if(Utils.isIdFromLongPath(id)){ return null; } String path = Utils.getPathFromId(id); if (!PathUtils.isValid(path)) { return null; } if(PathUtils.denotesRoot(path)){ return null; } String parentPath = PathUtils.getParentPath(path); return Utils.getIdFromPath(parentPath); } public static boolean isLongPath(String path) { // the most common case: a short path // avoid calculating the parent path if (path.length() < PATH_SHORT) { return false; } // check if the parent path is long byte[] parent = PathUtils.getParentPath(path).getBytes(UTF_8); if (parent.length < PATH_LONG) { return false; } String name = PathUtils.getName(path); if (name.getBytes(UTF_8).length > NODE_NAME_LIMIT) { throw new IllegalArgumentException("Node name is too long: " + path); } return true; } public static boolean isIdFromLongPath(String id) { int index = id.indexOf(':'); return id.charAt(index + 1) == 'h'; } public static String getPathFromId(String id) { if (isIdFromLongPath(id)) { throw new IllegalArgumentException("Id is hashed: " + id); } int index = id.indexOf(':'); return id.substring(index + 1); } public static String getPreviousPathFor(String path, Revision r, int height) { if (!PathUtils.isAbsolute(path)) { throw new IllegalArgumentException("path must be absolute: " + path); } StringBuilder sb = new StringBuilder(path.length() + REVISION_LENGTH + 3); sb.append("p").append(path); if (sb.charAt(sb.length() - 1) != '/') { sb.append('/'); } r.toStringBuilder(sb).append("/").append(height); return sb.toString(); } public static String getPreviousIdFor(String path, Revision r, int height) { return getIdFromPath(getPreviousPathFor(path, r, height)); } /** * Deep copy of a map that may contain map values. * * @param source the source map * @param target the target map * @param the type of the map key */ public static void deepCopyMap(Map source, Map target) { for (Entry e : source.entrySet()) { Object value = e.getValue(); Comparator comparator = null; if (value instanceof SortedMap) { @SuppressWarnings("unchecked") SortedMap map = (SortedMap) value; comparator = map.comparator(); } if (value instanceof Map) { @SuppressWarnings("unchecked") Map old = (Map) value; Map c = new TreeMap(comparator); deepCopyMap(old, c); value = c; } target.put(e.getKey(), value); } } /** * Returns the lower key limit to retrieve the children of the given * path. * * @param path a path. * @return the lower key limit. */ public static String getKeyLowerLimit(String path) { String from = PathUtils.concat(path, "a"); from = getIdFromPath(from); from = from.substring(0, from.length() - 1); return from; } /** * Returns the upper key limit to retrieve the children of the given * path. * * @param path a path. * @return the upper key limit. */ public static String getKeyUpperLimit(String path) { String to = PathUtils.concat(path, "z"); to = getIdFromPath(to); to = to.substring(0, to.length() - 2) + "0"; return to; } /** * Returns parentId extracted from the fromKey. fromKey is usually constructed * using Utils#getKeyLowerLimit * * @param fromKey key used as start key in queries * @return parentId if possible. */ @CheckForNull public static String getParentIdFromLowerLimit(String fromKey){ //If key just ends with slash 2:/foo/ then append a fake //name to create a proper id if(fromKey.endsWith("/")){ fromKey = fromKey + "a"; } return getParentId(fromKey); } /** * Returns true if a revision tagged with the given revision * should be considered committed, false otherwise. Committed * revisions have a tag, which equals 'c' or starts with 'c-'. * * @param tag the tag (may be null). * @return true if committed; false otherwise. */ public static boolean isCommitted(@Nullable String tag) { return tag != null && (tag.equals("c") || tag.startsWith("c-")); } /** * Resolve the commit revision for the given revision rev and * the associated commit tag. * * @param rev a revision. * @param tag the associated commit tag. * @return the actual commit revision for rev. */ @Nonnull public static Revision resolveCommitRevision(@Nonnull Revision rev, @Nonnull String tag) { return checkNotNull(tag).startsWith("c-") ? Revision.fromString(tag.substring(2)) : rev; } /** * Closes the obj its of type {@link java.io.Closeable}. It is mostly * used to close Iterator/Iterables which are backed by say DBCursor * * @param obj object to close */ public static void closeIfCloseable(Object obj){ if(obj instanceof Closeable){ try{ ((Closeable) obj).close(); } catch (IOException e) { LOG.warn("Error occurred while closing {}", obj, e); } } } /** * Provides a readable string for given timestamp */ public static String timestampToString(long timestamp){ return (new Timestamp(timestamp) + "00").substring(0, 23); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy