/* * 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.apache.commons.io.IOUtils; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.state.NodeReferences; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import javax.jcr.PropertyType; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * <code>Serializer</code> is a utility class that provides static methods * for serializing & deserializing <code>ItemState</code> and * <code>NodeReferences</code> objects using a simple binary serialization * format. */ public final class Serializer { private static final byte[] NULL_UUID_PLACEHOLDER_BYTES = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /** * encoding used for serializing String values */ private static final String ENCODING = "UTF-8"; /** * Serializes the specified <code>NodeState</code> object to the given * binary <code>stream</code>. * * @param state <code>state</code> to serialize * @param stream the stream where the <code>state</code> should be * serialized to * @throws Exception if an error occurs during the serialization * @see #deserialize(NodeState, InputStream) */ public static void serialize(NodeState state, OutputStream stream) throws Exception { DataOutputStream out = new DataOutputStream(stream); // primaryType out.writeUTF(state.getNodeTypeName().toString()); // parentUUID if (state.getParentId() == null) { out.write(NULL_UUID_PLACEHOLDER_BYTES); } else { out.write(state.getParentId().getRawBytes()); } // definitionId out.writeUTF(""); // mixin types Collection<Name> c = state.getMixinTypeNames(); out.writeInt(c.size()); // count for (Iterator<Name> iter = c.iterator(); iter.hasNext();) { out.writeUTF(iter.next().toString()); // name } // modCount out.writeShort(state.getModCount()); // properties (names) c = state.getPropertyNames(); out.writeInt(c.size()); // count for (Iterator<Name> iter = c.iterator(); iter.hasNext();) { Name propName = iter.next(); out.writeUTF(propName.toString()); // name } // child nodes (list of name/uuid pairs) Collection<ChildNodeEntry> collChildren = state.getChildNodeEntries(); out.writeInt(collChildren.size()); // count for (Iterator<ChildNodeEntry> iter = collChildren.iterator(); iter.hasNext();) { ChildNodeEntry entry = iter.next(); out.writeUTF(entry.getName().toString()); // name out.write(entry.getId().getRawBytes()); // uuid } } /** * Deserializes a <code>NodeState</code> object from the given binary * <code>stream</code>. * * @param state <code>state</code> to deserialize * @param stream the stream where the <code>state</code> should be deserialized from * @throws Exception if an error occurs during the deserialization * @see #serialize(NodeState, OutputStream) */ public static void deserialize(NodeState state, InputStream stream) throws Exception { DataInputStream in = new DataInputStream(stream); // primaryType String s = in.readUTF(); state.setNodeTypeName(NameFactoryImpl.getInstance().create(s)); // parentUUID (may be null) byte[] uuidBytes = new byte[NodeId.UUID_BYTE_LENGTH]; in.readFully(uuidBytes); if (!Arrays.equals(uuidBytes, NULL_UUID_PLACEHOLDER_BYTES)) { state.setParentId(new NodeId(uuidBytes)); } // definitionId in.readUTF(); // mixin types int count = in.readInt(); // count Set<Name> set = new HashSet<Name>(count); for (int i = 0; i < count; i++) { set.add(NameFactoryImpl.getInstance().create(in.readUTF())); } if (set.size() > 0) { state.setMixinTypeNames(set); } // modCount short modCount = in.readShort(); state.setModCount(modCount); // properties (names) count = in.readInt(); // count for (int i = 0; i < count; i++) { state.addPropertyName(NameFactoryImpl.getInstance().create(in.readUTF())); // name } // child nodes (list of name/uuid pairs) count = in.readInt(); // count for (int i = 0; i < count; i++) { Name name = NameFactoryImpl.getInstance().create(in.readUTF()); // name // uuid in.readFully(uuidBytes); state.addChildNodeEntry(name, new NodeId(uuidBytes)); } } /** * Serializes the specified <code>PropertyState</code> object to the given * binary <code>stream</code>. Binary values are stored in the specified * <code>BLOBStore</code>. * * @param state <code>state</code> to serialize * @param stream the stream where the <code>state</code> should be * serialized to * @param blobStore handler for BLOB data * @throws Exception if an error occurs during the serialization * @see #deserialize(PropertyState, InputStream,BLOBStore) */ public static void serialize(PropertyState state, OutputStream stream, BLOBStore blobStore) throws Exception { DataOutputStream out = new DataOutputStream(stream); // type out.writeInt(state.getType()); // multiValued out.writeBoolean(state.isMultiValued()); // definitionId out.writeUTF(""); // modCount out.writeShort(state.getModCount()); // values InternalValue[] values = state.getValues(); out.writeInt(values.length); // count for (int i = 0; i < values.length; i++) { InternalValue val = values[i]; if (state.getType() == PropertyType.BINARY) { // special handling required for binary value: // put binary value in BLOB store InputStream in = val.getStream(); String blobId = blobStore.createId(state.getPropertyId(), i); try { blobStore.put(blobId, in, val.getLength()); } finally { IOUtils.closeQuietly(in); } // store id of BLOB as property value out.writeUTF(blobId); // value // replace value instance with value backed by resource // in BLOB store and discard old value instance (e.g. temp file) if (blobStore instanceof ResourceBasedBLOBStore) { // optimization: if the BLOB store is resource-based // retrieve the resource directly rather than having // to read the BLOB from an input stream FileSystemResource fsRes = ((ResourceBasedBLOBStore) blobStore).getResource(blobId); values[i] = InternalValue.create(fsRes); } else { in = blobStore.get(blobId); try { values[i] = InternalValue.create(in); } finally { IOUtils.closeQuietly(in); } } val.discard(); } else { /** * because writeUTF(String) has a size limit of 65k, * Strings are serialized as <length><byte[]> */ //out.writeUTF(val.toString()); // value byte[] bytes = val.toString().getBytes(ENCODING); out.writeInt(bytes.length); // lenght of byte[] out.write(bytes); // byte[] } } } /** * Deserializes a <code>PropertyState</code> object from the given binary * <code>stream</code>. Binary values are retrieved from the specified * <code>BLOBStore</code>. * * @param state <code>state</code> to deserialize * @param stream the stream where the <code>state</code> should be * deserialized from * @param blobStore handler for BLOB data * @throws Exception if an error occurs during the deserialization * @see #serialize(PropertyState, OutputStream, BLOBStore) */ public static void deserialize(PropertyState state, InputStream stream, BLOBStore blobStore) throws Exception { DataInputStream in = new DataInputStream(stream); // type int type = in.readInt(); state.setType(type); // multiValued boolean multiValued = in.readBoolean(); state.setMultiValued(multiValued); // definitionId in.readUTF(); // modCount short modCount = in.readShort(); state.setModCount(modCount); // values int count = in.readInt(); // count InternalValue[] values = new InternalValue[count]; for (int i = 0; i < count; i++) { InternalValue val; if (type == PropertyType.BINARY) { String s = in.readUTF(); // value (i.e. blobId) // special handling required for binary value: // the value stores the id of the BLOB data // in the BLOB store if (blobStore instanceof ResourceBasedBLOBStore) { // optimization: if the BLOB store is resource-based // retrieve the resource directly rather than having // to read the BLOB from an input stream FileSystemResource fsRes = ((ResourceBasedBLOBStore) blobStore).getResource(s); val = InternalValue.create(fsRes); } else { InputStream is = blobStore.get(s); try { val = InternalValue.create(is); } finally { try { is.close(); } catch (IOException e) { // ignore } } } } else { /** * because writeUTF(String) has a size limit of 65k, * Strings are serialized as <length><byte[]> */ //s = in.readUTF(); // value int len = in.readInt(); // lenght of byte[] byte[] bytes = new byte[len]; in.readFully(bytes); // byte[] String s = new String(bytes, ENCODING); val = InternalValue.valueOf(s, type); } values[i] = val; } state.setValues(values); } /** * Serializes the specified <code>NodeReferences</code> object to the given * binary <code>stream</code>. * * @param refs object to serialize * @param stream the stream where the object should be serialized to * @throws Exception if an error occurs during the serialization * @see #deserialize(NodeReferences, InputStream) */ public static void serialize(NodeReferences refs, OutputStream stream) throws Exception { DataOutputStream out = new DataOutputStream(stream); // references Collection<PropertyId> c = refs.getReferences(); out.writeInt(c.size()); // count for (Iterator<PropertyId> iter = c.iterator(); iter.hasNext();) { PropertyId propId = iter.next(); out.writeUTF(propId.toString()); // propertyId } } /** * Deserializes a <code>NodeReferences</code> object from the given * binary <code>stream</code>. * * @param refs object to deserialize * @param stream the stream where the object should be deserialized from * @throws Exception if an error occurs during the deserialization * @see #serialize(NodeReferences, OutputStream) */ public static void deserialize(NodeReferences refs, InputStream stream) throws Exception { DataInputStream in = new DataInputStream(stream); refs.clearAllReferences(); // references int count = in.readInt(); // count for (int i = 0; i < count; i++) { refs.addReference(PropertyId.valueOf(in.readUTF())); // propertyId } } }