blob: 7af06a7191f0add8193c57a8f0ea2a6ff5fc45ef [file] [log] [blame]
/*
* 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.vault.fs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.LinkedList;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.vault.fs.api.AccessType;
import org.apache.jackrabbit.vault.fs.api.Artifact;
import org.apache.jackrabbit.vault.fs.api.ArtifactType;
import org.apache.jackrabbit.vault.fs.api.ExportArtifact;
import org.apache.jackrabbit.vault.fs.api.SerializationType;
import org.apache.jackrabbit.vault.fs.api.VaultInputSource;
import org.apache.jackrabbit.vault.fs.impl.AbstractArtifact;
import org.apache.jackrabbit.vault.util.JcrConstants;
/**
* Implements a artifact that is based on a property value.
*
*/
public class PropertyValueArtifact extends AbstractArtifact implements ExportArtifact {
/**
* The property of this artifact
*/
private final Property property;
/**
* the path to the property
*/
private final String path;
/**
* Temporary file if the value is detached
*/
private File tmpFile;
/**
* cached content length
*/
private Long contentLength;
private final long lastModified;
/**
* The value index for multi value properties.
*/
private final int valueIndex;
public PropertyValueArtifact(Artifact parent, String relPath, String ext, ArtifactType type,
Property prop, long lastModified)
throws RepositoryException {
this(parent, relPath, ext, type, prop, -1, lastModified);
}
public PropertyValueArtifact(Artifact parent, String relPath, String ext, ArtifactType type,
Property prop, int index, long lastModified)
throws RepositoryException {
super(parent, relPath, ext, type);
this.property = prop;
this.path = prop.getPath();
if (prop.getDefinition().isMultiple()) {
if (index < 0) {
index = 0;
}
} else {
if (index >=0) {
index = -1;
}
}
this.valueIndex = index;
this.lastModified = lastModified;
}
/**
* Creates a collection of {@link PropertyValueArtifact} from the given
* property. If the property is multivalued there will be an artifact
* created for each value with the value index appended to it's name.
*
* @param parent parent artifact
* @param relPath the base name for the artifact(s).
* @param ext the extension
* @param type the type for the artifact(s).
* @param prop the property for the artifact(s).
* @param lastModified the last modified date.
*
* @return a collection of Artifacts.
* @throws RepositoryException if an error occurs
*/
public static Collection<PropertyValueArtifact> create(Artifact parent,
String relPath, String ext, ArtifactType type, Property prop, long lastModified)
throws RepositoryException {
LinkedList<PropertyValueArtifact> list = new LinkedList<PropertyValueArtifact>();
if (prop.getDefinition().isMultiple()) {
Value[] values = prop.getValues();
for (int i=0; i<values.length; i++) {
StringBuffer n = new StringBuffer(relPath);
n.append('[').append(i).append(']');
list.add(new PropertyValueArtifact(parent, n.toString(), ext, type, prop, i, lastModified));
}
} else {
list.add(new PropertyValueArtifact(parent, relPath, ext, type, prop, lastModified));
}
return list;
}
/**
* {@inheritDoc}
*/
public SerializationType getSerializationType() {
return SerializationType.GENERIC;
}
/**
* {@inheritDoc}
*
* @return always {@link AccessType#STREAM}
*/
public AccessType getPreferredAccess() {
return AccessType.STREAM;
}
/**
* {@inheritDoc}
*/
public InputStream getInputStream() throws IOException, RepositoryException {
return tmpFile == null ? new PVAInputStream() : new FileInputStream(tmpFile);
}
/**
* Detaches the value from the underlying property value.
*
* @throws IOException if an I/O error occurs
* @throws RepositoryException if a repository error occurs.
*/
public void detach() throws IOException, RepositoryException {
if (tmpFile == null) {
// ensure caching of content type
getContentType();
// copy value to temp file
tmpFile = File.createTempFile("jcrfs", "dat");
tmpFile.setLastModified(getLastModified());
tmpFile.deleteOnExit();
FileOutputStream out = new FileOutputStream(tmpFile);
InputStream in = getValue().getStream();
IOUtils.copy(in, out);
in.close();
out.close();
}
}
/**
* {@inheritDoc}
*
* @return a input source which systemId is the path of the underlying property
*/
public VaultInputSource getInputSource() throws IOException, RepositoryException {
final InputStream in = getInputStream();
return new VaultInputSource() {
@Override
public String getSystemId() {
return path;
}
@Override
public InputStream getByteStream() {
return in;
}
public long getContentLength() {
return PropertyValueArtifact.this.getContentLength();
}
public long getLastModified() {
return PropertyValueArtifact.this.getLastModified();
}
};
}
/**
* Returns the value either from field or from property.
*
* @return the jcr value.
* @throws RepositoryException if an repository error occurs.
*/
private Value getValue() throws RepositoryException {
if (valueIndex < 0) {
return property.getValue();
} else {
Value[] values = property.getValues();
if (valueIndex >= values.length) {
throw new RepositoryException("Illegal value index: " + valueIndex);
}
return values[valueIndex];
}
}
/**
* {@inheritDoc}
*/
public long getContentLength() {
if (contentLength == null) {
if (tmpFile == null) {
contentLength = -1L;
try {
if (valueIndex < 0) {
contentLength = property.getLength();
} else {
long[] lengths = property.getLengths();
if (valueIndex < lengths.length) {
contentLength = lengths[valueIndex];
}
}
} catch (RepositoryException e) {
// ignore
}
} else {
contentLength = tmpFile.length();
}
}
return contentLength;
}
/**
* Returns the underlying property
* @return the underlying property
*/
public Property getProperty() {
return property;
}
/**
* {@inheritDoc}
*/
public long getLastModified() {
return lastModified;
}
/**
* {@inheritDoc}
*/
public String getContentType() {
String ct = super.getContentType();
if (ct == null && tmpFile == null) {
try {
Node parent = property.getParent();
if (parent.hasProperty(JcrConstants.JCR_MIMETYPE)) {
ct = parent.getProperty(JcrConstants.JCR_MIMETYPE).getString();
}
} catch (RepositoryException e) {
// ignore
}
super.setContentType(ct);
}
return ct;
}
/**
* Internal defered input stream on this property value
*/
private class PVAInputStream extends InputStream {
private InputStream stream;
private boolean closed;
private void assertOpen() throws IOException {
if (stream == null) {
if (closed) {
throw new IOException("Stream already closed.");
}
try {
stream = getValue().getStream();
} catch (RepositoryException e) {
throw new IOException("Error while opening stream: " + e.toString());
}
}
}
public int read() throws IOException {
assertOpen();
return stream.read();
}
public int read(byte[] b) throws IOException {
assertOpen();
return stream.read(b);
}
public int read(byte[] b, int off, int len) throws IOException {
assertOpen();
return stream.read(b, off, len);
}
public long skip(long n) throws IOException {
assertOpen();
return stream.skip(n);
}
public int available() throws IOException {
assertOpen();
return stream.available();
}
public void close() throws IOException {
try {
if (stream != null) {
stream.close();
}
} finally {
closed = true;
stream = null;
}
}
public void mark(int readlimit) {
try {
assertOpen();
} catch (IOException e) {
throw new IllegalStateException(e);
}
stream.mark(readlimit);
}
public void reset() throws IOException {
assertOpen();
stream.reset();
}
public boolean markSupported() {
try {
assertOpen();
} catch (IOException e) {
throw new IllegalStateException(e);
}
return stream.markSupported();
}
}
}