All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.basex.http.webdav.WebDAVService Maven / Gradle / Ivy

The newest version!
package org.basex.http.webdav;

import static org.basex.http.webdav.WebDAVUtils.*;
import static org.basex.query.func.Function.*;

import java.io.*;
import java.util.*;
import java.util.List;

import org.basex.api.client.*;
import org.basex.core.*;
import org.basex.core.cmd.*;
import org.basex.http.*;
import org.basex.index.resource.*;
import org.basex.io.in.*;
import org.basex.io.serial.*;
import org.basex.query.func.*;
import org.basex.util.*;
import org.basex.util.http.*;

/**
 * Service handling the various WebDAV operations.
 *
 * @author BaseX Team 2005-24, BSD License
 * @author Dimitar Popov
 */
final class WebDAVService {
  /** Static WebDAV character map. */
  private static final String WEBDAV;

  static {
    final StringBuilder sb = new StringBuilder();
    add(160, sb);
    for(int cp = 8192; cp <= 8207; cp++) add(cp, sb);
    for(int cp = 8232; cp <= 8239; cp++) add(cp, sb);
    for(int cp = 8287; cp <= 8303; cp++) add(cp, sb);
    WEBDAV = sb.toString();
  }

  /** HTTP connection. */
  final HTTPConnection conn;
  /** Session. */
  private LocalSession ls;

  /**
   * Constructor.
   * @param conn HTTP connection
   */
  WebDAVService(final HTTPConnection conn) {
    this.conn = conn;
  }

  /**
   * Closes an open session.
   */
  void close() {
    if(ls != null) ls.close();
  }

  /**
   * Checks a folder for a dummy document and delete it.
   * @param db database
   * @param path path
   * @throws IOException I/O exception
   */
  void deleteDummy(final String db, final String path) throws IOException {
    final String dummy = path + SEP + DUMMY;
    if(!pathExists(db, dummy)) return;

    // path contains dummy document
    final LocalSession session = session();
    session.execute(new Open(db));
    session.execute(new Delete(dummy));
  }

  /**
   * Checks if the specified database exists.
   * @param db database to be found
   * @return result of check
   * @throws IOException I/O exception
   */
  boolean dbExists(final String db) throws IOException {
    final WebDAVQuery query = new WebDAVQuery(_DB_EXISTS.args(" $db")).variable("db", db);
    return query.execute(session()).equals(Text.TRUE);
  }

  /**
   * Retrieves the last modified timestamp of a database.
   * @param db database
   * @return timestamp in milliseconds
   * @throws IOException I/O exception
   */
  String timestamp(final String db) throws IOException {
    final WebDAVQuery query = new WebDAVQuery(DATA.args(_DB_INFO.args(" $db") +
        "/descendant::timestamp[1]")).variable("db", db);
    return query.execute(session());
  }

  /**
   * Retrieves metadata about the resource at the given path.
   * @param db database
   * @param path resource path
   * @return resource meta data
   * @throws IOException I/O exception
   */
  private WebDAVMetaData metaData(final String db, final String path) throws IOException {
    final WebDAVQuery query = new WebDAVQuery(
      "let $a := " + _DB_LIST_DETAILS.args(" $db", " $path") + "[1] " +
      "return string-join(($a, $a/(@type, @content-type, @modified-date, @size)), '\t')");
    query.variable("db", db);
    query.variable("path", path);

    final String[] result = Strings.split(query.execute(session()), '\t');
    return new WebDAVMetaData(db, stripLeadingSlash(result[0]), result[1], result[2],
        result[3], null);
  }

  /**
   * Deletes a document or folder.
   * @param db database
   * @param path path
   * @throws IOException I/O exception
   */
  void remove(final String db, final String path) throws IOException {
    final LocalSession session = session();
    session.execute(new Open(db));
    session.execute(new Delete(path));

    // create dummy if parent is an empty folder
    final int ix = path.lastIndexOf(SEP);
    if(ix > 0) createDummy(db, path.substring(0, ix));
  }

