Index: src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java (revision 705259) +++ src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java (working copy) @@ -16,19 +16,27 @@ */ package org.apache.jackrabbit.core.cluster; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.RepositoryException; + import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.cluster.WorkspaceRecord.CreateWorkspaceAction; import org.apache.jackrabbit.core.config.ClusterConfig; import org.apache.jackrabbit.core.config.ConfigurationException; import org.apache.jackrabbit.core.config.JournalConfig; -import org.apache.jackrabbit.core.NodeId; import org.apache.jackrabbit.core.journal.AbstractJournal; +import org.apache.jackrabbit.core.journal.InstanceRevision; import org.apache.jackrabbit.core.journal.Journal; +import org.apache.jackrabbit.core.journal.JournalException; +import org.apache.jackrabbit.core.journal.Record; import org.apache.jackrabbit.core.journal.RecordConsumer; -import org.apache.jackrabbit.core.journal.Record; -import org.apache.jackrabbit.core.journal.JournalException; -import org.apache.jackrabbit.core.journal.InstanceRevision; import org.apache.jackrabbit.core.journal.RecordProducer; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeDef; @@ -33,19 +41,13 @@ import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeDef; import org.apache.jackrabbit.core.state.ChangeLog; +import org.apache.jackrabbit.core.xml.ClonedInputSource; import org.apache.jackrabbit.uuid.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import EDU.oswego.cs.dl.util.concurrent.Mutex; -import javax.jcr.RepositoryException; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Collection; - /** * Default clustered node implementation. */ @@ -51,7 +53,7 @@ */ public class ClusterNode implements Runnable, NamespaceEventChannel, NodeTypeEventChannel, RecordConsumer, - ClusterRecordProcessor { + ClusterRecordProcessor, WorkspaceEventChannel { /** * System property specifying a node id to use. @@ -139,6 +141,11 @@ private NamespaceEventListener namespaceListener; /** + * Create workspace listener + */ + private WorkspaceListener createWorkspaceListener; + + /** * Node type listener. */ private NodeTypeEventListener nodeTypeListener; @@ -887,6 +894,58 @@ } } + public void process(WorkspaceRecord record) { + if (createWorkspaceListener == null) { + String msg = "Create Workspace listener unavailable."; + log.error(msg); + return; + } + try { + if (record.getActionType() == WorkspaceRecord.CREATE_WORKSPACE_ACTION_TYPE) { + CreateWorkspaceAction action = record.getCreateWorkspaceAction(); + createWorkspaceListener.externalWorkspaceCreated(record.getWorkspace(), action.getInputSource()); + } + } catch (RepositoryException e) { + String msg = "Unable to create workspace: " + + e.getMessage(); + log.error(msg); + } + } + + // -----------------------------------------------< CreateWorkspaceChannel > + + public void setListener(WorkspaceListener listener) { + createWorkspaceListener = listener; + } + + public void workspaceCreated(String workspaceName, + ClonedInputSource inputSource) { + if (status != STARTED) { + log.info("not started: namespace operation ignored."); + return; + } + ClusterRecord record = null; + boolean succeeded = false; + + try { + record = new WorkspaceRecord(workspaceName, inputSource, producer.append()); + record.write(); + record.update(); + setRevision(record.getRevision()); + succeeded = true; + } catch (JournalException e) { + String msg = "Unable to create log entry: " + e.getMessage(); + log.error(msg); + } catch (Throwable e) { + String msg = "Unexpected error while creating log entry."; + log.error(msg, e); + } finally { + if (!succeeded && record != null) { + record.cancelUpdate(); + } + } + } + /** * Invoked when a cluster operation has ended. If successful, * attempts to fill the journal record and update it, otherwise cancels Index: src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java (revision 704752) +++ src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java (working copy) @@ -55,6 +55,10 @@ clusterRecord = new NodeTypeRecord(record); clusterRecord.read(); break; + case WorkspaceRecord.IDENTIFIER: + clusterRecord = new WorkspaceRecord(record); + clusterRecord.read(); + break; default: String msg = "Unknown record identifier: " + c; throw new JournalException(msg); Index: src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java (revision 704752) +++ src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java (working copy) @@ -52,4 +52,10 @@ * @param record node type record */ public void process(NodeTypeRecord record); + + /** + * Process a workspace record + * @param record workspace record + */ + public void process(WorkspaceRecord record); } Index: src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java (revision 0) @@ -0,0 +1,30 @@ +/* + * 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.cluster; + +import org.apache.jackrabbit.core.xml.ClonedInputSource; + +/** + * Event channel for reporting workspace change events. + */ +public interface WorkspaceEventChannel { + + void workspaceCreated(String workspaceName, ClonedInputSource inputSource); + + void setListener(WorkspaceListener listener); + +} Index: src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java (revision 0) @@ -0,0 +1,38 @@ +/* + * 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.cluster; + +import javax.jcr.RepositoryException; + +import org.xml.sax.InputSource; + +/** + * Listener for external workspace changes. + */ +public interface WorkspaceListener { + + /** + * Workspace created on another cluster node. + * + * @param workspaceName + * @param configTemplate + * @throws RepositoryException + */ + public void externalWorkspaceCreated(String workspaceName, + InputSource configTemplate) throws RepositoryException; + +} Index: src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java (revision 0) @@ -0,0 +1,199 @@ +/* + * 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.cluster; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; + +import org.apache.jackrabbit.core.journal.JournalException; +import org.apache.jackrabbit.core.journal.Record; +import org.apache.jackrabbit.core.xml.ClonedInputSource; +import org.xml.sax.InputSource; + +/** + * Record for propagating workspace modifications across the cluster. Currently + * only workspace creation is propagated because workspace deletion is not yet + * implemented. + */ +public class WorkspaceRecord extends ClusterRecord { + + /** + * Identifier: NAMESPACE. + */ + static final char IDENTIFIER = 'W'; + + /** + * Subtype for determining workspace action. + */ + public static final int CREATE_WORKSPACE_ACTION_TYPE = 1; + + /** + * Base workspace action + */ + public static abstract class Action { + abstract int getType(); + + abstract void write(Record record) throws JournalException; + + abstract void read(Record record) throws JournalException; + } + + /** + * Action for workspace creation. + */ + static final class CreateWorkspaceAction extends Action { + private InputSource inputSource; + private char charArray[]; + private byte byteArray[]; + + int getType() { + return CREATE_WORKSPACE_ACTION_TYPE; + } + + CreateWorkspaceAction() { + + } + + CreateWorkspaceAction(ClonedInputSource inputSource) { + this.inputSource = inputSource; + this.charArray = inputSource.getCharacterArray(); + this.byteArray = inputSource.getByteArray(); + } + + void write(Record record) throws JournalException { + // store the input source + record.writeString(inputSource.getEncoding()); + record.writeString(inputSource.getPublicId()); + record.writeString(inputSource.getSystemId()); + + // save character array if present + if (charArray != null) { + record.writeBoolean(true); + record.writeString(new String(charArray)); + } else { + record.writeBoolean(false); + } + + // save the bytearray if present + if (byteArray != null) { + record.writeBoolean(true); + record.writeInt(byteArray.length); + record.write(byteArray); + } else { + record.writeBoolean(false); + } + } + + void read(Record record) throws JournalException { + // restore the input source + inputSource = new InputSource(); + inputSource.setEncoding(record.readString()); + inputSource.setPublicId(record.readString()); + inputSource.setSystemId(record.readString()); + + if (record.readBoolean()) { + charArray = record.readString().toCharArray(); + inputSource.setCharacterStream(new CharArrayReader(charArray)); + } + if (record.readBoolean()) { + final int size = record.readInt(); + byteArray = new byte[size]; + record.readFully(byteArray); + inputSource.setByteStream(new ByteArrayInputStream(byteArray)); + } + } + + public InputSource getInputSource() { + return inputSource; + } + } + + // current action + private Action action; + + /** + * Creates a new {@link WorkspaceRecord} for create workspace action. + * + * @param workspace + * workspace name + * @param inputSource + * input source with configuration for the workspace + * @param record + * journal record + */ + protected WorkspaceRecord(String workspace, ClonedInputSource inputSource, + Record record) { + super(record, workspace); + + action = new CreateWorkspaceAction(inputSource); + } + + /** + * Creates a new empty {@link WorkspaceRecord}. + * + * @param record + */ + protected WorkspaceRecord(Record record) { + super(record); + } + + protected void doRead() throws JournalException { + + workspace = record.readString(); + + // determine type + int action = record.readInt(); + + if (action == CREATE_WORKSPACE_ACTION_TYPE) { + this.action = new CreateWorkspaceAction(); + } + + if (this.action != null) { + this.action.read(record); + } else { + throw new JournalException("Unknown workspace action type"); + } + } + + protected void doWrite() throws JournalException { + + record.writeChar(IDENTIFIER); + + record.writeString(workspace); + + // store the type + record.writeInt(getActionType()); + + if (action != null) { + action.write(record); + } else { + throw new JournalException("Can not write empty workspace action"); + } + } + + public int getActionType() { + return action != null ? action.getType() : -1; + } + + public CreateWorkspaceAction getCreateWorkspaceAction() { + return (CreateWorkspaceAction) action; + } + + public void process(ClusterRecordProcessor processor) { + processor.process(this); + } +} Index: src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java (revision 704752) +++ src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java (working copy) @@ -23,6 +23,7 @@ import org.apache.jackrabbit.core.fs.FileSystemException; import org.apache.jackrabbit.core.fs.FileSystemFactory; import org.apache.jackrabbit.core.fs.FileSystemPathUtil; +import org.apache.jackrabbit.core.xml.ClonedInputSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; @@ -45,6 +46,8 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.util.Collection; @@ -340,7 +343,7 @@ + "for default workspace: " + defaultWorkspace); } // create initial default workspace - createWorkspaceConfig(defaultWorkspace); + createWorkspaceConfig(defaultWorkspace, (StringBuffer)null); } } @@ -460,6 +463,8 @@ * * @param name workspace name * @param template the workspace template + * @param configContent optional stringbuffer that will have the content + * of workspace configuration file written in * @return created workspace configuration * @throws ConfigurationException if creating the workspace configuration * failed @@ -465,7 +470,8 @@ * failed */ private synchronized WorkspaceConfig internalCreateWorkspaceConfig(String name, - Element template) + Element template, + StringBuffer configContent) throws ConfigurationException { // The physical workspace home directory on disk (TODO encode name?) @@ -511,6 +517,7 @@ try { // Create the directory virtualFS.createFolder(configDir); + configWriter = new OutputStreamWriter( virtualFS.getOutputStream(configFile)); } catch (FileSystemException e) { @@ -537,8 +544,25 @@ TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.transform( - new DOMSource(template), new StreamResult(configWriter)); + + if (configContent == null) + { + transformer.transform( + new DOMSource(template), new StreamResult(configWriter)); + } + else + { + StringWriter writer = new StringWriter(); + transformer.transform( + new DOMSource(template), new StreamResult(writer)); + + String s = writer.getBuffer().toString(); + configWriter.write(s); + configContent.append(s); + } + } catch (IOException e) { + throw new ConfigurationException( + "Cannot create a workspace configuration file", e); } catch (TransformerConfigurationException e) { throw new ConfigurationException( "Cannot create a workspace configuration writer", e); @@ -585,6 +609,8 @@ * the caller. * * @param name workspace name + * @param configContent optional StringBuffer that will have the content + * of workspace configuration file written in * @return created workspace configuration * @throws ConfigurationException if creating the workspace configuration * failed @@ -589,10 +615,10 @@ * @throws ConfigurationException if creating the workspace configuration * failed */ - public WorkspaceConfig createWorkspaceConfig(String name) + public WorkspaceConfig createWorkspaceConfig(String name, StringBuffer configContent) throws ConfigurationException { // use workspace template from repository.xml - return internalCreateWorkspaceConfig(name, template); + return internalCreateWorkspaceConfig(name, template, configContent); } /** @@ -618,7 +644,7 @@ throws ConfigurationException { ConfigurationParser parser = new ConfigurationParser(new Properties()); Element workspaceTemplate = parser.parseXML(template); - return internalCreateWorkspaceConfig(name, workspaceTemplate); + return internalCreateWorkspaceConfig(name, workspaceTemplate, null); } /** Index: src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (revision 704752) +++ src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (working copy) @@ -27,6 +27,8 @@ import org.apache.jackrabbit.core.cluster.ClusterContext; import org.apache.jackrabbit.core.cluster.ClusterException; import org.apache.jackrabbit.core.cluster.ClusterNode; +import org.apache.jackrabbit.core.cluster.WorkspaceEventChannel; +import org.apache.jackrabbit.core.cluster.WorkspaceListener; import org.apache.jackrabbit.core.cluster.LockEventChannel; import org.apache.jackrabbit.core.cluster.UpdateEventChannel; import org.apache.jackrabbit.core.cluster.UpdateEventListener; @@ -64,6 +66,7 @@ import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.version.VersionManager; import org.apache.jackrabbit.core.version.VersionManagerImpl; +import org.apache.jackrabbit.core.xml.ClonedInputSource; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; import org.apache.jackrabbit.spi.commons.namespace.RegistryNamespaceResolver; @@ -89,6 +92,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.StringReader; import java.security.AccessControlContext; import java.security.AccessController; import java.util.Arrays; @@ -103,7 +107,7 @@ * A RepositoryImpl ... */ public class RepositoryImpl extends AbstractRepository - implements JackrabbitRepository, SessionListener, EventListener { + implements JackrabbitRepository, SessionListener, EventListener, WorkspaceListener { private static Logger log = LoggerFactory.getLogger(RepositoryImpl.class); @@ -230,6 +234,11 @@ private final ItemStateCacheFactory cacheFactory = new ManagedMLRUItemStateCacheFactory(cacheMgr); /** + * Chanel for posting create workspace messages. + */ + private WorkspaceEventChannel createWorkspaceEventChannel; + + /** * private constructor * * @param repConfig @@ -292,6 +301,9 @@ clusterNode = createClusterNode(); nsReg.setEventChannel(clusterNode); ntReg.setEventChannel(clusterNode); + + createWorkspaceEventChannel = clusterNode; + clusterNode.setListener(this); } // init version manager @@ -789,16 +801,35 @@ + workspaceName + "' already exists."); } + // needed to get newly created workspace config file content when runnin in clustered environment + StringBuffer workspaceConfigContent = clusterNode != null ? new StringBuffer() : null; + // create the workspace configuration - WorkspaceConfig config = repConfig.createWorkspaceConfig(workspaceName); + WorkspaceConfig config = repConfig.createWorkspaceConfig(workspaceName, workspaceConfigContent); WorkspaceInfo info = createWorkspaceInfo(config); wspInfos.put(workspaceName, info); + + if (workspaceConfigContent != null && createWorkspaceEventChannel != null) { + // notify other cluster node that workspace has been created + InputSource s = new InputSource(new StringReader(workspaceConfigContent.toString())); + createWorkspaceEventChannel.workspaceCreated(workspaceName, new ClonedInputSource(s)); + } } } + public void externalWorkspaceCreated(String workspaceName, + InputSource configTemplate) throws RepositoryException { + + createWorkspaceInternal(workspaceName, configTemplate); + } + /** * Creates a workspace with the given name and given workspace configuration * template. + * + * The difference between this method and {@link #createWorkspace(String, InputSource)} + * is that the later notifies the other cluster node that workspace has been created + * whereas this method only creates the workspace. * * @param workspaceName name of the new workspace * @param configTemplate the workspace configuration template of the new @@ -807,7 +838,7 @@ * exists or if another error occurs * @see SessionImpl#createWorkspace(String,InputSource) */ - protected void createWorkspace(String workspaceName, + private void createWorkspaceInternal(String workspaceName, InputSource configTemplate) throws RepositoryException { synchronized (wspInfos) { @@ -822,6 +853,32 @@ wspInfos.put(workspaceName, info); } } + + /** + * Creates a workspace with the given name and given workspace configuration + * template. + * + * @param workspaceName name of the new workspace + * @param configTemplate the workspace configuration template of the new + * workspace + * @throws RepositoryException if a workspace with the given name already + * exists or if another error occurs + * @see SessionImpl#createWorkspace(String,InputSource) + */ + protected void createWorkspace(String workspaceName, + InputSource configTemplate) + throws RepositoryException { + + if (createWorkspaceEventChannel == null) { + createWorkspaceInternal(workspaceName, configTemplate); + } + else { + + ClonedInputSource template = new ClonedInputSource(configTemplate); + createWorkspaceInternal(workspaceName, template.cloneInputSource()); + createWorkspaceEventChannel.workspaceCreated(workspaceName, template); + } + } SharedItemStateManager getWorkspaceStateManager(String workspaceName) throws NoSuchWorkspaceException, RepositoryException { Index: src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java (revision 0) @@ -0,0 +1,168 @@ +/* + * 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.xml; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import javax.jcr.RepositoryException; + +import org.xml.sax.InputSource; + +/** + * Input source that clones existing input source. After cloning the existing + * input source should not be used anymore. To make more copies call + * {@link #cloneInputSource()} method. + */ +public class ClonedInputSource extends InputSource { + private final char characterArray[]; + private final byte byteArray[]; + + /** + * Clone existing input source. + * + * @param input + * @throws RepositoryException + */ + public ClonedInputSource(InputSource input) throws RepositoryException { + + if (input == null) { + throw new IllegalArgumentException( + "Argument 'input' may not be null."); + } + + characterArray = read(input.getCharacterStream()); + byteArray = read(input.getByteStream()); + + setEncoding(input.getEncoding()); + setPublicId(input.getPublicId()); + setSystemId(input.getSystemId()); + if (characterArray != null) { + setCharacterStream(new CharArrayReader(characterArray)); + } + if (byteArray != null) { + setByteStream(new ByteArrayInputStream(byteArray)); + } + } + + private ClonedInputSource(char characterArray[], byte byteArray[]) { + super(); + this.characterArray = characterArray; + this.byteArray = byteArray; + } + + public char[] getCharacterArray() { + return characterArray; + } + + public byte[] getByteArray() { + return byteArray; + } + + /** + * Make a clone if this input source. The input source being cloned is still + * valid after cloning. + * + * @return input source clone. + */ + public ClonedInputSource cloneInputSource() { + + ClonedInputSource res = new ClonedInputSource(characterArray, byteArray); + + res.setEncoding(getEncoding()); + res.setPublicId(getPublicId()); + res.setSystemId(getSystemId()); + + if (byteArray != null) { + res.setByteStream(new ByteArrayInputStream(byteArray)); + } + if (characterArray != null) { + res.setCharacterStream(new CharArrayReader(characterArray)); + } + + return res; + } + + private static byte[] read(InputStream stream) throws RepositoryException { + if (stream != null) { + try { + final int bufferSize = Math.min(stream.available(), 4096); + ByteArrayOutputStream s = new ByteArrayOutputStream(bufferSize); + + byte buffer[] = new byte[bufferSize]; + while (true) { + int numRead = stream.read(buffer); + if (numRead > 0) { + s.write(buffer, 0, numRead); + } + if (numRead != bufferSize) { + break; + } + } + + return s.toByteArray(); + } catch (IOException e) { + throw new RepositoryException(e); + } finally { + try { + stream.close(); + } catch (IOException ignore) { + + } + } + } else { + return null; + } + } + + private static char[] read(Reader reader) throws RepositoryException { + if (reader != null) { + try { + final int bufferSize = 4096; + CharArrayWriter w = new CharArrayWriter(bufferSize); + + char buffer[] = new char[bufferSize]; + while (true) { + int numRead = reader.read(buffer); + if (numRead > 0) { + w.write(buffer, 0, numRead); + } + if (numRead != bufferSize) { + break; + } + } + return w.toCharArray(); + } catch (IOException e) { + throw new RepositoryException(e); + } finally { + try { + reader.close(); + } catch (IOException ignore) { + + } + } + } else { + return null; + } + + } +} \ No newline at end of file Index: src/test/java/org/apache/jackrabbit/core/config/RepositoryConfigTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/config/RepositoryConfigTest.java (revision 704752) +++ src/test/java/org/apache/jackrabbit/core/config/RepositoryConfigTest.java (working copy) @@ -220,7 +220,7 @@ public void testCreateWorkspaceConfig() throws Exception { RepositoryConfig config = RepositoryConfig.create(REPOSITORY_XML, REPOSITORY_HOME); - config.createWorkspaceConfig("test-workspace"); + config.createWorkspaceConfig("test-workspace", (StringBuffer)null); File workspaces_dir = new File(REPOSITORY_HOME, "workspaces"); File workspace_dir = new File(workspaces_dir, "test-workspace"); File workspace_xml = new File(workspace_dir, "workspace.xml"); @@ -231,7 +231,7 @@ try { RepositoryConfig config = RepositoryConfig.create(REPOSITORY_XML, REPOSITORY_HOME); - config.createWorkspaceConfig("default"); + config.createWorkspaceConfig("default", (StringBuffer)null); fail("No exception thrown when creating a duplicate workspace"); } catch (ConfigurationException e) { // test passed