blob: 4c96bc2e3cb3a27c76187b3cbd695ce6a0f79cff [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.lenya.cms.repository;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.excalibur.source.ModifiableSource;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.source.TraversableSource;
import org.apache.lenya.cms.cocoon.source.SourceUtil;
import org.apache.lenya.cms.publication.DocumentFactory;
import org.apache.lenya.cms.publication.DocumentUtil;
import org.apache.lenya.cms.publication.Publication;
import org.apache.lenya.cms.rc.CheckInEntry;
import org.apache.lenya.cms.rc.RevisionControlException;
import org.apache.lenya.util.Assert;
/**
* Provide access to a source.
*/
public class SourceWrapper extends AbstractLogEnabled {
private SourceNode node;
private String sourceUri;
protected ServiceManager manager;
/**
* Ctor.
* @param node
* @param sourceUri
* @param manager
* @param logger
*/
public SourceWrapper(SourceNode node, String sourceUri, ServiceManager manager, Logger logger) {
Assert.notNull("node", node);
this.node = node;
Assert.notNull("source URI", sourceUri);
this.sourceUri = sourceUri;
Assert.notNull("service manager", manager);
this.manager = manager;
enableLogging(logger);
}
protected static final String FILE_PREFIX = "file:/";
protected static final String CONTEXT_PREFIX = "context://";
protected SourceNode getNode() {
return this.node;
}
private String realSourceUri;
/**
* Returns the URI of the actual source which is used.
*
* @return A string.
*/
protected String getRealSourceUri() {
if (this.realSourceUri == null) {
this.realSourceUri = computeRealSourceUri(this.manager, getNode().getSession(),
this.sourceUri, getLogger());
}
return this.realSourceUri;
}
protected static final String computeRealSourceUri(ServiceManager manager, Session session,
String sourceUri, Logger logger) {
String contentDir = null;
String publicationId = null;
try {
String pubBase = Node.LENYA_PROTOCOL + Publication.PUBLICATION_PREFIX_URI + "/";
String publicationsPath = sourceUri.substring(pubBase.length());
int firstSlashIndex = publicationsPath.indexOf("/");
publicationId = publicationsPath.substring(0, firstSlashIndex);
DocumentFactory factory = DocumentUtil.createDocumentFactory(manager, session);
Publication pub = factory.getPublication(publicationId);
contentDir = pub.getContentDir();
} catch (Exception e) {
throw new RuntimeException(e);
}
String contentBaseUri = null;
String urlID = sourceUri.substring(Node.LENYA_PROTOCOL.length());
// Substitute e.g. "lenya://lenya/pubs/PUB_ID/content" by "contentDir"
String filePrefix = urlID.substring(0, urlID.indexOf(publicationId)) + publicationId;
String tempString = urlID.substring(filePrefix.length() + 1);
String fileMiddle = tempString.substring(0, tempString.indexOf("/"));
String fileSuffix = tempString.substring(fileMiddle.length() + 1, tempString.length());
String uriSuffix;
if (new File(contentDir).isAbsolute()) {
// Absolute
contentBaseUri = FILE_PREFIX + contentDir;
uriSuffix = File.separator + fileSuffix;
} else {
// Relative
contentBaseUri = CONTEXT_PREFIX + contentDir;
uriSuffix = "/" + fileSuffix;
}
String realSourceUri = contentBaseUri + uriSuffix;
if (logger.isDebugEnabled()) {
try {
if (!SourceUtil.exists(contentBaseUri, manager)) {
logger.debug("The content directory [" + contentBaseUri + "] does not exist. "
+ "It will be created as soon as documents are added.");
}
} catch (ServiceException e) {
throw new RuntimeException(e);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
logger.debug("Real Source URI: " + realSourceUri);
}
return realSourceUri;
}
/**
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.transaction.Transactionable#deleteTransactionable()
*/
public void deleteTransactionable() throws RepositoryException {
try {
if (!getNode().isCheckedOut()) {
throw new RuntimeException("Cannot delete source [" + getSourceUri()
+ "]: not checked out!");
} else {
this.data = null;
SourceUtil.delete(getRealSourceUri(), this.manager);
}
} catch (Exception e) {
throw new RepositoryException(e);
}
}
byte[] data = null;
/**
* @return An input stream.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#getInputStream()
*/
public synchronized InputStream getInputStream() throws RepositoryException {
loadData();
if (this.data == null) {
throw new RuntimeException(this + " does not exist!");
}
return new ByteArrayInputStream(this.data);
}
/**
* @return A boolean value.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#exists()
*/
public boolean exists() throws RepositoryException {
if (this.deleted == true) {
return false;
} else if (this.data != null) {
return true;
} else {
try {
return SourceUtil.exists(getRealSourceUri(), this.manager);
} catch (Exception e) {
throw new RepositoryException(e);
}
}
}
private boolean deleted;
private int loadRevision = -1;
protected void delete() {
this.deleted = true;
}
/**
* Loads the data from the real source.
*
* @throws RepositoryException if an error occurs.
*/
protected synchronized void loadData() throws RepositoryException {
if (this.deleted || this.data != null) {
return;
}
ByteArrayOutputStream out = null;
InputStream in = null;
SourceResolver resolver = null;
TraversableSource source = null;
try {
resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
source = (TraversableSource) resolver.resolveURI(getRealSourceUri());
if (source.exists() && !source.isCollection()) {
byte[] buf = new byte[4096];
out = new ByteArrayOutputStream();
in = source.getInputStream();
int read = in.read(buf);
while (read > 0) {
out.write(buf, 0, read);
read = in.read(buf);
}
this.data = out.toByteArray();
this.mimeType = source.getMimeType();
}
} catch (Exception e) {
throw new RepositoryException(e);
} finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (Exception e) {
throw new RepositoryException(e);
}
if (resolver != null) {
if (source != null) {
resolver.release(source);
}
this.manager.release(resolver);
}
}
this.loadRevision = this.node.getCurrentRevisionNumber();
}
/**
* Store the source URLs which are currently written.
*/
private static Map lockedUris = new WeakHashMap();
/**
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.transaction.Transactionable#saveTransactionable()
*/
protected synchronized void saveTransactionable() throws RepositoryException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Saving [" + this + "] to source [" + getRealSourceUri() + "]");
}
if (this.data != null) {
String realSourceUri = getRealSourceUri();
Object lock = lockedUris.get(realSourceUri);
if (lock == null) {
lock = new Object();
lockedUris.put(realSourceUri, lock);
}
synchronized (lock) {
saveTransactionable(realSourceUri);
}
}
}
protected void saveTransactionable(String realSourceUri) throws RepositoryException {
SourceResolver resolver = null;
ModifiableSource source = null;
InputStream in = null;
OutputStream out = null;
try {
resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
source = (ModifiableSource) resolver.resolveURI(realSourceUri);
out = source.getOutputStream();
byte[] buf = new byte[4096];
in = new ByteArrayInputStream(this.data);
int read = in.read(buf);
while (read > 0) {
out.write(buf, 0, read);
read = in.read(buf);
}
} catch (Exception e) {
throw new RepositoryException(e);
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.flush();
out.close();
}
} catch (Throwable t) {
throw new RuntimeException("Could not close streams: ", t);
}
if (resolver != null) {
if (source != null) {
resolver.release(source);
}
manager.release(resolver);
}
}
}
/**
* Output stream.
*/
private class NodeOutputStream extends ByteArrayOutputStream {
/**
* @see java.io.OutputStream#close()
*/
public synchronized void close() throws IOException {
SourceWrapper.this.data = super.toByteArray();
SourceWrapper.this.lastModified = new Date().getTime();
try {
SourceWrapper.this.getNode().registerDirty();
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
super.close();
}
}
/**
* @return The content length.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#getContentLength()
*/
public long getContentLength() throws RepositoryException {
loadData();
return this.data.length;
}
private long lastModified = -1;
/**
* @return The last modification date.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#getLastModified()
*/
public long getLastModified() throws RepositoryException {
try {
CheckInEntry entry = this.node.getRcml().getLatestCheckInEntry();
if (entry != null) {
this.lastModified = entry.getTime();
}
} catch (RevisionControlException e) {
throw new RepositoryException(e);
}
return this.lastModified;
}
private String mimeType;
/**
* @return A string.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#getMimeType()
*/
public String getMimeType() throws RepositoryException {
loadData();
return this.mimeType;
}
/**
* @return The source URI.
*/
public String getSourceUri() {
return this.sourceUri;
}
/**
* @return An output stream.
* @throws RepositoryException if an error occurs.
* @see org.apache.lenya.cms.repository.Node#getOutputStream()
*/
public synchronized OutputStream getOutputStream() throws RepositoryException {
if (getLogger().isDebugEnabled())
getLogger().debug("Get OutputStream for " + getSourceUri());
loadData();
return new NodeOutputStream();
}
protected int getLoadRevision() {
return this.loadRevision;
}
}