/* * ==================================================================== * 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.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.logging.Level; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNRevisionProperty; 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.FSPathChange; import org.tmatesoft.svn.core.internal.io.fs.FSPathChangeKind; import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot; import org.tmatesoft.svn.core.internal.io.fs.FSTransactionInfo; import org.tmatesoft.svn.core.internal.io.fs.FSTransactionRoot; import org.tmatesoft.svn.core.internal.server.dav.DAVException; import org.tmatesoft.svn.core.internal.server.dav.DAVPathUtil; import org.tmatesoft.svn.core.internal.server.dav.DAVRepositoryManager; import org.tmatesoft.svn.core.internal.server.dav.DAVResource; import org.tmatesoft.svn.core.internal.server.dav.DAVResourceKind; import org.tmatesoft.svn.core.internal.server.dav.DAVResourceType; import org.tmatesoft.svn.core.internal.server.dav.DAVServlet; import org.tmatesoft.svn.core.internal.server.dav.DAVServletUtil; import org.tmatesoft.svn.core.internal.server.dav.DAVXMLUtil; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.util.SVNXMLUtil; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.2.0 * @author TMate Software Ltd. */ public class DAVMergeHandler extends ServletDAVHandler { private DAVMergeRequest myDAVRequest; protected DAVMergeHandler(DAVRepositoryManager connector, HttpServletRequest request, HttpServletResponse response) { super(connector, request, response); } public void execute() throws SVNException { long readLength = readInput(false); if (readLength <= 0) { getMergeRequest().invalidXMLRoot(); } DAVMergeRequest requestXMLObject = getMergeRequest(); DAVElementProperty rootElement = requestXMLObject.getRoot(); DAVElementProperty sourceElement = rootElement.getChild(DAVElement.SOURCE); if (sourceElement == null) { throw new DAVException("The DAV:merge element must contain a DAV:source element.", HttpServletResponse.SC_BAD_REQUEST, 0); } DAVElementProperty hrefElement = sourceElement.getChild(DAVElement.HREF); if (hrefElement == null) { throw new DAVException("The DAV:source element must contain a DAV:href element.", HttpServletResponse.SC_BAD_REQUEST, 0); } String source = hrefElement.getFirstValue(false); URI uri = null; try { uri = DAVServletUtil.lookUpURI(source, getRequest(), false); } catch (DAVException dave) { if (dave.getResponseCode() == HttpServletResponse.SC_BAD_REQUEST) { throw dave; } response(dave.getMessage(), DAVServlet.getStatusLine(dave.getResponseCode()), dave.getResponseCode()); } String path = uri.getPath(); DAVRepositoryManager manager = getRepositoryManager(); String resourceContext = manager.getResourceContext(); if (!path.startsWith(resourceContext)) { throw new DAVException("Destination url starts with a wrong context", HttpServletResponse.SC_BAD_REQUEST, 0); } //TODO: cut away the servlet context part path = path.substring(resourceContext.length()); DAVResource srcResource = getRequestedDAVResource(false, false, path); //NOTE: for now this all are no-ops, just commented them for a while //boolean noAutoMerge = rootElement.hasChild(DAVElement.NO_AUTO_MERGE); //boolean noCheckOut = rootElement.hasChild(DAVElement.NO_CHECKOUT); //DAVElementProperty propElement = rootElement.getChild(DAVElement.PROP); DAVResource resource = getRequestedDAVResource(false, false); if (!resource.exists()) { sendError(HttpServletResponse.SC_NOT_FOUND, null); return; } setResponseHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_VALUE); String response = null; try { response = merge(resource, srcResource); } catch (DAVException dave) { throw new DAVException("Could not MERGE resource \"{0}\" into \"{1}\".", new Object[] { SVNEncodingUtil.xmlEncodeCDATA(source), SVNEncodingUtil.xmlEncodeCDATA(getURI()) }, dave.getResponseCode(), null, SVNLogType.NETWORK, Level.FINE, dave, null, null, 0, null); } try { setResponseContentLength(response.getBytes(UTF8_ENCODING).length); } catch (UnsupportedEncodingException e) { } try { getResponseWriter().write(response); } catch (IOException ioe) { throw new DAVException(ioe.getMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, SVNErrorCode.IO_ERROR.getCode()); } } protected DAVRequest getDAVRequest() { return getMergeRequest(); } private String merge(DAVResource targetResource, DAVResource sourceResource) throws DAVException { boolean disableMergeResponse = false; if (sourceResource.getType() != DAVResourceType.ACTIVITY) { throw new DAVException("MERGE can only be performed using an activity as the source [at this time].", null, HttpServletResponse.SC_METHOD_NOT_ALLOWED, null, SVNLogType.NETWORK, Level.FINE, null, DAVXMLUtil.SVN_DAV_ERROR_TAG, DAVElement.SVN_DAV_ERROR_NAMESPACE, SVNErrorCode.INCORRECT_PARAMS.getCode(), null); } Map locks = parseLocks(getMergeRequest().getRootElement(), targetResource.getResourceURI().getPath()); if (!locks.isEmpty()) { sourceResource.setLockTokens(locks.values()); } FSFS fsfs = sourceResource.getFSFS(); String txnName = sourceResource.getTxnName(); FSTransactionInfo txn = DAVServletUtil.openTxn(fsfs, txnName); FSTransactionRoot txnRoot = null; try { txnRoot = fsfs.createTransactionRoot(txn); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not open a (transaction) root in the repository", null); } FSCommitter committer = getCommitter(sourceResource.getFSFS(), txnRoot, txn, sourceResource.getLockTokens(), sourceResource.getUserName()); StringBuffer buffer = new StringBuffer(); SVNErrorMessage[] postCommitHookErr = new SVNErrorMessage[1]; String postCommitErrMessage = null; long newRev = -1; try { newRev = committer.commitTxn(true, true, postCommitHookErr, buffer); } catch (SVNException svne) { if (postCommitHookErr[0] == null) { try { FSCommitter.abortTransaction(fsfs, txnName); } catch (SVNException svne1) { // } if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_CONFLICT) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_CONFLICT, "A conflict occurred during the MERGE processing. The problem occurred with the \"{0}\" resource.", new Object[] { buffer.toString() }); } throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_CONFLICT, "An error occurred while committing the transaction.", null); } } if (postCommitHookErr[0] != null) { SVNErrorMessage childErr = postCommitHookErr[0].getChildErrorMessage(); if (childErr != null && childErr.getMessage() != null) { postCommitErrMessage = childErr.getMessage(); } } //TODO: maybe add logging here DAVServletUtil.storeActivity(sourceResource, ""); String clientOptions = sourceResource.getClientOptions(); if (clientOptions != null) { if (clientOptions.indexOf(DAVLockInfoProvider.RELEASE_LOCKS_OPTION) != -1 && !locks.isEmpty()) { for (Iterator locksIter = locks.keySet().iterator(); locksIter.hasNext();) { String path = (String) locksIter.next(); String lockToken = (String) locks.get(path); try { fsfs.unlockPath(path, lockToken, sourceResource.getUserName(), false, true); } catch (SVNException svne) { // TODO: ignore exceptions. maybe add logging } } } if (clientOptions.indexOf(DAVLockInfoProvider.NO_MERGE_RESPONSE) != -1) { disableMergeResponse = true; } } return response(fsfs, newRev, postCommitErrMessage, disableMergeResponse); } private String response(FSFS fsfs, long newRev, String postCommitErr, boolean disableMergeResponse) throws DAVException { FSRevisionRoot root = null; try { root = fsfs.createRevisionRoot(newRev); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not open the FS root for the revision just committed.", null); } String vcc = DAVPathUtil.buildURI(getRepositoryManager().getResourceContext(), DAVResourceKind.VCC, -1, null, false); StringBuffer buffer = new StringBuffer(); Map prefixMap = new HashMap(); Collection namespaces = new LinkedList(); namespaces.add(DAVElement.DAV_NAMESPACE); prefixMap.put(DAVElement.DAV_NAMESPACE, SVNXMLUtil.DAV_NAMESPACE_PREFIX); String postCommitErrElement = null; if (postCommitErr != null) { namespaces.add(DAVElement.SVN_NAMESPACE); prefixMap.put(DAVElement.SVN_NAMESPACE, SVNXMLUtil.SVN_NAMESPACE_PREFIX); SVNXMLUtil.openXMLTag(SVNXMLUtil.SVN_NAMESPACE_PREFIX, DAVElement.POST_COMMIT_ERROR.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); buffer.append(postCommitErr); SVNXMLUtil.closeXMLTag(SVNXMLUtil.SVN_NAMESPACE_PREFIX, DAVElement.POST_COMMIT_ERROR.getName(), buffer); postCommitErrElement = buffer.toString(); buffer.delete(0, buffer.length()); } else { postCommitErrElement = ""; } String creationDate = null; String creatorDisplayName = null; try { SVNProperties revProps = fsfs.getRevisionProperties(newRev); creationDate = revProps.getStringValue(SVNRevisionProperty.DATE); creatorDisplayName = revProps.getStringValue(SVNRevisionProperty.AUTHOR); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not get author of newest revision", null); } SVNXMLUtil.addXMLHeader(buffer); SVNXMLUtil.openNamespaceDeclarationTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.MERGE_RESPONSE.getName(), namespaces, prefixMap, null, buffer, true); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.UPDATE_SET.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESPONSE.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); SVNXMLUtil.openCDataTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.HREF.getName(), vcc, null, true, true, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROPSTAT.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROP.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESOURCE_TYPE.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.BASELINE.getName(), SVNXMLUtil.XML_STYLE_SELF_CLOSING | SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESOURCE_TYPE.getName(), buffer); buffer.append(postCommitErrElement); buffer.append('\n'); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.VERSION_NAME.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); buffer.append(newRev); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.VERSION_NAME.getName(), buffer); if (creationDate != null ) { SVNXMLUtil.openCDataTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.CREATION_DATE.getName(), creationDate, null, true, true, buffer); } if (creatorDisplayName != null ) { SVNXMLUtil.openCDataTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.CREATOR_DISPLAY_NAME.getName(), creatorDisplayName, null, true, true, buffer); } SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROP.getName(), buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.STATUS.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); buffer.append("HTTP/1.1 200 OK"); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.STATUS.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROPSTAT.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESPONSE.getName(), buffer); if (!disableMergeResponse) { try { doResources(root, buffer); } catch (SVNException svne) { throw DAVException.convertError(svne.getErrorMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error constructing resource list.", null); } } SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.UPDATE_SET.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.MERGE_RESPONSE.getName(), buffer); return buffer.toString(); } private void doResources(FSRevisionRoot root, StringBuffer buffer) throws SVNException { Map changedPaths = root.getChangedPaths(); Map sentPaths = new HashMap(); for (Iterator pathsIter = changedPaths.keySet().iterator(); pathsIter.hasNext();) { String path = (String) pathsIter.next(); FSPathChange pathChange = (FSPathChange) changedPaths.get(path); boolean sendSelf = false; boolean sendParent = false; FSPathChangeKind changeKind = pathChange.getChangeKind(); if (changeKind == FSPathChangeKind.FS_PATH_CHANGE_DELETE) { sendSelf = false; sendParent = true; } else if (changeKind == FSPathChangeKind.FS_PATH_CHANGE_ADD || changeKind == FSPathChangeKind.FS_PATH_CHANGE_REPLACE) { sendSelf = true; sendParent = true; } else { sendSelf = true; sendParent = false; } if (sendSelf) { if (!sentPaths.containsKey(path)) { SVNNodeKind pathKind = root.checkNodeKind(path); sendResponse(root, path, pathKind == SVNNodeKind.DIR, buffer); sentPaths.put(path, path); } } if (sendParent) { String parentPath = SVNPathUtil.removeTail(path); if (!sentPaths.containsKey(parentPath)) { sendResponse(root, parentPath, true, buffer); sentPaths.put(parentPath, parentPath); } } } } private void sendResponse(FSRevisionRoot root, String path, boolean isDir, StringBuffer buffer) { String context = getRepositoryManager().getResourceContext(); String href = DAVPathUtil.buildURI(context, DAVResourceKind.PUBLIC, -1, path, false); long revToUse = DAVServletUtil.getSafeCreatedRevision(root, path); String vsnURL = DAVPathUtil.buildURI(context, DAVResourceKind.VERSION, revToUse, path, false); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESPONSE.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); SVNXMLUtil.openCDataTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.HREF.getName(), href, null, true, true, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROPSTAT.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROP.getName(), SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); if (isDir) { SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESOURCE_TYPE.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.COLLECTION.getName(), SVNXMLUtil.XML_STYLE_SELF_CLOSING | SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESOURCE_TYPE.getName(), buffer); } else { SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESOURCE_TYPE.getName(), SVNXMLUtil.XML_STYLE_SELF_CLOSING | SVNXMLUtil.XML_STYLE_NORMAL, null, buffer); } SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.CHECKED_IN.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); SVNXMLUtil.openCDataTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.HREF.getName(), vsnURL, null, true, true, buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.CHECKED_IN.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROP.getName(), buffer); SVNXMLUtil.openXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.STATUS.getName(), SVNXMLUtil.XML_STYLE_PROTECT_CDATA, null, buffer); buffer.append("HTTP/1.1 200 OK"); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.STATUS.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.PROPSTAT.getName(), buffer); SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX, DAVElement.RESPONSE.getName(), buffer); } private DAVMergeRequest getMergeRequest() { if (myDAVRequest == null) { myDAVRequest = new DAVMergeRequest(); } return myDAVRequest; } }