package org.basex.core; import org.basex.core.Commands.CmdPerm; import static org.basex.core.Text.*; import org.basex.core.cmd.Close; import org.basex.data.Data; import org.basex.data.Result; import org.basex.io.IOFile; import org.basex.io.out.ArrayOutput; import org.basex.io.out.NullOutput; import org.basex.io.out.PrintOutput; import org.basex.util.Performance; import org.basex.util.TokenBuilder; import org.basex.util.Util; import org.basex.util.list.StringList; import org.xml.sax.InputSource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Locale; /** * This class provides the architecture for all internal command * implementations. It evaluates queries that are sent by the GUI, the client or * the standalone version. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public abstract class Command extends Progress { /** Commands flag: standard. */ protected static final int STANDARD = 256; /** Commands flag: data reference needed. */ protected static final int DATAREF = 512; /** Container for query information. */ private final TokenBuilder info = new TokenBuilder(); /** Command arguments. */ protected final String[] args; /** Performance measurements. */ protected Performance perf; /** Database context. */ protected Context context; /** Output stream. */ protected PrintOutput out; /** Optional input source. */ protected InputSource in; /** Database properties. */ protected Prop prop; /** Main properties. */ protected MainProp mprop; /** Flags for controlling command processing. */ private final int flags; /** * Constructor. * @param flag command flags * @param arg arguments */ protected Command(final int flag, final String... arg) { flags = flag; args = arg; } /** * Executes the command and prints the result to the specified output * stream. If an exception occurs, a {@link BaseXException} is thrown. * @param ctx database context * @param os output stream reference * @throws BaseXException command exception */ public final void execute(final Context ctx, final OutputStream os) throws BaseXException { if(!exec(ctx, os)) throw new BaseXException(info()); } /** * Executes the command and returns the result as string. * If an exception occurs, a {@link BaseXException} is thrown. * @param ctx database context * @return string result * @throws BaseXException command exception */ public final String execute(final Context ctx) throws BaseXException { final ArrayOutput ao = new ArrayOutput(); execute(ctx, ao); return ao.toString(); } /** * Attaches an input stream. * @param is input stream */ public void setInput(final InputStream is) { in = new InputSource(is); } /** * Attaches an input source. * @param is input source */ public void setInput(final InputSource is) { in = is; } /** * Runs the command without permission, data and concurrency checks. * Should be called with care, and only by other database commands. * @param ctx database context * @return result of check */ public final boolean run(final Context ctx) { return run(ctx, new NullOutput()); } /** * Returns command information. * @return info string */ public final String info() { return info.toString(); } /** * Returns the result set, generated by a query command. * Will only yield results if {@link Prop#CACHEQUERY} is set. * @return result set */ public Result result() { return null; } /** * Checks if the command performs updates/write operations. * @param ctx database context * @return result of check */ @SuppressWarnings("unused") public boolean updating(final Context ctx) { return (flags & (User.CREATE | User.WRITE)) != 0; } /** * Returns true if this command will change the {@link Context#data} * reference. This method is required by the progress dialog in the frontend. * @param ctx database context * @return result of check */ @SuppressWarnings("unused") public boolean newData(final Context ctx) { return false; } /** * Returns true if this command returns a progress value. * This method is required by the progress dialog in the frontend. * @return result of check */ public boolean supportsProg() { return false; } /** * Returns true if this command can be stopped. * This method is required by the progress dialog in the frontend. * @return result of check */ public boolean stoppable() { return false; } /** * Builds a string representation from the command. This string must be * correctly built, as commands are sent to the server as strings. * @param cb command builder */ protected void build(final CommandBuilder cb) { cb.init().args(); } @Override public final String toString() { final CommandBuilder cb = new CommandBuilder(this); build(cb); return cb.toString(); } // PROTECTED METHODS ======================================================== /** * Executes the command and serializes the result (internal call). * @return success of operation * @throws IOException I/O exception */ protected abstract boolean run() throws IOException; /** * Adds the error message to the message buffer {@link #info}. * @param msg error message * @param ext error extension * @return {@code false} */ protected final boolean error(final String msg, final Object... ext) { info.reset(); info.addExt(msg == null ? "" : msg, ext); return false; } /** * Adds information on command execution. * @param str information to be added * @param ext extended info * @return {@code true} */ protected final boolean info(final String str, final Object... ext) { info.addExt(str, ext).add(NL); return true; } /** * Returns all databases matching the specified glob pattern. * If the specified pattern does not contain any special characters, * it is treated as literal. * @param name database name pattern * @return array with database names */ protected final String[] databases(final String name) { final String pat = name.matches(".*[*?,].*") ? IOFile.regex(name) : name; final StringList sl = new StringList(); for(final IOFile f : mprop.dbpath().children(pat)) { final String n = f.name(); if(!n.contains(".")) sl.add(n); } return sl.toArray(); } /** * Returns the specified command option. * @param typ options enumeration * @param <E> token type * @return option */ protected final <E extends Enum<E>> E getOption(final Class<E> typ) { final E e = getOption(args[0], typ); if(e == null) error(UNKNOWN_TRY_X, args[0]); return e; } /** * Returns the specified command option. * @param s string to be found * @param typ options enumeration * @param <E> token type * @return option */ protected static <E extends Enum<E>> E getOption(final String s, final Class<E> typ) { try { return Enum.valueOf(typ, s.toUpperCase(Locale.ENGLISH)); } catch(final Exception ex) { return null; } } /** * Closes the specified database if it is currently opened and only * pinned once. * @param ctx database context * @param db database to be closed * @return closed flag */ protected static boolean close(final Context ctx, final String db) { final boolean close = ctx.data() != null && db.equals(ctx.data().meta.name) && ctx.datas.pins(db) == 1; return close && new Close().run(ctx); } // PRIVATE METHODS ========================================================== /** * Executes the command, prints the result to the specified output stream * and returns a success flag. * @param ctx database context * @param os output stream * @return success flag. The {@link #info()} method returns information * on a potential exception */ private boolean exec(final Context ctx, final OutputStream os) { // check if data reference is available final Data data = ctx.data(); if(data == null && (flags & DATAREF) != 0) return error(NO_DB_OPENED); // check permissions if(!ctx.perm(flags & 0xFF, data != null ? data.meta : null)) { final CmdPerm[] perms = CmdPerm.values(); int i = perms.length; final int f = flags & 0xFF; while(--i >= 0 && (1 << i & f) == 0); return error(PERM_NEEDED_X, perms[i + 1]); } // check concurrency of commands final boolean ok; final boolean writing = updating(ctx); ctx.register(writing); ok = run(ctx, os); ctx.unregister(writing); return ok; } /** * Runs the command without permission, data and concurrency checks. * @param ctx database context * @param os output stream * @return result of check */ private boolean run(final Context ctx, final OutputStream os) { perf = new Performance(); context = ctx; prop = ctx.prop; mprop = ctx.mprop; out = PrintOutput.get(os); try { return run(); } catch(final ProgressException ex) { // process was interrupted by the user or server abort(); return error(INTERRUPTED); } catch(final Throwable ex) { // unexpected error Performance.gc(2); abort(); if(ex instanceof OutOfMemoryError) { Util.debug(ex); return error(OUT_OF_MEM + ((flags & (User.CREATE | User.WRITE)) != 0 ? HELP_OUT_OF_MEM : "")); } return error(Util.bug(ex)); } finally { // flushes the output try { if(out != null) out.flush(); } catch(final IOException ex) { } } } }