Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 17111 invoked from network); 26 Oct 2006 09:11:48 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 26 Oct 2006 09:11:48 -0000 Received: (qmail 8176 invoked by uid 500); 26 Oct 2006 09:11:59 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 8109 invoked by uid 500); 26 Oct 2006 09:11:59 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 8067 invoked by uid 99); 26 Oct 2006 09:11:59 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Oct 2006 02:11:59 -0700 X-ASF-Spam-Status: No, hits=0.6 required=10.0 tests=NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (herse.apache.org: local policy) Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Oct 2006 02:11:43 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id C32F11A9851; Thu, 26 Oct 2006 02:11:22 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r467925 [3/6] - in /jackrabbit/trunk/jackrabbit: applications/test/ applications/test/workspaces/default/ applications/test/workspaces/test/ src/main/config/ src/main/java/org/apache/jackrabbit/core/ src/main/java/org/apache/jackrabbit/core... Date: Thu, 26 Oct 2006 09:11:20 -0000 To: commits@jackrabbit.apache.org From: tripod@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20061026091122.C32F11A9851@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,516 @@ +/* + * 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.obj; + +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.fs.BasedFileSystem; +import org.apache.jackrabbit.core.fs.FileSystem; +import org.apache.jackrabbit.core.fs.FileSystemException; +import org.apache.jackrabbit.core.fs.FileSystemResource; +import org.apache.jackrabbit.core.fs.local.LocalFileSystem; +import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager; +import org.apache.jackrabbit.core.persistence.PMContext; +import org.apache.jackrabbit.core.persistence.util.BLOBStore; +import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore; +import org.apache.jackrabbit.core.persistence.util.Serializer; +import org.apache.jackrabbit.core.state.ItemStateException; +import org.apache.jackrabbit.core.state.NoSuchItemStateException; +import org.apache.jackrabbit.core.state.NodeReferences; +import org.apache.jackrabbit.core.state.NodeReferencesId; +import org.apache.jackrabbit.core.state.NodeState; +import org.apache.jackrabbit.core.state.PropertyState; +import org.apache.jackrabbit.core.value.BLOBFileValue; +import org.apache.jackrabbit.core.value.InternalValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.PropertyType; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * ObjectPersistenceManager is a FileSystem-based + * PersistenceManager that persists ItemState + * and NodeReferences objects using a simple custom binary + * serialization format (see {@link Serializer}). + */ +public class ObjectPersistenceManager extends AbstractPersistenceManager { + + private static Logger log = LoggerFactory.getLogger(ObjectPersistenceManager.class); + + /** + * hexdigits for toString + */ + private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); + + private static final String NODEFILENAME = ".node"; + + private static final String NODEREFSFILENAME = ".references"; + + private boolean initialized; + + // file system where the item state is stored + private FileSystem itemStateFS; + // file system where BLOB data is stored + private FileSystem blobFS; + // BLOBStore that manages BLOB data in the file system + private BLOBStore blobStore; + + /** + * Creates a new ObjectPersistenceManager instance. + */ + public ObjectPersistenceManager() { + initialized = false; + } + + private static String buildNodeFolderPath(NodeId id) { + StringBuffer sb = new StringBuffer(); + char[] chars = id.getUUID().toString().toCharArray(); + int cnt = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '-') { + continue; + } + //if (cnt > 0 && cnt % 4 == 0) { + if (cnt == 2 || cnt == 4) { + sb.append(FileSystem.SEPARATOR_CHAR); + } + sb.append(chars[i]); + cnt++; + } + return sb.toString(); + } + + private static String buildPropFilePath(PropertyId id) { + String fileName; + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(id.getName().getNamespaceURI().getBytes()); + md5.update(id.getName().getLocalName().getBytes()); + byte[] bytes = md5.digest(); + char[] chars = new char[32]; + for (int i = 0, j = 0; i < 16; i++) { + chars[j++] = HEXDIGITS[(bytes[i] >> 4) & 0x0f]; + chars[j++] = HEXDIGITS[bytes[i] & 0x0f]; + } + fileName = new String(chars); + } catch (NoSuchAlgorithmException nsae) { + // should never get here as MD5 should always be available in the JRE + String msg = "MD5 not available: "; + log.error(msg, nsae); + throw new InternalError(msg + nsae); + } + return buildNodeFolderPath(id.getParentId()) + FileSystem.SEPARATOR + fileName; + } + + private static String buildNodeFilePath(NodeId id) { + return buildNodeFolderPath(id) + FileSystem.SEPARATOR + NODEFILENAME; + } + + private static String buildNodeReferencesFilePath(NodeReferencesId id) { + return buildNodeFolderPath(id.getTargetId()) + FileSystem.SEPARATOR + NODEREFSFILENAME; + } + + //---------------------------------------------------< PersistenceManager > + /** + * {@inheritDoc} + */ + public void init(PMContext context) throws Exception { + if (initialized) { + throw new IllegalStateException("already initialized"); + } + + FileSystem wspFS = context.getFileSystem(); + itemStateFS = new BasedFileSystem(wspFS, "/data"); + + /** + * store BLOB data in local file system in a sub directory + * of the workspace home directory + */ + LocalFileSystem blobFS = new LocalFileSystem(); + blobFS.setRoot(new File(context.getHomeDir(), "blobs")); + blobFS.init(); + this.blobFS = blobFS; + blobStore = new FileSystemBLOBStore(blobFS); + + initialized = true; + } + + /** + * {@inheritDoc} + */ + public synchronized void close() throws Exception { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + // close BLOB file system + blobFS.close(); + blobFS = null; + blobStore = null; + /** + * there's no need close the item state store because it + * is based in the workspace's file system which is + * closed by the repository + */ + } finally { + initialized = false; + } + } + + /** + * {@inheritDoc} + */ + public synchronized NodeState load(NodeId id) + throws NoSuchItemStateException, ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String nodeFilePath = buildNodeFilePath(id); + + try { + if (!itemStateFS.isFile(nodeFilePath)) { + throw new NoSuchItemStateException(nodeFilePath); + } + } catch (FileSystemException fse) { + String msg = "failed to read node state: " + nodeFilePath; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + + try { + BufferedInputStream in = + new BufferedInputStream(itemStateFS.getInputStream(nodeFilePath)); + try { + NodeState state = createNew(id); + Serializer.deserialize(state, in); + return state; + } catch (Exception e) { + String msg = "failed to read node state: " + id.getUUID(); + log.debug(msg); + throw new ItemStateException(msg, e); + } finally { + in.close(); + } + } catch (Exception e) { + String msg = "failed to read node state: " + nodeFilePath; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + public synchronized PropertyState load(PropertyId id) + throws NoSuchItemStateException, ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String propFilePath = buildPropFilePath(id); + + try { + if (!itemStateFS.isFile(propFilePath)) { + throw new NoSuchItemStateException(propFilePath); + } + } catch (FileSystemException fse) { + String msg = "failed to read property state: " + propFilePath; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + + try { + BufferedInputStream in = + new BufferedInputStream(itemStateFS.getInputStream(propFilePath)); + try { + PropertyState state = createNew(id); + Serializer.deserialize(state, in, blobStore); + return state; + } finally { + in.close(); + } + } catch (Exception e) { + String msg = "failed to read property state: " + propFilePath; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + public synchronized NodeReferences load(NodeReferencesId id) + throws NoSuchItemStateException, ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String refsFilePath = buildNodeReferencesFilePath(id); + + try { + if (!itemStateFS.isFile(refsFilePath)) { + throw new NoSuchItemStateException(id.toString()); + } + } catch (FileSystemException fse) { + String msg = "failed to load references: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + + try { + BufferedInputStream in = + new BufferedInputStream(itemStateFS.getInputStream(refsFilePath)); + try { + NodeReferences refs = new NodeReferences(id); + Serializer.deserialize(refs, in); + return refs; + } finally { + in.close(); + } + } catch (Exception e) { + String msg = "failed to load references: " + id; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void store(NodeState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String nodeFilePath = buildNodeFilePath(state.getNodeId()); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + try { + nodeFile.makeParentDirs(); + BufferedOutputStream out = new BufferedOutputStream(nodeFile.getOutputStream()); + try { + // serialize node state + Serializer.serialize(state, out); + } finally { + out.close(); + } + } catch (Exception e) { + String msg = "failed to write node state: " + state.getNodeId(); + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void store(PropertyState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String propFilePath = buildPropFilePath(state.getPropertyId()); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + try { + propFile.makeParentDirs(); + BufferedOutputStream out = new BufferedOutputStream(propFile.getOutputStream()); + try { + // serialize property state + Serializer.serialize(state, out, blobStore); + } finally { + out.close(); + } + } catch (Exception e) { + String msg = "failed to store property state: " + state.getParentId() + "/" + state.getName(); + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void store(NodeReferences refs) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String refsFilePath = buildNodeReferencesFilePath(refs.getId()); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + try { + refsFile.makeParentDirs(); + OutputStream out = new BufferedOutputStream(refsFile.getOutputStream()); + try { + Serializer.serialize(refs, out); + } finally { + out.close(); + } + } catch (Exception e) { + String msg = "failed to store references: " + refs.getId(); + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(NodeState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String nodeFilePath = buildNodeFilePath(state.getNodeId()); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + try { + if (nodeFile.exists()) { + // delete resource and prune empty parent folders + nodeFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete node state: " + state.getNodeId(); + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(PropertyState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + // delete binary values (stored as files) + InternalValue[] values = state.getValues(); + if (values != null) { + for (int i = 0; i < values.length; i++) { + InternalValue val = values[i]; + if (val != null) { + if (val.getType() == PropertyType.BINARY) { + BLOBFileValue blobVal = (BLOBFileValue) val.internalValue(); + // delete blob file and prune empty parent folders + blobVal.delete(true); + } + } + } + } + // delete property file + String propFilePath = buildPropFilePath(state.getPropertyId()); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + try { + if (propFile.exists()) { + // delete resource and prune empty parent folders + propFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete property state: " + state.getParentId() + "/" + state.getName(); + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(NodeReferences refs) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String refsFilePath = buildNodeReferencesFilePath(refs.getId()); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + try { + if (refsFile.exists()) { + // delete resource and prune empty parent folders + refsFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete node references: " + refs.getId(); + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(PropertyId id) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String propFilePath = buildPropFilePath(id); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + return propFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of item state: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(NodeId id) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String nodeFilePath = buildNodeFilePath(id); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + return nodeFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of item state: " + id; + log.error(msg, fse); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(NodeReferencesId id) + throws ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String refsFilePath = buildNodeReferencesFilePath(id); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + return refsFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of references: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/obj/ObjectPersistenceManager.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,72 @@ +/* + * 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.jackrabbit.core.PropertyId; + +import java.io.InputStream; + +/** + * BLOBStore represents an abstract store for binary property + * values (BLOBs). + * + * @see ResourceBasedBLOBStore + */ +public interface BLOBStore { + /** + * Creates a unique identifier for the BLOB data associated with the given + * property id and value subscript. + * + * @param id id of the property associated with the BLOB data + * @param index subscript of the value holding the BLOB data + * @return a string identifying the BLOB data + */ + String createId(PropertyId id, int index); + + /** + * Stores the BLOB data and returns a unique identifier. + * + * @param blobId identifier of the BLOB data as returned by + * {@link #createId(PropertyId, int)} + * @param in stream containing the BLOB data + * @param size size of the BLOB data + * @throws Exception if an error occured + */ + void put(String blobId, InputStream in, long size) throws Exception; + + /** + * Retrieves the BLOB data with the specified id as a binary stream. + * + * @param blobId identifier of the BLOB data as returned by + * {@link #createId(PropertyId, int)} + * @return an input stream that delivers the BLOB data + * @throws Exception if an error occured + */ + InputStream get(String blobId) throws Exception; + + /** + * Removes the BLOB data with the specified id. + * + * @param blobId identifier of the BLOB data as returned by + * {@link #createId(PropertyId, int)} + * @return true if BLOB data with the given id exists and has + * been successfully removed, false if there's no BLOB + * data with the given id. + * @throws Exception if an error occured + */ + boolean remove(String blobId) throws Exception; +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/BLOBStore.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,129 @@ +/* + * 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.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.fs.FileSystem; +import org.apache.jackrabbit.core.fs.FileSystemPathUtil; +import org.apache.jackrabbit.core.fs.FileSystemResource; + +import java.io.BufferedOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * FileSystemBLOBStore is a ResourceBasedBLOBStore + * implementation that stores BLOB data in a FileSystem. + */ +public class FileSystemBLOBStore implements ResourceBasedBLOBStore { + + /** + * the file system where the BLOBs are stored + */ + private final FileSystem fs; + + /** + * Creates a new FileSystemBLOBStore instance. + * + * @param fs file system for storing the BLOB data + */ + public FileSystemBLOBStore(FileSystem fs) { + this.fs = fs; + } + + //------------------------------------------------------------< BLOBStore > + /** + * {@inheritDoc} + */ + public String createId(PropertyId id, int index) { + // the blobId is an absolute file system path + StringBuffer sb = new StringBuffer(); + sb.append(FileSystem.SEPARATOR_CHAR); + char[] chars = id.getParentId().getUUID().toString().toCharArray(); + int cnt = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '-') { + continue; + } + //if (cnt > 0 && cnt % 4 == 0) { + if (cnt == 2 || cnt == 4) { + sb.append(FileSystem.SEPARATOR_CHAR); + } + sb.append(chars[i]); + cnt++; + } + sb.append(FileSystem.SEPARATOR_CHAR); + sb.append(FileSystemPathUtil.escapeName(id.getName().toString())); + sb.append('.'); + sb.append(index); + sb.append(".bin"); + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + public InputStream get(String blobId) throws Exception { + return getResource(blobId).getInputStream(); + } + + /** + * {@inheritDoc} + */ + public void put(String blobId, InputStream in, long size) throws Exception { + OutputStream out = null; + // the blobId is an absolute file system path + FileSystemResource internalBlobFile = new FileSystemResource(fs, blobId); + internalBlobFile.makeParentDirs(); + try { + out = new BufferedOutputStream(internalBlobFile.getOutputStream()); + byte[] buffer = new byte[8192]; + int read; + while ((read = in.read(buffer)) > 0) { + out.write(buffer, 0, read); + } + } finally { + if (out != null) { + out.close(); + } + } + } + + /** + * {@inheritDoc} + */ + public boolean remove(String blobId) throws Exception { + // the blobId is an absolute file system path + FileSystemResource res = new FileSystemResource(fs, blobId); + if (!res.exists()) { + return false; + } + // delete resource and prune empty parent folders + res.delete(true); + return true; + } + + //-----------------------------------------------< ResourceBasedBLOBStore > + /** + * {@inheritDoc} + */ + public FileSystemResource getResource(String blobId) + throws Exception { + // the blobId is an absolute file system path + return new FileSystemResource(fs, blobId); + } +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/FileSystemBLOBStore.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,36 @@ +/* + * 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.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.fs.FileSystemResource; + +/** + * ResourceBasedBLOBStore extends the BLOBStore + * interface with the method {@link #getResource(String)} + */ +public interface ResourceBasedBLOBStore extends BLOBStore { + /** + * Retrieves the BLOB data with the specified id as a permanent resource. + * + * @param blobId identifier of the BLOB data as returned by + * {@link #createId(PropertyId, int)} + * @return a resource representing the BLOB data + * @throws Exception if an error occured + */ + FileSystemResource getResource(String blobId) throws Exception; +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/ResourceBasedBLOBStore.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,364 @@ +/* + * 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.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.fs.FileSystemResource; +import org.apache.jackrabbit.core.nodetype.NodeDefId; +import org.apache.jackrabbit.core.nodetype.PropDefId; +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.value.BLOBFileValue; +import org.apache.jackrabbit.core.value.InternalValue; +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.uuid.UUID; + +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; + +/** + * Serializer is a utility class that provides static methods + * for serializing & deserializing ItemState and + * NodeReferences 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 NodeState object to the given + * binary stream. + * + * @param state state to serialize + * @param stream the stream where the state 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().getUUID().getRawBytes()); + } + // definitionId + out.writeUTF(state.getDefinitionId().toString()); + // mixin types + Collection c = state.getMixinTypeNames(); + out.writeInt(c.size()); // count + for (Iterator 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 iter = c.iterator(); iter.hasNext();) { + QName propName = (QName) iter.next(); + out.writeUTF(propName.toString()); // name + } + // child nodes (list of name/uuid pairs) + c = state.getChildNodeEntries(); + out.writeInt(c.size()); // count + for (Iterator iter = c.iterator(); iter.hasNext();) { + NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) iter.next(); + out.writeUTF(entry.getName().toString()); // name + out.write(entry.getId().getUUID().getRawBytes()); // uuid + } + } + + /** + * Deserializes a NodeState object from the given binary + * stream. + * + * @param state state to deserialize + * @param stream the stream where the state 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(QName.valueOf(s)); + // parentUUID (may be null) + byte[] uuidBytes = new byte[UUID.UUID_BYTE_LENGTH]; + in.readFully(uuidBytes); + if (!Arrays.equals(uuidBytes, NULL_UUID_PLACEHOLDER_BYTES)) { + state.setParentId(new NodeId(new UUID(uuidBytes))); + } + // definitionId + s = in.readUTF(); + state.setDefinitionId(NodeDefId.valueOf(s)); + // mixin types + int count = in.readInt(); // count + Set set = new HashSet(count); + for (int i = 0; i < count; i++) { + set.add(QName.valueOf(in.readUTF())); // name + } + 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(QName.valueOf(in.readUTF())); // name + } + // child nodes (list of name/uuid pairs) + count = in.readInt(); // count + for (int i = 0; i < count; i++) { + QName name = QName.valueOf(in.readUTF()); // name + // uuid + in.readFully(uuidBytes); + state.addChildNodeEntry(name, new NodeId(new UUID(uuidBytes))); + } + } + + /** + * Serializes the specified PropertyState object to the given + * binary stream. Binary values are stored in the specified + * BLOBStore. + * + * @param state state to serialize + * @param stream the stream where the state 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(state.getDefinitionId().toString()); + // 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 + BLOBFileValue blobVal = (BLOBFileValue) val.internalValue(); + InputStream in = blobVal.getStream(); + String blobId = blobStore.createId(state.getPropertyId(), i); + try { + blobStore.put(blobId, in, blobVal.getLength()); + } finally { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + // 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, false); + } finally { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + blobVal.discard(); + } else { + /** + * because writeUTF(String) has a size limit of 65k, + * Strings are serialized as + */ + //out.writeUTF(val.toString()); // value + byte[] bytes = val.toString().getBytes(ENCODING); + out.writeInt(bytes.length); // lenght of byte[] + out.write(bytes); // byte[] + } + } + } + + /** + * Deserializes a PropertyState object from the given binary + * stream. Binary values are retrieved from the specified + * BLOBStore. + * + * @param state state to deserialize + * @param stream the stream where the state 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 + String s = in.readUTF(); + state.setDefinitionId(PropDefId.valueOf(s)); + // 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) { + 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, false); + } finally { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + } + } else { + /** + * because writeUTF(String) has a size limit of 65k, + * Strings are serialized as + */ + //s = in.readUTF(); // value + int len = in.readInt(); // lenght of byte[] + byte[] bytes = new byte[len]; + in.readFully(bytes); // byte[] + s = new String(bytes, ENCODING); + val = InternalValue.valueOf(s, type); + } + values[i] = val; + } + state.setValues(values); + } + + /** + * Serializes the specified NodeReferences object to the given + * binary stream. + * + * @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 c = refs.getReferences(); + out.writeInt(c.size()); // count + for (Iterator iter = c.iterator(); iter.hasNext();) { + PropertyId propId = (PropertyId) iter.next(); + out.writeUTF(propId.toString()); // propertyId + } + } + + /** + * Deserializes a NodeReferences object from the given + * binary stream. + * + * @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 + } + } +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/util/Serializer.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java?view=auto&rev=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java (added) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java Thu Oct 26 02:11:18 2006 @@ -0,0 +1,919 @@ +/* + * 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.xml; + +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.fs.BasedFileSystem; +import org.apache.jackrabbit.core.fs.FileSystem; +import org.apache.jackrabbit.core.fs.FileSystemException; +import org.apache.jackrabbit.core.fs.FileSystemResource; +import org.apache.jackrabbit.core.fs.local.LocalFileSystem; +import org.apache.jackrabbit.core.nodetype.NodeDefId; +import org.apache.jackrabbit.core.nodetype.PropDefId; +import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager; +import org.apache.jackrabbit.core.state.ItemStateException; +import org.apache.jackrabbit.core.state.NoSuchItemStateException; +import org.apache.jackrabbit.core.state.NodeReferences; +import org.apache.jackrabbit.core.state.NodeReferencesId; +import org.apache.jackrabbit.core.state.NodeState; +import org.apache.jackrabbit.core.persistence.PMContext; +import org.apache.jackrabbit.core.state.PropertyState; +import org.apache.jackrabbit.core.persistence.util.BLOBStore; +import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore; +import org.apache.jackrabbit.core.persistence.util.ResourceBasedBLOBStore; +import org.apache.jackrabbit.core.util.DOMWalker; +import org.apache.jackrabbit.core.value.BLOBFileValue; +import org.apache.jackrabbit.core.value.InternalValue; +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.PropertyType; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * XMLPersistenceManager is a FileSystem-based + * PersistenceManager that persists ItemState + * and NodeReferences objects in XML format. + */ +public class XMLPersistenceManager extends AbstractPersistenceManager { + + private static Logger log = LoggerFactory.getLogger(XMLPersistenceManager.class); + + /** + * hexdigits for toString + */ + private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); + + /** + * The default encoding used in serialization + */ + public static final String DEFAULT_ENCODING = "UTF-8"; + + /** + * The XML elements and attributes used in serialization + */ + private static final String NODE_ELEMENT = "node"; + private static final String UUID_ATTRIBUTE = "uuid"; + private static final String NODETYPE_ATTRIBUTE = "nodeType"; + private static final String PARENTUUID_ATTRIBUTE = "parentUUID"; + private static final String DEFINITIONID_ATTRIBUTE = "definitionId"; + private static final String MODCOUNT_ATTRIBUTE = "modCount"; + + private static final String MIXINTYPES_ELEMENT = "mixinTypes"; + private static final String MIXINTYPE_ELEMENT = "mixinType"; + + private static final String PROPERTIES_ELEMENT = "properties"; + private static final String PROPERTY_ELEMENT = "property"; + private static final String NAME_ATTRIBUTE = "name"; + private static final String TYPE_ATTRIBUTE = "type"; + private static final String MULTIVALUED_ATTRIBUTE = "multiValued"; + + private static final String VALUES_ELEMENT = "values"; + private static final String VALUE_ELEMENT = "value"; + + private static final String NODES_ELEMENT = "nodes"; + + private static final String NODEREFERENCES_ELEMENT = "references"; + private static final String TARGETID_ATTRIBUTE = "targetId"; + private static final String NODEREFERENCE_ELEMENT = "reference"; + private static final String PROPERTYID_ATTRIBUTE = "propertyId"; + + private static final String NODEFILENAME = ".node.xml"; + + private static final String NODEREFSFILENAME = ".references.xml"; + + private boolean initialized; + + // file system where the item state is stored + private FileSystem itemStateFS; + // file system where BLOB data is stored + private FileSystem blobFS; + // BLOBStore that manages BLOB data in the file system + private BLOBStore blobStore; + + /** + * Creates a new XMLPersistenceManager instance. + */ + public XMLPersistenceManager() { + initialized = false; + } + + private String buildNodeFolderPath(NodeId id) { + StringBuffer sb = new StringBuffer(); + char[] chars = id.getUUID().toString().toCharArray(); + int cnt = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '-') { + continue; + } + //if (cnt > 0 && cnt % 4 == 0) { + if (cnt == 4 || cnt == 8) { + sb.append('/'); + } + sb.append(chars[i]); + cnt++; + } + return sb.toString(); + } + + private String buildPropFilePath(PropertyId id) { + String fileName; + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(id.getName().getNamespaceURI().getBytes()); + md5.update(id.getName().getLocalName().getBytes()); + byte[] bytes = md5.digest(); + char[] chars = new char[32]; + for (int i = 0, j = 0; i < 16; i++) { + chars[j++] = HEXDIGITS[(bytes[i] >> 4) & 0x0f]; + chars[j++] = HEXDIGITS[bytes[i] & 0x0f]; + } + fileName = new String(chars) + ".xml"; + } catch (NoSuchAlgorithmException nsae) { + // should never get here as MD5 should always be available in the JRE + String msg = "MD5 not available"; + log.error(msg, nsae); + throw new InternalError(msg + nsae); + } + return buildNodeFolderPath(id.getParentId()) + "/" + fileName; + } + + private String buildNodeFilePath(NodeId id) { + return buildNodeFolderPath(id) + "/" + NODEFILENAME; + } + + private String buildNodeReferencesFilePath(NodeReferencesId id) { + return buildNodeFolderPath(id.getTargetId()) + "/" + NODEREFSFILENAME; + } + + private void readState(DOMWalker walker, NodeState state) + throws ItemStateException { + // first do some paranoid sanity checks + if (!walker.getName().equals(NODE_ELEMENT)) { + String msg = "invalid serialization format (unexpected element: " + + walker.getName() + ")"; + log.debug(msg); + throw new ItemStateException(msg); + } + // check uuid + if (!state.getNodeId().getUUID().toString().equals(walker.getAttribute(UUID_ATTRIBUTE))) { + String msg = "invalid serialized state: uuid mismatch"; + log.debug(msg); + throw new ItemStateException(msg); + } + // check nodetype + String ntName = walker.getAttribute(NODETYPE_ATTRIBUTE); + if (!QName.valueOf(ntName).equals(state.getNodeTypeName())) { + String msg = "invalid serialized state: nodetype mismatch"; + log.debug(msg); + throw new ItemStateException(msg); + } + + // now we're ready to read state + + // primary parent + String parentUUID = walker.getAttribute(PARENTUUID_ATTRIBUTE); + if (parentUUID.length() > 0) { + state.setParentId(NodeId.valueOf(parentUUID)); + } + + // definition id + String definitionId = walker.getAttribute(DEFINITIONID_ATTRIBUTE); + state.setDefinitionId(NodeDefId.valueOf(definitionId)); + + // modification count + String modCount = walker.getAttribute(MODCOUNT_ATTRIBUTE); + state.setModCount(Short.parseShort(modCount)); + + // mixin types + if (walker.enterElement(MIXINTYPES_ELEMENT)) { + Set mixins = new HashSet(); + while (walker.iterateElements(MIXINTYPE_ELEMENT)) { + mixins.add(QName.valueOf(walker.getAttribute(NAME_ATTRIBUTE))); + } + if (mixins.size() > 0) { + state.setMixinTypeNames(mixins); + } + walker.leaveElement(); + } + + // property entries + if (walker.enterElement(PROPERTIES_ELEMENT)) { + while (walker.iterateElements(PROPERTY_ELEMENT)) { + String propName = walker.getAttribute(NAME_ATTRIBUTE); + // @todo deserialize type and values + state.addPropertyName(QName.valueOf(propName)); + } + walker.leaveElement(); + } + + // child node entries + if (walker.enterElement(NODES_ELEMENT)) { + while (walker.iterateElements(NODE_ELEMENT)) { + String childName = walker.getAttribute(NAME_ATTRIBUTE); + String childUUID = walker.getAttribute(UUID_ATTRIBUTE); + state.addChildNodeEntry(QName.valueOf(childName), NodeId.valueOf(childUUID)); + } + walker.leaveElement(); + } + } + + private void readState(DOMWalker walker, PropertyState state) + throws ItemStateException { + // first do some paranoid sanity checks + if (!walker.getName().equals(PROPERTY_ELEMENT)) { + String msg = "invalid serialization format (unexpected element: " + + walker.getName() + ")"; + log.debug(msg); + throw new ItemStateException(msg); + } + // check name + if (!state.getName().equals(QName.valueOf(walker.getAttribute(NAME_ATTRIBUTE)))) { + String msg = "invalid serialized state: name mismatch"; + log.debug(msg); + throw new ItemStateException(msg); + } + // check parentUUID + NodeId parentId = NodeId.valueOf(walker.getAttribute(PARENTUUID_ATTRIBUTE)); + if (!parentId.equals(state.getParentId())) { + String msg = "invalid serialized state: parentUUID mismatch"; + log.debug(msg); + throw new ItemStateException(msg); + } + + // now we're ready to read state + + // type + String typeName = walker.getAttribute(TYPE_ATTRIBUTE); + int type; + try { + type = PropertyType.valueFromName(typeName); + } catch (IllegalArgumentException iae) { + // should never be getting here + throw new ItemStateException("unexpected property-type: " + typeName, iae); + } + state.setType(type); + + // multiValued + String multiValued = walker.getAttribute(MULTIVALUED_ATTRIBUTE); + state.setMultiValued(Boolean.getBoolean(multiValued)); + + // definition id + String definitionId = walker.getAttribute(DEFINITIONID_ATTRIBUTE); + state.setDefinitionId(PropDefId.valueOf(definitionId)); + + // modification count + String modCount = walker.getAttribute(MODCOUNT_ATTRIBUTE); + state.setModCount(Short.parseShort(modCount)); + + // values + ArrayList values = new ArrayList(); + if (walker.enterElement(VALUES_ELEMENT)) { + while (walker.iterateElements(VALUE_ELEMENT)) { + // read serialized value + String content = walker.getContent(); + if (PropertyType.STRING == type) { + // STRING value can be empty; ignore length + values.add(InternalValue.valueOf(content, type)); + } else if (content.length() > 0) { + // non-empty non-STRING value + if (type == PropertyType.BINARY) { + try { + // 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(content); + values.add(InternalValue.create(fsRes)); + } else { + InputStream in = blobStore.get(content); + try { + values.add(InternalValue.create(in, false)); + } finally { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + } catch (Exception e) { + String msg = "error while reading serialized binary value"; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } else { + // non-empty non-STRING non-BINARY value + values.add(InternalValue.valueOf(content, type)); + } + } else { + // empty non-STRING value + log.warn(state.getPropertyId() + ": ignoring empty value of type " + + PropertyType.nameFromValue(type)); + } + } + walker.leaveElement(); + } + state.setValues((InternalValue[]) + values.toArray(new InternalValue[values.size()])); + } + + private void readState(DOMWalker walker, NodeReferences refs) + throws ItemStateException { + // first do some paranoid sanity checks + if (!walker.getName().equals(NODEREFERENCES_ELEMENT)) { + String msg = "invalid serialization format (unexpected element: " + walker.getName() + ")"; + log.debug(msg); + throw new ItemStateException(msg); + } + // check targetId + if (!refs.getId().equals(NodeReferencesId.valueOf(walker.getAttribute(TARGETID_ATTRIBUTE)))) { + String msg = "invalid serialized state: targetId mismatch"; + log.debug(msg); + throw new ItemStateException(msg); + } + + // now we're ready to read the references data + + // property id's + refs.clearAllReferences(); + while (walker.iterateElements(NODEREFERENCE_ELEMENT)) { + refs.addReference(PropertyId.valueOf(walker.getAttribute(PROPERTYID_ATTRIBUTE))); + } + } + + //---------------------------------------------------< PersistenceManager > + /** + * {@inheritDoc} + */ + public void init(PMContext context) throws Exception { + if (initialized) { + throw new IllegalStateException("already initialized"); + } + + itemStateFS = new BasedFileSystem(context.getFileSystem(), "/data"); + + /** + * store BLOB data in local file system in a sub directory + * of the workspace home directory + */ + LocalFileSystem blobFS = new LocalFileSystem(); + blobFS.setRoot(new File(context.getHomeDir(), "blobs")); + blobFS.init(); + this.blobFS = blobFS; + blobStore = new FileSystemBLOBStore(blobFS); + + initialized = true; + } + + /** + * {@inheritDoc} + */ + public synchronized void close() throws Exception { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + // close BLOB file system + blobFS.close(); + blobFS = null; + blobStore = null; + /** + * there's no need close the item state store because it + * is based in the workspace's file system which is + * closed by the repository + */ + } finally { + initialized = false; + } + } + + /** + * {@inheritDoc} + */ + public synchronized NodeState load(NodeId id) + throws NoSuchItemStateException, ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + Exception e = null; + String nodeFilePath = buildNodeFilePath(id); + + try { + if (!itemStateFS.isFile(nodeFilePath)) { + throw new NoSuchItemStateException(id.toString()); + } + InputStream in = itemStateFS.getInputStream(nodeFilePath); + + try { + DOMWalker walker = new DOMWalker(in); + String ntName = walker.getAttribute(NODETYPE_ATTRIBUTE); + + NodeState state = createNew(id); + state.setNodeTypeName(QName.valueOf(ntName)); + readState(walker, state); + return state; + } finally { + in.close(); + } + } catch (IOException ioe) { + e = ioe; + // fall through + } catch (FileSystemException fse) { + e = fse; + // fall through + } + String msg = "failed to read node state: " + id; + log.debug(msg); + throw new ItemStateException(msg, e); + } + + /** + * {@inheritDoc} + */ + public synchronized PropertyState load(PropertyId id) + throws NoSuchItemStateException, ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + Exception e = null; + String propFilePath = buildPropFilePath(id); + + try { + if (!itemStateFS.isFile(propFilePath)) { + throw new NoSuchItemStateException(id.toString()); + } + InputStream in = itemStateFS.getInputStream(propFilePath); + try { + DOMWalker walker = new DOMWalker(in); + PropertyState state = createNew(id); + readState(walker, state); + return state; + } finally { + in.close(); + } + } catch (IOException ioe) { + e = ioe; + // fall through + } catch (FileSystemException fse) { + e = fse; + // fall through + } + String msg = "failed to read property state: " + id.toString(); + log.debug(msg); + throw new ItemStateException(msg, e); + } + + /** + * {@inheritDoc} + */ + protected void store(NodeState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + NodeId id = state.getNodeId(); + String nodeFilePath = buildNodeFilePath(id); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + try { + nodeFile.makeParentDirs(); + OutputStream os = nodeFile.getOutputStream(); + Writer writer = null; + try { + String encoding = DEFAULT_ENCODING; + try { + writer = new BufferedWriter(new OutputStreamWriter(os, encoding)); + } catch (UnsupportedEncodingException e) { + // should never get here! + OutputStreamWriter osw = new OutputStreamWriter(os); + encoding = osw.getEncoding(); + writer = new BufferedWriter(osw); + } + + writer.write("\n"); + writer.write("<" + NODE_ELEMENT + " " + + UUID_ATTRIBUTE + "=\"" + id.getUUID() + "\" " + + PARENTUUID_ATTRIBUTE + "=\"" + (state.getParentId() == null ? "" : state.getParentId().getUUID().toString()) + "\" " + + DEFINITIONID_ATTRIBUTE + "=\"" + state.getDefinitionId().toString() + "\" " + + MODCOUNT_ATTRIBUTE + "=\"" + state.getModCount() + "\" " + + NODETYPE_ATTRIBUTE + "=\"" + Text.encodeIllegalXMLCharacters(state.getNodeTypeName().toString()) + "\">\n"); + + // mixin types + writer.write("\t<" + MIXINTYPES_ELEMENT + ">\n"); + Iterator iter = state.getMixinTypeNames().iterator(); + while (iter.hasNext()) { + writer.write("\t\t<" + MIXINTYPE_ELEMENT + " " + + NAME_ATTRIBUTE + "=\"" + Text.encodeIllegalXMLCharacters(iter.next().toString()) + "\"/>\n"); + } + writer.write("\t\n"); + + // properties + writer.write("\t<" + PROPERTIES_ELEMENT + ">\n"); + iter = state.getPropertyNames().iterator(); + while (iter.hasNext()) { + QName propName = (QName) iter.next(); + writer.write("\t\t<" + PROPERTY_ELEMENT + " " + + NAME_ATTRIBUTE + "=\"" + Text.encodeIllegalXMLCharacters(propName.toString()) + "\">\n"); + // @todo serialize type, definition id and values + writer.write("\t\t\n"); + } + writer.write("\t\n"); + + // child nodes + writer.write("\t<" + NODES_ELEMENT + ">\n"); + iter = state.getChildNodeEntries().iterator(); + while (iter.hasNext()) { + NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) iter.next(); + writer.write("\t\t<" + NODE_ELEMENT + " " + + NAME_ATTRIBUTE + "=\"" + Text.encodeIllegalXMLCharacters(entry.getName().toString()) + "\" " + + UUID_ATTRIBUTE + "=\"" + entry.getId().getUUID().toString() + "\">\n"); + writer.write("\t\t\n"); + } + writer.write("\t\n"); + + writer.write("\n"); + } finally { + writer.close(); + } + } catch (Exception e) { + String msg = "failed to write node state: " + id; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void store(PropertyState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + String propFilePath = buildPropFilePath(state.getPropertyId()); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + try { + propFile.makeParentDirs(); + OutputStream os = propFile.getOutputStream(); + // write property state to xml file + Writer writer = null; + try { + String encoding = DEFAULT_ENCODING; + try { + writer = new BufferedWriter(new OutputStreamWriter(os, encoding)); + } catch (UnsupportedEncodingException e) { + // should never get here! + OutputStreamWriter osw = new OutputStreamWriter(os); + encoding = osw.getEncoding(); + writer = new BufferedWriter(osw); + } + + String typeName; + int type = state.getType(); + try { + typeName = PropertyType.nameFromValue(type); + } catch (IllegalArgumentException iae) { + // should never be getting here + throw new ItemStateException("unexpected property-type ordinal: " + type, iae); + } + + writer.write("\n"); + writer.write("<" + PROPERTY_ELEMENT + " " + + NAME_ATTRIBUTE + "=\"" + Text.encodeIllegalXMLCharacters(state.getName().toString()) + "\" " + + PARENTUUID_ATTRIBUTE + "=\"" + state.getParentId().getUUID() + "\" " + + MULTIVALUED_ATTRIBUTE + "=\"" + Boolean.toString(state.isMultiValued()) + "\" " + + DEFINITIONID_ATTRIBUTE + "=\"" + state.getDefinitionId().toString() + "\" " + + MODCOUNT_ATTRIBUTE + "=\"" + state.getModCount() + "\" " + + TYPE_ATTRIBUTE + "=\"" + typeName + "\">\n"); + // values + writer.write("\t<" + VALUES_ELEMENT + ">\n"); + InternalValue[] values = state.getValues(); + if (values != null) { + for (int i = 0; i < values.length; i++) { + writer.write("\t\t<" + VALUE_ELEMENT + ">"); + InternalValue val = values[i]; + if (val != null) { + if (type == PropertyType.BINARY) { + // special handling required for binary value: + // put binary value in BLOB store + BLOBFileValue blobVal = (BLOBFileValue) val.internalValue(); + InputStream in = blobVal.getStream(); + String blobId = blobStore.createId(state.getPropertyId(), i); + try { + blobStore.put(blobId, in, blobVal.getLength()); + } finally { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + // store id of BLOB as property value + writer.write(blobId); + // 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, false); + } finally { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + blobVal.discard(); + } else { + writer.write(Text.encodeIllegalXMLCharacters(val.toString())); + } + } + writer.write("\n"); + } + } + writer.write("\t\n"); + writer.write("\n"); + } finally { + writer.close(); + } + } catch (Exception e) { + String msg = "failed to store property state: " + state.getParentId() + "/" + state.getName(); + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(NodeState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + NodeId id = state.getNodeId(); + String nodeFilePath = buildNodeFilePath(id); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + try { + if (nodeFile.exists()) { + // delete resource and prune empty parent folders + nodeFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete node state: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(PropertyState state) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + // delete binary values (stored as files) + InternalValue[] values = state.getValues(); + if (values != null) { + for (int i = 0; i < values.length; i++) { + InternalValue val = values[i]; + if (val != null) { + if (val.getType() == PropertyType.BINARY) { + BLOBFileValue blobVal = (BLOBFileValue) val.internalValue(); + // delete blob file and prune empty parent folders + blobVal.delete(true); + } + } + } + } + // delete property file + String propFilePath = buildPropFilePath(state.getPropertyId()); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + try { + if (propFile.exists()) { + // delete resource and prune empty parent folders + propFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete property state: " + state.getParentId() + "/" + state.getName(); + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized NodeReferences load(NodeReferencesId id) + throws NoSuchItemStateException, ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + Exception e = null; + String refsFilePath = buildNodeReferencesFilePath(id); + try { + if (!itemStateFS.isFile(refsFilePath)) { + throw new NoSuchItemStateException(id.toString()); + } + + InputStream in = itemStateFS.getInputStream(refsFilePath); + + try { + DOMWalker walker = new DOMWalker(in); + NodeReferences refs = new NodeReferences(id); + readState(walker, refs); + return refs; + } finally { + in.close(); + } + } catch (IOException ioe) { + e = ioe; + // fall through + } catch (FileSystemException fse) { + e = fse; + // fall through + } + String msg = "failed to load references: " + id; + log.debug(msg); + throw new ItemStateException(msg, e); + } + + /** + * {@inheritDoc} + */ + protected void store(NodeReferences refs) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + NodeReferencesId id = refs.getId(); + String refsFilePath = buildNodeReferencesFilePath(id); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + try { + refsFile.makeParentDirs(); + OutputStream os = refsFile.getOutputStream(); + BufferedWriter writer = null; + try { + String encoding = DEFAULT_ENCODING; + try { + writer = new BufferedWriter(new OutputStreamWriter(os, encoding)); + } catch (UnsupportedEncodingException e) { + // should never get here! + OutputStreamWriter osw = new OutputStreamWriter(os); + encoding = osw.getEncoding(); + writer = new BufferedWriter(osw); + } + writer.write("\n"); + writer.write("<" + NODEREFERENCES_ELEMENT + " " + + TARGETID_ATTRIBUTE + "=\"" + refs.getId() + "\">\n"); + // write references (i.e. the id's of the REFERENCE properties) + Iterator iter = refs.getReferences().iterator(); + while (iter.hasNext()) { + PropertyId propId = (PropertyId) iter.next(); + writer.write("\t<" + NODEREFERENCE_ELEMENT + " " + + PROPERTYID_ATTRIBUTE + "=\"" + propId + "\"/>\n"); + } + writer.write("\n"); + } finally { + writer.close(); + } + } catch (Exception e) { + String msg = "failed to store references: " + id; + log.debug(msg); + throw new ItemStateException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + protected void destroy(NodeReferences refs) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + NodeReferencesId id = refs.getId(); + String refsFilePath = buildNodeReferencesFilePath(id); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + try { + if (refsFile.exists()) { + // delete resource and prune empty parent folders + refsFile.delete(true); + } + } catch (FileSystemException fse) { + String msg = "failed to delete references: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(NodeId id) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String nodeFilePath = buildNodeFilePath(id); + FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath); + return nodeFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of item state: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(PropertyId id) throws ItemStateException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String propFilePath = buildPropFilePath(id); + FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath); + return propFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of item state: " + id; + log.error(msg, fse); + throw new ItemStateException(msg, fse); + } + } + + /** + * {@inheritDoc} + */ + public synchronized boolean exists(NodeReferencesId id) + throws ItemStateException { + + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + try { + String refsFilePath = buildNodeReferencesFilePath(id); + FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath); + return refsFile.exists(); + } catch (FileSystemException fse) { + String msg = "failed to check existence of references: " + id; + log.debug(msg); + throw new ItemStateException(msg, fse); + } + } +} Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/persistence/xml/XMLPersistenceManager.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url rev Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/repository.xml URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/repository.xml?view=diff&rev=467925&r1=467924&r2=467925 ============================================================================== --- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/repository.xml (original) +++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/repository.xml Thu Oct 26 02:11:18 2006 @@ -193,7 +193,7 @@ persistence manager of the workspace: class: FQN of class implementing the PersistenceManager interface --> - + @@ -224,7 +224,7 @@ a 'normal' persistence manager, but this could change in future implementations. --> - +