/* * 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.io.IOExceptionWithCause; import org.apache.commons.io.input.CountingInputStream; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.jackrabbit.spi.commons.name.NameConstants; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashSet; import java.util.Set; import java.util.TimeZone; import java.math.BigDecimal; import javax.jcr.PropertyType; /** * Bundle deserializer. See the {@link BundleWriter} class for details of * the serialization format. * * @see BundleWriter */ class BundleReader { /* * Implementation note: if you change this class, also change BundleDumper * accordingly. */ /** Logger instance */ private static Logger log = LoggerFactory.getLogger(BundleReader.class); /** * 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 }; private final BundleBinding binding; /** * Counter for the number of bytes read from the input stream. */ private final CountingInputStream cin; /** * Wrapper for reading structured data from the input stream. */ private final DataInputStream in; private final int version; /** * The default namespace and the first six other namespaces used in this * bundle. Used by the {@link #readName()} method to keep track of * already seen namespaces. */ private final String[] namespaces = // NOTE: The length of this array must be seven { Name.NS_DEFAULT_URI, null, null, null, null, null, null }; /** * Creates a new bundle deserializer. * * @param binding bundle binding * @param stream stream from which the bundle is read * @throws IOException if an I/O error occurs. */ public BundleReader(BundleBinding binding, InputStream stream) throws IOException { this.binding = binding; this.cin = new CountingInputStream(stream); this.in = new DataInputStream(cin); this.version = in.readUnsignedByte(); } /** * Deserializes a <code>NodePropBundle</code> from a data input stream. * * @param id the node id for the new bundle * @return the bundle * @throws IOException if an I/O error occurs. */ public NodePropBundle readBundle(NodeId id) throws IOException { long start = cin.getByteCount(); NodePropBundle bundle = new NodePropBundle(id); if (version >= BundleBinding.VERSION_3) { readBundleNew(bundle); } else { readBundleOld(bundle); } bundle.setSize(cin.getByteCount() - start); return bundle; } private void readBundleNew(NodePropBundle bundle) throws IOException { // node type bundle.setNodeTypeName(readName()); // parentUUID NodeId parentId = readNodeId(); if (BundleBinding.NULL_PARENT_ID.equals(parentId)) { parentId = null; } bundle.setParentId(parentId); // read modcount bundle.setModCount((short) readVarInt()); int b = in.readUnsignedByte(); bundle.setReferenceable((b & 1) != 0); // mixin types int mn = readVarInt((b >> 7) & 1, 1); if (mn == 0) { bundle.setMixinTypeNames(Collections.<Name>emptySet()); } else if (mn == 1) { bundle.setMixinTypeNames(Collections.singleton(readName())); } else { Set<Name> mixins = new HashSet<Name>(mn * 2); for (int i = 0; i < mn; i++) { mixins.add(readName()); } bundle.setMixinTypeNames(mixins); } // properties int pn = readVarInt((b >> 4) & 7, 7); for (int i = 0; i < pn; i++) { PropertyId id = new PropertyId(bundle.getId(), readName()); bundle.addProperty(readPropertyEntry(id)); } // child nodes (list of name/uuid pairs) int nn = readVarInt((b >> 2) & 3, 3); for (int i = 0; i < nn; i++) { Name name = readQName(); NodeId id = readNodeId(); bundle.addChildNodeEntry(name, id); } // read shared set int sn = readVarInt((b >> 1) & 1, 1); if (sn == 0) { bundle.setSharedSet(Collections.<NodeId>emptySet()); } else if (sn == 1) { bundle.setSharedSet(Collections.singleton(readNodeId())); } else { Set<NodeId> shared = new HashSet<NodeId>(); for (int i = 0; i < sn; i++) { shared.add(readNodeId()); } bundle.setSharedSet(shared); } } private void readBundleOld(NodePropBundle bundle) throws IOException { // read primary type...special handling int a = in.readUnsignedByte(); int b = in.readUnsignedByte(); int c = in.readUnsignedByte(); String uri = binding.nsIndex.indexToString(a << 16 | b << 8 | c); String local = binding.nameIndex.indexToString(in.readInt()); bundle.setNodeTypeName( NameFactoryImpl.getInstance().create(uri, local)); // parentUUID bundle.setParentId(readNodeId()); // definitionId in.readUTF(); // mixin types Name name = readIndexedQName(); if (name != null) { Set<Name> mixinTypeNames = new HashSet<Name>(); do { mixinTypeNames.add(name); name = readIndexedQName(); } while (name != null); bundle.setMixinTypeNames(mixinTypeNames); } else { bundle.setMixinTypeNames(Collections.<Name>emptySet()); } // properties name = readIndexedQName(); while (name != null) { PropertyId pId = new PropertyId(bundle.getId(), name); NodePropBundle.PropertyEntry pState = readPropertyEntry(pId); // skip redundant primaryType, mixinTypes and uuid properties if (!name.equals(NameConstants.JCR_PRIMARYTYPE) && !name.equals(NameConstants.JCR_UUID)) { bundle.addProperty(pState); } name = readIndexedQName(); } // set referenceable flag bundle.setReferenceable(in.readBoolean()); // child nodes (list of uuid/name pairs) NodeId childId = readNodeId(); while (childId != null) { bundle.addChildNodeEntry(readQName(), childId); childId = readNodeId(); } // read modcount, since version 1.0 if (version >= BundleBinding.VERSION_1) { bundle.setModCount(in.readShort()); } // read shared set, since version 2.0 if (version >= BundleBinding.VERSION_2) { // shared set (list of parent uuids) NodeId parentId = readNodeId(); if (parentId != null) { Set<NodeId> shared = new HashSet<NodeId>(); do { shared.add(parentId); parentId = readNodeId(); } while (parentId != null); bundle.setSharedSet(shared); } else { bundle.setSharedSet(Collections.<NodeId>emptySet()); } } else { bundle.setSharedSet(Collections.<NodeId>emptySet()); } } /** * 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 NodePropBundle.PropertyEntry readPropertyEntry(PropertyId id) throws IOException { NodePropBundle.PropertyEntry entry = new NodePropBundle.PropertyEntry(id); int count = 1; if (version >= BundleBinding.VERSION_3) { int b = in.readUnsignedByte(); entry.setType(b & 0x0f); int len = b >>> 4; if (len != 0) { entry.setMultiValued(true); if (len == 0x0f) { count = readVarInt() + 0x0f - 1; } else { count = len - 1; } } entry.setModCount((short) readVarInt()); } else { // type and modcount int type = in.readInt(); entry.setModCount((short) ((type >> 16) & 0x0ffff)); type &= 0x0ffff; entry.setType(type); // multiValued entry.setMultiValued(in.readBoolean()); // definitionId in.readUTF(); // count count = in.readInt(); } // values InternalValue[] values = new InternalValue[count]; String[] blobIds = new String[count]; for (int i = 0; i < count; i++) { InternalValue val; int type = entry.getType(); switch (type) { case PropertyType.BINARY: int size = in.readInt(); if (size == BundleBinding.BINARY_IN_DATA_STORE) { val = InternalValue.create(binding.dataStore, readString()); } else if (size == BundleBinding.BINARY_IN_BLOB_STORE) { blobIds[i] = readString(); try { BLOBStore blobStore = binding.getBlobStore(); if (blobStore instanceof ResourceBasedBLOBStore) { val = InternalValue.create(((ResourceBasedBLOBStore) blobStore).getResource(blobIds[i])); } else { val = InternalValue.create(blobStore.get(blobIds[i])); } } catch (IOException e) { if (binding.errorHandling.ignoreMissingBlobs()) { log.warn("Ignoring error while reading blob-resource: " + e); val = InternalValue.create(new byte[0]); } else { throw e; } } catch (Exception e) { throw new IOExceptionWithCause("Unable to create property value: " + e.toString(), e); } } else { // short values into memory byte[] data = new byte[size]; in.readFully(data); val = InternalValue.create(data); } break; case PropertyType.DOUBLE: val = InternalValue.create(in.readDouble()); break; case PropertyType.DECIMAL: val = InternalValue.create(readDecimal()); break; case PropertyType.LONG: if (version >= BundleBinding.VERSION_3) { val = InternalValue.create(readVarLong()); } else { val = InternalValue.create(in.readLong()); } break; case PropertyType.BOOLEAN: val = InternalValue.create(in.readBoolean()); break; case PropertyType.NAME: val = InternalValue.create(readQName()); break; case PropertyType.WEAKREFERENCE: val = InternalValue.create(readNodeId(), true); break; case PropertyType.REFERENCE: val = InternalValue.create(readNodeId(), false); break; case PropertyType.DATE: if (version >= BundleBinding.VERSION_3) { val = InternalValue.create(readDate()); break; } // else fall through default: if (version >= BundleBinding.VERSION_3) { val = InternalValue.valueOf( readString(), entry.getType()); } 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); String stringVal = new String(bytes, "UTF-8"); // https://issues.apache.org/jira/browse/JCR-3083 if (PropertyType.DATE == entry.getType()) { val = InternalValue.createDate(stringVal); } else { val = InternalValue.valueOf(stringVal, entry.getType()); } } } values[i] = val; } entry.setValues(values); entry.setBlobIds(blobIds); return entry; } /** * Deserializes a node identifier * * @return the node id * @throws IOException in an I/O error occurs. */ private NodeId readNodeId() throws IOException { if (version >= BundleBinding.VERSION_3 || in.readBoolean()) { long msb = in.readLong(); long lsb = in.readLong(); return new NodeId(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 Name readQName() throws IOException { if (version >= BundleBinding.VERSION_3) { return readName(); } String uri = binding.nsIndex.indexToString(in.readInt()); String local = in.readUTF(); return NameFactoryImpl.getInstance().create(uri, local); } /** * Deserializes an indexed Name * * @return the qname * @throws IOException in an I/O error occurs. */ private Name readIndexedQName() throws IOException { if (version >= BundleBinding.VERSION_3) { return readName(); } int index = in.readInt(); if (index < 0) { return null; } else { String uri = binding.nsIndex.indexToString(index); String local = binding.nameIndex.indexToString(in.readInt()); return NameFactoryImpl.getInstance().create(uri, local); } } /** * Deserializes a name written using bundle serialization version 3. * * @return deserialized name * @throws IOException if an I/O error occurs */ private Name readName() throws IOException { int b = in.readUnsignedByte(); if ((b & 0x80) == 0) { return BundleNames.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 NameFactoryImpl.getInstance().create(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 >= BundleBinding.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; } }