  /**
   * Renames a document or folder.
   * @param db database
   * @param path path
   * @param npath new path
   * @throws IOException I/O exception
   */
  void rename(final String db, final String path, final String npath) throws IOException {
    final LocalSession session = session();
    session.execute(new Open(db));
    session.execute(new Rename(path, npath));

    // create dummy if old parent is an empty folder
    final int i1 = path.lastIndexOf(SEP);
    if(i1 > 0) createDummy(db, path.substring(0, i1));

    // delete dummy if new parent is an empty folder
    final int i2 = npath.lastIndexOf(SEP);
    if(i2 > 0) deleteDummy(db, npath.substring(0, i2));
  }

  /**
   * Copies a document to the specified target.
   * @param db source database
   * @param path source path
   * @param tdb target database
   * @param tpath target path
   * @throws IOException I/O exception
   */
  void copyDoc(final String db, final String path, final String tdb, final String tpath)
      throws IOException {

    final WebDAVQuery query = new WebDAVQuery(
      "let $type := " + _DB_TYPE.args(" $db", " $path") +
      "return if($type = '" + ResourceType.VALUE + "') then " +
        _DB_PUT_VALUE.args(" $tdb", _DB_GET_VALUE.args(" $db", " $path"), " $tpath") +
      "else if($type = '" + ResourceType.BINARY + "') then " +
        _DB_PUT_BINARY.args(" $tdb", _DB_GET_BINARY.args(" $db", " $path"), " $tpath") +
      " else " + _DB_PUT.args(" $tdb", _DB_GET.args(" $db", " $path"), " $tpath"));
    query.variable("db", db);
    query.variable("path", path);
    query.variable("tdb", tdb);
    query.variable("tpath", tpath);
    query.execute(session());
  }

  /**
   * Copies all documents in a folder to another folder.
   * @param db source database
   * @param path source path
   * @param tdb target database
   * @param tpath target folder
   * @throws IOException I/O exception
   */
  void copyAll(final String db, final String path, final String tdb, final String tpath)
      throws IOException {

    final WebDAVQuery query = new WebDAVQuery(
      "for $d in " + _DB_LIST.args(" $db", " $path") +
      "let $t := $tpath ||'/'|| substring($d, string-length($path) + 1) return " +
      "let $type := " + _DB_TYPE.args(" $db", " $d") +
      "return if($type = '" + ResourceType.VALUE + "') then " +
        _DB_PUT_VALUE.args(" $tdb", _DB_GET_VALUE.args(" $db", " $d"), " $t") +
      "else if($type = '" + ResourceType.BINARY + "') then " +
        _DB_PUT_BINARY.args(" $tdb", _DB_GET_BINARY.args(" $db", " $d"), " $t") +
      " else " + _DB_PUT.args(" $tdb", _DB_GET.args(" $db", " $d"), " $t"));
    query.variable("db", db);
    query.variable("path", path);
    query.variable("tdb", tdb);
    query.variable("tpath", tpath);
    query.execute(session());
  }

  /**
   * Writes a file to the specified output stream.
   * @param db database
   * @param path path
   * @param type resource type
   * @param out output stream
   * @throws IOException I/O exception
   */
  void get(final String db, final String path, final String type, final OutputStream out)
      throws IOException {

    session().setOutputStream(out);
    final Function func = type.equals("xml") ? _DB_GET :
      type.equals("binary") ? _DB_GET_BINARY : _DB_GET_VALUE;
    final WebDAVQuery query = new WebDAVQuery(func.args(" $db", " $path"),
      SerializerOptions.USE_CHARACTER_MAPS.arg(WEBDAV));
    query.variable("db", db);
    query.variable("path", path);
    query.execute(session());
  }

  /**
   * Creates an empty database with the given name.
   * @param db database name
   * @return object representing the newly created database
   * @throws IOException I/O exception
   */
  WebDAVResource createDb(final String db) throws IOException {
    session().execute(new CreateDB(db));
    return WebDAVFactory.database(this, new WebDAVMetaData(db, timestamp(db)));
  }

  /**
   * Drops the database with the given name.
   * @param db database name
   * @throws IOException I/O exception
   */
  void dropDb(final String db) throws IOException {
    session().execute(new DropDB(db));
  }

  /**
   * Renames the database with the given name.
   * @param old database name
   * @param db new name
   * @throws IOException I/O exception
   */
  void renameDb(final String old, final String db) throws IOException {
    session().execute(new AlterDB(old, dbName(db)));
  }

