/*
* ====================================================================
* Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.server.dav.handlers;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.io.dav.DAVElement;
import org.tmatesoft.svn.core.internal.io.fs.FSCommitter;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
import org.tmatesoft.svn.core.internal.io.fs.FSRoot;
import org.tmatesoft.svn.core.internal.io.fs.FSTransactionInfo;
import org.tmatesoft.svn.core.internal.server.dav.DAVConfig;
import org.tmatesoft.svn.core.internal.server.dav.DAVErrorCode;
import org.tmatesoft.svn.core.internal.server.dav.DAVException;
import org.tmatesoft.svn.core.internal.server.dav.DAVResource;
import org.tmatesoft.svn.core.internal.server.dav.DAVResourceType;
import org.tmatesoft.svn.core.internal.server.dav.DAVResourceURI;
import org.tmatesoft.svn.core.internal.util.SVNBase64;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.util.SVNXMLUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
/**
* @version 1.2.0
* @author TMate Software Ltd.
*/
public class DAVPropertiesProvider {
private boolean myIsDeferred;
private boolean myIsOperative;
private DAVResource myResource;
private ServletDAVHandler myOwner;
private SVNProperties myProperties;
private DAVPropertiesProvider(boolean isDeferred, ServletDAVHandler owner, DAVResource resource) {
myIsDeferred = isDeferred;
myOwner = owner;
myResource = resource;
}
public static DAVPropertiesProvider createPropertiesProvider(DAVResource resource, ServletDAVHandler owner) throws DAVException {
DAVResourceURI resourceURI = resource.getResourceURI();
if (resourceURI.getURI() == null) {
throw new DAVException("INTERNAL DESIGN ERROR: resource must define its URI.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0);
}
DAVPropertiesProvider provider = new DAVPropertiesProvider(true, owner, resource);
return provider;
}
public void open(boolean readOnly) throws DAVException {
myIsDeferred = false;
try {
doOpen(readOnly);
} catch (DAVException dave) {
throw new DAVException("Could not open the property database.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DAVErrorCode.PROP_OPENING);
}
}
public void applyRollBack(DAVElement propName, SVNPropertyValue propValue) throws DAVException {
if (propValue == null) {
removeProperty(propName);
return;
}
saveValue(propName, propValue);
}
public void removeProperty(DAVElement propName) throws DAVException {
String reposPropName = getReposPropName(propName);
if (reposPropName == null) {
return;
}
try {
FSFS fsfs = myResource.getFSFS();
if (myResource.isBaseLined()) {
if (myResource.isWorking()) {
FSTransactionInfo txn = myResource.getTxnInfo();
SVNProperties props = new SVNProperties();
props.put(reposPropName, (SVNPropertyValue) null);
fsfs.changeTransactionProperties(txn.getTxnId(), props);
} else {
SVNRepository repos = myResource.getRepository();
repos.setRevisionPropertyValue(myResource.getRevision(), reposPropName, null);
}
} else {
DAVResourceURI resourceURI = myResource.getResourceURI();
FSCommitter committer = getCommitter();
committer.changeNodeProperty(resourceURI.getPath(), reposPropName, null);
}
} catch (SVNException svne) {
throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "could not remove a property", null);
}
myProperties = null;
}
public void storeProperty(DAVElementProperty property) throws DAVException {
DAVElement propName = property.getName();
String propValue = property.getFirstValue(false);
String reposPropName = getReposPropName(propName);
SVNPropertyValue value = null;
Map attributes = property.getAttributes();
if (attributes != null) {
for (Iterator attrsIter = attributes.keySet().iterator(); attrsIter.hasNext();) {
String attrName = (String) attrsIter.next();
if (ServletDAVHandler.ENCODING_ATTR.equals(attrName)) {
String encodingType = (String) attributes.get(attrName);
if (ServletDAVHandler.BASE64_ENCODING.equals(encodingType)) {
byte[] buffer = new byte[propValue.length()];
int length = SVNBase64.base64ToByteArray(new StringBuffer(propValue), buffer);
value = SVNPropertyValue.create(reposPropName, buffer, 0, length);
} else {
throw new DAVException("Unknown property encoding", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0);
}
break;
}
}
}
if (value == null) {
value = SVNPropertyValue.create(propValue);
}
saveValue(propName, value);
}
public void defineNamespaces(Map namespaces) {
namespaces.put(DAVElement.SVN_SVN_PROPERTY_NAMESPACE, "S");
namespaces.put(DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE, "C");
namespaces.put(DAVElement.SVN_DAV_PROPERTY_NAMESPACE, "V");
}
/**
* @return Collection of DAVElement objects
*/
public Collection getPropertyNames() throws DAVException {
FSFS fsfs = myResource.getFSFS();
SVNException exc = null;
if (myProperties == null) {
if (myResource.isBaseLined()) {
if (myResource.getType() == DAVResourceType.WORKING) {
try {
myProperties = fsfs.getTransactionProperties(myResource.getTxnName());
} catch (SVNException svne) {
exc = svne;
}
} else {
try {
myProperties = fsfs.getRevisionProperties(myResource.getRevision());
} catch (SVNException svne) {
exc = svne;
}
}
} else {
FSRoot root = myResource.getRoot();
String path = myResource.getResourceURI().getPath();
try {
FSRevisionNode node = root.getRevisionNode(path);
myProperties = node.getProperties(fsfs);
} catch (SVNException svne) {
exc = svne;
}
if (exc == null) {
try {
root.checkNodeKind(path);
} catch (SVNException svne) {
exc = svne;
}
//TODO: add logging here?
}
}
if (exc != null) {
throw DAVException.convertError(exc.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"could not begin sequencing through properties", null);
}
}
Collection propNames = new ArrayList();
for (Iterator namesIter = myProperties.nameSet().iterator(); namesIter.hasNext();) {
String propName = (String) namesIter.next();
DAVElement propElementName = null;
if (propName.startsWith(SVNProperty.SVN_PREFIX)) {
propElementName = DAVElement.getElement(DAVElement.SVN_SVN_PROPERTY_NAMESPACE,
propName.substring(SVNProperty.SVN_PREFIX.length()));
} else {
propElementName = DAVElement.getElement(DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE, propName);
}
propNames.add(propElementName);
}
return propNames;
}
public boolean outputValue(DAVElement propName, StringBuffer buffer) throws DAVException {
SVNPropertyValue propValue = getPropertyValue(propName);
boolean found = propValue != null;
if (!found) {
return found;
}
String prefix = null;
if (DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE.equals(propName.getNamespace())) {
prefix = "C";
} else {
prefix = "S";
}
String propValueString = SVNPropertyValue.getPropertyAsString(propValue);
if ("".equals(propValueString)) {
SVNXMLUtil.openXMLTag(prefix, propName.getName(), SVNXMLUtil.XML_STYLE_SELF_CLOSING, null, buffer);
} else {
String xmlSafeValue = null;
Map attrs = null;
if (!SVNEncodingUtil.isXMLSafe(propValueString)) {
try {
xmlSafeValue = SVNBase64.byteArrayToBase64(propValueString.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
xmlSafeValue = SVNBase64.byteArrayToBase64(propValueString.getBytes());
}
attrs = new HashMap();
attrs.put("V:encoding", "base64");
} else {
xmlSafeValue = SVNEncodingUtil.xmlEncodeCDATA(propValueString);
}
SVNXMLUtil.openXMLTag(prefix, propName.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, attrs, buffer);
buffer.append(xmlSafeValue);
SVNXMLUtil.closeXMLTag(prefix, propName.getName(), buffer);
}
return found;
}
public SVNPropertyValue getPropertyValue(DAVElement propName) throws DAVException {
String reposPropName = getReposPropName(propName);
if (reposPropName == null) {
return null;
}
SVNProperties props = null;
FSFS fsfs = myResource.getFSFS();
try {
//TODO: if myProperties != null, try searching there first
if (myResource.isBaseLined()) {
if (myResource.getType() == DAVResourceType.WORKING) {
FSTransactionInfo txn = myResource.getTxnInfo();
props = fsfs.getTransactionProperties(txn.getTxnId());
} else {
long revision = myResource.getRevision();
props = fsfs.getRevisionProperties(revision);
}
} else {
FSRoot root = myResource.getRoot();
props = fsfs.getProperties(root.getRevisionNode(myResource.getResourceURI().getPath()));
}
} catch (SVNException svne) {
throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"could not fetch a property", null);
}
if (props != null) {
return props.getSVNPropertyValue(reposPropName);
}
return null;
}
public DAVResource getResource() {
return myResource;
}
private void saveValue(DAVElement propName, SVNPropertyValue value) throws DAVException {
String reposPropName = getReposPropName(propName);
if (reposPropName == null) {
DAVConfig config = myResource.getRepositoryManager().getDAVConfig();
if (config.isAutoVersioning()) {
reposPropName = propName.getName();
} else {
throw new DAVException("Properties may only be defined in the {0} and {1} namespaces.",
new Object[] { DAVElement.SVN_SVN_PROPERTY_NAMESPACE, DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE },
HttpServletResponse.SC_CONFLICT, 0);
}
}
try {
FSFS fsfs = myResource.getFSFS();
if (myResource.isBaseLined()) {
if (myResource.isWorking()) {
FSTransactionInfo txn = myResource.getTxnInfo();
SVNProperties props = new SVNProperties();
props.put(reposPropName, value);
fsfs.changeTransactionProperties(txn.getTxnId(), props);
} else {
SVNRepository repos = myResource.getRepository();
repos.setRevisionPropertyValue(myResource.getRevision(), reposPropName, value);
//TODO: maybe add logging here
}
} else {
DAVResourceURI resourceURI = myResource.getResourceURI();
FSCommitter committer = getCommitter();
committer.changeNodeProperty(resourceURI.getPath(), reposPropName, value);
}
} catch (SVNException svne) {
throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, null);
}
myProperties = null;
}
private String getReposPropName(DAVElement propName) {
if (DAVElement.SVN_SVN_PROPERTY_NAMESPACE.equals(propName.getNamespace())) {
return SVNProperty.SVN_PREFIX + propName.getName();
} else if (DAVElement.SVN_CUSTOM_PROPERTY_NAMESPACE.equals(propName.getNamespace())) {
return propName.getName();
}
return null;
}
public boolean isOperative() {
return myIsOperative;
}
public boolean isDeferred() {
return myIsDeferred;
}
public void setDeferred(boolean isDeferred) {
myIsDeferred = isDeferred;
}
private void doOpen(boolean readOnly) throws DAVException {
myIsOperative = true;
DAVResourceType resType = myResource.getType();
if (resType == DAVResourceType.HISTORY || resType == DAVResourceType.ACTIVITY || resType == DAVResourceType.PRIVATE) {
myIsOperative = false;
return;
}
if (!readOnly && resType != DAVResourceType.WORKING) {
if (!(myResource.isBaseLined() && resType == DAVResourceType.VERSION)) {
throw new DAVException("Properties may only be changed on working resources.", HttpServletResponse.SC_CONFLICT, 0);
}
}
}
private FSCommitter getCommitter() {
return myOwner.getCommitter(myResource.getFSFS(), myResource.getRoot(), myResource.getTxnInfo(), myResource.getLockTokens(),
myResource.getUserName());
}
}