Page MenuHomePhorge

No OneTemporary

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,508 +1,510 @@
Copyright (C) 2016-2017 Virgo Venture, Inc.
Copyright (C) 2011-2015 Lannocc Technologies
@%@~LICENSE~@%@
+saw_020617_1 - MySql driver now ensures configs are loaded from real files beneath project root.
+
saw_020317_4 - Updated /bin/view from [W] with more features and better error-handling.
saw_020317_3 - Add 'projectKey' attribute to web-build task for isolating mysql config files of the
same name in inherited IOVAR webapps.
saw_020317_2 - Update MySql driver config namespace to http://iovar.win/ns/mysql.
saw_020317_1 - Add /bin/qtui for easily generating interfaces from QT5 .ui files!
saw_013017_5 - WebBuilder now includes mysql references in the links file.
saw_013017_4 - Updated build process for tracking dependent webapp servlet mappings.
saw_013017_3 - Fix sample.project.xml for new location of idfree jar.
saw_013017_2 - Include the dependency .url files and store them under /sys/jars/ when building the
jar.
saw_013017_1 - All JARs now removed from the repository! New Ant task 'project.update' created to
handle updating the binary dependencies. Tag 'jars_included' set to mark the
previous commit as the last one where jars were still included.
saw_012017_1 - Working on proper session propagation through local loopback.
saw_122516_1 - XArgs now supports text/xml input.
saw_122216_1 - Add /bin/print to safely and efficiently output any shell variable.
saw_120916_1 - Add /bin/date and /bin/uname commands. Fix Form2XML for CatTTamboo. Add ?replace
option to XMLSet. Fix to Default for handling canonical URLs.
saw_120616_1 - Remove some debug calls that were inadvertantly committed.
saw_112816_1 - Fix the regression in Default that prevented normal form POST data from being
propagated (bug introduced in saw_102716_1).
saw_112716_4 - Starting to solve some exit value propagation issues but this is going to take a
pretty serious rewrite to really work well.
saw_112716_3 - Fix a little bug in /bin/echo where if echo is first statement in a script it was
printing an extra space first due to bad input determination.
saw_112716_2 - Add ?graph option to the shell to display the graph of the task to be executed.
saw_112716_1 - Start to take a hard line on session exit reassignment (will be tackling this more
in depth soon).
saw_112316_3 - Beginning a data store for the resource legend stuff (not done).
saw_112316_2 - Add /bin/cookie and /bin/form from [W].
saw_112316_1 - Begin /bin/ls for listing resource entries. Only works for file: right now.
saw_112216_1 - Fix /bin/json ?clean option to not print newlines.
saw_112116_3 - Add /bin/json for grabbing a value from json data. Finally incorporated the json
parser I wrote a couple years ago in response to a challenge from Chuck Lowery.
saw_112116_2 - Clean up the distribution jar.
saw_112116_1 - Move win.iovar.web.proc.ShellSession$Instance to win.iovar.web.lib.Session and
rename ShellSession to Sessions.
saw_112016_4 - Moved win.iovar.web.proc.Job to win.iovar.web.lib package.
saw_112016_3 - Package com.iovar renamed to win.iovar to reflect domain changes. Bumped version
string to 1.0 since that's the version we're working towards (a 1.0-alpha branch
has been created for the previous changeset in case fixes need to get applied for
production services that are using the alpha version.
saw_111916_1 - Moved etc/web.xml to etc/sample.web.xml. Was working on debugging form submission
pass-through issue but this debugging has been commented out for the moment.
saw_111616_1 - Add support for ${#1}, ${#2}, ... to get the length of the anonymous arguments. MySql
device now actually returns proper column labels to allow "SELECT COUNT(*) AS X" in
list queries. Add a handlerHref() utility method to Transport that allows a query
embedded in the path (required for backwards compatibility with Thompson project).
/bin/test now internally uses BigInteger for numerical tests, allowing for very large
integer comparisons.
Interactive mode stylesheet path updated to new /usr/include/iovar location.
saw_110216_1 - A fix to frame.js.
saw_103116_4 - Pull in new common, ajax, and frame user libraries from [W].
saw_103116_3 - Move all /usr/include stuff into /usr/include/iovar.
saw_103116_2 - Trying to get Access-Control-Allow-Origin working (not there yet).
saw_103116_1 - Add 'excludeDirs' option to the WebBuilder ant task which takes
a colon-separated list of relative path names to exclude.
Add '?variables' option to XSLT.
saw_102916_1 - Add a simple Application.java that turns on auto-exec support (you'll have to add
it to web.xml and map it for a directory e.g. /app).
saw_102716_1 - WebBuilder ant task and MySql device now support configs for
multiple databases beneath /dev/mysql. ShellSession gets
setUser() and getUser(). Improvements to xmlget, xmlin, and
xmlset. XSLT TransportResolver now handles embedded
data:text/xml content-type. More changes for auto-exec.
saw_092716_1 - Pull in xmlset, xmlget, and xmlwrap commands from [W].
saw_092116_1 - First commit for Virgo Venture, changes include:
- /bin/redirect displays usable output when VIEW=text.
- /bin/system now better handles processes that output lots of data.
- /dev/trans/https added for https transport support.
- /dev/trans/file now looks for index file on POST to directory,
checks execute bit.
- /bin/test gets new test to check if input string is given type
(integer, for now).
- Add an auto-exec mode to the Default servlet handler.
saw_100115_1 - Add direct command-line syntax support to the shell alias (e.g.: /$?=echo+hello).
saw_092215_1 - #6: Some work on remote session handling. Very poor support at the moment.
saw_092115_2 - Fix to ShellSession... don't store actual Instance in HttpSession. In order to
minimize stale instances floating around we now just store the ID instead.
saw_092115_1 - Interactive servlet now supports an XML+XSL mode via 'style' property defined in
/etc/interactive.conf and also updates HISTORY variable.
saw_091915_1 - Misc. fixes and enhancements. Added the (temporary) ?preproc option to xslt. Simple
built-in support for canonicalized/keyword URLs aka canon.
saw_080315_1 - Add missing files for creating new codetable entries.
saw_072415_1 - #6: Improving the way sessions are handled when calling scripts.
saw_071715_1 - #6: Adding cookie support to HTTP transport handler.
saw_071315_2 - #5: Fix to default handler for references to directories.
Make "app.jars" the default target in build.xml.
saw_071315_1 - #2: Fixes to Resource for handling JAR-packaged files.
saw_071115_1 - #2: Moved the default local file/resource handler servlet
(com.iovar.web.dev.trans.Local$Servlet) to com.iovar.web.Default.
saw_070815_1 - Splitting off iovar-demo as a separate repository.
saw_070715_1 - Update copyright year in LICENSE file.
saw_062015_2 - Some significant reorganization in the interest of making it easier to secure sites
deployed with IOVAR. The /usr directory now only contains resources accessible to
end-users directly:
- /usr/bin moved to /bin
- /usr/sbin moved to /sbin
- views (/usr/xhtml, /usr/xml, etc.) have been moved to /srv
saw_062015_1 - Fix NullPointerException in /bin/which. Add aditional comparison tests to
/usr/bin/test.
saw_041215_1 - Incorporating many UI scripts and images, still a work-in-progress.
saw_032215_1 - Add do/while loop. Add /usr/bin/expr for evaluating arithmetic expressions. Fix to
/usr/bin/uptime.
saw_020315_5 - Allow customizable wait time for /sbin/init process.
saw_020315_4 - Add support for .ioshrc in /root/ and /home/$USER/.
saw_020315_3 - Fixes on recent ShellSession work.
saw_020315_2 - Intermediate shell sessions are now automatically removed once out of scope.
saw_020315_1 - Much work on ShellSession to properly handle user sessions.
saw_013115_1 - Add /proc/meminfo, /proc/uptime, /usr/bin/uptime, and /sbin/debug. Modify
/proc/shell to return session count when referenced as /proc/shell/.
saw_012915_1 - Create TaskData class for encapsulating the data necessary for task execution,
thereby simplifying numerous method signatures.
saw_012615_1 - Enhancements and fixes:
- User support.
- Rewritten Call exec with improved variable handling.
- Pass-through for content-disposition.
- /bin/set now exports variable to parent session, which is in the caller's scope.
- /bin/cat support for setting content-disposition.
- /bin/echo gets ?noline option.
- MySQL device improvements, including: bulk update (POST); multi-table delete;
?join, ?group, and ?limit options.
- /usr/bin/formfile gets ?quiet option; ?export renamed to ?set.
- /usr/bin/sed now requires the "g" option since it currently only operates as a
global substitution.
- /usr/bin/xlink gets ?quiet option and also allows embedding non-XML resources.
- /usr/bin/xmlout now escapes tag names, adds "tag-name-orig" attribute.
- Add /usr/bin/xmlin (also /bin/xi).
- /usr/bin/xslt passes in extra anonymous arguments as arg1, arg2,... parameters.
saw_082214_1 - More features and fixes:
- Add /bin/exists.
- Add /bin/patch.
- Add /usr/bin/formfile.
- Add /usr/bin/headers.
- Add /usr/bin/test.
- Initial support for hot quote actions (e.g. `echo foo`).
- /bin/export and /bin/set now accept a value from standard input.
- /$ execution now ignores ?iosession parameter (needs redesign).
- Add ?first option to /bin/cat to display command-line resources before standard
input.
- /bin/delete and /bin/put now pass-through any arguments.
- /bin/echo now prints literal newline instead of system default.
- Add double-dash to /bin/system command.
- MySQL device improvements.
- /usr/bin/form2xml now handles multipart/form-data.
- Add daily and specific-time modes to /usr/sbin/cron.
- Add a version of pipe() to Utils that stops when a specified sequence is found.
saw_061014_1 - Many improvements:
- /usr/bin/sed (simple stream editor), uses Java's String.replaceAll ().
- /usr/bin/java2xml and /usr/bin/xml2java (dependency on XStream).
- /sbin/cron for simple command scheduling.
- Shell session instance management slightly improved and added concept of a
'master' session.
- New (optional) init system and implementation via Init servlet (/sbin/init),
init configuration at /etc/inittab, and scripts at /etc/rc and /etc/environment.
- Add support for 'iovar.sessions', 'iovar.log.level', and 'iovar.log.file'
context parameters.
- Log and Init servlets are specified explicitly in web.xml to ensure load on
startup.
- WebBuilder Ant task adds optional support for a starting <load-on-startup> value
that all automatic servlet entries can be given incrementally.
- Http transport now supports PATCH method (dependency on Apache httpclient).
- Better handling and pass-through of content-type (still not complete).
- Misc. bug fixes.
saw_050914_1 - /bin/help.
saw_050814_10 - Transport handlers now extend HttpServlet and behave as shell utilities. This
allows pluggable handlers to work when WAR-packaged and lacking the original
symlinks.
saw_050814_9 - Clean out more old files.
saw_050814_8 - Updated INSTALL file for Google Code.
saw_050814_7 - Minor INSTALL / README file updates.
saw_050814_6 - Add LICENSE (MIT). Add sample.project.xml for Netbeans.
saw_050814_5 - Add sample.build.properties and sample.app.properties.
saw_050814_4 - Add /bin/iosh as another link to the IOVAR shell. Scripts that cannot or should not
be used in BASH now reference /bin/iosh instead of /bin/sh (or /bin/bash, etc.).
saw_050814_3 - Preparing for beta release: removing old and extra stuff.
saw_050814_2 - More build.xml fixes.
saw_050814_1 - Cleaning up build.xml. Ant 'project' task now builds a WAR (deployment not tested).
saw_050214_1 - /usr/bin/xlink now copies any additional attributes onto root node.
saw_042714_2 - XML Copy now adds prefix mappings as necessary.
saw_042714_1 - /usr/bin/xlink.
saw_042514_1 - /dev/buffer.
saw_042414_6 - Simply /dev/null.
saw_042414_5 - /bin/system will now pass other named parameters as environment variables.
saw_042414_4 - Escaped shell graph item now properly handles \n, \r, and \t. /bin/system now
passes input to the system process.
saw_042414_3 - Shell parameter handling (?x=y) now properly handles multiple assignments for the
same parameter name.
saw_042414_2 - MySql ?follow support for getList also.
saw_042414_1 - Add ?follow parameter to MySql driver for following selected columns.
saw_042114_13 - Add ?where and ?order parameters to MySql driver when retrieving a list.
/usr/bin/xmlout now (roughly) handles any input passed in and inserts the new
node(s) in the top of the document.
saw_042114_12 - Links aren't properly handled when they're pointing to folders (need to work it out
later).
saw_042114_11 - The beginnings of proper redirect handling (no longer do we allow HttpURLConnection
to follow them automatically). Right now only Call actually properly follows a
redirect on invocation.
saw_042114_10 - /bin/system now looks for colon in command arguments and interprets as a local
transport handler (still very hacky).
saw_042114_9 - Fix problem when variable assignment is the only command in an operation.
saw_042114_8 - /bin/system should send process error stream to stdout.
saw_042114_7 - /bin/sh should only try processing POST data if no anonymous args passed in.
saw_042114_6 - /bin/sh will now process POST data as though it were a shell script.
saw_042114_5 - MySql driver now supports DELETE.
saw_042114_4 - MySql driver needs to quote column names when doing insert.
saw_042114_3 - MySql driver now does insert in PUT / (instead of POST).
saw_042114_2 - Make sure table name is quoted when getting description in MySql driver.
saw_042114_1 - MySql driver now differentiates between request for directory listing (ending with
slash) and request for new item template (no slash).
saw_041914_7 - Fix problem of Background ('&') gobbling up '&&'.
saw_041914_6 - /bin/sleep.
saw_041914_5 - Basic job control.
saw_041914_4 - /bin/system - execute system call. Add file: transport support.
saw_041914_3 - MySql driver and /usr/bin/form2xml now add "tag-name-orig" attribute if the tag
name had to be modified to follow XML spec.
saw_041914_2 - Some basic column name sanitation for XML tag name in MySql driver.
saw_041914_1 - Remove the "iolink" init-parameter hack; use getServletPath () instead.
saw_041714_8 - /usr/bin/form2xml - converts standard input URLEncoded data into simple xml tree.
saw_041714_7 - /usr/bin/get now passes along any parameters. MySql and PostreSQL drivers will now
fetch an optional list of columns when requesting a list.
saw_041714_6 - /usr/bin/xslt now passes named parameters to stylesheets. Created /usr/bin/setxsl
for easily adding <?xsl-stylesheet?> instructions to XMl documents.
Scripts can now utilize standard input (passed automatically to the first
operation).
saw_041714_5 - Make sure session gets persisted when variable assignment happens.
saw_041714_4 - Variable assignments as part of an operation work again (instead of only via the
export command).
saw_041714_3 - Default session handler should also execute /etc/shrc.
saw_041714_2 - Now supporting GET requests to $/* as a convenience mechanism to POST to * and
utilize a default session.
saw_041714_1 - /usr/bin/xargs, currently handling application/x-www-form-urlencoded input.
saw_041614_1 - Added Parameter type. Http transport and task Return struct now handle content-type.
/bin/cat is the first to support named parameters with ?help and ?type= options.
saw_041514_3 - Basic implementations for mkdir, rm, rmdir, touch.
saw_041514_2 - Improve the interactive interface.
saw_041514_1 - GraphReader now supports multiple read-ahead (peek at many). Conditional tests have
been moved out of Operation and into Group where they belong, greatly simplifying
and improving logic and nested block handling.
Still some work to do with status code propagation in certain spots where it should
be automatically cleared. E.g. `cat foo || { echo bad && echo good }`.
saw_041414_4 - /bin/which.
saw_041414_3 - All binaries now implement the standard utility doHead, doGet, and doPut methods
(where applicable).
saw_041414_2 - Standard utility doGet () and doPut () look for a corresponding source file in /src
to retrieve or update; /usr/bin/cat is the first to implement this.
saw_041414_1 - Exit codes ($?) now working properly; /usr/bin/cat now sets exit value.
saw_041114_10 - Starting to imlement some basic error checking for HTTP GET. See the big note in
Cat.java for what needs to be done next for proper return code $? variable usage.
saw_041114_9 - Implemented And command ("&&"). Fixed compile error from previous commit.
saw_041114_8 - Basic status support; implemented Or command ("||"). Removed legacy task parser.
saw_041114_7 - Interactive mode now automatically sets focus to the command input.
saw_041114_6 - Echo will now output its input first before any arguments, just like Cat.
saw_041114_5 - Partial support for HTTP PATCH (append).
saw_041114_4 - Fixes to parser text handling.
saw_041114_3 - Misc./minor.
saw_041114_2 - Minor visual improvements to the interactive interface.
saw_041114_1 - Path changes (working on debussy... need to get installation-specific settings out
of source control).
saw_101613_1 - Misc. fixes incorporating new parser.
saw_101213_1 - New Graph/Task parser now working with I/O redirection and chaining, and supporting
enough features to replace the old parser.
saw_100613_2 - New Operation Graph now successfully assembles with multiple chain, input, and
output redirection, though only a single call will currently execute (work in
progress).
saw_100613_1 - New Graph/Task parser hooked up to shell (not all features implemented yet).
saw_100313_1 - Fix spelling in comments.
saw_100213_2 - New parser work.
saw_100213_1 - New parser, using Graph and Task interface model (work in progress).
saw_041013_1 - Simple PostgreSQL device (still needs work to handle updates properly).
saw_021713_1 - Simple MySQL device.
saw_021613_1 - /usr/bin/xmlout (aka /usr/bin/xo). Fix WebBuilder task to only add servlet entries
once when multiple links to the same servlet are found.
saw_012013_1 - /bin/exit and /bin/status (work in progress).
saw_011313_2 - Misc. cleanup.
saw_011313_1 - Transport handlers now installed in /dev/trans.
saw_011113_5 - Basic i/o redirection support ( < and > ).
saw_011113_4 - Improved output of /bin/set for local variables.
saw_011113_3 - /usr/bin/basename.
saw_011113_2 - Calls are now invoked with local argument variables $#, $@, $1, $2, $3, etc.
saw_011113_1 - Basic inline variable assignment (e.g. $ foo=bar ./do_something).
saw_011013_8 - Bug fixes in Utils and Local.
saw_011013_7 - /usr/bin/head and /usr/bin/tail. Everything now uses the new Resource.handler ()
approach.
saw_011013_6 - Misc. cleanup/organization.
saw_011013_5 - Convert Cat and Get to new Resource.handler () approach. Utils.pipe () will now call
out.flush ().
saw_011013_4 - Chain command implemented. Misc. bug fixes and improvements.
saw_011013_3 - XSLT processor installed at /usr/bin/xslt.
saw_011013_2 - Remove unused Java files.
saw_011013_1 - Remove Orbeon-related stuff.
saw_010913_1 - Implemented $PATH searching. Interactive mode now runs /etc/shrc to set up $PATH.
saw_010813_2 - Make sure Ant tasks are built by default.
saw_010813_1 - Parser rewritten and greatly simplified.
saw_123012_1 - Split Parser into com.iovar.web.bin.shell.input package. Added Script input type.
saw_122412_1 - Reorganize Parser.java.
saw_122312_1 - /bin/export. Also some work on resource handling and shell session reference
passing.
saw_122212_1 - Symbolic links to servlets now reference the .class file directly instead of using
the 'iojava:' prefix hack.
saw_122112_1 - Add README, INSTALL files.
saw_122012_2 - Proper /bin/cat.
saw_122012_1 - Misc. cleanup/improvements.
saw_121912_2 - Misc. cleanup.
saw_121912_1 - Now includes a basic system logger at /dev/log.
saw_121812_1 - Now depends on Java 1.7. Moved a bunch of stuff around. Implemented filesystem-based
symbolic links for mapping url resources to Java servlets.
saw_012012_2 - Some basic commands now working from the interactive prompt!
saw_012012_1 - Fix Parser bugs. Split Shell interactive move into its own servlet.
saw_102611_1 - Shell and Parser integration. Still need to exclude comment strings.
saw_102511_2 - Hashbang execution support; updating shell for new invocation methods (not complete).
saw_102511_1 - Working get, put (empty), and delete with new anonymous argument syntax (?=).
saw_060411_3 - /bin/cat.
--
--
--
saw_060411_2 - Created timescroll database.
saw_060411_1 - Cloned from usians repository.
diff --git a/src/win/iovar/ant/WebBuilder.java b/src/win/iovar/ant/WebBuilder.java
--- a/src/win/iovar/ant/WebBuilder.java
+++ b/src/win/iovar/ant/WebBuilder.java
@@ -1,452 +1,455 @@
/*
* Copyright (C) 2016-2017 Virgo Venture, Inc.
* Copyright (C) 2012-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package win.iovar.ant;
// local imports:
// java imports:
import java.io.*;
import java.nio.file.*;
import java.util.*;
// 3rd-party imports:
import org.apache.tools.ant.*;
//import org.apache.tools.ant.types.Reference;
/**
* Take a web.xml "stub" and insert servlet definitions by looking at the filesystem for symbolic
* links to .class files. The web.xml stub file should contain an XML comment with "[iovar]" to
* indicate the insertion point.
*
* @author shawn@lannocc.com
*/
public class WebBuilder extends Task
{
File src;
File dst;
File dir;
File include;
File links;
Integer startupBegin;
Integer sqlStart;
String excludeDirs;
//Reference classpathRef;
String projectKey;
public void setSrc (final File src)
{
this.src = src;
}
public void setDst (final File dst)
{
this.dst = dst;
}
public void setDir (final File dir)
{
this.dir = dir;
}
public void setInclude (final File include)
{
this.include = include;
}
public void setLinks (final File links)
{
this.links = links;
}
public void setStartupBegin (final Integer startupBegin)
{
this.startupBegin = startupBegin;
}
public void setSqlStart (final Integer sqlStart)
{
this.sqlStart = sqlStart;
}
/**
* list of directories, relative to dir (root), separated by colons, e.g.:
* "pub:tmp:var/cache"
*/
public void setExcludeDirs (final String excludeDirs)
{
this.excludeDirs = excludeDirs;
}
/**
* Classpath reference to use when checking to see if links extend Servlet class.
*/
/*
public void setClasspathRef (final Reference classpathRef)
{
this.classpathRef = classpathRef;
}
*/
/**
* A unique identifier string prepended to the mysql servlet names so we can avoid conflict when
* an inherited IOVAR application references the same /dev/mysql/ config file.
*/
public void setProjectKey (final String projectKey)
{
this.projectKey = projectKey;
}
void validateAttributes () throws BuildException, IOException
{
if (src==null) throw new BuildException ("Must specify source web.xml file with 'src' attribute");
if (! (src.exists () && src.isFile ())) throw new BuildException ("Source web.xml does not exist or is not a file: "+src.getCanonicalPath ());
if (dst==null) throw new BuildException ("Must specify destination web.xml file with 'dst' attribute");
if (dir==null) throw new BuildException ("Must specify root directory to search with 'dir' attribute");
if (! (dir.exists () && dir.isDirectory ())) throw new BuildException ("Search root does not exist or is not a directory: "+dir.getCanonicalPath ());
if (include!=null && ! (include.exists () && (include.isFile () || include.isDirectory()))) throw new BuildException ("Include file does not exist or is not a file/directory: "+include.getCanonicalPath ());
if (links!=null && links.exists () && !links.isFile ()) throw new BuildException ("Links target exists but is not a file: "+links.getCanonicalPath ());
if (startupBegin!=null && startupBegin<0) throw new BuildException ("Startup begin parameter must be positive");
if (sqlStart!=null && sqlStart<0) throw new BuildException ("sqlStart parameter must be positive");
if (projectKey==null) projectKey = "";
}
public void execute () throws BuildException
{
final int rootln = dir.getAbsolutePath ().length ();
try
{
final Map<File,String> iovar = findLinks ();
final BufferedReader in = new BufferedReader (new FileReader (src));
final BufferedWriter out = new BufferedWriter (new FileWriter (dst));
for (String line; (line = in.readLine ())!=null; )
{
if (line.contains ("[iovar]"))
{
if (include!=null)
{
if (include.isFile ())
{
includeFile (include, out);
}
else if (include.isDirectory ())
{
for (final File file : include.listFiles ())
{
includeFile (file, out);
}
}
else
{
throw new IOException ("not a file or directory: " + include);
}
}
out.write (" <!-- iovar auto-generation begin -->");
out.newLine ();
final Set<String> servlets = new HashSet<String> ();
final BufferedWriter lout;
if (links!=null) lout = new BufferedWriter (new FileWriter (links));
else lout = null;
if (iovar!=null) for (final Iterator<Map.Entry<File,String>> links = iovar.entrySet ().iterator (); links.hasNext (); )
{
final Map.Entry<File,String> entry = links.next ();
// FIXME: make this more robust
final File file = entry.getKey ().getAbsoluteFile ();
final String url = file.getPath ().substring (rootln);
boolean check = true;
String clname = entry.getValue ();
if (clname.startsWith (":"))
{
clname = clname.substring (1);
check = false;
}
//try
{
final int mysql = clname.indexOf ("dev/mysql/");
if (mysql >= 0)
{
if (! servlets.contains (clname))
{
String config = new File (file, "../" + clname).getCanonicalPath ();
+ // FIXME: store config path relative to project root
+ // (facilitates app embedded and running on different systems)
+
out.write (" <servlet>");
out.write ("<servlet-name>" + projectKey + ":" + clname + "</servlet-name>");
out.write ("<servlet-class>win.iovar.web.dev.MySql</servlet-class>");
if (sqlStart!=null)
{
out.write ("<load-on-startup>" + sqlStart + "</load-on-startup>");
sqlStart++;
}
out.write ("<init-param><param-name>config</param-name><param-value>" + config + "</param-value></init-param>");
out.write ("</servlet>");
out.newLine ();
if (lout!=null)
{
lout.write (" <servlet>");
lout.write ("<servlet-name>" + projectKey + ":" + clname + "</servlet-name>");
lout.write ("<servlet-class>win.iovar.web.dev.MySql</servlet-class>");
if (sqlStart!=null)
{
lout.write ("<load-on-startup>" + sqlStart + "</load-on-startup>");
sqlStart++;
}
lout.write ("<init-param><param-name>config</param-name><param-value>" + config + "</param-value></init-param>");
lout.write ("</servlet>");
lout.newLine ();
}
servlets.add (clname);
}
out.write (" <servlet-mapping>");
out.write ("<servlet-name>"+projectKey + ":" + clname+"</servlet-name>");
out.write ("<url-pattern>"+url+"</url-pattern>");
out.write ("</servlet-mapping>");
out.newLine ();
out.write (" <servlet-mapping>");
out.write ("<servlet-name>"+projectKey + ":" + clname+"</servlet-name>");
out.write ("<url-pattern>"+url+"/*</url-pattern>");
out.write ("</servlet-mapping>");
out.newLine ();
if (lout != null)
{
lout.write (" <servlet-mapping>");
lout.write ("<servlet-name>"+projectKey + ":" + clname+"</servlet-name>");
lout.write ("<url-pattern>"+url+"</url-pattern>");
lout.write ("</servlet-mapping>");
lout.newLine ();
lout.write (" <servlet-mapping>");
lout.write ("<servlet-name>"+projectKey + ":" + clname+"</servlet-name>");
lout.write ("<url-pattern>"+url+"/*</url-pattern>");
lout.write ("</servlet-mapping>");
lout.newLine ();
}
}
else if (!check || true /* Servlet.class.isAssignableFrom (Class.forName (clname))*/)
// It's too cumbersome to try checking for servlet inheritence here...
// because the class we're checking may not be in our current classpath.
{
if (! servlets.contains (clname))
{
out.write (" <servlet>");
out.write ("<servlet-name>"+clname+"</servlet-name>");
out.write ("<servlet-class>"+clname+"</servlet-class>");
if (startupBegin!=null)
{
out.write ("<load-on-startup>"+startupBegin+"</load-on-startup>");
startupBegin++;
}
out.write ("</servlet>");
out.newLine ();
if (lout!=null)
{
lout.write (" <servlet>");
lout.write ("<servlet-name>"+clname+"</servlet-name>");
lout.write ("<servlet-class>"+clname+"</servlet-class>");
if (startupBegin!=null) out.write ("<load-on-startup>"+startupBegin+"</load-on-startup>");
lout.write ("</servlet>");
lout.newLine ();
}
servlets.add (clname);
}
out.write (" <servlet-mapping>");
out.write ("<servlet-name>"+clname+"</servlet-name>");
out.write ("<url-pattern>"+url+"</url-pattern>");
out.write ("</servlet-mapping>");
out.newLine ();
out.write (" <servlet-mapping>");
out.write ("<servlet-name>"+clname+"</servlet-name>");
out.write ("<url-pattern>"+url+"/*</url-pattern>");
out.write ("</servlet-mapping>");
out.newLine ();
if (lout!=null)
{
lout.write (" <servlet-mapping>");
lout.write ("<servlet-name>"+clname+"</servlet-name>");
lout.write ("<url-pattern>"+url+"</url-pattern>");
lout.write ("</servlet-mapping>");
lout.newLine ();
lout.write (" <servlet-mapping>");
lout.write ("<servlet-name>"+clname+"</servlet-name>");
lout.write ("<url-pattern>"+url+"/*</url-pattern>");
lout.write ("</servlet-mapping>");
lout.newLine ();
}
}
else
{
log ("(skipping) not a servlet class: "+clname);
}
}
/*
catch (final ClassNotFoundException e)
{
log ("class not found: "+clname, Project.MSG_ERR);
}
*/
}
else
{
out.write (" <!-- no iovar links found -->");
out.newLine ();
}
out.write (" <!-- iovar auto-generation end -->");
out.newLine ();
if (lout!=null)
{
lout.close ();
}
}
else
{
out.write (line);
out.newLine ();
}
}
in.close ();
out.close ();
}
catch (final IOException e)
{
throw new BuildException ("I/O building web-app definition file", e);
}
log ("web-app definition generated at: "+dst);
}
static void includeFile (final File file, final BufferedWriter out) throws IOException
{
out.write (" <!-- iovar include begin ("+file+") -->");
out.newLine ();
final BufferedReader iin = new BufferedReader (new FileReader (file));
for (String iline; (iline = iin.readLine ())!=null; )
{
out.write (iline);
out.newLine ();
}
out.write (" <!-- iovar include end -->");
out.newLine ();
out.newLine (); }
Map<File,String> findLinks () throws IOException
{
log ("scanning for symbolic links to .class files...");
return findLinks (dir);
}
Map<File,String> findLinks (final File start) throws IOException
{
final File files[] = start.listFiles ();
if (files==null) return null;
final List<File> sorted = Arrays.asList (files);
Collections.sort (sorted);
Map<File,String> links = null;
file: for (final File file : sorted)
{
if (file.isDirectory ())
{
if (excludeDirs!=null && ! excludeDirs.isEmpty ())
{
for (final StringTokenizer excludes = new StringTokenizer (excludeDirs, ":"); excludes.hasMoreTokens (); )
{
final String exclude = excludes.nextToken ();
if (file.getCanonicalFile ().equals (new File (dir, exclude).getCanonicalFile ()))
{
// current file (directory) matches one of our exclude directories!
continue file;
}
}
}
final Map<File,String> sublinks = findLinks (file);
if (sublinks!=null)
{
if (links==null) links = sublinks;
else links.putAll (sublinks);
}
}
else
{
final Path path = file.toPath ();
if (Files.isSymbolicLink (path))
{
if (file.exists ())
{
final Path target = Files.readSymbolicLink (path);
final String name = target.toString ();
/*
if (target.getRoot ()==null && name.startsWith ("iovar:"))
{
final String clname = name.substring (7);
if (links==null) links = new HashMap<File,String> ();
links.put (file, clname);
}
*/
// FIXME: more robust path->class name translation
if (target.getRoot ()==null)
{
int idx;
if ((idx = name.indexOf ("WEB-INF/classes/")) >= 0)
{
final boolean real = name.endsWith (".class");
final String clname = (real ? "" : ":") + name.substring (idx+16, name.length () - (real ? 6 : 0)).replaceAll ("/", ".")/*.replaceAll ("\\$", ".")*/;
if (links==null) links = new HashMap<File,String> ();
links.put (file, clname);
}
else if ((idx = name.indexOf ("dev/mysql/")) >= 0)
{
if (links==null) links = new HashMap<File,String> ();
links.put (file, name);
}
else
{
log ("link to unsupported location: "+name, Project.MSG_ERR);
}
}
}
else
{
log ("broken link: "+file, Project.MSG_WARN);
}
}
}
}
return links;
}
}
diff --git a/src/win/iovar/web/bin/shell/Status.java b/src/win/iovar/web/bin/shell/Status.java
--- a/src/win/iovar/web/bin/shell/Status.java
+++ b/src/win/iovar/web/bin/shell/Status.java
@@ -1,104 +1,110 @@
/*
- * Copyright (C) 2016 Virgo Venture, Inc.
+ * Copyright (C) 2016-2017 Virgo Venture, Inc.
* Copyright (C) 2013-2014 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package win.iovar.web.bin.shell;
// local imports:
import win.iovar.web.*;
import win.iovar.web.dev.*;
// java imports:
import java.io.*;
import java.util.*;
+import javax.servlet.*;
import javax.servlet.http.*;
// 3rd-party imports:
/**
* Return HTTP status code and optional message.
*
* @author shawn@lannocc.com
*
* @see Exit
*/
public class Status extends HttpServlet
{
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doGet (this, req, resp);
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
+ public void usage (final HttpServletResponse resp) throws IOException
+ {
+ Log.debug ("displaying usage");
+
+ resp.setContentType ("text/plain");
+ final PrintWriter out = resp.getWriter ();
+
+ out.println ("usage: status <http-code> [<message>]");
+ out.println ();
+ out.println ("Sets HTTP status to the specified response code and optional message.");
+ out.println ();
+ out.println ("Options:");
+ out.println (" ?help - display this help screen");
+ }
+
/**
* Execute. Anonymous parameters expected:
* arg[0]: HTTP status code
* arg[1]: (optional) message
*/
protected void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
+ final ServletContext context = getServletContext ();
final String query = req.getQueryString ();
-
- if (query==null)
+ final Map<String,List<String>> params = Utils.getParams (query);
+
+ if (params.containsKey ("help"))
{
usage (resp);
+ Shell.exit (req, context, 0);
return;
}
-
- final Map<String,List<String>> params = Utils.getParams (query);
+
final List<String> vals = params.get (null);
-
if (vals==null || vals.size ()<1 || vals.size ()>2)
{
usage (resp);
+ Shell.exit (req, context, 1);
return;
}
try
{
final int status = Integer.parseInt (vals.get (0));
if (status < 0) set (resp, resp.SC_BAD_REQUEST, "not a valid http status code: "+status); // FIXME: throw IllegalArgumentException instead (to differentiate status here vs status passed in)
else set (resp, status, vals.size ()>1 ? vals.get (1) : null);
}
catch (final NumberFormatException e)
{
set (resp, resp.SC_BAD_REQUEST, "not a number: "+vals.get (0));
}
}
- public void usage (final HttpServletResponse resp) throws IOException
- {
- Log.debug ("displaying usage");
-
- resp.setContentType ("text/plain");
- final PrintWriter out = resp.getWriter ();
-
- out.println ("usage: status <http-code> [<message>]");
-
- Status.set (resp, resp.SC_BAD_REQUEST);
- }
-
public static void set (final HttpServletResponse resp, final int status) throws IOException
{
set (resp, status, null);
}
public static void set (final HttpServletResponse resp, final int status, final String message) throws IOException
{
Log.debug ("returning status: "+status+" - "+message);
resp.setStatus (status);
//resp.getWriter ().println (status);
if (message!=null) resp.getWriter ().println (message);
}
}
diff --git a/src/win/iovar/web/dev/MySql.java b/src/win/iovar/web/dev/MySql.java
--- a/src/win/iovar/web/dev/MySql.java
+++ b/src/win/iovar/web/dev/MySql.java
@@ -1,1146 +1,1152 @@
/*
* Copyright (C) 2016-2017 Virgo Venture, Inc.
* Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package win.iovar.web.dev;
// local imports:
import win.iovar.web.*;
import win.iovar.web.bin.shell.*;
// java imports:
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
// 3rd-party imports:
/**
* FIXME: SECURITY WARNING: NOT SAFE! Need to sanitize all inputs when building SQL statements.
*
* @author shawn@lannocc.com
*/
public class MySql extends HttpServlet
{
public static final String NAMESPACE = "http://iovar.win/ns/mysql";
static final String CONFIG_PARAM = "config";
/** @deprecated **/
static final String CONFIG_URI = "local:/etc/mysql.conf";
public static final String PARAM_XSL = "xsl";
static Map<String,Map<Config,String>> configs;
String label; // the config name for this servlet instance
enum Config
{
name ("database"),
host ("host"),
user ("user"),
pass ("password");
final String key;
Config (final String key)
{
this.key = key;
}
public String toString ()
{
return key;
}
}
public void init () throws ServletException
{
+ final ServletContext context = getServletContext ();
+
if (configs==null) try
{
Class.forName ("com.mysql.jdbc.Driver").newInstance ();
configs = new HashMap<String,Map<Config,String>> ();
}
catch (final ClassNotFoundException e)
{
throw new ServletException (e);
}
catch (final InstantiationException e)
{
throw new ServletException (e);
}
catch (final IllegalAccessException e)
{
throw new ServletException (e);
}
final String path = getInitParameter (CONFIG_PARAM);
if (path!=null)
{
- final int idx = path.indexOf ("dev/mysql/");
+ final int idx = path.indexOf ("dev/mysql/"); // FIXME: super hacky
if (idx >= 0)
{
this.label = path.substring (idx + 10);
try
{
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ();
factory.setNamespaceAware (true);
factory.setCoalescing (true);
factory.setIgnoringElementContentWhitespace (true);
final DocumentBuilder parser = factory.newDocumentBuilder ();
- final Document xml = parser.parse (new File (path)); // FIXME: using File here instead of a Transport
+ // FIXME (?):
+ // For security and portability, we require all configs to exist as real
+ // files beneath the project root. This prevents any embedded application's
+ // stored configs from getting used.
+ final Document xml = parser.parse (new File (context.getRealPath("/"), path.substring (idx)));
Node node = xml.getDocumentElement ();
final String namespace = node.getNamespaceURI ();
if (! NAMESPACE.equals (namespace))
{
throw new ServletException ("config namespace not valid: " + namespace + " (should be: "+NAMESPACE+")");
}
final String name = node.getNodeName ();
if (! "database".equals (name))
{
throw new ServletException ("expecting config root tag <database>, got: <"+name+">");
}
final Map<Config,String> config = new HashMap<Config,String> ();
for (node = node.getFirstChild (); node != null; node = node.getNextSibling ()) try
{
if (NAMESPACE.equals (node.getNamespaceURI ()))
{
config.put (Config.valueOf (node.getLocalName ()), node.getTextContent ());
}
}
catch (final IllegalArgumentException e)
{
throw new ServletException (e);
}
if (config.get (Config.host) == null) throw new ServletException ("missing config entry required: "+Config.host);
if (config.get (Config.name) == null) throw new ServletException ("missing config entry required: "+Config.name);
configs.put (label, config);
}
catch (final ParserConfigurationException e)
{
throw new ServletException (e);
}
catch (final SAXException e)
{
throw new ServletException (e);
}
catch (final IOException e)
{
throw new ServletException (e);
}
}
else
{
throw new ServletException ("unsupported config path location: " + path);
}
}
else // legacy behavior
{
if (configs.containsKey (null))
{
throw new ServletException ("only one servlet instantiation allowed at a time in legacy mode");
}
this.label = null;
- final ServletContext context = getServletContext ();
try
{
final Map<String,String> loaded = Utils.configMap (Transport.handler (CONFIG_URI, context, null).get ());
final Map<Config,String> config = new HashMap<Config,String> ();
config.put (Config.host, loaded.get (Config.host.key));
config.put (Config.name, loaded.get (Config.name.key));
config.put (Config.user, loaded.get (Config.user.key));
config.put (Config.pass, loaded.get (Config.pass.key));
if (config.get (Config.host) == null) throw new ServletException ("missing config entry required: "+Config.host);
if (config.get (Config.name) == null) throw new ServletException ("missing config entry required: "+Config.name);
configs.put (null, config);
}
catch (final IOException e)
{
throw new ServletException (e);
}
}
}
Connection getConnection () throws IOException, SQLException, ServletException
{
return getConnection (getServletContext (), label);
}
public static Connection getConnection (final ServletContext context, final String label) throws IOException, SQLException, ServletException
{
if (configs == null)
{
throw new ServletException ("servlet must be loaded first");
}
final Map<Config,String> config = configs.get (label);
if (config == null)
{
throw new ServletException ("no config matching this label: "+ label);
}
final String uri = "jdbc:mysql://" + config.get (Config.host) + "/" + config.get (Config.name);
return DriverManager.getConnection (uri, config.get (Config.user), config.get (Config.pass));
}
public void service (final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException
{
final String method = req.getMethod ();
if ("PATCH".equalsIgnoreCase (method))
{
doPatch (req, resp);
}
else
{
super.service (req, resp);
}
}
public void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
+ // FIXME: let this be a smarter check so we can use `exists` test with SQL records
Utils.doHead (this, req, resp);
}
/**
* Any anonymous arguments are taken to be column names to fetch.
*/
public void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final String path = req.getPathInfo ();
final PrintWriter out = resp.getWriter ();
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
final List<String> cols = params.get (null);
final List<String> xsl = params.get (PARAM_XSL);
final String table = new File (req.getServletPath ()).getName ();
Log.debug ("table: "+table);
try
{
final Connection conn = getConnection (); try
{
resp.setContentType ("text/xml");
out.println ("<?xml version=\"1.0\"?>");
if (xsl!=null) out.println ("<?xml-stylesheet href=\""+xsl.get (0)+"\" type=\"text/xsl\"?>");
if (path==null)
{
out.println ("<"+table+">");
for (final ResultSet desc = conn.prepareStatement ("DESC `"+table+"`").executeQuery (); desc.next (); )
{
final String name = desc.getString (1);
boolean show = true;
if (cols!=null && !cols.isEmpty ())
{
show = false;
for (final String col : cols)
{
if (name.equals (col))
{
show = true;
break;
}
}
}
if (show)
{
final String tag = Utils.tagify (name);
out.println (" <"+tag+(tag!=null ? " tag-name-orig=\""+name+"\"" : "")+"/>");
}
}
out.println ("</"+table+">");
}
else if ("/".equals (path))
{
//out.println ("<"+table+"-list xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
out.println ("<"+table+"-list>");
for (final ResultSet list = getList (conn, table, cols, params.get ("join"), params.get ("where"), params.get ("group"), params.get ("order"), params.get ("limit")); list.next (); )
{
final ResultSetMetaData meta = list.getMetaData ();
//out.println (" <"+table+" xlink:href=\"./"+list.getObject (1)+"\">");
out.println (" <"+table+">");
for (int i=1; i <= meta.getColumnCount (); i++)
{
final String name = meta.getColumnLabel (i);
final String tag = Utils.tagify (name);
final Object val = list.getObject (i);
out.print (" <"+tag+" tag-name-orig=\""+name+"\"");
if (val==null)
{
out.println ("/>");
}
else
{
final List<String> follows = params.get ("follow");
boolean follow = false;
if (follows!=null)
{
for (String s : follows)
{
if (name.equals (s))
{
follow = true;
break;
}
}
}
if (follow)
{
final ResultSet frow = getRow (conn, name, "/"+val.toString (), null);
final ResultSetMetaData fmeta = frow.getMetaData ();
if (! frow.next ())
{
Log.warn ("not found: "+val);
out.println ("/>");
}
else if (! frow.isLast ())
{
Log.warn ("multiple results: "+val);
out.println ("/>");
}
else
{
out.println (">");
for (int fi=1; fi <= fmeta.getColumnCount (); fi++)
{
final String fname = fmeta.getColumnLabel (fi);
final String ftag = Utils.tagify (fname);
final Object fval = frow.getObject (fi);
out.print (" <"+ftag+" tag-name-orig=\""+fname+"\"");
if (fval==null)
{
out.println ("/>");
}
else
{
out.println (">"+Utils.toXML (fval.toString ())+"</"+ftag+">");
}
}
out.println (" </"+tag+">");
}
}
else
{
out.println (">"+Utils.toXML (val.toString ())+"</"+tag+">");
}
}
}
out.println (" </"+table+">");
}
out.println ("</"+table+"-list>");
}
else
{
displayRow (conn, table, path, params, resp);
}
}
finally
{
conn.close ();
}
}
catch (final SQLException e)
{
Log.error (e);
throw new IOException (e);
}
/*
catch (final Exception e)
{
Log.error (e);
for (StackTraceElement s : e.getStackTrace ())
{
Log.error (s.toString ());
}
throw new IOException (e);
}
*/
}
void displayRow (final Connection conn, final String table, final String path, final Map<String,List<String>> params, final HttpServletResponse resp) throws IOException, SQLException
{
Log.info ("displaying row "+path+" for table "+table);
final PrintWriter out = resp.getWriter ();
final ResultSet row = getRow (conn, table, path, params.get ("id"));
final ResultSetMetaData meta = row.getMetaData ();
if (! row.next ())
{
Log.error ("not found: "+path);
Status.set (resp, resp.SC_NOT_FOUND, "not found: "+path);
return;
}
if (! row.isLast ())
{
Log.error ("multiple results: "+path);
Status.set (resp, resp.SC_NOT_FOUND, "multiple results: "+path);
return;
}
out.println ("<"+table+">");
for (int i=1; i <= meta.getColumnCount (); i++)
{
final String name = meta.getColumnLabel (i);
final String tag = Utils.tagify (name);
final Object val = row.getObject (i);
out.print (" <"+tag+" tag-name-orig=\""+name+"\"");
if (val==null)
{
out.println ("/>");
}
else
{
final List<String> follows = params.get ("follow");
boolean follow = false;
if (follows!=null)
{
for (String s : follows)
{
if (name.equals (s))
{
follow = true;
break;
}
}
}
if (follow)
{
final ResultSet frow = getRow (conn, name, "/"+val.toString (), null);
final ResultSetMetaData fmeta = frow.getMetaData ();
if (! frow.next ())
{
Log.warn ("not found: "+val);
out.println ("/>");
}
else if (! frow.isLast ())
{
Log.warn ("multiple results: "+val);
out.println ("/>");
}
else
{
out.println (">");
for (int fi=1; fi <= fmeta.getColumnCount (); fi++)
{
final String fname = fmeta.getColumnLabel (fi);
final String ftag = Utils.tagify (fname);
final Object fval = frow.getObject (fi);
out.print (" <"+ftag+" tag-name-orig=\""+fname+"\"");
if (fval==null)
{
out.println ("/>");
}
else
{
out.println (">"+Utils.toXML (fval.toString ())+"</"+ftag+">");
}
}
out.println (" </"+tag+">");
}
}
else
{
out.println (">"+Utils.toXML (val.toString ())+"</"+tag+">");
}
}
}
out.println ("</"+table+">");
}
/*
* FIXME: currently PUT is identical to PATCH
*/
public void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final String path = req.getPathInfo ();
if (path==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "PUT to root location (no slash) not supported");
return;
}
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
final InputStream in = req.getInputStream ();
if (in==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "expecting XML data to create/update an entry");
return;
}
final String table = new File (req.getServletPath ()).getName ();
Log.debug ("table: "+table);
try
{
final Document tree = DocumentBuilderFactory.newInstance ().newDocumentBuilder ().parse (in);
final Element root = tree.getDocumentElement ();
if (! table.equals (root.getNodeName ()))
{
throw new IllegalArgumentException ("expecting <"+table+">, got: "+root.getNodeName ());
}
final Map<String,String> data = new HashMap<String,String> ();
for (Node node = root.getFirstChild (); node!=null; node = node.getNextSibling ())
{
if (Node.ELEMENT_NODE != node.getNodeType ()) continue;
final Node child = node.getFirstChild ();
Node col = node.getAttributes ().getNamedItem ("tag-name-orig");
if (col!=null)
{
data.put (col.getNodeValue (), child!=null ? child.getNodeValue () : null);
}
else
{
data.put (node.getNodeName (), child!=null ? child.getNodeValue () : null);
}
}
final Connection conn = getConnection (); try
{
if ("/".equals (path))
{
if (params.get ("display")!=null)
{
Integer id = add (conn, table, data, true);
//resp.sendRedirect (String.valueOf (id));
resp.getWriter ().println ("<?xml version=\"1.0\"?>");
displayRow (conn, table, "/"+id, params, resp);
}
else
{
add (conn, table, data);
}
}
else
{
update (conn, table, path, params.get ("id"), data);
if (params.get ("display")!=null)
{
resp.getWriter ().println ("<?xml version=\"1.0\"?>");
displayRow (conn, table, path, params, resp);
}
}
}
finally
{
conn.close ();
}
}
catch (final ParserConfigurationException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final SAXException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final IllegalArgumentException e)
{
Log.error (e);
Status.set (resp, resp.SC_BAD_REQUEST, "bad data: "+e);
return;
}
catch (final SQLException e)
{
Log.error (e);
throw new IOException (e);
}
}
/*
* FIXME: currently PATCH is identical to PUT
*/
public void doPatch (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
Log.info ("PATCH");
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
final String path = req.getPathInfo ();
if (path==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "PATCH to root location (no slash) not supported");
return;
}
final InputStream in = req.getInputStream ();
if (in==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "expecting XML data to create/update an entry");
return;
}
final String table = new File (req.getServletPath ()).getName ();
Log.debug ("table: "+table);
try
{
//resp.getWriter ().println ("XXX: "+new BufferedReader (new InputStreamReader (in)).readLine ());
//if (true) return;
final Document tree = DocumentBuilderFactory.newInstance ().newDocumentBuilder ().parse (in);
final Element root = tree.getDocumentElement ();
if (! table.equals (root.getNodeName ()))
{
throw new IllegalArgumentException ("expecting <"+table+">, got: "+root.getNodeName ());
}
final Map<String,String> data = new HashMap<String,String> ();
for (Node node = root.getFirstChild (); node!=null; node = node.getNextSibling ())
{
if (Node.ELEMENT_NODE != node.getNodeType ()) continue;
final Node child = node.getFirstChild ();
Node col = node.getAttributes ().getNamedItem ("tag-name-orig");
if (col!=null)
{
data.put (col.getNodeValue (), child!=null ? child.getNodeValue () : null);
}
else
{
data.put (node.getNodeName (), child!=null ? child.getNodeValue () : null);
}
}
final Connection conn = getConnection (); try
{
if ("/".equals (path))
{
add (conn, table, data);
}
else
{
update (conn, table, path, params.get ("id"), data);
}
}
finally
{
conn.close ();
}
}
catch (final ParserConfigurationException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final SAXException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final IllegalArgumentException e)
{
Log.error (e);
Status.set (resp, resp.SC_BAD_REQUEST, "bad data: "+e);
return;
}
catch (final SQLException e)
{
Log.error (e);
throw new IOException (e);
}
}
public void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
Log.info ("POST");
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
final String path = req.getPathInfo ();
if (path==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "POST to root location (no slash) not supported");
return;
}
final InputStream in = req.getInputStream ();
if (in==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "expecting XML data for bulk update");
return;
}
if (! "/".equals (path))
{
Status.set (resp, resp.SC_BAD_REQUEST, "POST to slash required for bulk update");
return;
}
final String table = new File (req.getServletPath ()).getName ();
Log.debug ("table: "+table);
try
{
final Document tree = DocumentBuilderFactory.newInstance ().newDocumentBuilder ().parse (in);
final Element root = tree.getDocumentElement ();
final NodeList updates = root.getChildNodes ();
final Connection conn = getConnection (); try
{
for (int u=0; u<updates.getLength (); u++)
{
final Node update = updates.item (u);
if (! table.equals (update.getNodeName ()))
{
throw new IllegalArgumentException ("bulk update: expecting <"+table+">, got: "+update.getNodeName ());
}
final Node upath = update.getAttributes ().getNamedItem ("path");
if (upath==null)
{
throw new IllegalArgumentException ("expecting path attribute for bulk update item");
}
else if (upath.getNodeValue ()==null || upath.getNodeValue ().length ()<2 || ! upath.getNodeValue ().startsWith ("/"))
{
throw new IllegalArgumentException ("path attribute must begin with a slash");
}
final Map<String,String> data = new HashMap<String,String> ();
for (Node node = update.getFirstChild (); node!=null; node = node.getNextSibling ())
{
if (Node.ELEMENT_NODE != node.getNodeType ()) continue;
final Node child = node.getFirstChild ();
Node col = node.getAttributes ().getNamedItem ("tag-name-orig");
if (col!=null)
{
data.put (col.getNodeValue (), child!=null ? child.getNodeValue () : null);
}
else
{
data.put (node.getNodeName (), child!=null ? child.getNodeValue () : null);
}
}
update (conn, table, upath.getNodeValue (), params.get ("id"), data);
}
}
finally
{
conn.close ();
}
}
catch (final ParserConfigurationException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final SAXException e)
{
Log.error (e);
throw new ServletException (e);
}
catch (final IllegalArgumentException e)
{
Log.error (e);
Status.set (resp, resp.SC_BAD_REQUEST, "bad data: "+e);
return;
}
catch (final SQLException e)
{
Log.error (e);
throw new IOException (e);
}
}
public void doDelete (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final String path = req.getPathInfo ();
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
final List<String> xsl = params.get (PARAM_XSL);
final String table = new File (req.getServletPath ()).getName ();
Log.debug ("table: "+table);
if (path==null)
{
Status.set (resp, resp.SC_BAD_REQUEST, "DELETE to root location not supported");
return;
}
try
{
final Connection conn = getConnection (); try
{
if ("/".equals (path))
{
final StringBuilder stmt = new StringBuilder ("DELETE");
final List<String> tables = params.get (null);
if (tables != null && ! tables.isEmpty ())
{
String sep = " ";
for (final String name : tables)
{
stmt.append (sep).append ('`').append (name).append ('`');
sep = ", ";
}
}
stmt.append (" FROM `").append (table).append ("`");
final List<String> join = params.get ("join");
if (join!=null && ! join.isEmpty ())
{
for (final String clause : join)
{
stmt.append (" LEFT JOIN ").append (clause);
}
}
final List<String> where = params.get ("where");
if (where!=null && ! where.isEmpty ())
{
stmt.append (" WHERE ");
String sep = "";
for (final String clause : where)
{
stmt.append (sep).append (clause);
sep = " AND ";
}
}
Log.debug ("delete statement: "+stmt);
final int count = conn.prepareStatement (stmt.toString ()).executeUpdate ();
// FIXME: do something with delete count
}
else
{
final int count = delete (conn, table, path, params.get ("id"));
if (count!=1)
{
Log.error ("bad update count: "+count);
Status.set (resp, resp.SC_BAD_REQUEST, "bad upate count: "+count);
return;
}
}
}
finally
{
conn.close ();
}
}
catch (final SQLException e)
{
Log.error (e);
throw new IOException (e);
}
}
protected ResultSet getList (final Connection conn, final String table, final List<String> cols, final List<String> join, final List<String> where, final List<String> group, final List<String> order, final List<String> limit) throws SQLException
{
final StringBuffer stmt = new StringBuffer ("SELECT ");
if (cols!=null && !cols.isEmpty ())
{
if (cols.size ()==1 && "*".equals (cols.get (0)))
{
stmt.append ("*");
}
else
{
String sep = "";
for (final String col : cols)
{
// FIXME: this is all so hacky and brittle...
if (col.indexOf ("(")>0 || col.indexOf (" AS ")>0)
{
stmt.append (sep).append (col);
}
else
{
final int dot = col.indexOf ('.');
if (dot>0)
{
/*
stmt.append (sep).append (col);
*/
stmt.append (sep).append (col.substring(0, dot+1));
stmt.append ('`').append (col.substring (dot+1)).append ('`');
}
else
{
stmt.append (sep).append ('`').append (col).append ('`');
}
}
sep = ", ";
}
}
}
else
{
stmt.append ("id");
}
stmt.append (" FROM `").append (table).append ("`");
if (join!=null && ! join.isEmpty ())
{
for (final String clause : join)
{
stmt.append (" LEFT JOIN ").append (clause);
}
}
if (where!=null && ! where.isEmpty ())
{
stmt.append (" WHERE ");
String sep = "";
for (final String clause : where)
{
stmt.append (sep).append (clause);
sep = " AND ";
}
}
if (group!=null && ! group.isEmpty ())
{
stmt.append (" GROUP BY ");
String sep = "";
for (final String clause : group)
{
stmt.append (sep).append (clause);
sep = ", ";
}
}
if (order!=null && ! order.isEmpty ())
{
stmt.append (" ORDER BY ");
String sep = "";
for (final String clause : order)
{
stmt.append (sep).append (clause);
sep = ", ";
}
}
if (limit!=null && ! limit.isEmpty ())
{
stmt.append (" LIMIT ").append (limit.get (0));
}
//Log.fatal (stmt.toString ());
return conn.prepareStatement (stmt.toString ()).executeQuery ();
}
protected ResultSet getRow (final Connection conn, final String table, final String idPath, final List<String> idCols) throws SQLException
{
final StringBuffer query = new StringBuffer ("SELECT * FROM `"+table+"` WHERE");
String sep = " ";
if (idCols==null || idCols.isEmpty ())
{
query.append (sep).append ("id = ?");
}
else for (final String idCol : idCols)
{
query.append (sep).append (idCol).append (" = ?");
sep = ", ";
}
Log.info ("getRow ["+idPath.substring (1)+"]: "+query);
final PreparedStatement stmt = conn.prepareStatement (query.toString ());
stmt.setObject (1, idPath.substring (1));
return stmt.executeQuery ();
}
protected void add (final Connection conn, final String table, final Map<String,String> data) throws SQLException
{
add (conn, table, data, false);
}
protected Integer add (final Connection conn, final String table, final Map<String,String> data, final boolean getId) throws SQLException
{
final StringBuffer sb = new StringBuffer ("INSERT INTO `"+table+"` (");
String sep = "";
for (final String key : data.keySet ())
{
sb.append (sep).append ('`').append (key).append ("`");
sep = ",";
}
sb.append (") VALUES (");
sep = "";
for (int i=0; i < data.size (); i++)
{
sb.append (sep).append ("?");
sep = ",";
}
sb.append (")");
PreparedStatement stmt = conn.prepareStatement (sb.toString ());
int col = 0;
for (final String key : data.keySet ())
{
stmt.setObject (++col, data.get (key));
}
final int count = stmt.executeUpdate ();
if (count!=1)
{
throw new SQLException ("unexpected update count for row add: "+count);
}
if (getId)
{
// FIXME: this is dangerous and not safe for concurrency
// (should be in a single transaction with the insert)
stmt = conn.prepareStatement ("SELECT MAX(id) FROM `"+table+"`");
final ResultSet id = stmt.executeQuery ();
if (id.first () && id.isLast ())
{
return id.getInt (1);
}
else
{
throw new SQLException ("zero or multiple rows returned for MAX(id)");
}
}
else
{
return null;
}
}
protected void update (final Connection conn, final String table, final String idPath, final List<String> idCols, final Map<String,String> data) throws SQLException
{
final StringBuffer sb = new StringBuffer ("UPDATE `"+table+"` SET ");
String sep = "";
for (final String key : data.keySet ())
{
sb.append (sep).append ('`').append (key).append ("`=?");
sep = ",";
}
sep = " WHERE ";
if (idCols==null || idCols.isEmpty ())
{
sb.append (sep).append ("id = ?");
}
else for (final String idCol : idCols)
{
sb.append (sep).append (idCol).append (" = ?");
sep = " AND ";
}
final PreparedStatement stmt = conn.prepareStatement (sb.toString ());
int col = 0;
for (final String key : data.keySet ())
{
stmt.setObject (++col, data.get (key));
}
// FIXME: this doesn't handle multiple id columns
stmt.setObject (++col, idPath.substring (1));
stmt.executeUpdate ();
}
protected int delete (final Connection conn, final String table, final String idPath, final List<String> idCols) throws SQLException
{
final StringBuffer sb = new StringBuffer ("DELETE FROM `"+table+"`");
String sep = " WHERE ";
if (idCols==null || idCols.isEmpty ())
{
sb.append (sep).append ("id = ?");
}
else for (final String idCol : idCols)
{
sb.append (sep).append (idCol).append (" = ?");
sep = " AND ";
}
final PreparedStatement stmt = conn.prepareStatement (sb.toString ());
stmt.setObject (1, idPath.substring (1));
return stmt.executeUpdate();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 13, 7:14 PM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3553
Default Alt Text
(88 KB)

Event Timeline