  /**
   * Copies the database with the given name.
   * @param old database name
   * @param db new database name
   * @throws IOException I/O exception
   */
  void copyDb(final String old, final String db) throws IOException {
    session().execute(new Copy(old, dbName(db)));
  }

  /**
   * Lists the direct children of a path.
   * @param db database
   * @param path path
   * @return children
   * @throws IOException I/O exception
   */
  List list(final String db, final String path) throws IOException {
    final WebDAVQuery query = new WebDAVQuery("string-join(" +
      _DB_DIR.args(" $db", " $path") + " ! (string(), name() = 'dir', " +
      "for $a in ('type', 'content-type', 'modified-date', 'size') " +
      "return string(@*[name() = $a]))" + ", '\t')");
    query.variable("db", db);
    query.variable("path", path);

    final String[] result = Strings.split(query.execute(session()), '\t');
    final List ch = new ArrayList<>();
    final int rs = result.length - 5;
    for(int r = 0; r < rs; r += 6) {
      final String name = result[r];
      final boolean dir = Boolean.parseBoolean(result[r + 1]);
      // check if document or folder
      final String pth = path + SEP + name;
      if(dir) {
        ch.add(WebDAVFactory.folder(this, new WebDAVMetaData(db, pth, result[r + 4])));
      } else if(!name.equals(DUMMY)) {
        ch.add(WebDAVFactory.file(this, new WebDAVMetaData(db, pth, result[r + 2], result[r + 3],
            result[r + 4], result[r + 5])));
      }
    }
    return ch;
  }

  /**
   * Lists all databases.
   * @return a list of database resources
   * @throws IOException I/O exception
   */
  List listDbs() throws IOException {
    final WebDAVQuery query = new WebDAVQuery(STRING_JOIN.args(
        _DB_LIST_DETAILS.args() + " ! (string(), @modified-date)", CHAR.args(9)));

    final String[] result = Strings.split(query.execute(session()), '\t');
    final List dbs = new ArrayList<>();
    final int rs = result.length - 1;
    for(int r = 0; r < rs; r += 2) {
      final String name = result[r];
      final String ms = result[r + 1];
      dbs.add(WebDAVFactory.database(this, new WebDAVMetaData(name, ms)));
    }
    return dbs;
  }

  /**
   * Creates a folder at the given path.
   * @param db database
   * @param path path
   * @param name new folder name
   * @return new folder resource
   * @throws IOException I/O exception
   */
  WebDAVResource createFolder(final String db, final String path, final String name)
      throws IOException {

    deleteDummy(db, path);
    final String newFolder = path + SEP + name;
    createDummy(db, newFolder);
    return WebDAVFactory.folder(this, new WebDAVMetaData(db, newFolder, timestamp(db)));
  }

  /**
   * Gets the resource at the given path.
   * @param db database
   * @param path path
   * @return resource
   * @throws IOException I/O exception
   */
  WebDAVResource resource(final String db, final String path) throws IOException {
    return exists(db, path) ?
      WebDAVFactory.file(this, metaData(db, path)) :
      pathExists(db, path) ?
        WebDAVFactory.folder(this, new WebDAVMetaData(db, path, timestamp(db))) :
        null;
  }

  /**
   * Adds the given file to the specified path.
   * @param db database
   * @param path path
   * @param name file name
   * @param in file content
   * @return object representing the newly added file
   * @throws IOException I/O exception
   */
  WebDAVResource createFile(final String db, final String path, final String name,
    final InputStream in) throws IOException {

    final LocalSession session = session();
    session.execute(new Open(db));
    final String dbp = path.isEmpty() ? name : path + SEP + name;
    // delete old resource if it already exists
    if(pathExists(db, dbp)) {
      session.execute(new Open(db));
      session.execute(new Delete(dbp));
    } else {
      // otherwise, delete dummy file
      deleteDummy(db, path);
    }
    return addFile(db, dbp, in);
  }

  /**
   * Creates a new database from the given file.
   * @param n file name
   * @param in file content
   * @return object representing the newly created database
   * @throws IOException I/O exception
   */
  WebDAVResource createFile(final String n, final InputStream in) throws IOException {
    return addFile(null, n, in);
  }

