jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r1160729 - in /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util: BundleDumper.java BundleReader.java
Date Tue, 23 Aug 2011 14:57:27 GMT
Author: thomasm
Date: Tue Aug 23 14:57:27 2011
New Revision: 1160729

URL: http://svn.apache.org/viewvc?rev=1160729&view=rev
Log:
JCR-3058 BundleDumper to analyze broken bundles

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleDumper.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleDumper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleDumper.java?rev=1160729&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleDumper.java
(added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleDumper.java
Tue Aug 23 14:57:27 2011
@@ -0,0 +1,634 @@
+/*
+ * 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.core.persistence.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.UUID;
+
+/**
+ * This utility class can dump the contents of a node bundle. This class is
+ * based on BundleReader, but is able to dump even if the data is corrupt
+ * (unlike the BundleReader). The class does not have any dependencies so it can
+ * be run from the command line without problems (without having to add any jar
+ * files to the classpath).
+ */
+public class BundleDumper {
+
+    private static final int VERSION_1 = 1;
+    private static final int VERSION_2 = 2;
+    private static final int VERSION_3 = 3;
+
+    private static final int BINARY_IN_BLOB_STORE = -1;
+    private static final int BINARY_IN_DATA_STORE = -2;
+    private static final char[] HEX = "0123456789abcdef".toCharArray();
+
+    public static final int UNDEFINED = 0, STRING = 1, BINARY = 2, LONG = 3, DOUBLE = 4,
DATE = 5, BOOLEAN = 6,
+        NAME = 7, PATH = 8, REFERENCE = 9, WEAKREFERENCE = 10, URI = 11, DECIMAL = 12;
+
+    private static final String[] NAMES = { "undefined", "String", "Binary", "Long", "Double",
"Date", "Boolean",
+        "Name", "Path", "Reference", "WeakReference", "URI", "Decimal" };
+
+    private StringBuilder buffer = new StringBuilder();
+
+    /**
+     * Pre-calculated {@link TimeZone} objects for common timezone offsets.
+     */
+    private static final TimeZone[] COMMON_TIMEZONES = {
+        TimeZone.getTimeZone("GMT+00:00"), // 0b00000
+        TimeZone.getTimeZone("GMT+01:00"), // 0b00001
+        TimeZone.getTimeZone("GMT+02:00"), // 0b00010
+        TimeZone.getTimeZone("GMT+03:00"), // 0b00011
+        TimeZone.getTimeZone("GMT+04:00"), // 0b00100
+        TimeZone.getTimeZone("GMT+05:00"), // 0b00101
+        TimeZone.getTimeZone("GMT+06:00"), // 0b00110
+        TimeZone.getTimeZone("GMT+07:00"), // 0b00111
+        TimeZone.getTimeZone("GMT+08:00"), // 0b01000
+        TimeZone.getTimeZone("GMT+09:00"), // 0b01001
+        TimeZone.getTimeZone("GMT+10:00"), // 0b01010
+        TimeZone.getTimeZone("GMT+11:00"), // 0b01011
+        TimeZone.getTimeZone("GMT+12:00"), // 0b01100
+        TimeZone.getTimeZone("GMT+13:00"), // 0b01101
+        TimeZone.getTimeZone("GMT+14:00"), // 0b01110
+        TimeZone.getTimeZone("GMT+15:00"), // 0b01111
+        TimeZone.getTimeZone("GMT-16:00"), // 0b10000
+        TimeZone.getTimeZone("GMT-15:00"), // 0b10001
+        TimeZone.getTimeZone("GMT-14:00"), // 0b10010
+        TimeZone.getTimeZone("GMT-13:00"), // 0b10011
+        TimeZone.getTimeZone("GMT-12:00"), // 0b10100
+        TimeZone.getTimeZone("GMT-11:00"), // 0b10101
+        TimeZone.getTimeZone("GMT-10:00"), // 0b10110
+        TimeZone.getTimeZone("GMT-09:00"), // 0b10111
+        TimeZone.getTimeZone("GMT-08:00"), // 0b11000
+        TimeZone.getTimeZone("GMT-07:00"), // 0b11001
+        TimeZone.getTimeZone("GMT-06:00"), // 0b11010
+        TimeZone.getTimeZone("GMT-05:00"), // 0b11011
+        TimeZone.getTimeZone("GMT-04:00"), // 0b11100
+        TimeZone.getTimeZone("GMT-03:00"), // 0b11101
+        TimeZone.getTimeZone("GMT-02:00"), // 0b11110
+        TimeZone.getTimeZone("GMT-01:00"), // 0b11111
+    };
+
+    /**
+     * Wrapper for reading structured data from the input stream.
+     */
+    private DataInputStream in;
+
+    private int version;
+
+    private final String[] namespaces =
+        // NOTE: The length of this array must be seven
+        { "", null, null, null, null, null, null };
+
+
+    static final UUID NULL_PARENT_ID =
+        UUID.fromString("bb4e9d10-d857-11df-937b-0800200c9a66");
+
+
+    public static void main(String... args) throws IOException {
+        new BundleDumper().run(args);
+    }
+
+    void run(String... args) throws IOException {
+        if (args.length < 1) {
+            System.out.println("Usage: java " + getClass().getName() + " <fileName>");
+            System.out.println("where the file name points to a node bundle.");
+            return;
+        }
+        RandomAccessFile f = new RandomAccessFile(args[0], "r");
+        byte[] bundle = new byte[(int) f.length()];
+        f.readFully(bundle);
+        f.close();
+        System.out.println(dump(bundle));
+    }
+
+    public String dump(byte[] bundle) throws IOException {
+        try {
+            ByteArrayInputStream bin = new ByteArrayInputStream(bundle);
+            this.in = new DataInputStream(bin);
+            version = in.readUnsignedByte();
+            buffer.append("version: ").append(version).append("\n");
+            if (version >= VERSION_3) {
+                readBundleNew();
+            } else {
+                readBundleOld();
+            }
+        } catch (Exception e) {
+            buffer.append("\n");
+            buffer.append("error: ").append(e.toString());
+        }
+        return buffer.toString();
+    }
+
+    private void readBundleNew() throws IOException {
+        // node type
+        buffer.append("nodeTypeName: ").append(readName()).append("\n");
+
+        // parentUUID
+        UUID parentId = readNodeId();
+        buffer.append("parentId: ").append(parentId).append("\n");
+        if (NULL_PARENT_ID.equals(parentId)) {
+            parentId = null;
+            buffer.append("parentId is null\n");
+        }
+
+        // read modcount
+        buffer.append("modCount: ").append((short) readVarInt()).append("\n");
+
+        int b = in.readUnsignedByte();
+        buffer.append("referenceable: ").append((b & 1) != 0).append("\n");
+
+        // mixin types
+        int mn = readVarInt((b >> 7) & 1, 1);
+        if (mn == 0) {
+            // 0
+        } else if (mn == 1) {
+            buffer.append("mixing type:").append(readName()).append("\n");
+        } else {
+            buffer.append("mixing type count:").append(mn).append("\n");
+            for (int i = 0; i < mn; i++) {
+                buffer.append("mixing type:").append(readName()).append("\n");
+            }
+        }
+
+        // properties
+        int pn = readVarInt((b >> 4) & 7, 7);
+        for (int i = 0; i < pn; i++) {
+            buffer.append("property: ").append(readName()).append("\n");
+            readPropertyEntry();
+        }
+
+        // child nodes (list of name/uuid pairs)
+        int nn = readVarInt((b >> 2) & 3, 3);
+        for (int i = 0; i < nn; i++) {
+            buffer.append("child node: ").append(readQName()).
+                    append(" id: ").append(readNodeId()).append("\n");
+        }
+
+        // read shared set
+        int sn = readVarInt((b >> 1) & 1, 1);
+        if (sn == 0) {
+            // 0
+        } else if (sn == 1) {
+            buffer.append("shared set:").append(readNodeId()).append("\n");
+        } else {
+            buffer.append("shared set count:").append(sn).append("\n");
+            for (int i = 0; i < sn; i++) {
+                buffer.append("shared set:").append(readNodeId()).append("\n");
+            }
+        }
+    }
+
+    private void readBundleOld() throws IOException {
+        // read primary type...special handling
+        int a = in.readUnsignedByte();
+        int b = in.readUnsignedByte();
+        int c = in.readUnsignedByte();
+        String uri = "#" + (a << 16 | b << 8 | c);
+        String local = "#" + in.readInt();
+        buffer.append("nodeTypeName: ").append(uri).append(":").append(local).append("\n");
+
+        // parentUUID
+        buffer.append("parentUUID: ").append(readNodeId()).append("\n");
+
+        // definitionId
+        buffer.append("definitionId: ").append(in.readUTF()).append("\n");
+
+        // mixin types
+        String name = readIndexedQName();
+        if (name != null) {
+            do {
+                buffer.append("mixin: ").append(name).append("\n");
+                name = readIndexedQName();
+            } while (name != null);
+        } else {
+            buffer.append("mixins: -\n");
+        }
+
+        // properties
+        name = readIndexedQName();
+        while (name != null) {
+            buffer.append("property: ").append(name).append("\n");
+            readPropertyEntry();
+            name = readIndexedQName();
+        }
+
+        // set referenceable flag
+        buffer.append("referenceable: ").append(in.readBoolean()).append("\n");
+
+        // child nodes (list of uuid/name pairs)
+        UUID childId = readNodeId();
+        while (childId != null) {
+            buffer.append("childId: ").append(childId).append(" ").append(readQName()).append("\n");
+            childId = readNodeId();
+        }
+
+        // read modcount, since version 1.0
+        if (version >= VERSION_1) {
+            buffer.append("modCount: ").append(in.readShort()).append("\n");
+        }
+
+        // read shared set, since version 2.0
+        if (version >= VERSION_2) {
+            // shared set (list of parent uuids)
+            UUID parentId = readNodeId();
+            if (parentId != null) {
+                do {
+                    buffer.append("shared set parentId: ").append(parentId).append("\n");
+                    parentId = readNodeId();
+                } while (parentId != null);
+            }
+        }
+    }
+
+    private static String getType(int type) {
+        try {
+            return NAMES[type];
+        } catch (Exception e) {
+            return "unknown type " + type;
+        }
+    }
+
+    /**
+     * Deserializes a <code>PropertyState</code> from the data input stream.
+     *
+     * @param id the property id for the new property entry
+     * @return the property entry
+     * @throws IOException if an I/O error occurs.
+     */
+    private void readPropertyEntry()
+            throws IOException {
+        int count = 1;
+        int type;
+        if (version >= VERSION_3) {
+            int b = in.readUnsignedByte();
+            type = b & 0x0f;
+            buffer.append("  type: ").append(getType(type)).append("\n");
+            int len = b >>> 4;
+            if (len != 0) {
+                buffer.append("  multivalued\n");
+                if (len == 0x0f) {
+                    count = readVarInt() + 0x0f - 1;
+                } else {
+                    count = len - 1;
+                }
+            }
+            buffer.append("  modcount: ").append((short) readVarInt()).append("\n");
+        } else {
+            // type and modcount
+            type = in.readInt();
+            buffer.append("  modcount: ").append((short) ((type >> 16) & 0x0ffff)).append("\n");
+            type &= 0x0ffff;
+            buffer.append("  type: ").append(getType(type)).append("\n");
+
+            // multiValued
+            boolean mv = in.readBoolean();
+            if (mv) {
+                buffer.append("  multivalued\n");
+            }
+
+            // definitionId
+            buffer.append("  definitionId: ").append(in.readUTF()).append("\n");
+
+            // count
+            count = in.readInt();
+            if (count != 1) {
+                buffer.append("  count: ").append(count).append("\n");
+            }
+        }
+
+        // values
+        for (int i = 0; i < count; i++) {
+            switch (type) {
+                case BINARY:
+                    int size = in.readInt();
+                    if (size == BINARY_IN_DATA_STORE) {
+                        buffer.append("  value: binary in datastore: ").append(readString()).append("\n");
+                    } else if (size == BINARY_IN_BLOB_STORE) {
+                        buffer.append("  value: binary in blobstore: ").append(readString()).append("\n");
+                    } else {
+                        // short values into memory
+                        byte[] data = new byte[size];
+                        in.readFully(data);
+                        buffer.append("  value: binary: ").append(convertBytesToHex(data)).append("\n");
+                    }
+                    break;
+                case DOUBLE:
+                    buffer.append("  value: double: ").append(in.readDouble()).append("\n");
+                    break;
+                case DECIMAL:
+                    buffer.append("  value: double: ").append(readDecimal()).append("\n");
+                    break;
+                case LONG:
+                    if (version >= VERSION_3) {
+                        buffer.append("  value: varLong: ").append(readVarLong()).append("\n");
+                    } else {
+                        buffer.append("  value: long: ").append(in.readLong()).append("\n");
+                    }
+                    break;
+                case BOOLEAN:
+                    buffer.append("  value: boolean: ").append(in.readBoolean()).append("\n");
+                    break;
+                case NAME:
+                    buffer.append("  value: name: ").append(readQName()).append("\n");
+                    break;
+                case WEAKREFERENCE:
+                    buffer.append("  value: weakreference: ").append(readNodeId()).append("\n");
+                    break;
+                case REFERENCE:
+                    buffer.append("  value: reference: ").append(readNodeId()).append("\n");
+                    break;
+                case DATE:
+                    if (version >= VERSION_3) {
+                        buffer.append("  value: date: ").append(readDate()).append("\n");
+                        break;
+                    } // else fall through
+                default:
+                    if (version >= VERSION_3) {
+                        buffer.append("  value: string: ").append(readString()).append("\n");
+                    } else {
+                        // because writeUTF(String) has a size limit of 64k,
+                        // Strings are serialized as <length><byte[]>
+                        int len = in.readInt();
+                        byte[] bytes = new byte[len];
+                        in.readFully(bytes);
+                        buffer.append("  value: string: ").append(new String(bytes, "UTF-8")).append("\n");
+                    }
+            }
+        }
+    }
+
+    /**
+     * Deserializes a node identifier
+     *
+     * @return the node id
+     * @throws IOException in an I/O error occurs.
+     */
+    private UUID readNodeId() throws IOException {
+        if (version >= VERSION_3 || in.readBoolean()) {
+            long msb = in.readLong();
+            long lsb = in.readLong();
+            return new UUID(msb, lsb);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Deserializes a BigDecimal
+     *
+     * @return the decimal
+     * @throws IOException in an I/O error occurs.
+     */
+    private BigDecimal readDecimal() throws IOException {
+        if (in.readBoolean()) {
+            // TODO more efficient serialization format
+            return new BigDecimal(readString());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Deserializes a Name
+     *
+     * @return the qname
+     * @throws IOException in an I/O error occurs.
+     */
+    private String readQName() throws IOException {
+        if (version >= VERSION_3) {
+            return readName();
+        }
+
+        String uri = "#" + in.readInt();
+        String local = in.readUTF();
+        return uri + ":" + local;
+    }
+
+    /**
+     * Deserializes an indexed Name
+     *
+     * @return the qname
+     * @throws IOException in an I/O error occurs.
+     */
+    private String readIndexedQName() throws IOException {
+        if (version >= VERSION_3) {
+            return readName();
+        }
+
+        int index = in.readInt();
+        if (index < 0) {
+            return null;
+        } else {
+            String uri = "#" + index;
+            String local = "#" + in.readInt();
+            return uri + ":" + local;
+        }
+    }
+
+    /**
+     * Deserializes a name written using bundle serialization version 3.
+     *
+     * @return deserialized name
+     * @throws IOException if an I/O error occurs
+     */
+    private String readName() throws IOException {
+        int b = in.readUnsignedByte();
+        if ((b & 0x80) == 0) {
+            return "indexToName #" + b;
+        } else {
+            String uri;
+            int ns = (b >> 4) & 0x07;
+            if (ns < namespaces.length && namespaces[ns] != null) {
+                uri = namespaces[ns];
+            } else {
+                uri = readString();
+                if (ns < namespaces.length) {
+                    namespaces[ns] = uri;
+                }
+            }
+            String local = new String(readBytes((b & 0x0f) + 1, 0x10), "UTF-8");
+            return uri + ":" + local;
+        }
+    }
+
+    /**
+     * Deserializes a variable-length integer written using bundle
+     * serialization version 3.
+     *
+     * @return deserialized integer
+     * @throws IOException if an I/O error occurs
+     */
+    private int readVarInt() throws IOException {
+        int b = in.readUnsignedByte();
+        if ((b & 0x80) == 0) {
+            return b;
+        } else {
+            return readVarInt() << 7 | b & 0x7f;
+        }
+    }
+
+    private int readVarInt(int value, int base) throws IOException {
+        if (value < base) {
+            return value;
+        } else {
+            return readVarInt() + base;
+        }
+    }
+
+    /**
+     * Deserializes a variable-length long written using bundle
+     * serialization version 3.
+     *
+     * @return deserialized long
+     * @throws IOException if an I/O error occurs
+     */
+    private long readVarLong() throws IOException {
+        long value = 0;
+        int bits = 0;
+        long b;
+        do {
+            b = in.readUnsignedByte();
+            if (bits < 57) {
+                value = (b & 0x7f) << 57 | value >>> 7;
+                bits += 7;
+            } else {
+                value = (b & 0x01) << 63 | value >>> 1;
+                bits = 64;
+            }
+        } while ((b & 0x80) != 0);
+        value = value >>> (64 - bits);
+        if ((value & 1) != 0) {
+            return ~(value >>> 1);
+        } else {
+            return value >>> 1;
+        }
+    }
+
+    /**
+     * Deserializes a specially encoded date written using bundle
+     * serialization version 3.
+     *
+     * @return deserialized date
+     * @throws IOException if an I/O error occurs
+     */
+    private Calendar readDate() throws IOException {
+        long ts = readVarLong();
+
+        TimeZone tz;
+        if ((ts & 1) == 0) {
+            tz = COMMON_TIMEZONES[0];
+            ts >>= 1;
+        } else if ((ts & 2) == 0) {
+            tz = COMMON_TIMEZONES[((int) ts >> 2) & 0x1f]; // 5 bits;
+            ts >>= 7;
+        } else {
+            int m = ((int) ts << 19) >> 21; // 11 bits, sign-extended
+            int h = m / 60;
+            String s;
+            if (m < 0) {
+                s = String.format("GMT-%02d:%02d", -h, h * 60 - m);
+            } else {
+                s = String.format("GMT+%02d:%02d", h, m - h * 60);
+            }
+            tz = TimeZone.getTimeZone(s);
+            ts >>= 13;
+        }
+
+        int u = 0;
+        int s = 0;
+        int m = 0;
+        int h = 0;
+        int type = (int) ts & 3;
+        ts >>= 2;
+        switch (type) {
+        case 3:
+            u = (int) ts & 0x3fffffff; // 30 bits
+            s = u / 1000;
+            m = s / 60;
+            h = m / 60;
+            m -= h * 60;
+            s -= (h * 60 + m) * 60;
+            u -= ((h * 60 + m) * 60 + s) * 1000;
+            ts >>= 30;
+            break;
+        case 2:
+            m = (int) ts & 0x07ff; // 11 bits
+            h = m / 60;
+            m -= h * 60;
+            ts >>= 11;
+            break;
+        case 1:
+            h = (int) ts & 0x1f; // 5 bits
+            ts >>= 5;
+            break;
+        }
+
+        int d = (int) ts & 0x01ff; // 9 bits;
+        ts >>= 9;
+        int y = (int) (ts + 2010);
+
+        Calendar value = Calendar.getInstance(tz);
+        if (y <= 0) {
+            value.set(Calendar.YEAR, 1 - y);
+            value.set(Calendar.ERA, GregorianCalendar.BC);
+        } else {
+            value.set(Calendar.YEAR, y);
+            value.set(Calendar.ERA, GregorianCalendar.AD);
+        }
+        value.set(Calendar.DAY_OF_YEAR, d);
+        value.set(Calendar.HOUR_OF_DAY, h);
+        value.set(Calendar.MINUTE, m);
+        value.set(Calendar.SECOND, s);
+        value.set(Calendar.MILLISECOND, u);
+
+        return value;
+    }
+
+    private String readString() throws IOException {
+        if (version >= VERSION_3) {
+            return new String(readBytes(0, 0), "UTF-8");
+        } else {
+            return in.readUTF();
+        }
+    }
+
+    private byte[] readBytes(int len, int base) throws IOException {
+        byte[] bytes = new byte[readVarInt(len, base)];
+        in.readFully(bytes);
+        return bytes;
+    }
+
+    public static String convertBytesToHex(byte[] value) {
+        int len = value.length;
+        char[] buff = new char[len + len];
+        char[] hex = HEX;
+        for (int i = 0; i < len; i++) {
+            int c = value[i] & 0xff;
+            buff[i + i] = hex[c >> 4];
+            buff[i + i + 1] = hex[c & 0xf];
+        }
+        return new String(buff);
+    }
+
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java?rev=1160729&r1=1160728&r2=1160729&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
Tue Aug 23 14:57:27 2011
@@ -47,6 +47,11 @@ import javax.jcr.PropertyType;
  */
 class BundleReader {
 
+    /*
+     * Implementation note: if you change this class, also change BundleDumper
+     * accordingly.
+     */
+
     /** Logger instance */
     private static Logger log = LoggerFactory.getLogger(BundleReader.class);
 
@@ -578,7 +583,7 @@ class BundleReader {
         TimeZone tz;
         if ((ts & 1) == 0) {
             tz = COMMON_TIMEZONES[0];
-            ts >>= 1; 
+            ts >>= 1;
         } else if ((ts & 2) == 0) {
             tz = COMMON_TIMEZONES[((int) ts >> 2) & 0x1f]; // 5 bits;
             ts >>= 7;



Mime
View raw message