org.apache.jackrabbit.jcr2spi.xml.TargetImportHandler Maven / Gradle / Ivy
/*
* 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.jcr2spi.xml;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.util.TransientFileFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* TargetImportHandler
serves as the base class for the concrete
* classes {@link DocViewImportHandler}
and
* {@link SysViewImportHandler}
.
*/
abstract class TargetImportHandler extends DefaultHandler {
private static Logger log = LoggerFactory.getLogger(TargetImportHandler.class);
protected final Importer importer;
protected final NamePathResolver resolver;
protected TargetImportHandler(Importer importer, NamePathResolver resolver) {
this.importer = importer;
this.resolver = resolver;
}
/**
* Disposes all instances of AppendableValue
contained in the
* given property info's value array.
*
* @param prop property info
*/
protected void disposePropertyValues(Importer.PropInfo prop) {
Importer.TextValue[] vals = prop.getValues();
for (int i = 0; i < vals.length; i++) {
if (vals[i] instanceof AppendableValue) {
try {
((AppendableValue) vals[i]).dispose();
} catch (IOException ioe) {
log.warn("error while disposing temporary value", ioe);
// fall through...
}
}
}
}
//-------------------------------------------------------< ContentHandler >
/**
* Initializes the underlying {@link Importer} instance. This method
* is called by the XML parser when the XML document starts.
*
* @throws SAXException if the importer can not be initialized
* @see DefaultHandler#startDocument()
*/
@Override
public void startDocument() throws SAXException {
try {
importer.start();
} catch (RepositoryException re) {
throw new SAXException(re);
}
}
/**
* Closes the underlying {@link Importer} instance. This method
* is called by the XML parser when the XML document ends.
*
* @throws SAXException if the importer can not be closed
* @see DefaultHandler#endDocument()
*/
@Override
public void endDocument() throws SAXException {
try {
importer.end();
} catch (RepositoryException re) {
throw new SAXException(re);
}
}
//--------------------------------------------------------< inner classes >
/**
* AppendableValue
represents a serialized value that is
* appendable.
*
* Important: Note that in order to free resources
* {@link #dispose()}
should be called as soon as an
* AppendableValue
object is not used anymore.
*/
public interface AppendableValue extends Importer.TextValue {
/**
* Append a portion of an array of characters.
*
* @param chars the characters to be appended
* @param start the index of the first character to append
* @param length the number of characters to append
* @throws IOException if an I/O error occurs
*/
void append(char[] chars, int start, int length)
throws IOException;
/**
* Close this value. Once a value has been closed,
* further append() invocations will cause an IOException to be thrown.
*
* @throws IOException if an I/O error occurs
*/
void close() throws IOException;
/**
* Dispose this value, i.e. free all bound resources. Once a value has
* been disposed, further method invocations will cause an IOException
* to be thrown.
*
* @throws IOException if an I/O error occurs
*/
void dispose() throws IOException;
}
/**
* StringValue
represents an immutable serialized value.
*/
protected class StringValue implements Importer.TextValue {
private final String value;
/**
* Constructs a new StringValue
representing the given
* value.
*
* @param value
*/
protected StringValue(String value) {
this.value = value;
}
//--------------------------------------------------------< TextValue >
/**
* {@inheritDoc}
*/
public long length() {
return value.length();
}
/**
* {@inheritDoc}
*/
public String retrieve() {
return value;
}
/**
* {@inheritDoc}
*/
public Reader reader() {
return new StringReader(value);
}
}
/**
* BufferedStringValue
represents an appendable
* serialized value that is either buffered in-memory or backed
* by a temporary file if its size exceeds a certain limit.
*
* Important: Note that in order to free resources
* {@link #dispose()}
should be called as soon as
* BufferedStringValue
instance is not used anymore.
*/
protected class BufferedStringValue implements AppendableValue {
/**
* max size for buffering data in memory
*/
private static final int MAX_BUFFER_SIZE = 0x10000;
/**
* size of increment if capacity buffer needs to be enlarged
*/
private static final int BUFFER_INCREMENT = 0x2000;
/**
* in-memory buffer
*/
private char[] buffer;
/**
* current position within buffer (size of actual data in buffer)
*/
private int bufferPos;
/**
* backing temporary file created when size of data exceeds
* MAX_BUFFER_SIZE
*/
private File tmpFile;
/**
* writer used to write to tmpFile; writer & tmpFile are always
* instantiated together, i.e. they are either both null or both not null.
*/
private Writer writer;
/**
* Constructs a new empty BufferedStringValue
.
*/
protected BufferedStringValue() {
buffer = new char[0x2000];
bufferPos = 0;
tmpFile = null;
writer = null;
}
//--------------------------------------------------------< TextValue >
/**
* {@inheritDoc}
*/
public long length() throws IOException {
if (buffer != null) {
return bufferPos;
} else if (tmpFile != null) {
// flush writer first
writer.flush();
return tmpFile.length();
} else {
throw new IOException("this instance has already been disposed");
}
}
/**
* {@inheritDoc}
*/
public String retrieve() throws IOException {
if (buffer != null) {
return new String(buffer, 0, bufferPos);
} else if (tmpFile != null) {
// flush writer first
writer.flush();
if (tmpFile.length() > Integer.MAX_VALUE) {
throw new IOException("size of value is too big, use reader()");
}
StringBuffer sb = new StringBuffer((int) tmpFile.length());
char[] chunk = new char[0x2000];
int read;
Reader reader = new FileReader(tmpFile);
try {
while ((read = reader.read(chunk)) > -1) {
sb.append(chunk, 0, read);
}
} finally {
reader.close();
}
return sb.toString();
} else {
throw new IOException("this instance has already been disposed");
}
}
/**
* {@inheritDoc}
*/
public Reader reader() throws IOException {
if (buffer != null) {
return new StringReader(new String(buffer, 0, bufferPos));
} else if (tmpFile != null) {
// flush writer first
writer.flush();
return new FileReader(tmpFile);
} else {
throw new IOException("this instance has already been disposed");
}
}
//--------------------------------------------------< AppendableValue >
/**
* {@inheritDoc}
*/
public void append(char[] chars, int start, int length)
throws IOException {
if (buffer != null) {
if (bufferPos + length > MAX_BUFFER_SIZE) {
// threshold for keeping data in memory exceeded;
// create temp file and spool buffer contents
TransientFileFactory fileFactory = TransientFileFactory.getInstance();
tmpFile = fileFactory.createTransientFile("txt", null, null);
final FileOutputStream fout = new FileOutputStream(tmpFile);
writer = new OutputStreamWriter(fout) {
@Override
public void flush() throws IOException {
// flush this writer
super.flush();
// force synchronization with underlying file
fout.getFD().sync();
}
};
writer.write(buffer, 0, bufferPos);
writer.write(chars, start, length);
// reset fields
buffer = null;
bufferPos = 0;
} else {
if (bufferPos + length > buffer.length) {
// reallocate new buffer and spool old buffer contents
char[] newBuffer = new char[ bufferPos + length + BUFFER_INCREMENT];
System.arraycopy(buffer, 0, newBuffer, 0, bufferPos);
buffer = newBuffer;
}
System.arraycopy(chars, start, buffer, bufferPos, length);
bufferPos += length;
}
} else if (tmpFile != null) {
writer.write(chars, start, length);
} else {
throw new IOException("this instance has already been disposed");
}
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
if (buffer != null) {
// nop
} else if (tmpFile != null) {
writer.close();
} else {
throw new IOException("this instance has already been disposed");
}
}
/**
* {@inheritDoc}
*/
public void dispose() throws IOException {
if (buffer != null) {
buffer = null;
bufferPos = 0;
} else if (tmpFile != null) {
writer.close();
tmpFile.delete();
tmpFile = null;
writer = null;
} else {
throw new IOException("this instance has already been disposed");
}
}
}
}