You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/09/29 18:06:54 UTC
svn commit: r1002707 [2/2] - in
/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence:
bundle/ pool/ util/
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java?rev=1002707&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java Wed Sep 29 16:06:53 2010
@@ -0,0 +1,431 @@
+/*
+ * 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.IOUtils;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.state.ChildNodeEntry;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+/**
+ * Bundle serializater.
+ *
+ * @see BundleReader
+ */
+class BundleWriter {
+
+ /** Logger instance */
+ private static Logger log = LoggerFactory.getLogger(BundleWriter.class);
+
+ private final BundleBinding binding;
+
+ private final DataOutputStream out;
+
+ /**
+ * Creates a new bundle serializer.
+ *
+ * @param binding bundle binding
+ * @param stream stream to which the bundle will be written
+ */
+ public BundleWriter(BundleBinding binding, OutputStream stream) {
+ this.binding = binding;
+ this.out = new DataOutputStream(stream);
+ }
+
+ /**
+ * Serializes a <code>NodePropBundle</code> to a data output stream
+ *
+ * @param bundle the bundle to serialize
+ * @throws IOException if an I/O error occurs.
+ */
+ public void writeBundle(NodePropBundle bundle)
+ throws IOException {
+ long size = out.size();
+
+ // primaryType and version
+ out.writeInt(
+ (BundleBinding.VERSION_CURRENT << 24)
+ | binding.nsIndex.stringToIndex(bundle.getNodeTypeName().getNamespaceURI()));
+ out.writeInt(binding.nameIndex.stringToIndex(bundle.getNodeTypeName().getLocalName()));
+
+ // parentUUID
+ writeNodeId(bundle.getParentId());
+
+ // definitionId
+ out.writeUTF("");
+
+ // mixin types
+ for (Name name : bundle.getMixinTypeNames()) {
+ writeIndexedQName(name);
+ }
+ writeIndexedQName(null);
+
+ // properties
+ for (Name pName : bundle.getPropertyNames()) {
+ // skip redundant primaryType, mixinTypes and uuid properties
+ if (pName.equals(NameConstants.JCR_PRIMARYTYPE)
+ || pName.equals(NameConstants.JCR_MIXINTYPES)
+ || pName.equals(NameConstants.JCR_UUID)) {
+ continue;
+ }
+ NodePropBundle.PropertyEntry pState = bundle.getPropertyEntry(pName);
+ if (pState == null) {
+ log.error("PropertyState missing in bundle: " + pName);
+ } else {
+ writeIndexedQName(pName);
+ writeState(pState);
+ }
+ }
+ writeIndexedQName(null);
+
+ // write uuid flag
+ out.writeBoolean(bundle.isReferenceable());
+
+ // child nodes (list of uuid/name pairs)
+ for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+ writeNodeId(entry.getId()); // uuid
+ writeQName(entry.getName()); // name
+ }
+ writeNodeId(null);
+
+ // write mod count
+ writeModCount(bundle.getModCount());
+
+ // write shared set
+ for (NodeId nodeId: bundle.getSharedSet()) {
+ writeNodeId(nodeId);
+ }
+ writeNodeId(null);
+
+ // set size of bundle
+ bundle.setSize(out.size() - size);
+ }
+
+ /**
+ * Serializes a <code>PropertyState</code> to the data output stream
+ *
+ * @param state the property entry to store
+ * @throws IOException if an I/O error occurs.
+ */
+ private void writeState(NodePropBundle.PropertyEntry state)
+ throws IOException {
+ // type & mod count
+ out.writeInt(state.getType() | (state.getModCount() << 16));
+ // multiValued
+ out.writeBoolean(state.isMultiValued());
+ // definitionId
+ out.writeUTF("");
+ // values
+ InternalValue[] values = state.getValues();
+ out.writeInt(values.length); // count
+ for (int i = 0; i < values.length; i++) {
+ InternalValue val = values[i];
+ switch (state.getType()) {
+ case PropertyType.BINARY:
+ try {
+ long size = val.getLength();
+ DataStore dataStore = binding.dataStore;
+ if (dataStore != null) {
+ int maxMemorySize = dataStore.getMinRecordLength() - 1;
+ if (size < maxMemorySize) {
+ writeSmallBinary(val, state, i);
+ } else {
+ out.writeInt(BundleBinding.BINARY_IN_DATA_STORE);
+ val.store(dataStore);
+ out.writeUTF(val.toString());
+ }
+ break;
+ }
+ // special handling required for binary value:
+ // spool binary value to file in blob store
+ if (size < 0) {
+ log.warn("Blob has negative size. Potential loss of data. "
+ + "id={} idx={}", state.getId(), String.valueOf(i));
+ out.writeInt(0);
+ values[i] = InternalValue.create(new byte[0]);
+ val.discard();
+ } else if (size > binding.getMinBlobSize()) {
+ out.writeInt(BundleBinding.BINARY_IN_BLOB_STORE);
+ String blobId = state.getBlobId(i);
+ if (blobId == null) {
+ BLOBStore blobStore = binding.getBlobStore();
+ try {
+ InputStream in = val.getStream();
+ try {
+ blobId = blobStore.createId(state.getId(), i);
+ blobStore.put(blobId, in, size);
+ state.setBlobId(blobId, i);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ } catch (Exception e) {
+ String msg = "Error while storing blob. id="
+ + state.getId() + " idx=" + i + " size=" + size;
+ log.error(msg, e);
+ throw new IOException(msg);
+ }
+ try {
+ // replace value instance with value
+ // backed by resource in blob store and delete temp file
+ if (blobStore instanceof ResourceBasedBLOBStore) {
+ values[i] = InternalValue.create(((ResourceBasedBLOBStore) blobStore).getResource(blobId));
+ } else {
+ values[i] = InternalValue.create(blobStore.get(blobId));
+ }
+ } catch (Exception e) {
+ log.error("Error while reloading blob. truncating. id="
+ + state.getId() + " idx=" + i + " size=" + size, e);
+ values[i] = InternalValue.create(new byte[0]);
+ }
+ val.discard();
+ }
+ // store id of blob as property value
+ out.writeUTF(blobId); // value
+ } else {
+ // delete evt. blob
+ byte[] data = writeSmallBinary(val, state, i);
+ // replace value instance with value
+ // backed by resource in blob store and delete temp file
+ values[i] = InternalValue.create(data);
+ val.discard();
+ }
+ } catch (RepositoryException e) {
+ String msg = "Error while storing blob. id="
+ + state.getId() + " idx=" + i + " value=" + val;
+ log.error(msg, e);
+ throw new IOException(msg);
+ }
+ break;
+ case PropertyType.DOUBLE:
+ try {
+ out.writeDouble(val.getDouble());
+ } catch (RepositoryException e) {
+ // should never occur
+ throw new IOException("Unexpected error while writing DOUBLE value.");
+ }
+ break;
+ case PropertyType.DECIMAL:
+ try {
+ writeDecimal(val.getDecimal());
+ } catch (RepositoryException e) {
+ // should never occur
+ throw new IOException("Unexpected error while writing DECIMAL value.");
+ }
+ break;
+ case PropertyType.LONG:
+ try {
+ out.writeLong(val.getLong());
+ } catch (RepositoryException e) {
+ // should never occur
+ throw new IOException("Unexpected error while writing LONG value.");
+ }
+ break;
+ case PropertyType.BOOLEAN:
+ try {
+ out.writeBoolean(val.getBoolean());
+ } catch (RepositoryException e) {
+ // should never occur
+ throw new IOException("Unexpected error while writing BOOLEAN value.");
+ }
+ break;
+ case PropertyType.NAME:
+ try {
+ writeQName(val.getName());
+ } catch (RepositoryException e) {
+ // should never occur
+ throw new IOException("Unexpected error while writing NAME value.");
+ }
+ break;
+ case PropertyType.WEAKREFERENCE:
+ case PropertyType.REFERENCE:
+ writeNodeId(val.getNodeId());
+ break;
+ default:
+ // because writeUTF(String) has a size limit of 64k,
+ // we're using write(byte[]) instead
+ byte[] bytes = val.toString().getBytes("UTF-8");
+ out.writeInt(bytes.length); // length of byte[]
+ out.write(bytes); // byte[]
+ }
+ }
+ }
+
+ /**
+ * Write a small binary value and return the data.
+ *
+ * @param value the binary value
+ * @param state the property state (for error messages)
+ * @param i the index (for error messages)
+ * @return the data
+ * @throws IOException if the data could not be read
+ */
+ private byte[] writeSmallBinary(
+ InternalValue value, NodePropBundle.PropertyEntry state, int i)
+ throws IOException {
+ try {
+ int size = (int) value.getLength();
+ out.writeInt(size);
+ byte[] data = new byte[size];
+ DataInputStream in =
+ new DataInputStream(value.getStream());
+ try {
+ in.readFully(data);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ out.write(data, 0, data.length);
+ return data;
+ } catch (Exception e) {
+ String msg = "Error while storing blob. id="
+ + state.getId() + " idx=" + i + " value=" + value;
+ log.error(msg, e);
+ throw new IOException(msg);
+ }
+ }
+
+ /**
+ * Serializes a <code>NodeState</code> to the data output stream
+ *
+ * @param state the state to write
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeState(NodeState state) throws IOException {
+ // primaryType & version
+ out.writeInt((BundleBinding.VERSION_CURRENT << 24)
+ | binding.nsIndex.stringToIndex(state.getNodeTypeName().getNamespaceURI()));
+ out.writeUTF(state.getNodeTypeName().getLocalName());
+ // parentUUID
+ writeNodeId(state.getParentId());
+ // definitionId
+ out.writeUTF("");
+ // mixin types
+ Collection<Name> c = state.getMixinTypeNames();
+ out.writeInt(c.size()); // count
+ for (Iterator<Name> iter = c.iterator(); iter.hasNext();) {
+ writeQName(iter.next());
+ }
+ // properties (names)
+ c = state.getPropertyNames();
+ out.writeInt(c.size()); // count
+ for (Iterator<Name> iter = c.iterator(); iter.hasNext();) {
+ Name pName = iter.next();
+ writeIndexedQName(pName);
+ }
+ // child nodes (list of name/uuid pairs)
+ Collection<ChildNodeEntry> collChild = state.getChildNodeEntries();
+ out.writeInt(collChild.size()); // count
+ for (Iterator<ChildNodeEntry> iter = collChild.iterator(); iter.hasNext();) {
+ ChildNodeEntry entry = iter.next();
+ writeQName(entry.getName()); // name
+ writeNodeId(entry.getId()); // uuid
+ }
+ writeModCount(state.getModCount());
+
+ // shared set (list of parent uuids)
+ Collection<NodeId> collShared = state.getSharedSet();
+ out.writeInt(collShared.size()); // count
+ for (Iterator<NodeId> iter = collShared.iterator(); iter.hasNext();) {
+ writeNodeId(iter.next());
+ }
+ }
+
+ /**
+ * Serializes a node identifier
+ *
+ * @param id the node id
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeNodeId(NodeId id) throws IOException {
+ if (id == null) {
+ out.writeBoolean(false);
+ } else {
+ out.writeBoolean(true);
+ out.write(id.getRawBytes());
+ }
+ }
+
+ /**
+ * Serializes a BigDecimal
+ *
+ * @param decimal the decimal number
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeDecimal(BigDecimal decimal) throws IOException {
+ if (decimal == null) {
+ out.writeBoolean(false);
+ } else {
+ out.writeBoolean(true);
+ // TODO more efficient serialization format
+ out.writeUTF(decimal.toString());
+ }
+ }
+
+ /**
+ * Sersializes a mod-count
+ *
+ * @param modCount the mod count
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeModCount(short modCount) throws IOException {
+ out.writeShort(modCount);
+ }
+
+ /**
+ * Serializes a Name
+ *
+ * @param name the name
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeQName(Name name) throws IOException {
+ out.writeInt(binding.nsIndex.stringToIndex(name.getNamespaceURI()));
+ out.writeUTF(name.getLocalName());
+ }
+
+ /**
+ * Serializes a indexed Name
+ *
+ * @param name the name
+ * @throws IOException in an I/O error occurs.
+ */
+ private void writeIndexedQName(Name name) throws IOException {
+ if (name == null) {
+ out.writeInt(-1);
+ } else {
+ out.writeInt(binding.nsIndex.stringToIndex(name.getNamespaceURI()));
+ out.writeInt(binding.nameIndex.stringToIndex(name.getLocalName()));
+ }
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java
------------------------------------------------------------------------------
svn:eol-style = native