  /**
   * Checks if any of the resources starts with the given path.
   * @param db name of database
   * @param path path
   * @return {@code true} if there are resources with the given prefix
   * @throws IOException I/O exception
   */
  private boolean pathExists(final String db, final String path) throws IOException {
    final WebDAVQuery query = new WebDAVQuery(EXISTS.args(_DB_LIST.args(" $db", " $path")));
    query.variable("db", db);
    query.variable("path", path);
    return query.execute(session()).equals(Text.TRUE);
  }

  /**
   * Checks if any resource with the specified name exists.
   * @param db name of database
   * @param path resource path
   * @return {@code true} if there are resources with the name
   * @throws IOException I/O exception
   */
  private boolean exists(final String db, final String path) throws IOException {
    final WebDAVQuery query = new WebDAVQuery(_DB_EXISTS.args(" $db", " $path"));
    query.variable("db", db);
    query.variable("path", path);
    return query.execute(session()).equals(Text.TRUE);
  }

  /**
   * Creates a database with the given name and add the given document.
   * @param db database name
   * @param in data stream
   * @return object representing the newly created database
   * @throws IOException I/O exception
   */
  private WebDAVResource createDb(final String db, final InputStream in) throws IOException {
    session().create(db, in);
    return WebDAVFactory.database(this, new WebDAVMetaData(db, timestamp(db)));
  }

  /**
   * Adds a document with the specified name to the given path.
   * @param db database
   * @param path path where the document will be added
   * @param in data stream
   * @return object representing the newly added XML
   * @throws IOException I/O exception
   */
  private WebDAVResource put(final String db, final String path, final InputStream in)
      throws IOException {

    final LocalSession session = session();
    session.execute(new Open(db));
    session.put(path, in);
    return WebDAVFactory.file(this, new WebDAVMetaData(db, path, ResourceType.XML.toString(),
        MediaType.APPLICATION_XML.toString(), timestamp(db), null));
  }

  /**
   * Adds a binary file with the specified name to the given path.
   * @param db database
   * @param path path where the file will be stored
   * @param in data stream
   * @return object representing the newly added file
   * @throws IOException I/O exception
   */
  private WebDAVResource putBinary(final String db, final String path, final InputStream in)
      throws IOException {

    final LocalSession session = session();
    session.execute(new Open(db));
    session.putBinary(path, in);
    return WebDAVFactory.file(this, metaData(db, path));
  }

  /**
   * Adds a file in to the given path.
   * @param db database
   * @param path path
   * @param in file content
   * @return object representing the newly added file
   * @throws IOException I/O exception
   */
  private WebDAVResource addFile(final String db, final String path, final InputStream in)
      throws IOException {

    // use 4MB as buffer input
    try(BufferInput bi = BufferInput.get(in)) {
      // guess the content type from the first character
      if(peek(bi) == '<') {
        try {
          // add input as XML document
          return db == null ? createDb(dbName(path), bi) : put(db, path, bi);
        } catch(final IOException ex) {
          // reset stream if it did not work out
          Util.debug(ex);
          try {
            bi.reset();
          } catch(final IOException e) {
            Util.debug(e);
            // throw original exception if input cannot be reset
            throw ex;
          }
        }
      }

      // add input as binary (do this also if an error occurred, and if the stream could be reset)
      final String d;
      if(db == null) {
        d = dbName(path);
        createDb(d);
      } else {
        d = db;
      }
      return putBinary(d, path, bi);
    }
  }

  /**
   * Checks if a folder is empty and create a dummy document.
   * @param db database
   * @param path path
   * @throws IOException I/O exception
   */
  private void createDummy(final String db, final String path) throws IOException {
    // check if path is a folder and is empty
    if(path.matches("[^/]") || pathExists(db, path)) return;

    final LocalSession session = session();
    session.execute(new Open(db));
    session.putBinary(path + SEP + DUMMY, new ArrayInput(Token.EMPTY));
  }

  /**
   * Constructor.
   * @return local session
   */
  private LocalSession session() {
    if(ls == null) ls = new LocalSession(conn.context);
    return ls;
  }

  /**
   * Adds a character mapping to the specified string builder.
   * @param ch character to be added
   * @param sb string builder
   */
  private static void add(final int ch, final StringBuilder sb) {
    if(sb.length() > 0) sb.append(',');
    sb.append((char) ch).append("=&#").append(ch).append(';');
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy