package org.basex.http.webdav;
import static com.bradmcevoy.http.LockResult.*;
import static org.basex.http.webdav.WebDAVUtils.*;
import java.io.*;
import java.util.*;
import com.bradmcevoy.http.LockInfo.LockDepth;
import com.bradmcevoy.http.LockInfo.LockScope;
import com.bradmcevoy.http.LockInfo.LockType;
import com.bradmcevoy.http.Request.Method;
import org.basex.util.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import com.bradmcevoy.http.*;
import com.bradmcevoy.http.exceptions.*;
/**
* WebDAV resource representing an abstract folder within a collection database.
*
* @author BaseX Team 2005-17, BSD License
* @author Rositsa Shadura
* @author Dimitar Popov
*/
abstract class WebDAVResource implements CopyableResource, DeletableResource, MoveableResource,
LockableResource {
/** Resource meta data. */
final WebDAVMetaData meta;
/** WebDAV service implementation. */
final WebDAVService service;
/**
* Constructor.
* @param meta resource meta data
* @param service service
*/
WebDAVResource(final WebDAVMetaData meta, final WebDAVService service) {
this.meta = meta;
this.service = service;
}
@Override
public Object authenticate(final String user, final String pass) {
return user;
}
@Override
public boolean authorise(final Request request, final Method method, final Auth auth) {
return WebDAVService.authorize(meta.db);
}
@Override
public String checkRedirect(final Request request) {
return null;
}
@Override
public String getRealm() {
return Prop.NAME;
}
@Override
public String getUniqueId() {
return null;
}
@Override
public String getName() {
return name(meta.path);
}
@Override
public Date getModifiedDate() {
return meta.mdate;
}
@Override
public void delete() throws BadRequestException, NotAuthorizedException {
new WebDAVCode<Object>(this) {
@Override
public void run() throws IOException {
del();
}
}.eval();
}
@Override
public void copyTo(final CollectionResource target, final String name) throws BadRequestException,
NotAuthorizedException {
new WebDAVCode<Object>(this) {
@Override
public void run() throws IOException {
if(target instanceof WebDAVRoot)
copyToRoot(name);
else if(target instanceof WebDAVFolder)
copyTo((WebDAVFolder) target, name);
}
}.eval();
}
@Override
public void moveTo(final CollectionResource target, final String name) throws BadRequestException,
NotAuthorizedException {
new WebDAVCode<Object>(this) {
@Override
public void run() throws IOException {
if(target instanceof WebDAVRoot)
moveToRoot(name);
else if(target instanceof WebDAVFolder)
moveTo((WebDAVFolder) target, name);
}
}.eval();
}
/**
* Lock this resource and return a token.
*
* @param timeout - in seconds, or null
* @param lockInfo lock info
* @return - a result containing the token representing the lock if successful,
* otherwise a failure reason code
*/
@Override
public LockResult lock(final LockTimeout timeout, final LockInfo lockInfo)
throws NotAuthorizedException, PreConditionFailedException, LockedException {
return new WebDAVCode<LockResult>(this) {
@Override
public LockResult get() {
return lockResource(timeout, lockInfo);
}
}.evalNoEx();
}
/**
* Renew the lock and return new lock info.
*
* @param token lock token
* @return lock result
*/
@Override
public LockResult refreshLock(final String token) throws NotAuthorizedException,
PreConditionFailedException {
return new WebDAVCode<LockResult>(this) {
@Override
public LockResult get() throws IOException {
return refresh(token);
}
}.evalNoEx();
}
/**
* If the resource is currently locked, and the tokenId matches the current
* one, unlock the resource.
*
* @param tokenId lock token
*/
@Override
public void unlock(final String tokenId) throws NotAuthorizedException,
PreConditionFailedException {
new WebDAVCode<Object>(this) {
@Override
public void run() throws IOException {
service.locking.unlock(tokenId);
}
}.evalNoEx();
}
/**
* Get the active lock for the current resource.
* @return - the current lock if the resource is locked, or null
*/
@Override
public LockToken getCurrentLock() {
return new WebDAVCode<LockToken>(this) {
@Override
public LockToken get() throws IOException {
return getCurrentActiveLock();
}
}.evalNoEx();
}
/**
* Delete document or folder.
* @throws IOException I/O exception
*/
void del() throws IOException {
service.delete(meta.db, meta.path);
}
/**
* Rename document or folder.
* @param n new name
* @throws IOException I/O exception
*/
void rename(final String n) throws IOException {
service.rename(meta.db, meta.path, n);
}
/**
* Copy folder to the root, creating a new database.
* @param n new name of the folder (database)
* @throws IOException I/O exception
*/
protected abstract void copyToRoot(String n) throws IOException;
/**
* Copy folder to another folder.
* @param f target folder
* @param n new name of the folder
* @throws IOException I/O exception
*/
protected abstract void copyTo(WebDAVFolder f, String n) throws IOException;
/**
* Move folder to the root, creating a new database.
* @param n new name of the folder (database)
* @throws IOException I/O exception
*/
void moveToRoot(final String n) throws IOException {
// folder is moved to the root: create new database with it
copyToRoot(n);
del();
}
/**
* Move folder to another folder.
* @param f target folder
* @param n new name of the folder
* @throws IOException I/O exception
*/
private void moveTo(final WebDAVFolder f, final String n) throws IOException {
if(f.meta.db.equals(meta.db)) {
// folder is moved to a folder in the same database
rename(f.meta.path + SEP + n);
} else {
// folder is moved to a folder in another database
copyTo(f, n);
del();
}
}
/**
* Lock the current resource.
* @param timeout lock timeout
* @param lockInfo lock info
* @return lock result
*/
private LockResult lockResource(final LockTimeout timeout, final LockInfo lockInfo) {
try {
final String tokenId = service.locking.lock(
meta.db,
meta.path,
lockInfo.scope.name().toLowerCase(Locale.ENGLISH),
lockInfo.type.name().toLowerCase(Locale.ENGLISH),
lockInfo.depth.name().toLowerCase(Locale.ENGLISH),
lockInfo.lockedByUser,
timeout.getSeconds()
);
return success(new LockToken(tokenId, lockInfo, timeout));
} catch(final IOException ex) {
return failed(FailureReason.ALREADY_LOCKED);
}
}
/**
* Get the active lock on the current resource.
* @return the token of the active lock or {@code null} if resource is not locked
* @throws IOException I/O exception
*/
private LockToken getCurrentActiveLock() throws IOException {
final String lockInfoStr = service.locking.lock(meta.db, meta.path);
return lockInfoStr == null ? null : parseLockInfo(lockInfoStr);
}
/**
* Renew a lock with the given token.
* @param token lock token
* @return lock result
* @throws IOException I/O exception
*/
private LockResult refresh(final String token) throws IOException {
service.locking.refreshLock(token);
final String lockInfoStr = service.locking.lock(token);
final LockToken lockToken = lockInfoStr == null ? null : parseLockInfo(lockInfoStr);
return lockToken == null ? failed(FailureReason.ALREADY_LOCKED) : success(lockToken);
}
/**
* Parse the lock info.
* @param lockInfo lock info as a string
* @return parsed lock info bean
* @throws IOException I/O exception
*/
private static LockToken parseLockInfo(final String lockInfo) throws IOException {
try {
final XMLReader reader = XMLReaderFactory.createXMLReader();
final LockTokenSaxHandler handler = new LockTokenSaxHandler();
reader.setContentHandler(handler);
reader.parse(new InputSource(new StringReader(lockInfo)));
return handler.lockToken;
} catch(final SAXException ex) {
Util.errln("Error while parsing lock info: %", ex);
return null;
}
}
/** SAX handler for lock token. */
private static final class LockTokenSaxHandler extends DefaultHandler {
/** Parsed lock token. */
private final LockToken lockToken = new LockToken(null, new LockInfo(), null);
/** Current element name. */
private String elementName;
@Override
public void startElement(final String uri, final String localName, final String name,
final Attributes attributes) throws SAXException {
elementName = localName;
super.startElement(uri, localName, name, attributes);
}
@Override
public void endElement(final String uri, final String localName, final String qName)
throws SAXException {
elementName = null;
super.endElement(uri, localName, qName);
}
@Override
public void characters(final char[] ch, final int start, final int length) {
final String v = String.valueOf(ch, start, length);
if("token".equals(elementName))
lockToken.tokenId = v;
else if("scope".equals(elementName))
lockToken.info.scope = LockScope.valueOf(v.toUpperCase(Locale.ENGLISH));
else if("type".equals(elementName))
lockToken.info.type = LockType.valueOf(v.toUpperCase(Locale.ENGLISH));
else if("depth".equals(elementName))
lockToken.info.depth = LockDepth.valueOf(v.toUpperCase(Locale.ENGLISH));
else if("owner".equals(elementName))
lockToken.info.lockedByUser = v;
else if("timeout".equals(elementName))
lockToken.timeout = LockTimeout.parseTimeout(v);
}
}
}