Though it's perhaps not exactly as I'd prefer, a consensus has formed on where
to put your source and test source code for Java projects. I'm going to toe the line here because I want to use SBT to publish samskivert to the centralized Maven repositories. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2807 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by the samskivert package.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static final Logger log = Logger.getLogger("com.samskivert");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<!-- $Id$ -->
|
||||
<!-- Exports various samskivert utilities to GWT. -->
|
||||
<module>
|
||||
<source path="text"/>
|
||||
<source path="util"/>
|
||||
</module>
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* An annotation for indicating that something has a preferred implementation elsewhere, without
|
||||
* going so far as to deprecate it.
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
|
||||
public @interface ReplacedBy
|
||||
{
|
||||
/**
|
||||
* A human-readable String containing a reference to the replacement Class, method, or field.
|
||||
* It is suggested that you follow the "@see" javadoc semantics for specifying a reference.
|
||||
*/
|
||||
String value ();
|
||||
|
||||
/**
|
||||
* The reason the replacement is suggested, in case it's not obvious.
|
||||
*/
|
||||
String reason() default "";
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* The byte array out/input stream is used for writing data to a byte
|
||||
* array output stream and then obtaining an input stream that can read
|
||||
* back the data written to the output stream without first having to copy
|
||||
* it to a separate buffer.
|
||||
*/
|
||||
public class ByteArrayOutInputStream extends ByteArrayOutputStream
|
||||
{
|
||||
/**
|
||||
* Creates a new byte array out/input stream. The buffer capacity is
|
||||
* initially 32 bytes, though its size increases if necessary.
|
||||
*/
|
||||
public ByteArrayOutInputStream ()
|
||||
{
|
||||
this(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new byte array out/input stream, with a buffer capacity
|
||||
* of the specified size, in bytes.
|
||||
*
|
||||
* @param size the initial size.
|
||||
*
|
||||
* @exception IllegalArgumentException if size is negative.
|
||||
*/
|
||||
public ByteArrayOutInputStream (int size)
|
||||
{
|
||||
super(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream configured to read only the bytes that have
|
||||
* been written this far to this byte array out/input stream.
|
||||
*/
|
||||
public InputStream getInputStream ()
|
||||
{
|
||||
return new ByteArrayInputStream(buf, 0, count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Wraps a supplied {@link PrintStream} to allow capturing all data
|
||||
* written to the stream via the various <code>print()</code> and
|
||||
* <code>println()</code> variants. Derived classes must implement the
|
||||
* {@link #handlePrinted} and {@link #handleNewLine} methods.
|
||||
*/
|
||||
public abstract class ExtensiblePrintStream extends PrintStream
|
||||
{
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (PrintStream out)
|
||||
{
|
||||
super(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (PrintStream out, boolean autoFlush)
|
||||
{
|
||||
super(out, autoFlush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (
|
||||
PrintStream out, boolean autoFlush, String encoding)
|
||||
throws UnsupportedEncodingException
|
||||
{
|
||||
super(out, autoFlush, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with any text printed to the stream, excepting newlines
|
||||
* resulting from calls to the various <code>println()</code> methods,
|
||||
* which are reported via {@link #handleNewLine}.
|
||||
*/
|
||||
public abstract void handlePrinted (String s);
|
||||
|
||||
/**
|
||||
* Called whenever a newline is printed to the stream by one of the
|
||||
* various <code>println()</code> methods.
|
||||
*/
|
||||
public abstract void handleNewLine ();
|
||||
|
||||
@Override
|
||||
public void print (boolean b)
|
||||
{
|
||||
super.print(b);
|
||||
handlePrinted(b ? "true" : "false");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (char c)
|
||||
{
|
||||
super.print(c);
|
||||
handlePrinted(String.valueOf(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (int i)
|
||||
{
|
||||
super.print(i);
|
||||
handlePrinted(String.valueOf(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (long l)
|
||||
{
|
||||
super.print(l);
|
||||
handlePrinted(String.valueOf(l));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (float f)
|
||||
{
|
||||
super.print(f);
|
||||
handlePrinted(String.valueOf(f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (double d)
|
||||
{
|
||||
super.print(d);
|
||||
handlePrinted(String.valueOf(d));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (char[] s)
|
||||
{
|
||||
super.print(s);
|
||||
handlePrinted(String.valueOf(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (String s)
|
||||
{
|
||||
super.print(s);
|
||||
handlePrinted((s == null) ? "null" : s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print (Object obj)
|
||||
{
|
||||
super.print(obj);
|
||||
handlePrinted(String.valueOf(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println ()
|
||||
{
|
||||
super.println();
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (boolean x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (char x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (int x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (long x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (float x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (double x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (char[] x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (String x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println (Object x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
/**
|
||||
* A persistence exception can be thrown when an error occurs in
|
||||
* underlying persistence code. By encapsulating errors, one retains the
|
||||
* ability to make changes to the implementation structure without
|
||||
* affecting the interface to persistence services presented to the
|
||||
* application.
|
||||
*/
|
||||
public class PersistenceException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructs a persistence exception with the specified error
|
||||
* message.
|
||||
*/
|
||||
public PersistenceException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a persistence exception with the specified error message
|
||||
* and the chained causing event.
|
||||
*/
|
||||
public PersistenceException (String message, Exception cause)
|
||||
{
|
||||
super(message);
|
||||
initCause(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a persistence exception with the specified chained
|
||||
* causing event.
|
||||
*/
|
||||
public PersistenceException (Exception cause)
|
||||
{
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Convenience methods for streams.
|
||||
*/
|
||||
public class StreamUtil
|
||||
{
|
||||
/**
|
||||
* Convenient close for a stream. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (InputStream in)
|
||||
{
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing input stream", "stream", in, "cause", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient close for a stream. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (OutputStream out)
|
||||
{
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing output stream", "stream", out, "cause", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient close for a Reader. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (Reader in)
|
||||
{
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing reader", "reader", in, "cause", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient close for a Writer. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (Writer out)
|
||||
{
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing writer", "writer", out, "cause", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of the supplied input stream to the supplied output stream.
|
||||
*/
|
||||
public static <T extends OutputStream> T copy (InputStream in, T out)
|
||||
throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
for (int read = 0; (read = in.read(buffer)) > 0; ) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the supplied stream into a byte array.
|
||||
*/
|
||||
public static byte[] toByteArray (InputStream stream)
|
||||
throws IOException
|
||||
{
|
||||
return copy(stream, new ByteArrayOutputStream()).toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the supplied stream into a string using the platform default charset.
|
||||
*/
|
||||
public static String toString (InputStream stream)
|
||||
throws IOException
|
||||
{
|
||||
return copy(stream, new ByteArrayOutputStream()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the supplied stream into a string using the supplied {@link Charset}.
|
||||
*/
|
||||
public static String toString (InputStream stream, String charset)
|
||||
throws IOException
|
||||
{
|
||||
return copy(stream, new ByteArrayOutputStream()).toString(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the supplied reader into a string.
|
||||
*/
|
||||
public static String toString (Reader reader)
|
||||
throws IOException
|
||||
{
|
||||
char[] inbuf = new char[4096];
|
||||
StringBuilder outbuf = new StringBuilder();
|
||||
for (int read = 0; (read = reader.read(inbuf)) > 0; ) {
|
||||
outbuf.append(inbuf, 0, read);
|
||||
}
|
||||
return outbuf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001-2005 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Various I/O related utilities.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* An {@link Repository.Operation} wrapper that automatically closes all statements opened by the
|
||||
* operation.
|
||||
*
|
||||
* @author Charlie Groves
|
||||
*/
|
||||
public abstract class AutoCloseOperation<V>
|
||||
implements Repository.Operation<V>
|
||||
{
|
||||
/**
|
||||
* See {@link Repository.Operation#invoke}. Any Statement or PreparedStatement objects created
|
||||
* during the invocation of this method will automatically be closed.
|
||||
*/
|
||||
public abstract V cleanInvoke (Connection conn, DatabaseLiaison liason)
|
||||
throws SQLException, PersistenceException;
|
||||
|
||||
/**
|
||||
* Invokes {@link #cleanInvoke} and then closes any statements opened by that call.
|
||||
*/
|
||||
public final V invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
List<Statement> stmts = new ArrayList<Statement>(1);
|
||||
conn = JDBCUtil.makeCollector(conn, stmts);
|
||||
try {
|
||||
return cleanInvoke(conn, liaison);
|
||||
} finally {
|
||||
// if closing a statement throws an SQLException, we don't attempt to close the rest of
|
||||
// the statements -- an SQLException thrown on close is a problem with the underlying
|
||||
// connection and the SimpleRepository closes the connection when it gets an exception
|
||||
// like that; the resources for the remaining statements will be collected in that case
|
||||
for (Statement stmt : stmts) {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2006-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* A superclass to help with the shrinking subset of SQL our supported dialects can agree on,
|
||||
* or when there is disagreement, implement the most standard-compliant version and let the
|
||||
* dialectal sub-classes override.
|
||||
*/
|
||||
public abstract class BaseLiaison implements DatabaseLiaison
|
||||
{
|
||||
public BaseLiaison ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean tableExists (Connection conn, String name) throws SQLException
|
||||
{
|
||||
ResultSet rs = conn.getMetaData().getTables(null, null, name, null);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
if (name.equals(tname)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean tableContainsColumn (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = conn.getMetaData().getColumns(null, null, table, column);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean tableContainsIndex (Connection conn, String table, String index)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = conn.getMetaData().getIndexInfo(null, null, table, false, true);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String iname = rs.getString("INDEX_NAME");
|
||||
if (tname.equals(table) && index.equals(iname)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean addIndexToTable (
|
||||
Connection conn, String table, String[] columns, String ixName, boolean unique)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableContainsIndex(conn, table, ixName)) {
|
||||
return false;
|
||||
}
|
||||
ixName = (ixName != null ? ixName : StringUtil.join(columns, "_"));
|
||||
|
||||
StringBuilder update = new StringBuilder("CREATE ");
|
||||
if (unique) {
|
||||
update.append("UNIQUE ");
|
||||
}
|
||||
update.append("INDEX ").append(indexSQL(ixName)).append(" ON ").
|
||||
append(tableSQL(table)).append(" (");
|
||||
for (int ii = 0; ii < columns.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
update.append(", ");
|
||||
}
|
||||
update.append(columnSQL(columns[ii]));
|
||||
}
|
||||
update.append(")");
|
||||
|
||||
executeQuery(conn, update.toString());
|
||||
log.info("Database index '" + ixName + "' added to table '" + table + "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void addPrimaryKey (Connection conn, String table, String[] columns)
|
||||
throws SQLException
|
||||
{
|
||||
StringBuilder fields = new StringBuilder("(");
|
||||
for (int ii = 0; ii < columns.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
fields.append(", ");
|
||||
}
|
||||
fields.append(columnSQL(columns[ii]));
|
||||
}
|
||||
fields.append(")");
|
||||
String update = "ALTER TABLE " + tableSQL(table) + " ADD PRIMARY KEY " + fields.toString();
|
||||
|
||||
executeQuery(conn, update);
|
||||
log.info("Primary key " + fields + " added to table '" + table + "'");
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void dropIndex (Connection conn, String table, String index) throws SQLException
|
||||
{
|
||||
executeQuery(conn, "DROP INDEX " + columnSQL(index));
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void dropPrimaryKey (Connection conn, String table, String pkName)
|
||||
throws SQLException
|
||||
{
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) +
|
||||
" DROP CONSTRAINT " + columnSQL(pkName));
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean addColumn (
|
||||
Connection conn, String table, String column, String definition, boolean check)
|
||||
throws SQLException
|
||||
{
|
||||
if (check && tableContainsColumn(conn, table, column)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ADD COLUMN " +
|
||||
columnSQL(column) + " " + definition);
|
||||
log.info("Database column '" + column + "' added to table '" + table + "'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean addColumn (Connection conn, String table, String column,
|
||||
ColumnDefinition newColumnDef, boolean check)
|
||||
throws SQLException
|
||||
{
|
||||
if (check && tableContainsColumn(conn, table, column)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " ADD COLUMN " +
|
||||
columnSQL(column) + " " + expandDefinition(newColumnDef));
|
||||
log.info("Database column '" + column + "' added to table '" + table + "'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean changeColumn (Connection conn, String table, String column, String type,
|
||||
Boolean nullable, Boolean unique, String defaultValue)
|
||||
throws SQLException
|
||||
{
|
||||
String defStr = expandDefinition(
|
||||
type, nullable != null ? nullable : false, unique != null ? unique : false,
|
||||
defaultValue);
|
||||
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " CHANGE " +
|
||||
columnSQL(column) + " " + columnSQL(column) + " " + defStr);
|
||||
log.info("Database column '" + column + "' of table '" + table + "' modified to have " +
|
||||
"definition '" + defStr + "'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean renameColumn (Connection conn, String table, String from, String to,
|
||||
ColumnDefinition newColumnDef)
|
||||
throws SQLException
|
||||
{
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " RENAME COLUMN " +
|
||||
columnSQL(from) + " TO " + columnSQL(to));
|
||||
log.info("Renamed column '" + from + "' on table '" + table + "' to '" + to + "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean dropColumn (Connection conn, String table, String column) throws SQLException
|
||||
{
|
||||
if (!tableContainsColumn(conn, table, column)) {
|
||||
return false;
|
||||
}
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP COLUMN " + columnSQL(column));
|
||||
log.info("Database column '" + column + "' removed from table '" + table + "'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean createTableIfMissing (
|
||||
Connection conn, String table, String[] columns, ColumnDefinition[] definitions,
|
||||
String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableExists(conn, table)) {
|
||||
return false;
|
||||
}
|
||||
if (columns.length != definitions.length) {
|
||||
throw new IllegalArgumentException("Column name and definition number mismatch");
|
||||
}
|
||||
|
||||
StringBuilder builder =
|
||||
new StringBuilder("CREATE TABLE ").append(tableSQL(table)).append(" (");
|
||||
for (int ii = 0; ii < columns.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(columnSQL(columns[ii])).append(" ");
|
||||
builder.append(expandDefinition(definitions[ii]));
|
||||
}
|
||||
|
||||
if (uniqueConstraintColumns != null && uniqueConstraintColumns.length > 0) {
|
||||
for (String[] uCols : uniqueConstraintColumns) {
|
||||
builder.append(", UNIQUE (");
|
||||
for (int ii = 0; ii < uCols.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(columnSQL(uCols[ii]));
|
||||
}
|
||||
builder.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
if (primaryKeyColumns != null && primaryKeyColumns.length > 0) {
|
||||
builder.append(", PRIMARY KEY (");
|
||||
for (int ii = 0; ii < primaryKeyColumns.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(columnSQL(primaryKeyColumns[ii]));
|
||||
}
|
||||
builder.append(")");
|
||||
}
|
||||
|
||||
builder.append(")");
|
||||
|
||||
executeQuery(conn, builder.toString());
|
||||
log.info("Database table '" + table + "' created.");
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean dropTable (Connection conn, String name)
|
||||
throws SQLException
|
||||
{
|
||||
if (!tableExists(conn, name)) {
|
||||
return false;
|
||||
}
|
||||
executeQuery(conn, "DROP TABLE " + tableSQL(name));
|
||||
log.info("Table '" + name + "' dropped.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SQL string that summarizes a column definition in that format generally
|
||||
* accepted in table creation and column addition statements, e.g.
|
||||
* INTEGER UNIQUE NOT NULL DEFAULT 100
|
||||
*/
|
||||
public String expandDefinition (ColumnDefinition def)
|
||||
{
|
||||
return expandDefinition(def.type, def.nullable, def.unique, def.defaultValue);
|
||||
}
|
||||
|
||||
protected int executeQuery (Connection conn, String query)
|
||||
throws SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
return stmt.executeUpdate(query);
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
protected String expandDefinition (
|
||||
String type, boolean nullable, boolean unique, String defaultValue)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(type);
|
||||
|
||||
if (!nullable) {
|
||||
builder.append(" NOT NULL");
|
||||
}
|
||||
if (unique) {
|
||||
builder.append(" UNIQUE");
|
||||
}
|
||||
|
||||
// append the default value if one was specified
|
||||
if (defaultValue != null) {
|
||||
builder.append(" DEFAULT ").append(defaultValue);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2006-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
/**
|
||||
* An object representing the configuration of a typical SQL column.
|
||||
*/
|
||||
public class ColumnDefinition
|
||||
{
|
||||
public String type;
|
||||
public boolean nullable;
|
||||
public boolean unique;
|
||||
public String defaultValue;
|
||||
|
||||
public ColumnDefinition ()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ColumnDefinition (String type)
|
||||
{
|
||||
this(type, false, false, null);
|
||||
}
|
||||
|
||||
public ColumnDefinition (
|
||||
String type, boolean nullable, boolean unique, String defaultValue)
|
||||
{
|
||||
this.type = type;
|
||||
this.nullable = nullable;
|
||||
this.unique = unique;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* As the repository aims to interface with whatever database pooling services a project cares to
|
||||
* use, it obtains all of its database connections through a connection provider. The connection
|
||||
* provider provides connections based on a database identifier (a string) which identifies a
|
||||
* particular connection to a particular database. A user of these services would then coordinate
|
||||
* the database identifier (or identifiers) used to invoke a database operation with the database
|
||||
* connections that are returned by the connection provider that they provided to the repository at
|
||||
* construct time.
|
||||
*/
|
||||
public interface ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Obtains a database connection based on the supplied database identifier. The repository
|
||||
* expects to have exclusive use of this connection instance until it releases it. This
|
||||
* connection will be released subsequently with a call to {@link #releaseConnection} or {@link
|
||||
* #connectionFailed} depending on the circumstances of the release. <code>close()</code>
|
||||
* <em>will not</em> be called on the connection. It is up to the connection provider to close
|
||||
* the connection when it is released if appropriate.
|
||||
*
|
||||
* @param ident the database connection identifier.
|
||||
* @param readOnly whether or not the connection may be to a read-only mirror of the
|
||||
* repository.
|
||||
*
|
||||
* @return an active JDBC connection (which may have come from a connection pool).
|
||||
*
|
||||
* @exception PersistenceException thrown if a problem occurs trying to open the requested
|
||||
* connection.
|
||||
*/
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Releases a database connection when it is no longer needed by the repository.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this connection.
|
||||
* @param readOnly the same value that was passed to {@link #getConnection} to obtain this
|
||||
* connection.
|
||||
* @param conn the connection to release (back into the pool or to be closed if pooling is not
|
||||
* going on).
|
||||
*/
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn);
|
||||
|
||||
/**
|
||||
* Called by the repository if a failure occurred on the connection. It is expected that the
|
||||
* connection will be disposed of and subsequent calls to <code>getConnection</code> will
|
||||
* return a freshly established connection to the database.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this connection.
|
||||
* @param readOnly the same value that was passed to {@link #getConnection} to obtain this
|
||||
* connection.
|
||||
* @param conn the connection that failed.
|
||||
* @param error the error thrown by the connection (which may be used to determine if the
|
||||
* connection should be closed or can be reused).
|
||||
*/
|
||||
public void connectionFailed (String ident, boolean readOnly, Connection conn,
|
||||
SQLException error);
|
||||
|
||||
/**
|
||||
* Returns the URL associated with this database identifier. This should be the same value that
|
||||
* would be used if {@link #getConnection} were called.
|
||||
*/
|
||||
public String getURL (String ident);
|
||||
|
||||
/**
|
||||
* Shuts down this connection provider, closing all connections currently in the pool. This
|
||||
* should only be called once all active connections have been released with {@link
|
||||
* #releaseConnection}.
|
||||
*/
|
||||
public void shutdown ();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Provides connections using a pair of {@link DataSource} instances (one for read-only operations
|
||||
* and one for read-write operations).
|
||||
*/
|
||||
public class DataSourceConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Creates a connection provider that will obtain connections from the supplied read-only and
|
||||
* read-write sources. The <code>ident</code> mechanism is not used by this provider.
|
||||
* Responsibility for the lifecycle of the supplied datasources is left to the caller and is
|
||||
* not managed by the connection provider ({@link #shutdown} does nothing).
|
||||
*
|
||||
* @param url a URL prefix that can be used by the {@link DatabaseLiaison} to identify the type
|
||||
* of database being accessed by the supplied data sources, this should be one of "jdbc:mysql"
|
||||
* or "jdbc:postgresql" as those are the only two types of database currently supported.
|
||||
*/
|
||||
public DataSourceConnectionProvider (String url, DataSource readSource, DataSource writeSource)
|
||||
{
|
||||
_url = url;
|
||||
_readSource = readSource;
|
||||
_writeSource = writeSource;
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
try {
|
||||
Connection conn;
|
||||
if (readOnly) {
|
||||
conn = _readSource.getConnection();
|
||||
conn.setReadOnly(true);
|
||||
} else {
|
||||
conn = _writeSource.getConnection();
|
||||
}
|
||||
return conn;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
throw new PersistenceException(sqe);
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure closing connection",
|
||||
"ident", ident, "ro", readOnly, "conn", conn, e);
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
public void connectionFailed (String ident, boolean readOnly, Connection conn,
|
||||
SQLException error)
|
||||
{
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure closing failed connection",
|
||||
"ident", ident, "ro", readOnly, "conn", conn, e);
|
||||
}
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
public String getURL (String ident)
|
||||
{
|
||||
return _url;
|
||||
}
|
||||
|
||||
// from interface ConnectionProvider
|
||||
public void shutdown ()
|
||||
{
|
||||
// nothing doing, the caller has to shutdown the datasources
|
||||
}
|
||||
|
||||
protected String _url;
|
||||
protected DataSource _readSource, _writeSource;
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Despite good intentions, JDBC and SQL do not provide a unified interface to all databases. There
|
||||
* remain idiosyncrasies that must be worked around when making code interact with different
|
||||
* database servers. The database liaison encapsulates the code needed to straighten out the curves
|
||||
* and curve out the straights.
|
||||
*/
|
||||
public interface DatabaseLiaison
|
||||
{
|
||||
/**
|
||||
* Indicates whether or not this database liaison is the proper liaison for the specified
|
||||
* database URL.
|
||||
*
|
||||
* @return true if we should use this liaison for connections created with the supplied URL,
|
||||
* false if we should not.
|
||||
*/
|
||||
public boolean matchesURL (String url);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception was caused by a duplicate row being
|
||||
* inserted into a table with a unique key.
|
||||
*
|
||||
* @return true if the exception was caused by the insertion of a duplicate row, false if not.
|
||||
*/
|
||||
public boolean isDuplicateRowException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception is a transient failure, meaning one
|
||||
* that is not related to the SQL being executed, but instead to the environment at the time of
|
||||
* execution, like the connection to the database having been lost.
|
||||
*
|
||||
* @return true if the exception was thrown due to a transient failure, false if not.
|
||||
*/
|
||||
public boolean isTransientException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Attempts as dialect-agnostic an interface as possible to the ability of certain databases to
|
||||
* auto-generated numerical values for i.e. key columns; there is MySQL's AUTO_INCREMENT and
|
||||
* PostgreSQL's DEFAULT nextval(sequence), for example.
|
||||
*/
|
||||
public int lastInsertedId (Connection conn, String table, String column)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Initializes the column value auto-generator described in {@link #lastInsertedId}. This
|
||||
* should be idempotent (meaning the generator may already exist in which case this method
|
||||
* should have no negative effect like resetting it).
|
||||
*/
|
||||
public void createGenerator (Connection conn, String tableName, String columnName,
|
||||
int initialValue)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Deletes the column value auto-generator described in {@link #lastInsertedId}.
|
||||
*/
|
||||
public void deleteGenerator (Connection conn, String tableName, String columnName)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Drops the given column from the given table. Returns true or false if the database did or
|
||||
* did not report a schema modification.
|
||||
*/
|
||||
public boolean dropColumn (Connection conn, String table, String column)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Adds a named index to a table on the given columns. Returns true or false if the database
|
||||
* did or did not report a schema modification.
|
||||
*/
|
||||
public boolean addIndexToTable (
|
||||
Connection conn, String table, String[] columns, String index, boolean unique)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Drops the named index from the given table.
|
||||
*/
|
||||
public void dropIndex (Connection conn, String table, String index)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Adds a primary key to a table of the given name and on the given columns. Returns true or
|
||||
* false if the database did nor did not report a schema modification.
|
||||
*/
|
||||
public void addPrimaryKey (Connection conn, String table, String[] columns)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Deletes the primary key from a table, if it exists.
|
||||
*/
|
||||
public void dropPrimaryKey (Connection conn, String table, String pkName)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Adds a column to a table with the given definition. Tests for the previous existence of
|
||||
* the column iff 'check' is true.
|
||||
*/
|
||||
public boolean addColumn (
|
||||
Connection conn, String table, String column, String definition, boolean check)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Adds a column to a table with the given definition. Tests for the previous existence of
|
||||
* the column iff 'check' is true.
|
||||
*/
|
||||
public boolean addColumn (
|
||||
Connection conn, String table, String column, ColumnDefinition columnDef, boolean check)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Alter the definition, but not the name, of a given column on a given table. Returns true or
|
||||
* false if the database did or did not report a schema modification. Any of the nullable,
|
||||
* unique and defaultValue arguments may be null, in which case the implementation will attempt
|
||||
* to leave that aspect of the column unchanged.
|
||||
*/
|
||||
public boolean changeColumn (
|
||||
Connection conn, String table, String column, String type, Boolean nullable,
|
||||
Boolean unique, String defaultValue)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Alter the name, but not the definition, of a given column on a given table. Returns true or
|
||||
* false if the database did or did not report a schema modification.
|
||||
*
|
||||
* @param columnDef the full definition of the new column, including its new name (MySQL
|
||||
* requires this for a column rename).
|
||||
*/
|
||||
public boolean renameColumn (
|
||||
Connection conn, String table, String oldColumn, String newColumn,
|
||||
ColumnDefinition columnDef)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Created a new table of the given name with the given column names and column definitions;
|
||||
* the given set of unique constraints (or null) and the given primary key columns (or null).
|
||||
* Returns true if the table was successfully created, false if it already existed.
|
||||
*/
|
||||
public boolean createTableIfMissing (
|
||||
Connection conn, String table, String[] columns, ColumnDefinition[] declarations,
|
||||
String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Create an SQL string that summarizes a column definition in that format generally
|
||||
* accepted in table creation and column addition statements, e.g.
|
||||
* INTEGER UNIQUE NOT NULL DEFAULT 100
|
||||
*/
|
||||
public String expandDefinition (ColumnDefinition coldef);
|
||||
|
||||
/**
|
||||
* Returns true if the specified table exists and contains an index of the specified name;
|
||||
* false if either conditions does not hold true. <em>Note:</em> the names are case sensitive.
|
||||
*/
|
||||
public boolean tableContainsIndex (Connection conn, String table, String index)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns true if the specified table exists and contains a column with the specified name;
|
||||
* false if either condition does not hold true. <em>Note:</em> the names are case sensitive.
|
||||
*/
|
||||
public boolean tableContainsColumn (Connection conn, String table, String column)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns true if the table with the specified name exists, false if it does not.
|
||||
* <em>Note:</em> the table name is case sensitive.
|
||||
*/
|
||||
public boolean tableExists (Connection conn, String name) throws SQLException;
|
||||
|
||||
/**
|
||||
* Drops the given table and returns true if the table exists, else returns false.
|
||||
* <em>Note:</em> the table name is case sensitive.
|
||||
*/
|
||||
public boolean dropTable (Connection conn, String name) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the proper SQL to identify a table. Some databases require table names to be quoted.
|
||||
*/
|
||||
public String tableSQL (String table);
|
||||
|
||||
/**
|
||||
* Returns the proper SQL to identify a column. Some databases require columns names to be
|
||||
* quoted.
|
||||
*/
|
||||
public String columnSQL (String column);
|
||||
|
||||
/**
|
||||
* Returns the proper SQL to identify an index. Some databases require index names to be
|
||||
* quoted.
|
||||
*/
|
||||
public String indexSQL (String index);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* The default liaison is used if no other liaison could be matched for a particular database
|
||||
* connection. It isn't very smart or useful but we need something.
|
||||
*/
|
||||
public class DefaultLiaison extends BaseLiaison
|
||||
{
|
||||
// from DatabaseLiaison
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
|
||||
throws SQLException
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void deleteGenerator (Connection conn, String tableName, String columnName)
|
||||
throws SQLException
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public int lastInsertedId (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String columnSQL (String column)
|
||||
{
|
||||
return column;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String tableSQL (String table)
|
||||
{
|
||||
return table;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String indexSQL (String index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import com.samskivert.jdbc.ColumnDefinition;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
|
||||
/**
|
||||
* Handles liaison for HSQLDB.
|
||||
*/
|
||||
public class HsqldbLiaison extends BaseLiaison
|
||||
{
|
||||
// from DatabaseLiaison
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return url.startsWith("jdbc:hsqldb");
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String columnSQL (String column)
|
||||
{
|
||||
return "\"" + column + "\"";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String tableSQL (String table)
|
||||
{
|
||||
return "\"" + table + "\"";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String indexSQL (String index)
|
||||
{
|
||||
return "\"" + index + "\"";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void createGenerator (Connection conn, String tableName,
|
||||
String columnName, int initValue)
|
||||
throws SQLException
|
||||
{
|
||||
// HSQL's IDENTITY() does not create any database entities
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void deleteGenerator (Connection conn, String tableName, String columnName)
|
||||
throws SQLException
|
||||
{
|
||||
// HSQL's IDENTITY() does not create any database entities that we need to delete
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public int lastInsertedId (Connection conn, String table, String column) throws SQLException
|
||||
{
|
||||
// HSQL does not keep track of per-table-and-column insertion data, so we are pretty much
|
||||
// going on blind faith here that we're fetching the right ID. In the overwhelming number
|
||||
// of cases that will be so, but it's still not pretty.
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("call IDENTITY()");
|
||||
return rs.next() ? rs.getInt(1) : -1;
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
return false; // no known transient exceptions for HSQLDB
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
// Violation of unique constraint SYS_PK_51: duplicate value(s) for column(s) FOO
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && msg.indexOf("duplicate value(s)") != -1);
|
||||
}
|
||||
|
||||
// BaseLiaison's implementation of table creation accepts unique constraints both as
|
||||
// part of the column definition and as a separate argument, and merrily passes this
|
||||
// duality onto the database. Postgres and MySQL both handle this fine but HSQL seems
|
||||
// to simply not allow uniqueness in the column definitions. So, for HSQL, we transfer
|
||||
// uniqueness from the ColumnDefinitions to the uniqueConstraintColumns before we pass
|
||||
// it in to the super implementation.
|
||||
//
|
||||
// TODO: Consider making this the general MO instead of a subclass override. In fact
|
||||
// it may be that uniqueness should be removed from ColumnDefinition.
|
||||
@Override // from DatabaseLiaison
|
||||
public boolean createTableIfMissing (
|
||||
Connection conn, String table, String[] columns, ColumnDefinition[] definitions,
|
||||
String[][] uniqueConstraintColumns, String[] primaryKeyColumns)
|
||||
throws SQLException
|
||||
{
|
||||
if (columns.length != definitions.length) {
|
||||
throw new IllegalArgumentException("Column name and definition number mismatch");
|
||||
}
|
||||
|
||||
// make a set of unique constraints already provided
|
||||
Set<List<String>> uColSet = new HashSet<List<String>>();
|
||||
if (uniqueConstraintColumns != null) {
|
||||
for (String[] uCols : uniqueConstraintColumns) {
|
||||
uColSet.add(Arrays.asList(uCols));
|
||||
}
|
||||
}
|
||||
|
||||
// go through the columns and find any that are unique; these we replace with a
|
||||
// non-unique variant, and instead add a new entry to the table unique constraint
|
||||
ColumnDefinition[] newDefinitions = new ColumnDefinition[definitions.length];
|
||||
for (int ii = 0; ii < definitions.length; ii ++) {
|
||||
ColumnDefinition def = definitions[ii];
|
||||
if (def.unique) {
|
||||
// let's be nice and not mutate the caller's object
|
||||
newDefinitions[ii] = new ColumnDefinition(
|
||||
def.type, def.nullable, false, def.defaultValue);
|
||||
// if a uniqueness constraint for this column was not in the
|
||||
// uniqueConstraintColumns parameter, add such an entry
|
||||
if (!uColSet.contains(Collections.singletonList(columns[ii]))) {
|
||||
String[] newConstraint = new String[] { columns[ii] };
|
||||
uniqueConstraintColumns = (uniqueConstraintColumns == null) ?
|
||||
new String[][] { newConstraint } :
|
||||
ArrayUtil.append(uniqueConstraintColumns, newConstraint);
|
||||
}
|
||||
} else {
|
||||
newDefinitions[ii] = def;
|
||||
}
|
||||
}
|
||||
|
||||
// now call the real implementation with our modified data
|
||||
return super.createTableIfMissing(
|
||||
conn, table, columns, newDefinitions, uniqueConstraintColumns, primaryKeyColumns);
|
||||
}
|
||||
|
||||
@Override // from DatabaseLiaison
|
||||
protected String expandDefinition (
|
||||
String type, boolean nullable, boolean unique, String defaultValue)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(type);
|
||||
|
||||
// append the default value if one was specified
|
||||
if (defaultValue != null) {
|
||||
if ("IDENTITY".equals(defaultValue)) {
|
||||
// this is a blatant hack, we need this method to join Depot's SQLBuilder
|
||||
builder.append(" GENERATED BY DEFAULT AS IDENTITY (START WITH 1)");
|
||||
} else {
|
||||
builder.append(" DEFAULT ").append(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nullable) {
|
||||
builder.append(" NOT NULL");
|
||||
}
|
||||
if (unique) {
|
||||
throw new IllegalArgumentException("HSQL can't deal with column uniqueness here");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* A repository for JDBC related utility functions.
|
||||
*/
|
||||
public class JDBCUtil
|
||||
{
|
||||
/** Used for {@link #batchQuery}. */
|
||||
public interface BatchProcessor
|
||||
{
|
||||
/**
|
||||
* Called for each row returned during our batch query. Do not advance the result set,
|
||||
* simply query its values with the get methods.
|
||||
*/
|
||||
public void process (ResultSet row)
|
||||
throws SQLException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the supplied JDBC statement and gracefully handles being passed null (by doing
|
||||
* nothing).
|
||||
*/
|
||||
public static void close (Statement stmt)
|
||||
throws SQLException
|
||||
{
|
||||
if (stmt != null) {
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the supplied JDBC connection and gracefully handles being passed null (by doing
|
||||
* nothing).
|
||||
*/
|
||||
public static void close (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given connection in a proxied instance that will add all statements returned by
|
||||
* methods called on the proxy (such as {@link Connection#createStatement}) to the supplied
|
||||
* list. Thus you can create the proxy, pass the proxy to code that creates and uses statements
|
||||
* and then close any statements created by the code that operated on that Connection before
|
||||
* returning it to a pool, for example.
|
||||
*/
|
||||
public static Connection makeCollector (final Connection conn, final List<Statement> stmts)
|
||||
{
|
||||
return (Connection)Proxy.newProxyInstance(
|
||||
Connection.class.getClassLoader(), PROXY_IFACES, new InvocationHandler() {
|
||||
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
|
||||
Object result = method.invoke(conn, args);
|
||||
if (result instanceof Statement) {
|
||||
stmts.add((Statement)result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement, checking to see that it
|
||||
* returns the expected update count and throwing a persistence exception if it does not.
|
||||
*/
|
||||
public static void checkedUpdate (PreparedStatement stmt, int expectedCount)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
int modified = stmt.executeUpdate();
|
||||
if (modified != expectedCount) {
|
||||
String err = "Statement did not modify expected number of rows [stmt=" + stmt +
|
||||
", expected=" + expectedCount + ", modified=" + modified + "]";
|
||||
throw new PersistenceException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement with the supplied query,
|
||||
* checking to see that it returns the expected update count and throwing a persistence
|
||||
* exception if it does not.
|
||||
*/
|
||||
public static void checkedUpdate (
|
||||
Statement stmt, String query, int expectedCount)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
int modified = stmt.executeUpdate(query);
|
||||
if (modified != expectedCount) {
|
||||
String err = "Statement did not modify expected number of rows [stmt=" + stmt +
|
||||
", expected=" + expectedCount + ", modified=" + modified + "]";
|
||||
throw new PersistenceException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement, checking to see that it
|
||||
* returns the expected update count and logging a warning if it does not.
|
||||
*/
|
||||
public static void warnedUpdate (PreparedStatement stmt, int expectedCount)
|
||||
throws SQLException
|
||||
{
|
||||
int modified = stmt.executeUpdate();
|
||||
if (modified != expectedCount) {
|
||||
log.warning("Statement did not modify expected number of rows", "stmt", stmt,
|
||||
"expected", expectedCount, "modified", modified);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a query with a potentially large number of keys in batches. For example, you might
|
||||
* have 10,000 ids that you wish to use in an "in" clause, but don't trust the database to be
|
||||
* smart about optimizing that many keys, so instead you use batchQuery like so:
|
||||
* <pre>
|
||||
* Collection<Integer> keys = ...;
|
||||
* String query = "select NAME from USERS where USER_ID in (#KEYS)";
|
||||
* JDBCUtil.BatchProcessor proc = new JDBCUtil.BatchProcessor() {
|
||||
* public void process (ResultSet row) {
|
||||
* String name = rs.getString(1);
|
||||
* // do whatever with name
|
||||
* }
|
||||
* };
|
||||
* JDBCUtil.batchQuery(conn, query, keys, false, 500, proc);
|
||||
* </pre>
|
||||
*
|
||||
* @param query the SQL query to run for each batch with the string <code>#KEYS#</code> in the
|
||||
* place where the batch of keys should be substituted.
|
||||
* @param escapeKeys if true, {@link #escape} will be called on each key to escape any
|
||||
* dangerous characters and wrap the key in quotes.
|
||||
* @param batchSize the number of keys at a time to substitute in for <code>#KEYS#</code>.
|
||||
*/
|
||||
public static void batchQuery (Connection conn, String query, Collection<?> keys,
|
||||
boolean escapeKeys, int batchSize, BatchProcessor processor)
|
||||
throws SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
Iterator<?> itr = keys.iterator();
|
||||
while (itr.hasNext()) {
|
||||
// group one batch of keys together
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int ii = 0; ii < batchSize && itr.hasNext(); ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(",");
|
||||
}
|
||||
String key = String.valueOf(itr.next());
|
||||
buf.append(escapeKeys ? escape(key) : key);
|
||||
}
|
||||
|
||||
// issue the query with that batch
|
||||
String squery = query.replace("#KEYS#", buf.toString());
|
||||
ResultSet rs = stmt.executeQuery(squery);
|
||||
while (rs.next()) {
|
||||
processor.process(rs);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement with the supplied query,
|
||||
* checking to see that it returns the expected update count and logging a warning if it does
|
||||
* not.
|
||||
*/
|
||||
public static void warnedUpdate (
|
||||
Statement stmt, String query, int expectedCount)
|
||||
throws SQLException
|
||||
{
|
||||
int modified = stmt.executeUpdate(query);
|
||||
if (modified != expectedCount) {
|
||||
log.warning("Statement did not modify expected number of rows", "stmt", stmt,
|
||||
"expected", expectedCount, "modified", modified);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes any single quotes in the supplied text and wraps it in single quotes to make it safe
|
||||
* for embedding into a database query.
|
||||
*/
|
||||
public static String escape (String text)
|
||||
{
|
||||
text = text.replace("\\", "\\\\");
|
||||
return "'" + text.replace("'", "\\'") + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a list of values, separating the escaped values by commas. See {@link
|
||||
* #escape(String)}.
|
||||
*/
|
||||
public static String escape (Object[] values)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(escape(String.valueOf(values[ii])));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Many databases simply fail to handle Unicode text properly and this routine provides a
|
||||
* common workaround which is to represent a UTF-8 string as an ISO-8859-1 string. If you don't
|
||||
* need to use the database's collation routines, this allows you to do pretty much exactly
|
||||
* what you want at the expense of having to jigger and dejigger every goddamned string that
|
||||
* might contain multibyte characters every time you access the database. Three cheers for
|
||||
* progress!
|
||||
*/
|
||||
public static String jigger (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new String(text.getBytes("UTF8"), "8859_1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
log.warning("Jigger failed", uee);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses {@link #jigger}.
|
||||
*/
|
||||
public static String unjigger (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new String(text.getBytes("8859_1"), "UTF8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
log.warning("Unjigger failed", uee);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to jigger the specified string so that it's safe to use in a regular
|
||||
* Statement.
|
||||
*/
|
||||
public static String safeJigger (String text)
|
||||
{
|
||||
return jigger(text).replace("'", "\\'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to programatically create a database table. Does nothing if the table already exists.
|
||||
*
|
||||
* @return true if the table was created, false if it already existed.
|
||||
*/
|
||||
public static boolean createTableIfMissing (
|
||||
Connection conn, String table, String[] definition, String postamble)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableExists(conn, table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
stmt.executeUpdate("create table " + table +
|
||||
"(" + StringUtil.join(definition, ", ") + ") " + postamble);
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
|
||||
log.info("Database table '" + table + "' created.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table with the specified name exists, false if it does
|
||||
* not. <em>Note:</em> the table name is case sensitive.
|
||||
*/
|
||||
public static boolean tableExists (Connection conn, String name)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getTables("", "", name, null);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
if (name.equals(tname)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table with the specified name exists and contains a column with the
|
||||
* specified name, false if either condition does not hold true. <em>Note:</em> the names are
|
||||
* case sensitive.
|
||||
*/
|
||||
public static boolean tableContainsColumn (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the index on the specified column exists for the specified table, false if
|
||||
* it does not. Optionally you can specifiy a non null index name, and the table will be
|
||||
* checked to see if it contains that specifically named index. <em>Note:</em> the names are
|
||||
* case sensitive.
|
||||
*/
|
||||
public static boolean tableContainsIndex (
|
||||
Connection conn, String table, String column, String index)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
String iname = rs.getString("INDEX_NAME");
|
||||
if (index == null) {
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (index.equals(iname)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified table contains a primary key on the specified column.
|
||||
*/
|
||||
public static boolean tableContainsPrimaryKey (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getPrimaryKeys("", "", table);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the index for the specified column in the specified table.
|
||||
*/
|
||||
public static String getIndexName (Connection conn, String table,
|
||||
String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false, true);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
String iname = rs.getString("INDEX_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
return iname;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type (as specified in {@link java.sql.Types} for the specified column in the
|
||||
* specified table.
|
||||
*/
|
||||
public static int getColumnType (Connection conn, String table,
|
||||
String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = getColumnMetaData(conn, table, column);
|
||||
try {
|
||||
return rs.getInt("DATA_TYPE");
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the specified column accepts null values.
|
||||
*
|
||||
* @return true if the column accepts null values, false if it does not (or its nullability is
|
||||
* unknown)
|
||||
*/
|
||||
public static boolean isColumnNullable (Connection conn, String table,
|
||||
String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = getColumnMetaData(conn, table, column);
|
||||
try {
|
||||
return rs.getString("IS_NULLABLE").equals("YES");
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size for the specified column in the specified table. For char or date types
|
||||
* this is the maximum number of characters, for numeric or decimal types this is the
|
||||
* precision.
|
||||
*/
|
||||
public static int getColumnSize (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = getColumnMetaData(conn, table, column);
|
||||
try {
|
||||
return rs.getInt("COLUMN_SIZE");
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the default value for the specified column in the
|
||||
* specified table. This may be null.
|
||||
*/
|
||||
public static String getColumnDefaultValue (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = getColumnMetaData(conn, table, column);
|
||||
try {
|
||||
return rs.getString("COLUMN_DEF");
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a column (with name 'cname' and definition 'cdef') to the specified table.
|
||||
*
|
||||
* @param afterCname (optional) the name of the column after which to add the new column.
|
||||
*
|
||||
* @return true if the column was added, false if it already existed.
|
||||
*/
|
||||
public static boolean addColumn (
|
||||
Connection conn, String table, String cname, String cdef, String afterCname)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableContainsColumn(conn, table, cname)) {
|
||||
// Log.info("Database table '" + table + "' already has column '" + cname + "'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
String update = "ALTER TABLE " + table + " ADD COLUMN " + cname + " " + cdef;
|
||||
if (afterCname != null) {
|
||||
update += " AFTER " + afterCname;
|
||||
}
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
log.info("Database column '" + cname + "' added to table '" + table + "'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a column's definition. Takes a full column definition 'cdef' (including the name of
|
||||
* the column) with which to replace the specified column 'cname'.
|
||||
*
|
||||
* NOTE: A handy thing you can do with this is to rename a column by providing a column
|
||||
* definition that has a different name, but the same column type.
|
||||
*/
|
||||
public static void changeColumn (Connection conn, String table, String cname, String cdef)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " CHANGE " + cname + " " + cdef;
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
log.info("Database column '" + cname + "' of table '" + table +
|
||||
"' modified to have this def '" + cdef + "'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a column from the specified table.
|
||||
*
|
||||
* @return true if the column was dropped, false if it did not exist in the first place.
|
||||
*/
|
||||
public static boolean dropColumn (Connection conn, String table, String cname)
|
||||
throws SQLException
|
||||
{
|
||||
if (!tableContainsColumn(conn, table, cname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String update = "ALTER TABLE " + table + " DROP COLUMN " + cname;
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
log.info("Database index '" + cname + "' removed from table '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a named index from the specified table.
|
||||
*
|
||||
* @return true if the index was dropped, false if it did not exist in the first place.
|
||||
*/
|
||||
public static boolean dropIndex (Connection conn, String table, String cname, String iname)
|
||||
throws SQLException
|
||||
{
|
||||
if (!tableContainsIndex(conn, table, cname, iname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String update = "ALTER TABLE " + table + " DROP INDEX " + iname;
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
log.info("Database index '" + iname + "' removed from table '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the primary key from the specified table.
|
||||
*/
|
||||
public static void dropPrimaryKey (Connection conn, String table)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " DROP PRIMARY KEY";
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
log.info("Database primary key removed from '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an index on the specified column (cname) to the specified table. Optionally supply an
|
||||
* index name, otherwise the index is named after the column.
|
||||
*
|
||||
* @return true if the index was added, false if it already existed.
|
||||
*/
|
||||
public static boolean addIndexToTable (
|
||||
Connection conn, String table, String cname, String iname)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableContainsIndex(conn, table, cname, iname)) {
|
||||
// Log.info("Database table '" + table + "' already has an index " +
|
||||
// "on column '" + cname + "'" +
|
||||
// (iname != null ? " named '" + iname + "'." : "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
String idx_name = (iname != null ? iname : cname);
|
||||
String update = "CREATE INDEX " + idx_name + " on " + table + "(" + cname + ")";
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
log.info("Database index '" + idx_name + "' added to table '" + table + "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for {@link #getColumnType}, etc.
|
||||
*/
|
||||
protected static ResultSet getColumnMetaData (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
throw new SQLException("Table or Column not defined. [table=" + table +
|
||||
", col=" + column + "].");
|
||||
}
|
||||
|
||||
/** Used by {@link #makeCollectingConnection}. */
|
||||
protected static final Class<?>[] PROXY_IFACES = { Connection.class };
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.jora.*;
|
||||
|
||||
/**
|
||||
* The JORA repository simplifies the process of building persistence
|
||||
* services that make use of the JORA object relational mapping package.
|
||||
*
|
||||
* @see com.samskivert.jdbc.jora.Table
|
||||
*/
|
||||
public abstract class JORARepository extends SimpleRepository
|
||||
{
|
||||
/**
|
||||
* Creates and initializes a JORA repository which will access the
|
||||
* database identified by the supplied database identifier.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
* @param dbident the identifier of the database that will be accessed
|
||||
* by this repository.
|
||||
*/
|
||||
public JORARepository (ConnectionProvider provider, String dbident)
|
||||
{
|
||||
super(provider, dbident);
|
||||
|
||||
// create our tables
|
||||
createTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the supplied object into the specified table.
|
||||
*
|
||||
* @return a call to {@link DatabaseLiaison#lastInsertedId} made
|
||||
* immediately following the insert.
|
||||
*/
|
||||
protected <T> int insert (final Table<T> table, final T object)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.insert(conn, object);
|
||||
return liaison.lastInsertedId(conn, table.getName(), "TODO");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the supplied object in the specified table.
|
||||
*
|
||||
* @return the number of rows modified by the update.
|
||||
*/
|
||||
protected <T> int update (final Table<T> table, final T object)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.update(conn, object);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates fields specified by the supplied field mask in the supplied
|
||||
* object in the specified table.
|
||||
*
|
||||
* @return the number of rows modified by the update.
|
||||
*/
|
||||
protected <T> int update (final Table<T> table, final T object,
|
||||
final FieldMask mask)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.update(conn, object, mask);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all objects from the specified table that match the supplied
|
||||
* query.
|
||||
*/
|
||||
protected <T> ArrayList<T> loadAll (final Table<T> table,
|
||||
final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<ArrayList<T>>() {
|
||||
public ArrayList<T> invoke (
|
||||
Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.select(conn, query).toArrayList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all objects from the specified table that match the supplied
|
||||
* query, joining with the supplied auxiliary table(s).
|
||||
*/
|
||||
protected <T> ArrayList<T> loadAll (
|
||||
final Table<T> table, final String auxtable, final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<ArrayList<T>>() {
|
||||
public ArrayList<T> invoke (
|
||||
Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.select(conn, auxtable, query).toArrayList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all objects from the specified table that match the supplied
|
||||
* example.
|
||||
*/
|
||||
protected <T> ArrayList<T> loadAllByExample (
|
||||
final Table<T> table, final T example)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<ArrayList<T>>() {
|
||||
public ArrayList<T> invoke (
|
||||
Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(conn, example).toArrayList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all objects from the specified table that match the supplied
|
||||
* example.
|
||||
*/
|
||||
protected <T> ArrayList<T> loadAllByExample (
|
||||
final Table<T> table, final T example, final FieldMask mask)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<ArrayList<T>>() {
|
||||
public ArrayList<T> invoke (
|
||||
Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(conn, example, mask).toArrayList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied query. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected <T> T load (final Table<T> table, final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<T>() {
|
||||
public T invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.select(conn, query).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the supplied
|
||||
* query, joining with the supplied auxiliary table(s). <em>Note:</em> the
|
||||
* query should match one or zero records, not more.
|
||||
*/
|
||||
protected <T> T load (
|
||||
final Table<T> table, final String auxtable, final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<T>() {
|
||||
public T invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.select(conn, auxtable, query).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied example. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected <T> T loadByExample (final Table<T> table, final T example)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<T>() {
|
||||
public T invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(conn, example).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied example. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected <T> T loadByExample (
|
||||
final Table<T> table, final T example, final FieldMask mask)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation<T>() {
|
||||
public T invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(conn, example, mask).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* First attempts to update the supplied object and if that modifies
|
||||
* zero rows, inserts the object into the specified table. The table
|
||||
* must be configured to store items of the supplied type.
|
||||
*
|
||||
* @return -1 if the object was updated, the last inserted id if it was
|
||||
* inserted.
|
||||
*/
|
||||
protected <T> int store (final Table<T> table, final T object)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
if (table.update(conn, object) == 0) {
|
||||
table.insert(conn, object);
|
||||
return liaison.lastInsertedId(conn, table.getName(), "TODO");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified field in the supplied object (which must
|
||||
* correspond to the supplied table).
|
||||
*
|
||||
* @return the number of rows modified by the update.
|
||||
*/
|
||||
protected <T> int updateField (
|
||||
final Table<T> table, final T object, String field)
|
||||
throws PersistenceException
|
||||
{
|
||||
final FieldMask mask = table.getFieldMask();
|
||||
mask.setModified(field);
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.update(conn, object, mask);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified fields in the supplied object (which must
|
||||
* correspond to the supplied table).
|
||||
*
|
||||
* @return the number of rows modified by the update.
|
||||
*/
|
||||
protected <T> int updateFields (
|
||||
final Table<T> table, final T object, String[] fields)
|
||||
throws PersistenceException
|
||||
{
|
||||
final FieldMask mask = table.getFieldMask();
|
||||
for (int ii = 0; ii < fields.length; ii++) {
|
||||
mask.setModified(fields[ii]);
|
||||
}
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.update(conn, object, mask);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified object from the table.
|
||||
*
|
||||
* @return the number of rows deleted.
|
||||
*/
|
||||
protected <T> int delete (final Table<T> table, final T object)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.delete(conn, object);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* During construction, this function will be called to give the repository
|
||||
* implementation the opportunity to create its table objects.
|
||||
*/
|
||||
protected abstract void createTables ();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The liaison registry provides access to the appropriate database liaison implementation for a
|
||||
* particular database connection.
|
||||
*/
|
||||
public class LiaisonRegistry
|
||||
{
|
||||
/**
|
||||
* Fetch the appropriate database liaison for the supplied URL, which should be the same string
|
||||
* that would be used to configure a connection to the database.
|
||||
*/
|
||||
public static DatabaseLiaison getLiaison (String url)
|
||||
{
|
||||
// see if we already have a liaison mapped for this connection
|
||||
DatabaseLiaison liaison = _mappings.get(url);
|
||||
if (liaison == null) {
|
||||
// scan the list looking for a matching liaison
|
||||
for (DatabaseLiaison candidate : _liaisons) {
|
||||
if (candidate.matchesURL(url)) {
|
||||
liaison = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find a matching liaison, use the default
|
||||
if (liaison == null) {
|
||||
log.warning("Unable to match liaison for database. Using default.", "url", url);
|
||||
liaison = new DefaultLiaison();
|
||||
}
|
||||
|
||||
// map this URL to this liaison
|
||||
_mappings.put(url, liaison);
|
||||
}
|
||||
|
||||
return liaison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the appropriate database liaison for the supplied database connection.
|
||||
*/
|
||||
public static DatabaseLiaison getLiaison (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
return getLiaison(conn.getMetaData().getURL());
|
||||
}
|
||||
|
||||
protected static void registerLiaisonClass (Class<? extends DatabaseLiaison> lclass)
|
||||
{
|
||||
// create a new instance and stick it on our list
|
||||
try {
|
||||
_liaisons.add(lclass.newInstance());
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to instantiate liaison", "class", lclass.getName(), "error", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static ArrayList<DatabaseLiaison> _liaisons = new ArrayList<DatabaseLiaison>();
|
||||
protected static Map<String,DatabaseLiaison> _mappings = new HashMap<String,DatabaseLiaison>();
|
||||
|
||||
// register our liaison classes
|
||||
static {
|
||||
registerLiaisonClass(MySQLLiaison.class);
|
||||
registerLiaisonClass(PostgreSQLLiaison.class);
|
||||
registerLiaisonClass(HsqldbLiaison.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* A database liaison for the MySQL database.
|
||||
*/
|
||||
public class MySQLLiaison extends BaseLiaison
|
||||
{
|
||||
// from DatabaseLiaison
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return url.startsWith("jdbc:mysql");
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && msg.indexOf("Duplicate entry") != -1);
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && (msg.indexOf("Lost connection") != -1 ||
|
||||
msg.indexOf("link failure") != -1 ||
|
||||
msg.indexOf("Broken pipe") != -1));
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
|
||||
throws SQLException
|
||||
{
|
||||
// TODO: is there any way we can set the initial AUTO_INCREMENT value?
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void deleteGenerator (Connection conn, String tableName, String columnName)
|
||||
throws SQLException
|
||||
{
|
||||
// AUTO_INCREMENT does not create any database entities that we need to delete
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public int lastInsertedId (Connection conn, String table, String column) throws SQLException
|
||||
{
|
||||
// MySQL does not keep track of per-table-and-column insertion data, so we are pretty much
|
||||
// going on blind faith here that we're fetching the right ID. In the overwhelming number
|
||||
// of cases that will be so, but it's still not pretty.
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
|
||||
return rs.next() ? rs.getInt(1) : -1;
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from BaseLiaison
|
||||
public boolean addIndexToTable (
|
||||
Connection conn, String table, String[] columns, String ixName, boolean unique)
|
||||
throws SQLException
|
||||
{
|
||||
if (tableContainsIndex(conn, table, ixName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MySQL's "CREATE INDEX" is buggy, it actually changes the case of the table names you're
|
||||
// working with. Luckily (?) ALTER TABLE ADD INDEX works, so we do that here.
|
||||
StringBuilder update = new StringBuilder("ALTER TABLE ").append(table).append(" ADD ");
|
||||
if (unique) {
|
||||
update.append("UNIQUE ");
|
||||
}
|
||||
update.append("INDEX ").append(indexSQL(ixName)).append(" (");
|
||||
for (int ii = 0; ii < columns.length; ii ++) {
|
||||
if (ii > 0) {
|
||||
update.append(", ");
|
||||
}
|
||||
update.append(columnSQL(columns[ii]));
|
||||
}
|
||||
update.append(")");
|
||||
|
||||
executeQuery(conn, update.toString());
|
||||
log.info("Database index '" + ixName + "' added to table '" + table + "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override // from BaseLiaison
|
||||
public void dropIndex (Connection conn, String table, String index)
|
||||
throws SQLException
|
||||
{
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP INDEX " + columnSQL(index));
|
||||
}
|
||||
|
||||
@Override // from BaseLiaison
|
||||
public void dropPrimaryKey (Connection conn, String table, String pkName)
|
||||
throws SQLException
|
||||
{
|
||||
executeQuery(conn, "ALTER TABLE " + tableSQL(table) + " DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
@Override // from BaseLiaison
|
||||
public boolean renameColumn (Connection conn, String table, String oldColumnName,
|
||||
String newColumnName, ColumnDefinition newColumnDef)
|
||||
throws SQLException
|
||||
{
|
||||
if (!tableContainsColumn(conn, table, oldColumnName)) {
|
||||
return false;
|
||||
}
|
||||
executeQuery(conn, "ALTER TABLE " + table + " CHANGE " + oldColumnName + " " +
|
||||
newColumnName + " " + expandDefinition(newColumnDef));
|
||||
log.info("Renamed column '" + oldColumnName + "' on table '" + table + "' to '" +
|
||||
newColumnName + "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String columnSQL (String column)
|
||||
{
|
||||
return "`" + column + "`";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String tableSQL (String table)
|
||||
{
|
||||
return "`" + table + "`";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String indexSQL (String index)
|
||||
{
|
||||
return "`" + index + "`";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* A database liaison for the MySQL database.
|
||||
*/
|
||||
public class PostgreSQLLiaison extends BaseLiaison
|
||||
{
|
||||
// from DatabaseLiaison
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return url.startsWith("jdbc:postgresql");
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && msg.indexOf("duplicate key") != -1);
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
// TODO: Add more error messages here as we encounter them.
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null &&
|
||||
msg.indexOf("An I/O error occured while sending to the backend") != -1);
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public int lastInsertedId (Connection conn, String table, String column) throws SQLException
|
||||
{
|
||||
// PostgreSQL's support for auto-generated ID's comes in the form of appropriately named
|
||||
// sequences and DEFAULT nextval(sequence) modifiers in the ID columns. To get the next ID,
|
||||
// we use the currval() method which is set in a database sessions when any given sequence
|
||||
// is incremented.
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(
|
||||
"select currval('\"" + table + "_" + column + "_seq\"')");
|
||||
return rs.next() ? rs.getInt(1) :-1;
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void createGenerator (Connection conn, String tableName, String columnName, int initValue)
|
||||
throws SQLException
|
||||
{
|
||||
if (initValue == 1) {
|
||||
return; // that's the default! yay, do nothing
|
||||
}
|
||||
|
||||
String seqname = "\"" + tableName + "_" + columnName + "_seq\"";
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
stmt.executeQuery("select setval('" + seqname + "', " + initValue + ", false)");
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
log.info("Initial value of " + seqname + " set to " + initValue + ".");
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public void deleteGenerator (Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
executeQuery(conn, "drop sequence if exists \"" + table + "_" + column + "_seq\"");
|
||||
}
|
||||
|
||||
@Override // from DatabaseLiaison
|
||||
public boolean changeColumn (Connection conn, String table, String column, String type,
|
||||
Boolean nullable, Boolean unique, String defaultValue)
|
||||
throws SQLException
|
||||
{
|
||||
StringBuilder lbuf = new StringBuilder();
|
||||
if (type != null) {
|
||||
executeQuery(
|
||||
conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
|
||||
" TYPE " + type);
|
||||
lbuf.append("type=").append(type);
|
||||
}
|
||||
if (nullable != null) {
|
||||
executeQuery(
|
||||
conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
|
||||
" " + (nullable ? "DROP NOT NULL" : "SET NOT NULL"));
|
||||
if (lbuf.length() > 0) {
|
||||
lbuf.append(", ");
|
||||
}
|
||||
lbuf.append("nullable=").append(nullable);
|
||||
}
|
||||
if (unique != null) {
|
||||
// TODO: I think this requires ALTER TABLE DROP CONSTRAINT and so on
|
||||
if (lbuf.length() > 0) {
|
||||
lbuf.append(", ");
|
||||
}
|
||||
lbuf.append("unique=").append(unique).append(" (not implemented yet)");
|
||||
}
|
||||
if (defaultValue != null) {
|
||||
executeQuery(
|
||||
conn, "ALTER TABLE " + tableSQL(table) + " ALTER COLUMN " + columnSQL(column) +
|
||||
" " + (defaultValue.length() > 0 ? "SET DEFAULT " + defaultValue : "DROP DEFAULT"));
|
||||
if (lbuf.length() > 0) {
|
||||
lbuf.append(", ");
|
||||
}
|
||||
lbuf.append("defaultValue=").append(defaultValue);
|
||||
}
|
||||
log.info("Database column '" + column + "' of table '" + table + "' modified to have " +
|
||||
"definition [" + lbuf + "].");
|
||||
return true;
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String columnSQL (String column)
|
||||
{
|
||||
return "\"" + column + "\"";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String tableSQL (String table)
|
||||
{
|
||||
return "\"" + table + "\"";
|
||||
}
|
||||
|
||||
// from DatabaseLiaison
|
||||
public String indexSQL (String index)
|
||||
{
|
||||
return "\"" + index + "\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* The repository class provides basic functionality upon which to build
|
||||
* an interface to a repository of information stored in a database (a
|
||||
* table or set of tables) that is accessed via JDBC.
|
||||
*
|
||||
* <p> It is expected that the repository class will encapsulate all
|
||||
* database access for a particular table or set of tables. The interface
|
||||
* provided to the rest of the application will involve only the
|
||||
* application object model. For example:
|
||||
*
|
||||
* <pre>
|
||||
* public class PeopleRepository extends SimpleRepository
|
||||
* {
|
||||
* public Person getPerson (int personid);
|
||||
* public Person[] getPeopleByFirstName (String firstName);
|
||||
* public void updatePerson (Person person);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p> It is probably also desirable to catch SQL exceptions and wrap them
|
||||
* in the <code>PersistenceException</code> class that is provided by this
|
||||
* package.
|
||||
*
|
||||
* <p> The repository comes in a few flavors depending on the needs of the
|
||||
* persistence services being developed:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link SimpleRepository}: The simple repository is used by services
|
||||
* that need access to a single JDBC connection to perform their database
|
||||
* operations.</li>
|
||||
*
|
||||
* <li>{@link JORARepository}: JORA repository is used by services that
|
||||
* wish to make use of the JORA Java/RDBMS interoperability package.</li>
|
||||
*
|
||||
* <li> Because the repository provides a unified interface to a
|
||||
* particular persistence service, it is conceivable that it would need to
|
||||
* talk to multiple databases to provide those services. Presently, the
|
||||
* repository only supports a single connection, but if the need arose,
|
||||
* implementing a repository flavor that supported multiple connections
|
||||
* would be the proper solution.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class Repository
|
||||
{
|
||||
/**
|
||||
* Creates and initializes the repository.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
*/
|
||||
public Repository (ConnectionProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database operations should be encapsulated in instances of this
|
||||
* class and then provided to the repository for invocation. This
|
||||
* allows the repository to manage transaction commits for you as well
|
||||
* as for it to automatically retry an operation if the connection
|
||||
* failed for some transient reason.
|
||||
*/
|
||||
public interface Operation<V>
|
||||
{
|
||||
/**
|
||||
* Invokes code that performs one or more database operations, all
|
||||
* of which will be encapsulated in a single transaction (which
|
||||
* can be retried in the event of a transient failure).
|
||||
*
|
||||
* @param conn the database connection on which the operations
|
||||
* will be performed.
|
||||
* @param liaison a database liaison for the supplied connection
|
||||
* which can be used to determine things for which there is no
|
||||
* standard way to determine via JDBC.
|
||||
*
|
||||
* @exception SQLException if thrown, this will be wrapped in a
|
||||
* {@link PersistenceException} before being passed up to the
|
||||
* operation invoker.
|
||||
* @exception PersistenceException can be thrown if something goes
|
||||
* awry when executing the operation. Note that the operation will
|
||||
* not be retried if a persistence exception is thrown. Such
|
||||
* exceptions are assumed to be application specific and not
|
||||
* indicative of a basic JDBC failure. The transaction
|
||||
* <em>will</em> be rolled back in such cases, however.
|
||||
*/
|
||||
public V invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException;
|
||||
}
|
||||
|
||||
/** Our database connection provider. */
|
||||
protected ConnectionProvider _provider;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
/**
|
||||
* Extends the {@link RepositoryUnit} and integrates with a {@link ResultListener}.
|
||||
*/
|
||||
public abstract class RepositoryListenerUnit<T> extends RepositoryUnit
|
||||
{
|
||||
/**
|
||||
* Creates a repository listener unit that will report its results to the supplied result
|
||||
* listener.
|
||||
*/
|
||||
public RepositoryListenerUnit (ResultListener<T> listener)
|
||||
{
|
||||
super(String.valueOf(listener));
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a repository listener unit that will report its results to the supplied result
|
||||
* listener and report the supplied name in {@link #toString}.
|
||||
*/
|
||||
public RepositoryListenerUnit (String name, ResultListener<T> listener)
|
||||
{
|
||||
super(name);
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to perform our persistent action and generate our result.
|
||||
*/
|
||||
public abstract T invokePersistResult ()
|
||||
throws Exception;
|
||||
|
||||
@Override // from RepositoryUnit
|
||||
public void invokePersist ()
|
||||
throws Exception
|
||||
{
|
||||
_result = invokePersistResult();
|
||||
}
|
||||
|
||||
@Override // from RepositoryUnit
|
||||
public void handleSuccess ()
|
||||
{
|
||||
_listener.requestCompleted(_result);
|
||||
}
|
||||
|
||||
@Override // from RepositoryUnit
|
||||
public void handleFailure (Exception pe)
|
||||
{
|
||||
_listener.requestFailed(pe);
|
||||
}
|
||||
|
||||
protected ResultListener<T> _listener;
|
||||
protected T _result;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
/**
|
||||
* A specialized invoker unit that does one or more database operations and then sends the results
|
||||
* back to the event processing thread. Override {@link #handleSuccess} to process your result on
|
||||
* the event thread and {@link #handleFailure} to handle a failure on the event thread. For
|
||||
* example:
|
||||
*
|
||||
* <pre>
|
||||
* final int userId = 123;
|
||||
* _invoker.postUnit(new RepositoryUnit("loadUser") {
|
||||
* public void invokePersist () throws Exception {
|
||||
* _user = _userRepo.loadUser(userId);
|
||||
* }
|
||||
* public void handleSuccess () {
|
||||
* // do something with _user
|
||||
* }
|
||||
* public void handleFailure (Exception cause) {
|
||||
* // report failure to load user
|
||||
* }
|
||||
* protected UserRecord _user;
|
||||
* });
|
||||
* </pre>
|
||||
*/
|
||||
public abstract class RepositoryUnit extends WriteOnlyUnit
|
||||
{
|
||||
/**
|
||||
* Create a RepositoryUnit which will report the supplied name in {@link #toString}.
|
||||
*/
|
||||
public RepositoryUnit (String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override // from WriteOnlyUnit
|
||||
public boolean invoke ()
|
||||
{
|
||||
try {
|
||||
invokePersist();
|
||||
} catch (Exception pe) {
|
||||
_error = pe;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override // from WriteOnlyUnit
|
||||
public void handleResult ()
|
||||
{
|
||||
if (_error != null) {
|
||||
handleFailure(_error);
|
||||
} else {
|
||||
handleSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if our persistent actions have succeeded, back on the non-invoker thread.
|
||||
*/
|
||||
public abstract void handleSuccess ();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
|
||||
/**
|
||||
* A RepositoryUnit that returns a single result from its database operations and operates on that.
|
||||
*/
|
||||
public abstract class ResultUnit<T> extends RepositoryUnit
|
||||
{
|
||||
public ResultUnit (String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs actions on the database and returns exciting data.
|
||||
*/
|
||||
public abstract T computeResult ()
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Operates on the result from <code>computeResult</code> back on the main thread, if
|
||||
* <code>computeResult</code> succeeded.
|
||||
*/
|
||||
public abstract void handleResult (T result);
|
||||
|
||||
@Override
|
||||
public void handleSuccess ()
|
||||
{
|
||||
handleResult(_result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokePersist ()
|
||||
throws Exception
|
||||
{
|
||||
_result = computeResult();
|
||||
}
|
||||
|
||||
protected T _result;
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The simple repository should be used for a repository that only needs access to a single JDBC
|
||||
* connection instance to perform its persistence services.
|
||||
*/
|
||||
public class SimpleRepository extends Repository
|
||||
{
|
||||
/** See {@link #setExecutePreCondition}. */
|
||||
public static interface PreCondition
|
||||
{
|
||||
/** See {@link #setExecutePreCondition}. */
|
||||
public boolean validate (String dbident, Operation<?> op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an operation that will be invoked prior to the execution of every database
|
||||
* operation to validate whether some pre-condition is met. This mainly exists for systems that
|
||||
* wish to ensure that all database operations take place on a particular thread (or not on a
|
||||
* particular thread) as database operations are generally slow and blocking.
|
||||
*/
|
||||
public static void setExecutePreCondition (PreCondition condition)
|
||||
{
|
||||
_precond = condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a simple repository which will access the database identified by the
|
||||
* supplied database identifier.
|
||||
*
|
||||
* @param provider the connection provider which will be used to obtain our database
|
||||
* connection.
|
||||
* @param dbident the identifier of the database that will be accessed by this repository or
|
||||
* null if the derived class will call {@link #configureDatabaseIdent} by hand later.
|
||||
*/
|
||||
public SimpleRepository (ConnectionProvider provider, String dbident)
|
||||
{
|
||||
super(provider);
|
||||
|
||||
if (dbident != null) {
|
||||
configureDatabaseIdent(dbident);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called automatically if a dbident is provided at construct time, but a derived class
|
||||
* can pass null to its constructor and then call this method itself later if it wishes to
|
||||
* obtain its database identifier from an overridable method which could not otherwise be
|
||||
* called at construct time.
|
||||
*/
|
||||
protected void configureDatabaseIdent (String dbident)
|
||||
{
|
||||
_dbident = dbident;
|
||||
|
||||
// give the repository a chance to do any schema migration before things get further
|
||||
// underway
|
||||
try {
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
migrateSchema(conn, liaison);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Failure migrating schema", "dbident", _dbident, pe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied read-only operation. In the event of a transient failure, the
|
||||
* repository will attempt to reestablish the database connection and try the operation again.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected <V> V execute (Operation<V> op)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(op, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied read-write operation. In the event of a transient failure, the
|
||||
* repository will attempt to reestablish the database connection and try the operation again.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected <V> V executeUpdate (Operation<V> op)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(op, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied operation followed by a call to <code>commit()</code> on the
|
||||
* connection unless a <code>PersistenceException</code> or runtime error occurs, in which case
|
||||
* a call to <code>rollback()</code> is executed on the connection.
|
||||
*
|
||||
* @param retryOnTransientFailure if true and the operation fails due to a transient failure
|
||||
* (like losing the connection to the database or deadlock detection), the connection to the
|
||||
* database will be reestablished (if necessary) and the operation attempted once more.
|
||||
* @param readOnly whether or not to request a read-only connection.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected <V> V execute (Operation<V> op, boolean retryOnTransientFailure, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
Connection conn = null;
|
||||
DatabaseLiaison liaison = null;
|
||||
V rv = null;
|
||||
boolean supportsTransactions = false;
|
||||
boolean attemptedOperation = false;
|
||||
Boolean oldAutoCommit = null;
|
||||
|
||||
// check our pre-condition
|
||||
if (_precond != null && !_precond.validate(_dbident, op)) {
|
||||
log.warning("Repository operation failed pre-condition check!", "dbident", _dbident,
|
||||
"op", op, new Exception());
|
||||
}
|
||||
|
||||
// obtain our database connection and associated goodies
|
||||
conn = _provider.getConnection(_dbident, readOnly);
|
||||
|
||||
// make sure that no one else performs a database operation using the same connection until
|
||||
// we're done
|
||||
synchronized (conn) {
|
||||
try {
|
||||
liaison = LiaisonRegistry.getLiaison(conn);
|
||||
|
||||
// find out if we support transactions
|
||||
DatabaseMetaData dmd = conn.getMetaData();
|
||||
if (dmd != null) {
|
||||
supportsTransactions = dmd.supportsTransactions();
|
||||
}
|
||||
|
||||
// turn off auto-commit
|
||||
if (supportsTransactions && conn.getAutoCommit()) {
|
||||
oldAutoCommit = conn.getAutoCommit();
|
||||
conn.setAutoCommit(false);
|
||||
}
|
||||
|
||||
// let derived classes do any got-connection processing
|
||||
gotConnection(conn);
|
||||
|
||||
// invoke the operation
|
||||
attemptedOperation = true;
|
||||
rv = op.invoke(conn, liaison);
|
||||
|
||||
// commit the transaction
|
||||
if (supportsTransactions) {
|
||||
conn.commit();
|
||||
}
|
||||
|
||||
// return the operation result
|
||||
return rv;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (attemptedOperation) {
|
||||
// back out our changes if something got hosed (but not if the hosage was a
|
||||
// result of losing our connection)
|
||||
try {
|
||||
if (supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
log.warning("Unable to roll back operation", "err", sqe, "rberr", rbe);
|
||||
}
|
||||
}
|
||||
|
||||
if (conn != null) {
|
||||
// let the provider know that the connection failed
|
||||
_provider.connectionFailed(_dbident, readOnly, conn, sqe);
|
||||
// clear out the reference so that we don't release it later
|
||||
conn = null;
|
||||
}
|
||||
|
||||
if (!retryOnTransientFailure || liaison == null ||
|
||||
!liaison.isTransientException(sqe)) {
|
||||
String err = "Operation invocation failed";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
|
||||
// the MySQL JDBC driver has the annoying habit of including the embedded exception
|
||||
// stack trace in the message of their outer exception; if I want a fucking stack
|
||||
// trace, I'll call printStackTrace() thanksverymuch
|
||||
String msg = StringUtil.split("" + sqe, "\n")[0];
|
||||
log.info("Transient failure executing operation, retrying", "error", msg);
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
// back out our changes if something got hosed
|
||||
try {
|
||||
if (supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
log.warning("Unable to roll back operation", "origerr", pe, "rberr", rbe);
|
||||
}
|
||||
throw pe;
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
// back out our changes if something got hosed
|
||||
try {
|
||||
if (supportsTransactions && conn != null && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
log.warning("Unable to roll back operation", "origerr", rte, "rberr", rbe);
|
||||
}
|
||||
throw rte;
|
||||
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
try {
|
||||
// restore our auto-commit settings
|
||||
if (oldAutoCommit != null && !conn.isClosed()) {
|
||||
conn.setAutoCommit(oldAutoCommit);
|
||||
}
|
||||
} catch (SQLException sace) {
|
||||
log.warning("Unable to restore auto-commit", "err", sace);
|
||||
}
|
||||
// release the database connection
|
||||
_provider.releaseConnection(_dbident, readOnly, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we'll only fall through here if the above code failed due to a transient exception (the
|
||||
// connection was closed for being idle, for example) and we've been asked to retry; so
|
||||
// let's do so
|
||||
return execute(op, false, readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, returning the number of rows
|
||||
* modified.
|
||||
*/
|
||||
protected int update (final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
return stmt.executeUpdate(query);
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, throwing an exception if the
|
||||
* modification count is not equal to the specified count.
|
||||
*/
|
||||
protected void checkedUpdate (final String query, final int count)
|
||||
throws PersistenceException
|
||||
{
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
JDBCUtil.checkedUpdate(stmt, query, 1);
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, logging a warning if the modification
|
||||
* count is not equal to the specified count.
|
||||
*/
|
||||
protected void warnedUpdate (final String query, final int count)
|
||||
throws PersistenceException
|
||||
{
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
JDBCUtil.warnedUpdate(stmt, query, 1);
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs MySQL to perform table maintenance on the specified table.
|
||||
*
|
||||
* @param action <code>analyze</code> recomputes the distribution of the keys for the specified
|
||||
* table. This can help certain joins to be performed more efficiently. <code>optimize</code>
|
||||
* instructs MySQL to coalesce fragmented records and reclaim space left by deleted records.
|
||||
* This can improve a tables efficiency but can take a long time to run on large tables.
|
||||
*/
|
||||
protected void maintenance (final String action, final String table)
|
||||
throws PersistenceException
|
||||
{
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(action + " table " + table);
|
||||
while (rs.next()) {
|
||||
String result = rs.getString("Msg_text");
|
||||
if (result == null ||
|
||||
(result.indexOf("up to date") == -1 && !result.equals("OK"))) {
|
||||
log.info("Table maintenance [" + SimpleRepository.toString(rs) + "].");
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this method and perform any schema migration they might need
|
||||
* (using the idempotent {@link JDBCUtil} schema migration methods). This is called during the
|
||||
* repository's constructor and will thus take place before derived classes (like the {@link
|
||||
* JORARepository} introspect on the schema to match it up to associated Java classes).
|
||||
*/
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we fetch a connection from the provider. This gives derived classes an
|
||||
* opportunity to configure whatever internals they might be using with the connection that was
|
||||
* fetched.
|
||||
*/
|
||||
protected void gotConnection (Connection conn)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a row of a result set to a string, prepending each column with the column name from
|
||||
* the result set metadata.
|
||||
*/
|
||||
protected static String toString (ResultSet rs)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSetMetaData md = rs.getMetaData();
|
||||
int ccount = md.getColumnCount();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int ii = 1; ii <= ccount; ii++) {
|
||||
if (buf.length() > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(md.getColumnName(ii)).append("=");
|
||||
buf.append(rs.getObject(ii));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected String _dbident;
|
||||
|
||||
protected static PreCondition _precond;
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The static connection provider generates JDBC connections based on configuration information
|
||||
* provided via a properties file. It does no connection pooling and always returns the same
|
||||
* connection for a particular identifier (unless that connection need be closed because of a
|
||||
* connection failure, in which case it opens a new one the next time the connection is requested).
|
||||
*
|
||||
* <p> The configuration properties file should contain the following information:
|
||||
*
|
||||
* <pre>
|
||||
* IDENT.driver=[jdbc driver class]
|
||||
* IDENT.url=[jdbc driver url]
|
||||
* IDENT.username=[jdbc username]
|
||||
* IDENT.password=[jdbc password]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>IDENT</code> is the database identifier for a particular database connection. When a
|
||||
* particular database identifier is requested, the configuration information will be fetched from
|
||||
* the properties.
|
||||
*
|
||||
* <p> Additionally, a default set of properties can be provided using the identifier
|
||||
* <code>default</code>. Values not provided for a specific identifier will be sought from the
|
||||
* defaults. For example:
|
||||
*
|
||||
* <pre>
|
||||
* default.driver=[jdbc driver class]
|
||||
* default.url=[jdbc driver class]
|
||||
*
|
||||
* IDENT1.username=[jdbc username]
|
||||
* IDENT1.password=[jdbc password]
|
||||
*
|
||||
* IDENT2.username=[jdbc username]
|
||||
* IDENT2.password=[jdbc password]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*/
|
||||
public class StaticConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Constructs a static connection provider which will load its configuration from a properties
|
||||
* file accessible via the classpath of the running application and identified by the specified
|
||||
* path.
|
||||
*
|
||||
* @param propPath the path (relative to the classpath) to the properties file that will be
|
||||
* used for configuration information.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs locating or loading the specified
|
||||
* properties file.
|
||||
*/
|
||||
public StaticConnectionProvider (String propPath)
|
||||
throws IOException
|
||||
{
|
||||
this(ConfigUtil.loadProperties(propPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a static connection provider which will fetch its configuration information from
|
||||
* the specified properties object.
|
||||
*
|
||||
* @param props the configuration for this connection provider.
|
||||
*/
|
||||
public StaticConnectionProvider (Properties props)
|
||||
{
|
||||
_props = props;
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public String getURL (String ident)
|
||||
{
|
||||
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
return props.getProperty("url");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Connection getConnection (String ident, boolean readOnly)
|
||||
throws PersistenceException
|
||||
{
|
||||
String mapkey = ident + ":" + readOnly;
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
|
||||
// open the connection if we haven't already
|
||||
if (conmap == null) {
|
||||
Properties props = PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
|
||||
// get the JDBC configuration info
|
||||
String err = "No driver class specified [ident=" + ident + "].";
|
||||
String driver = requireProp(props, "driver", err);
|
||||
err = "No driver URL specified [ident=" + ident + "].";
|
||||
String url = requireProp(props, "url", err);
|
||||
err = "No driver username specified [ident=" + ident + "].";
|
||||
String username = requireProp(props, "username", err);
|
||||
String password = props.getProperty("password", "");
|
||||
String autoCommit = props.getProperty("autocommit");
|
||||
|
||||
// if this is a read-only connection, we cache connections by username+url+readOnly to
|
||||
// avoid making more that one connection to a particular database server
|
||||
String key = username + "@" + url + ":" + readOnly;
|
||||
conmap = _keys.get(key);
|
||||
if (conmap == null) {
|
||||
log.debug("Creating " + key + " for " + ident + ".");
|
||||
conmap = new Mapping();
|
||||
conmap.key = key;
|
||||
conmap.connection = openConnection(driver, url, username, password);
|
||||
|
||||
// if we were requested to configure auto-commit, then do so
|
||||
if (autoCommit != null) {
|
||||
try {
|
||||
conmap.connection.setAutoCommit(Boolean.valueOf(autoCommit));
|
||||
} catch (SQLException sqe) {
|
||||
closeConnection(ident, conmap.connection);
|
||||
err = "Failed to configure auto-commit [key=" + key +
|
||||
", ident=" + ident + ", autoCommit=" + autoCommit + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
}
|
||||
|
||||
// make the connection read-only to let the JDBC driver know that it can and should
|
||||
// use the read-only mirror(s)
|
||||
if (readOnly) {
|
||||
try {
|
||||
conmap.connection.setReadOnly(true);
|
||||
} catch (SQLException sqe) {
|
||||
closeConnection(ident, conmap.connection);
|
||||
err = "Failed to make connection read-only [key=" + key +
|
||||
", ident=" + ident + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
}
|
||||
_keys.put(key, conmap);
|
||||
|
||||
} else {
|
||||
log.debug("Reusing " + key + " for " + ident + ".");
|
||||
}
|
||||
|
||||
// cache the connection
|
||||
conmap.idents.add(mapkey);
|
||||
_idents.put(mapkey, conmap);
|
||||
}
|
||||
|
||||
return conmap.connection;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void releaseConnection (String ident, boolean readOnly, Connection conn)
|
||||
{
|
||||
// nothing to do here, all is well
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void connectionFailed (
|
||||
String ident, boolean readOnly, Connection conn, SQLException error)
|
||||
{
|
||||
String mapkey = ident + ":" + readOnly;
|
||||
Mapping conmap = _idents.get(mapkey);
|
||||
if (conmap == null) {
|
||||
log.warning("Unknown connection failed!?", "key", mapkey);
|
||||
return;
|
||||
}
|
||||
|
||||
// attempt to close the connection
|
||||
closeConnection(ident, conmap.connection);
|
||||
|
||||
// clear it from our mapping tables
|
||||
for (int ii = 0; ii < conmap.idents.size(); ii++) {
|
||||
_idents.remove(conmap.idents.get(ii));
|
||||
}
|
||||
_keys.remove(conmap.key);
|
||||
}
|
||||
|
||||
// from ConnectionProvider
|
||||
public void shutdown ()
|
||||
{
|
||||
// close all of the connections
|
||||
for (Map.Entry<String, Mapping> entry : _keys.entrySet()) {
|
||||
Mapping conmap = entry.getValue();
|
||||
try {
|
||||
conmap.connection.close();
|
||||
} catch (SQLException sqe) {
|
||||
log.warning("Error shutting down connection", "key", entry.getKey(), "err", sqe);
|
||||
}
|
||||
}
|
||||
|
||||
// clear out our mapping tables
|
||||
_keys.clear();
|
||||
_idents.clear();
|
||||
}
|
||||
|
||||
protected Connection openConnection (String driver, String url, String username, String passwd)
|
||||
throws PersistenceException
|
||||
{
|
||||
// create an instance of the driver
|
||||
Driver jdriver;
|
||||
try {
|
||||
jdriver = (Driver)Class.forName(driver).newInstance();
|
||||
} catch (Exception e) {
|
||||
String err = "Error loading driver [class=" + driver + "].";
|
||||
throw new PersistenceException(err, e);
|
||||
}
|
||||
|
||||
// create the connection
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.put("user", username);
|
||||
props.put("password", passwd);
|
||||
return jdriver.connect(url, props);
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
String err = "Error creating database connection [driver=" + driver + ", url=" + url +
|
||||
", username=" + username + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
}
|
||||
|
||||
protected void closeConnection (String ident, Connection conn)
|
||||
{
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException sqe) {
|
||||
log.warning("Error closing failed connection", "ident", ident, "error", sqe);
|
||||
}
|
||||
}
|
||||
|
||||
protected static String requireProp (Properties props, String name, String errmsg)
|
||||
throws PersistenceException
|
||||
{
|
||||
String value = props.getProperty(name);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
errmsg = "Unable to get connection. " + errmsg; // augment the error message
|
||||
throw new PersistenceException(errmsg);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Contains information on a particular connection to which any number of database identifiers
|
||||
* can be mapped. */
|
||||
protected static class Mapping
|
||||
{
|
||||
/** The combination of username and JDBC url that uniquely identifies our database
|
||||
* connection. */
|
||||
public String key;
|
||||
|
||||
/** The connection itself. */
|
||||
public Connection connection;
|
||||
|
||||
/** The database identifiers that are mapped to this connection. */
|
||||
public List<String> idents = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/** Our configuration in the form of a properties object. */
|
||||
protected Properties _props;
|
||||
|
||||
/** A mapping from database identifier to connection records. */
|
||||
protected HashMap<String,Mapping> _idents = new HashMap<String,Mapping>();
|
||||
|
||||
/** A mapping from connection key to connection records. */
|
||||
protected HashMap<String,Mapping> _keys = new HashMap<String,Mapping>();
|
||||
|
||||
/** The key used as defaults for the database definitions. */
|
||||
protected static final String DEFAULTS_KEY = "default";
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Used to note that transitionary code has been run to migrate persistent data. This is especially
|
||||
* useful for data that one cannot examine to determine if it's been transitioned.
|
||||
*/
|
||||
public class TransitionRepository extends SimpleRepository
|
||||
{
|
||||
/** The identifier of this connection. */
|
||||
public static final String TRANSITION_DB_IDENT = "transitiondb";
|
||||
|
||||
/**
|
||||
* An interface for the transition.
|
||||
*/
|
||||
public static interface Transition
|
||||
{
|
||||
/**
|
||||
* Do the transition.
|
||||
*/
|
||||
public void run () throws PersistenceException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a TransitionRepository for the server.
|
||||
*/
|
||||
public TransitionRepository (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
super(conprov, TRANSITION_DB_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a transition if it has not already been applied, and record that it was applied.
|
||||
*/
|
||||
public void transition (Class<?> clazz, String name, Transition trans)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (!isTransitionApplied(clazz, name) && noteTransition(clazz, name)) {
|
||||
try {
|
||||
trans.run();
|
||||
|
||||
} catch (PersistenceException e) {
|
||||
try {
|
||||
clearTransition(clazz, name);
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Failed to clear failed transition", "class", clazz, "name", name,
|
||||
pe);
|
||||
}
|
||||
throw e;
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
try {
|
||||
clearTransition(clazz, name);
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Failed to clear failed transition", "class", clazz, "name", name,
|
||||
pe);
|
||||
}
|
||||
throw rte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified name transition been applied.
|
||||
*/
|
||||
public boolean isTransitionApplied (Class<?> clazz, final String name)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String cname = clazz.getName();
|
||||
return execute(new Operation<Boolean>() {
|
||||
public Boolean invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(
|
||||
" select " + liaison.columnSQL("NAME") +
|
||||
" from " + liaison.tableSQL("TRANSITIONS") +
|
||||
" where " + liaison.columnSQL("CLASS") + "=?" +
|
||||
" and " + liaison.columnSQL("NAME") + "=?");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Note in the database that a particular transition has been applied.
|
||||
*
|
||||
* @return true if the transition was noted, false if it could not be noted because another
|
||||
* process noted it first.
|
||||
*/
|
||||
public boolean noteTransition (Class<?> clazz, final String name)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String cname = clazz.getName();
|
||||
return executeUpdate(new Operation<Boolean>() {
|
||||
public Boolean invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(
|
||||
"insert into " + liaison.tableSQL("TRANSITIONS") + " (" +
|
||||
liaison.columnSQL("CLASS") + ", " + liaison.columnSQL("NAME") +
|
||||
", " + liaison.columnSQL("APPLIED") +
|
||||
") values (?, ?, ?)");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
|
||||
JDBCUtil.checkedUpdate(stmt, 1);
|
||||
return true;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (liaison.isDuplicateRowException(sqe)) {
|
||||
return false;
|
||||
} else {
|
||||
throw sqe;
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the transition.
|
||||
*/
|
||||
public void clearTransition (Class<?> clazz, final String name)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String cname = clazz.getName();
|
||||
executeUpdate(new Operation<Void>() {
|
||||
public Void invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(
|
||||
" delete from " + liaison.tableSQL("TRANSITIONS") +
|
||||
" where " + liaison.columnSQL("CLASS") + "=? " +
|
||||
" and " + liaison.columnSQL("NAME") + "=?");
|
||||
stmt.setString(1, cname);
|
||||
stmt.setString(2, name);
|
||||
stmt.executeUpdate(); // we don't care if it worked or not
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
liaison.createTableIfMissing(
|
||||
conn,
|
||||
"TRANSITIONS",
|
||||
new String[] { "CLASS", "NAME", "APPLIED" },
|
||||
new ColumnDefinition[] {
|
||||
new ColumnDefinition("VARCHAR(200)", true, false, null),
|
||||
new ColumnDefinition("VARCHAR(50)", true, false, null),
|
||||
new ColumnDefinition("TIMESTAMP")
|
||||
},
|
||||
null,
|
||||
new String[] { "CLASS", "NAME" });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Extends {@link com.samskivert.util.Invoker.Unit} and specializes it for writing to a database
|
||||
* repository.
|
||||
*/
|
||||
public abstract class WriteOnlyUnit extends Invoker.Unit
|
||||
{
|
||||
/**
|
||||
* Creates a unit which will report the supplied name in {@link #toString} and in the event of
|
||||
* failure.
|
||||
*/
|
||||
public WriteOnlyUnit (String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override // from abstract Invoker.Unit
|
||||
public boolean invoke ()
|
||||
{
|
||||
try {
|
||||
invokePersist();
|
||||
return false;
|
||||
} catch (Exception pe) {
|
||||
_error = pe;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from Invoker.Unit
|
||||
public void handleResult ()
|
||||
{
|
||||
handleFailure(_error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to perform our persistent actions.
|
||||
*/
|
||||
public abstract void invokePersist ()
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Called if our persistent actions failed, back on the non-invoker thread. The default
|
||||
* implementation logs an error message and a stack trace.
|
||||
*/
|
||||
public void handleFailure (Exception e)
|
||||
{
|
||||
log.warning(getFailureMessage(), e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error message to be logged if {@link #invokePersist} throws an exception.
|
||||
*/
|
||||
protected String getFailureMessage ()
|
||||
{
|
||||
return this + " failed.";
|
||||
}
|
||||
|
||||
protected Exception _error;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.util.*;
|
||||
import java.sql.*;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Cursor is used for successive access to records fetched by SELECT statement.
|
||||
* As far as records can be retrived from several derived tables (polymorphic
|
||||
* form of select), this class can issue several requests to database. Cursor
|
||||
* also provides methods for updating/deleting current record.
|
||||
*/
|
||||
public class Cursor<V>
|
||||
{
|
||||
/**
|
||||
* A cursor is initially positioned before its first row; the first call to
|
||||
* next makes the first row the current row; the second call makes the
|
||||
* second row the current row, etc.
|
||||
*
|
||||
* <P> If an input stream from the previous row is open, it is implicitly
|
||||
* closed. The ResultSet's warning chain is cleared when a new row is read.
|
||||
*
|
||||
* @return object constructed from fetched record or null if there are no
|
||||
* more rows
|
||||
*/
|
||||
public V next ()
|
||||
throws SQLException
|
||||
{
|
||||
// if we closed everything up after the last call to next(),
|
||||
// table will be null here and we should bail immediately
|
||||
if (_table == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_result == null) {
|
||||
if (_qbeObject != null) {
|
||||
PreparedStatement qbeStmt = _conn.prepareStatement(_query);
|
||||
_table.bindQueryVariables(qbeStmt, _qbeObject, _qbeMask);
|
||||
_result = qbeStmt.executeQuery();
|
||||
_stmt = qbeStmt;
|
||||
} else {
|
||||
if (_stmt == null) {
|
||||
_stmt = _conn.createStatement();
|
||||
}
|
||||
_result = _stmt.executeQuery(_query);
|
||||
}
|
||||
}
|
||||
if (_result.next()) {
|
||||
return _currObject = _table.load(_result);
|
||||
}
|
||||
|
||||
_result.close();
|
||||
_result = null;
|
||||
_currObject = null;
|
||||
_table = null;
|
||||
|
||||
if (_stmt != null) {
|
||||
_stmt.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first element matched by this cursor or null if no elements
|
||||
* were matched. Checks to ensure that no subsequent elements were matched
|
||||
* by the query, logs a warning if there were spurious additional matches.
|
||||
*/
|
||||
public V get ()
|
||||
throws SQLException
|
||||
{
|
||||
V result = next();
|
||||
if (result != null) {
|
||||
int spurious = 0;
|
||||
while (next() != null) {
|
||||
spurious++;
|
||||
}
|
||||
if (spurious > 0) {
|
||||
log.warning("Cursor.get() quietly tossed " + spurious + " spurious additional " +
|
||||
"records.", "query", _query);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current record pointed by cursor. This method can be called only
|
||||
* after next() method, which returns non-null object. This objects is used
|
||||
* to update current record fields.
|
||||
*
|
||||
* <P> If you are going to update or delete selected records, you should
|
||||
* add "for update" clause to select statement. So parameter of
|
||||
* <CODE>jora.Table.select()</CODE> statement should contain "for update"
|
||||
* clause: <CODE>record.table.Select("where name='xyz' for
|
||||
* update");</CODE><P>
|
||||
*
|
||||
* <I><B>Attention!</I></B> Not all database drivers support update
|
||||
* operation with cursor. This method will not work with such database
|
||||
* drivers.
|
||||
*/
|
||||
public void update ()
|
||||
throws SQLException
|
||||
{
|
||||
if (_currObject == null) {
|
||||
throw new IllegalStateException("No current object");
|
||||
}
|
||||
_table.updateVariables(_result, _currObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete current record pointed by cursor. This method can be called only
|
||||
* after next() method, which returns non-null object.
|
||||
*
|
||||
* <P> If you are going to update or delete selected records, you should
|
||||
* add "for update" clause to select statement. So parameter of
|
||||
* <CODE>jora.Table.select()</CODE> statement should contain "for update"
|
||||
* clause: <CODE>record.table.Select("where name='xyz' for
|
||||
* update");</CODE><P>
|
||||
*
|
||||
* <I><B>Attention!</I></B> Not all database drivers support delete
|
||||
* operation with cursor. This method will not work with such database
|
||||
* drivers.
|
||||
*/
|
||||
public void delete ()
|
||||
throws SQLException
|
||||
{
|
||||
if (_currObject == null) {
|
||||
throw new IllegalStateException("No current object");
|
||||
}
|
||||
_result.deleteRow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Cursor, even if we haven't read all the possible objects.
|
||||
*/
|
||||
public void close ()
|
||||
throws SQLException
|
||||
{
|
||||
if (_result != null) {
|
||||
_result.close();
|
||||
_result = null;
|
||||
}
|
||||
if (_stmt != null) {
|
||||
_stmt.close();
|
||||
_stmt = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts no more than <I>maxElements</I> records from database and store
|
||||
* them into array. It is possible to extract rest records by successive
|
||||
* next() or toArray() calls. Selected objects should have now components
|
||||
* of InputStream, Blob or Clob type, because their data will be not
|
||||
* available after fetching next record.
|
||||
*
|
||||
* @param maxElements limitation for result array size (and also for number
|
||||
* of fetched records)
|
||||
* @return List with objects constructed from fetched records.
|
||||
*/
|
||||
public ArrayList<V> toArrayList (int maxElements)
|
||||
throws SQLException
|
||||
{
|
||||
ArrayList<V> al = new ArrayList<V>(Math.min(maxElements, 100));
|
||||
V o;
|
||||
while (--maxElements >= 0 && (o = next()) != null) {
|
||||
al.add(o);
|
||||
}
|
||||
return al;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store all objects returned by SELECT query into a list of Object.
|
||||
* Selected objects should have now components of InputStream, Blob or Clob
|
||||
* type, because their data will be not available after fetching next
|
||||
* record.
|
||||
*
|
||||
* @return Array with objects constructed from fetched records.
|
||||
*/
|
||||
public ArrayList<V> toArrayList ()
|
||||
throws SQLException
|
||||
{
|
||||
return toArrayList(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
protected Cursor (Table<V> table, Connection conn, String query)
|
||||
{
|
||||
_table = table;
|
||||
_conn = conn;
|
||||
_query = query;
|
||||
}
|
||||
|
||||
protected Cursor (Table<V> table, Connection conn, V obj,
|
||||
FieldMask mask, boolean like)
|
||||
{
|
||||
_table = table;
|
||||
_conn = conn;
|
||||
_like = like;
|
||||
_qbeObject = obj;
|
||||
_qbeMask = mask;
|
||||
_query = table.buildQueryList(obj, mask, like);
|
||||
_stmt = null;
|
||||
}
|
||||
|
||||
protected Table<V> _table;
|
||||
protected Connection _conn;
|
||||
protected ResultSet _result;
|
||||
protected String _query;
|
||||
protected Statement _stmt;
|
||||
protected V _currObject, _qbeObject;
|
||||
protected FieldMask _qbeMask;
|
||||
protected boolean _like;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/** This error is raised when error is happened during data transfer
|
||||
* between program and database server (for example IOException was thrown
|
||||
* while operation with InputStream field)
|
||||
*/
|
||||
public class DataTransferError extends java.lang.Error {
|
||||
DataTransferError() {
|
||||
super("Database data transfer error");
|
||||
}
|
||||
DataTransferError(Exception ex) {
|
||||
super(ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.sql.*;
|
||||
import java.math.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class FieldDescriptor
|
||||
{
|
||||
protected FieldDescriptor (Field field, String name)
|
||||
{
|
||||
this.name = name;
|
||||
this.field = field;
|
||||
this.scale = -1;
|
||||
}
|
||||
|
||||
protected final boolean isAtomic ()
|
||||
{
|
||||
return inType < tClosure;
|
||||
}
|
||||
|
||||
protected final boolean isCompound ()
|
||||
{
|
||||
return inType >= tCompound;
|
||||
}
|
||||
|
||||
protected final boolean isBuiltin ()
|
||||
{
|
||||
return inType <= t_boolean;
|
||||
}
|
||||
|
||||
protected final boolean bindVariable (
|
||||
PreparedStatement pstmt, Object obj, int column)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
switch (outType) {
|
||||
case t_byte:
|
||||
pstmt.setByte(column, field.getByte(obj));
|
||||
break;
|
||||
case t_short:
|
||||
pstmt.setShort(column, field.getShort(obj));
|
||||
break;
|
||||
case t_int:
|
||||
pstmt.setInt(column, field.getInt(obj));
|
||||
break;
|
||||
case t_long:
|
||||
pstmt.setLong(column, field.getLong(obj));
|
||||
break;
|
||||
case t_float:
|
||||
pstmt.setFloat(column, field.getFloat(obj));
|
||||
break;
|
||||
case t_double:
|
||||
pstmt.setDouble(column, field.getDouble(obj));
|
||||
break;
|
||||
case t_boolean:
|
||||
pstmt.setBoolean(column, field.getBoolean(obj));
|
||||
break;
|
||||
case tByte:
|
||||
pstmt.setByte(column, ((Byte)field.get(obj)).byteValue());
|
||||
break;
|
||||
|
||||
case tShort:
|
||||
pstmt.setShort(column, ((Short)field.get(obj)).shortValue());
|
||||
break;
|
||||
case tInteger:
|
||||
pstmt.setInt(column, ((Integer)field.get(obj)).intValue());
|
||||
break;
|
||||
case tLong:
|
||||
pstmt.setLong(column, ((Long)field.get(obj)).longValue());
|
||||
break;
|
||||
case tFloat:
|
||||
pstmt.setFloat(column, ((Float)field.get(obj)).floatValue());
|
||||
break;
|
||||
case tDouble:
|
||||
pstmt.setDouble(column,((Double)field.get(obj)).doubleValue());
|
||||
break;
|
||||
case tBoolean:
|
||||
pstmt.setBoolean(column, ((Boolean)field.get(obj)).booleanValue());
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
pstmt.setBigDecimal(column, (BigDecimal)field.get(obj));
|
||||
break;
|
||||
case tString:
|
||||
pstmt.setString(column, (String)field.get(obj));
|
||||
break;
|
||||
case tBytes:
|
||||
pstmt.setBytes(column, (byte[])field.get(obj));
|
||||
break;
|
||||
case tDate:
|
||||
pstmt.setDate(column, (java.sql.Date)field.get(obj));
|
||||
break;
|
||||
case tTime:
|
||||
pstmt.setTime(column, (java.sql.Time)field.get(obj));
|
||||
break;
|
||||
case tTimestamp:
|
||||
pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj));
|
||||
break;
|
||||
case tStream:
|
||||
java.io.InputStream in = (java.io.InputStream)field.get(obj);
|
||||
pstmt.setBinaryStream(column, in, in.available());
|
||||
break;
|
||||
case tBlob:
|
||||
pstmt.setBlob(column, (Blob)field.get(obj));
|
||||
break;
|
||||
case tClob:
|
||||
pstmt.setClob(column, (Clob)field.get(obj));
|
||||
break;
|
||||
case tAsString:
|
||||
pstmt.setString(column, field.get(obj).toString());
|
||||
break;
|
||||
case tClosure:
|
||||
// There is no reason to use piped streams because
|
||||
// we need to pass total number of bytes to JDBC driver
|
||||
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
||||
java.io.ObjectOutputStream clu = new java.io.ObjectOutputStream(out);
|
||||
clu.writeObject(field.get(obj));
|
||||
clu.close();
|
||||
pstmt.setBytes(column, out.toByteArray());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch(SQLException ex) {
|
||||
if (outType != tClosure && outType != tAsString) {
|
||||
outType = tAsString;
|
||||
return bindVariable(pstmt, obj, column);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
} catch(IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new IllegalAccessError();
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final boolean updateVariable (ResultSet result, Object obj, int column)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
switch (outType) {
|
||||
case t_byte:
|
||||
result.updateByte(column, field.getByte(obj));
|
||||
break;
|
||||
case t_short:
|
||||
result.updateShort(column, field.getShort(obj));
|
||||
break;
|
||||
case t_int:
|
||||
result.updateInt(column, field.getInt(obj));
|
||||
break;
|
||||
case t_long:
|
||||
result.updateLong(column, field.getLong(obj));
|
||||
break;
|
||||
case t_float:
|
||||
result.updateFloat(column, field.getFloat(obj));
|
||||
break;
|
||||
case t_double:
|
||||
result.updateDouble(column, field.getDouble(obj));
|
||||
break;
|
||||
case t_boolean:
|
||||
result.updateBoolean(column, field.getBoolean(obj));
|
||||
break;
|
||||
|
||||
case tByte:
|
||||
result.updateByte(column, ((Byte)field.get(obj)).byteValue());
|
||||
break;
|
||||
case tShort:
|
||||
result.updateShort(column, ((Short)field.get(obj)).shortValue());
|
||||
break;
|
||||
case tInteger:
|
||||
result.updateInt(column, ((Integer)field.get(obj)).intValue());
|
||||
break;
|
||||
case tLong:
|
||||
result.updateLong(column, ((Long)field.get(obj)).longValue());
|
||||
break;
|
||||
case tFloat:
|
||||
result.updateFloat(column, ((Float)field.get(obj)).floatValue());
|
||||
break;
|
||||
case tDouble:
|
||||
result.updateDouble(column, ((Double)field.get(obj)).doubleValue());
|
||||
break;
|
||||
case tBoolean:
|
||||
result.updateBoolean(column, ((Boolean)field.get(obj)).booleanValue());
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
result.updateBigDecimal(column, (BigDecimal)field.get(obj));
|
||||
break;
|
||||
case tString:
|
||||
result.updateString(column, (String)field.get(obj));
|
||||
break;
|
||||
case tBytes:
|
||||
result.updateBytes(column, (byte[])field.get(obj));
|
||||
break;
|
||||
case tDate:
|
||||
result.updateDate(column, (java.sql.Date)field.get(obj));
|
||||
break;
|
||||
case tTime:
|
||||
result.updateTime(column, (java.sql.Time)field.get(obj));
|
||||
break;
|
||||
case tTimestamp:
|
||||
result.updateTimestamp(column, (java.sql.Timestamp)field.get(obj));
|
||||
break;
|
||||
case tStream:
|
||||
java.io.InputStream in = (java.io.InputStream)field.get(obj);
|
||||
result.updateBinaryStream(column, in, in.available());
|
||||
break;
|
||||
case tBlob:
|
||||
Blob blob = (Blob)field.get(obj);
|
||||
result.updateBinaryStream(column, blob.getBinaryStream(), (int)blob.length());
|
||||
break;
|
||||
case tClob:
|
||||
Clob clob = (Clob)field.get(obj);
|
||||
result.updateCharacterStream(column, clob.getCharacterStream(), (int)clob.length());
|
||||
break;
|
||||
case tAsString:
|
||||
result.updateString(column, field.get(obj).toString());
|
||||
break;
|
||||
case tClosure:
|
||||
// There is no reason to use piped streams because
|
||||
// we need to pass total number of bytes to JDBC driver
|
||||
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
||||
java.io.ObjectOutputStream clu = new java.io.ObjectOutputStream(out);
|
||||
clu.writeObject(field.get(obj));
|
||||
clu.close();
|
||||
result.updateBytes(column, out.toByteArray());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch(SQLException ex) {
|
||||
if (outType != tClosure && outType != tAsString) {
|
||||
outType = tAsString;
|
||||
return updateVariable(result, obj, column);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
} catch(IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new IllegalAccessError();
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final boolean loadVariable (
|
||||
ResultSet result, Object obj, int column)
|
||||
throws SQLException, IllegalAccessException
|
||||
{
|
||||
switch (inType) {
|
||||
case t_byte:
|
||||
field.setByte(obj, result.getByte(column));
|
||||
break;
|
||||
case t_short:
|
||||
field.setShort(obj, result.getShort(column));
|
||||
break;
|
||||
case t_int:
|
||||
field.setInt(obj, result.getInt(column));
|
||||
break;
|
||||
case t_long:
|
||||
field.setLong(obj, result.getLong(column));
|
||||
break;
|
||||
case t_float:
|
||||
field.setFloat(obj, result.getFloat(column));
|
||||
break;
|
||||
case t_double:
|
||||
field.setDouble(obj, result.getDouble(column));
|
||||
break;
|
||||
case t_boolean:
|
||||
field.setBoolean(obj, result.getBoolean(column));
|
||||
break;
|
||||
|
||||
case tByte:
|
||||
byte b = result.getByte(column);
|
||||
field.set(obj, result.wasNull() ? null : Byte.valueOf(b));
|
||||
break;
|
||||
case tShort:
|
||||
short s = result.getShort(column);
|
||||
field.set(obj, result.wasNull() ? null : Short.valueOf(s));
|
||||
break;
|
||||
case tInteger:
|
||||
int i = result.getInt(column);
|
||||
field.set(obj, result.wasNull() ? null : Integer.valueOf(i));
|
||||
break;
|
||||
case tLong:
|
||||
long l = result.getLong(column);
|
||||
field.set(obj, result.wasNull() ? null : Long.valueOf(l));
|
||||
break;
|
||||
case tFloat:
|
||||
float f = result.getFloat(column);
|
||||
field.set(obj, result.wasNull() ? null : Float.valueOf(f));
|
||||
field.setFloat(obj, result.getFloat(column));
|
||||
break;
|
||||
case tDouble:
|
||||
double d = result.getDouble(column);
|
||||
field.set(obj, result.wasNull() ? null : Double.valueOf(d));
|
||||
break;
|
||||
case tBoolean:
|
||||
boolean bl = result.getBoolean(column);
|
||||
field.set(obj, result.wasNull() ? null : Boolean.valueOf(bl));
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
field.set(obj, result.getBigDecimal(column));
|
||||
break;
|
||||
case tString:
|
||||
field.set(obj, result.getString(column));
|
||||
break;
|
||||
case tBytes:
|
||||
field.set(obj, result.getBytes(column));
|
||||
break;
|
||||
case tDate:
|
||||
field.set(obj, result.getDate(column));
|
||||
break;
|
||||
case tTime:
|
||||
field.set(obj, result.getTime(column));
|
||||
break;
|
||||
case tTimestamp:
|
||||
field.set(obj, result.getTimestamp(column));
|
||||
break;
|
||||
case tStream:
|
||||
field.set(obj, result.getBinaryStream(column));
|
||||
break;
|
||||
case tBlob:
|
||||
field.set(obj, result.getBlob(column));
|
||||
break;
|
||||
case tClob:
|
||||
field.set(obj, result.getClob(column));
|
||||
break;
|
||||
case tClosure:
|
||||
try {
|
||||
java.io.InputStream input = result.getBinaryStream(column);
|
||||
java.io.ObjectInputStream in = new java.io.ObjectInputStream(input);
|
||||
field.set(obj, in.readObject());
|
||||
in.close();
|
||||
} catch(ClassNotFoundException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected int inType; // type tag for field input (see constants below)
|
||||
protected int outType; // type tag for field output
|
||||
protected int scale; // scale for tDecimal type,
|
||||
protected String name; // full (compound) name of component
|
||||
protected Field field; // field info from java.lang.reflect
|
||||
|
||||
protected Constructor<?> constructor; // constructor of object component
|
||||
|
||||
protected static final int t_byte = 0;
|
||||
protected static final int t_short = 1;
|
||||
protected static final int t_int = 2;
|
||||
protected static final int t_long = 3;
|
||||
protected static final int t_float = 4;
|
||||
protected static final int t_double = 5;
|
||||
protected static final int t_boolean = 6;
|
||||
protected static final int tByte = 7;
|
||||
protected static final int tShort = 8;
|
||||
protected static final int tInteger = 9;
|
||||
protected static final int tLong = 10;
|
||||
protected static final int tFloat = 11;
|
||||
protected static final int tDouble = 12;
|
||||
protected static final int tBoolean = 13;
|
||||
protected static final int tDecimal = 14;
|
||||
protected static final int tString = 15;
|
||||
protected static final int tBytes = 16;
|
||||
protected static final int tDate = 17;
|
||||
protected static final int tTime = 18;
|
||||
protected static final int tTimestamp = 19;
|
||||
protected static final int tStream = 20;
|
||||
protected static final int tBlob = 21;
|
||||
protected static final int tClob = 22;
|
||||
protected static final int tAsString = 23;
|
||||
|
||||
protected static final int tClosure = 24;
|
||||
protected static final int tCompound = 25;
|
||||
|
||||
protected static final int[] sqlTypeMapping = {
|
||||
Types.INTEGER, // t_byte
|
||||
Types.INTEGER, // t_short
|
||||
Types.INTEGER, // t_int
|
||||
Types.BIGINT, // t_long
|
||||
Types.FLOAT, // t_float
|
||||
Types.DOUBLE, // t_double
|
||||
Types.BIT, // t_boolean
|
||||
Types.INTEGER, // tByte
|
||||
Types.INTEGER, // tShort
|
||||
Types.INTEGER, // tInteger
|
||||
Types.BIGINT, // tLong
|
||||
Types.FLOAT, // tFloat
|
||||
Types.DOUBLE, // tDouble
|
||||
Types.BIT, // tBoolean
|
||||
Types.NUMERIC, // tDecimal
|
||||
Types.VARCHAR, // tString
|
||||
Types.VARBINARY,// tBytes
|
||||
Types.DATE, // tDate
|
||||
Types.TIME, // tTime
|
||||
Types.TIMESTAMP, // tTimestamp
|
||||
Types.LONGVARBINARY, // tStream
|
||||
Types.LONGVARBINARY, // tBlob
|
||||
Types.LONGVARCHAR, // tClob
|
||||
Types.VARCHAR, // tAsString
|
||||
Types.LONGVARBINARY // tClosure
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides support for doing partial updates to objects in a JORA table.
|
||||
* A field mask can be obtained for a particular table, fields marked as
|
||||
* modified and the object subsequently updated in a fairly
|
||||
* straightforward manner:
|
||||
*
|
||||
* <pre>
|
||||
* // updating parts of a table that contains User objects
|
||||
* User user = // load user object from table
|
||||
* FieldMask mask = table.getFieldMask();
|
||||
* user.firstName = newFirstName;
|
||||
* mask.setModified("firstName");
|
||||
* user.lastName = newLastName;
|
||||
* mask.setModified("lastName");
|
||||
* table.update(user, mask);
|
||||
* </pre>
|
||||
*/
|
||||
public class FieldMask
|
||||
implements Cloneable
|
||||
{
|
||||
/**
|
||||
* Creates a field mask for a {@link Table} that uses the supplied field descriptors.
|
||||
*/
|
||||
public FieldMask (FieldDescriptor[] descrips)
|
||||
{
|
||||
this(toNames(descrips));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field mask using the supplied field names.
|
||||
*/
|
||||
public FieldMask (String[] names)
|
||||
{
|
||||
for (int ii = 0; ii < names.length; ii++) {
|
||||
_descripMap.put(names[ii], ii);
|
||||
}
|
||||
_modified = new boolean[names.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the fields in this mask are modified.
|
||||
*/
|
||||
public final boolean isModified ()
|
||||
{
|
||||
int mcount = _modified.length;
|
||||
for (int ii = 0; ii < mcount; ii++) {
|
||||
if (_modified[ii]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the field with the specified index is modified.
|
||||
*/
|
||||
public final boolean isModified (int index)
|
||||
{
|
||||
return _modified[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the field with the specified name is modifed.
|
||||
*/
|
||||
public final boolean isModified (String fieldName)
|
||||
{
|
||||
Integer index = _descripMap.get(fieldName);
|
||||
if (index == null) {
|
||||
throw new IllegalArgumentException("Field not in mask: " + fieldName);
|
||||
}
|
||||
return _modified[index.intValue()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only if the set of modified fields is a subset of the
|
||||
* fields specified.
|
||||
*/
|
||||
public final boolean onlySubsetModified (Set<String> fieldSet)
|
||||
{
|
||||
for (String field : _descripMap.keySet()) {
|
||||
if (isModified(field) && (!fieldSet.contains(field))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the specified field as modified.
|
||||
*/
|
||||
public void setModified (String fieldName)
|
||||
{
|
||||
Integer index = _descripMap.get(fieldName);
|
||||
if (index == null) {
|
||||
throw new IllegalArgumentException("Field not in mask: " + fieldName);
|
||||
}
|
||||
_modified[index.intValue()] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out the modification state of the fields in this mask.
|
||||
*/
|
||||
public void clear ()
|
||||
{
|
||||
Arrays.fill(_modified, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this field mask, with all fields set to
|
||||
* not-modified.
|
||||
*/
|
||||
@Override
|
||||
public FieldMask clone ()
|
||||
{
|
||||
try {
|
||||
FieldMask mask = (FieldMask)super.clone();
|
||||
mask._modified = new boolean[_modified.length];
|
||||
return mask;
|
||||
} catch (CloneNotSupportedException cnse) {
|
||||
throw new AssertionError(cnse); // won't happen; we're Cloneable
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
// return a list of the modified fields
|
||||
StringBuilder buf = new StringBuilder("FieldMask [modified={");
|
||||
boolean added = false;
|
||||
for (Map.Entry<String,Integer> entry : _descripMap.entrySet()) {
|
||||
if (_modified[entry.getValue().intValue()]) {
|
||||
if (added) {
|
||||
buf.append(", ");
|
||||
} else {
|
||||
added = true;
|
||||
}
|
||||
buf.append(entry.getKey());
|
||||
}
|
||||
}
|
||||
buf.append("}]");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected static String[] toNames (FieldDescriptor[] descrips)
|
||||
{
|
||||
// create a mapping from field name to descriptor index
|
||||
int dcount = (descrips == null ? 0 : descrips.length);
|
||||
String[] names = new String[dcount];
|
||||
for (int ii = 0; ii < dcount; ii++) {
|
||||
names[ii] = descrips[ii].field.getName();
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/** Modified flags for each field of an object in this table. */
|
||||
protected boolean[] _modified;
|
||||
|
||||
/** A mapping from field names to field descriptor index. */
|
||||
protected Map<String, Integer> _descripMap = new HashMap<String, Integer>();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
What have we here?
|
||||
------------------
|
||||
|
||||
This is a hacked version of JORA, a library for automatically mapping
|
||||
RDBMS database rows to Java objects and vice versa. It's simple, it uses
|
||||
reflection and doesn't require any classfile post-processing or external
|
||||
configuration information.
|
||||
|
||||
"That's all great but why is it hacked?" wonders the astute reader. Well,
|
||||
I couldn't quite cope with the error handling paradigm that it used (catch
|
||||
all SQL exceptions internally and pass them to an application-wide error
|
||||
handler method) and a few seconds thought on the matter led me to believe
|
||||
that there is not a simple way to allow a choice of error handling
|
||||
paradigms (the methods have to throw SQLException or not). So I took the
|
||||
easy way out and forked the codebase.
|
||||
|
||||
The code is pretty stable, so I don't expect to be merging in updates with
|
||||
any frequency. I promise to be a good monkey and submit any useful
|
||||
modifications that I make back to the original maintainer. It's a good
|
||||
thing that the license was unrestrictive enough to allow me to do this,
|
||||
otherwise I'd probably be stuck reinventing the wheel or using something
|
||||
that does less of what I want. Three cheers for free software.
|
||||
|
||||
The original version can be found here: http://www.ispras.ru/~knizhnik/
|
||||
(or at least it could when I wrote this README).
|
||||
|
||||
- mdb (2/12/2001)
|
||||
|
||||
$Id: README,v 1.2 2001/08/11 22:53:13 mdb Exp $
|
||||
@@ -0,0 +1,919 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.sql.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Used to establish mapping between corteges of database tables and Java
|
||||
* classes. This class is responsible for constructing SQL statements for
|
||||
* extracting, updating and deleting records of the database table.
|
||||
*/
|
||||
public class Table<T>
|
||||
{
|
||||
/**
|
||||
* Constructor for table object. Make association between Java class and
|
||||
* database table.
|
||||
*
|
||||
* @param clazz the class that represents a row entry.
|
||||
* @param tableName name of database table mapped on this Java class
|
||||
* @param key table's primary key. This parameter is used in UPDATE/DELETE
|
||||
* operations to locate record in the table.
|
||||
* @param mixedCaseConvert whether or not to convert mixed case field
|
||||
* names into underscore separated uppercase column names.
|
||||
*/
|
||||
public Table (Class<T> clazz, String tableName, String key,
|
||||
boolean mixedCaseConvert)
|
||||
{
|
||||
String[] keys = {key};
|
||||
init(clazz, tableName, keys, mixedCaseConvert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for table object. Make association between Java class and
|
||||
* database table.
|
||||
*
|
||||
* @param clazz the class that represents a row entry.
|
||||
* @param tableName name of database table mapped on this Java class
|
||||
* @param key table's primary key. This parameter is used in UPDATE/DELETE
|
||||
* operations to locate record in the table.
|
||||
*/
|
||||
public Table (Class<T> clazz, String tableName, String key) {
|
||||
String[] keys = {key};
|
||||
init(clazz, tableName, keys, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for table object. Make association between Java class and
|
||||
* database table.
|
||||
*
|
||||
* @param clazz the class that represents a row entry.
|
||||
* @param tableName name of database table mapped on this Java class
|
||||
* @param keys table primary keys. This parameter is used in UPDATE/DELETE
|
||||
* operations to locate record in the table.
|
||||
*/
|
||||
public Table (Class<T> clazz, String tableName, String[] keys)
|
||||
{
|
||||
init(clazz, tableName, keys, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for table object. Make association between Java class and
|
||||
* database table.
|
||||
*
|
||||
* @param clazz the class that represents a row entry.
|
||||
* @param tableName name of database table mapped on this Java class
|
||||
* @param keys table primary keys. This parameter is used in UPDATE/DELETE
|
||||
* operations to locate record in the table.
|
||||
* @param mixedCaseConvert whether or not to convert mixed case field
|
||||
* names into underscore separated uppercase column names.
|
||||
*/
|
||||
public Table (Class<T> clazz, String tableName, String[] keys,
|
||||
boolean mixedCaseConvert)
|
||||
{
|
||||
init(clazz, tableName, keys, mixedCaseConvert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL name of the table on which we operate.
|
||||
*/
|
||||
public String getName ()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select records from database table according to search condition
|
||||
*
|
||||
* @param condition valid SQL condition expression started with WHERE or
|
||||
* empty string if all records should be fetched.
|
||||
*/
|
||||
public final Cursor<T> select (Connection conn, String condition)
|
||||
{
|
||||
String query = "select " + listOfFields + " from " + name +
|
||||
" " + condition;
|
||||
return new Cursor<T>(this, conn, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select records from database table according to search condition
|
||||
* including the specified (comma separated) extra tables into the SELECT
|
||||
* clause to facilitate a join in determining the key.
|
||||
*
|
||||
* @param tables the (comma separated) names of extra tables to include in
|
||||
* the SELECT clause.
|
||||
* @param condition valid SQL condition expression started with WHERE.
|
||||
*/
|
||||
public final Cursor<T> select (Connection conn, String tables,
|
||||
String condition)
|
||||
{
|
||||
String query = "select " + qualifiedListOfFields +
|
||||
" from " + name + "," + tables + " " + condition;
|
||||
return new Cursor<T>(this, conn, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select records from database table according to search condition
|
||||
* including the specified (comma separated) extra tables into the SELECT
|
||||
* clause to facilitate a join in determining the key. To facilitate
|
||||
* situations where data from multiple tables is being combined into a
|
||||
* single object, the fields will not be qualified with the primary table
|
||||
* name.
|
||||
*
|
||||
* @param tables the (comma separated) names of extra tables to include in
|
||||
* the SELECT clause.
|
||||
* @param condition valid SQL condition expression started with WHERE.
|
||||
*/
|
||||
public final Cursor<T> join (Connection conn, String tables,
|
||||
String condition)
|
||||
{
|
||||
String query = "select " + listOfFields +
|
||||
" from " + name + "," + tables + " " + condition;
|
||||
return new Cursor<T>(this, conn, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #join} but does a straight join with the specified table.
|
||||
*/
|
||||
public final Cursor<T> straightJoin (Connection conn, String table,
|
||||
String condition)
|
||||
{
|
||||
String query = "select " + listOfFields +
|
||||
" from " + name + " straight_join " + table + " " + condition;
|
||||
return new Cursor<T>(this, conn, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select records from database table using <I>obj</I> object as template.
|
||||
*
|
||||
* @param obj example object for search: selected objects should match all
|
||||
* non-null fields.
|
||||
*/
|
||||
public final Cursor<T> queryByExample (Connection conn, T obj)
|
||||
{
|
||||
return new Cursor<T>(this, conn, obj, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select records from database table using <I>obj</I> object as template
|
||||
* for selection.
|
||||
*
|
||||
* @param obj example object for search.
|
||||
* @param mask field mask indicating which fields in the example object
|
||||
* should be used when building the query.
|
||||
*/
|
||||
public final Cursor<T> queryByExample (Connection conn, T obj,
|
||||
FieldMask mask)
|
||||
{
|
||||
return new Cursor<T>(this, conn, obj, mask, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as the queryByExample, but string fields for the obj are
|
||||
* matched using 'like' instead of equals, which allows you to send % in to
|
||||
* do matching.
|
||||
*/
|
||||
public final Cursor<T> queryByLikeExample (Connection conn, T obj)
|
||||
{
|
||||
return new Cursor<T>(this, conn, obj, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as the queryByExample, but string fields for the obj are
|
||||
* matched using 'like' instead of equals, which allows you to send % in to
|
||||
* do matching.
|
||||
*/
|
||||
public final Cursor<T> queryByLikeExample (Connection conn, T obj,
|
||||
FieldMask mask)
|
||||
{
|
||||
return new Cursor<T>(this, conn, obj, mask, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new record in the table. Values of inserted record fields are
|
||||
* taken from specifed object.
|
||||
*
|
||||
* @param obj object specifing values of inserted record fields
|
||||
*/
|
||||
public synchronized void insert (Connection conn, T obj)
|
||||
throws SQLException
|
||||
{
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"insert into " + name + " (" + listOfFields + ") values (?");
|
||||
for (int i = 1; i < nColumns; i++) {
|
||||
sql.append(",?");
|
||||
}
|
||||
sql.append(")");
|
||||
PreparedStatement insertStmt = conn.prepareStatement(sql.toString());
|
||||
bindUpdateVariables(insertStmt, obj, null);
|
||||
insertStmt.executeUpdate();
|
||||
insertStmt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert several new records in the table. Values of inserted records
|
||||
* fields are taken from objects of specified array.
|
||||
*
|
||||
* @param objects array with objects specifing values of inserted record
|
||||
* fields
|
||||
*/
|
||||
public synchronized void insert (Connection conn, T[] objects)
|
||||
throws SQLException
|
||||
{
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"insert into " + name + " (" + listOfFields + ") values (?");
|
||||
for (int i = 1; i < nColumns; i++) {
|
||||
sql.append(",?");
|
||||
}
|
||||
sql.append(")");
|
||||
PreparedStatement insertStmt = conn.prepareStatement(sql.toString());
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
bindUpdateVariables(insertStmt, objects[i], null);
|
||||
insertStmt.addBatch();
|
||||
}
|
||||
insertStmt.executeBatch();
|
||||
insertStmt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field mask that can be configured and used to update subsets
|
||||
* of entire objects via calls to {@link #update(Connection,Object,FieldMask)}.
|
||||
*/
|
||||
public FieldMask getFieldMask ()
|
||||
{
|
||||
return fMask.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update record in the table using table's primary key to locate record in
|
||||
* the table and values of fields of specified object <I>obj</I> to alter
|
||||
* record fields.
|
||||
*
|
||||
* @param obj object specifing value of primary key and new values of
|
||||
* updated record fields
|
||||
*
|
||||
* @return number of objects actually updated
|
||||
*/
|
||||
public int update (Connection conn, T obj)
|
||||
throws SQLException
|
||||
{
|
||||
return update(conn, obj, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update record in the table using table's primary key to locate record in
|
||||
* the table and values of fields of specified object <I>obj</I> to alter
|
||||
* record fields. Only the fields marked as modified in the supplied field
|
||||
* mask will be updated in the database.
|
||||
*
|
||||
* @param obj object specifing value of primary key and new values of
|
||||
* updated record fields
|
||||
* @param mask a {@link FieldMask} instance configured to indicate which of
|
||||
* the object's fields are modified and should be written to the database.
|
||||
*
|
||||
* @return number of objects actually updated
|
||||
*/
|
||||
public synchronized int update (Connection conn, T obj, FieldMask mask)
|
||||
throws SQLException
|
||||
{
|
||||
int nUpdated = 0;
|
||||
String sql = "update " + name + " set " +
|
||||
(mask != null ? buildListOfAssignments(mask) : listOfAssignments) +
|
||||
buildUpdateWhere();
|
||||
PreparedStatement ustmt = conn.prepareStatement(sql);
|
||||
int column = bindUpdateVariables(ustmt, obj, mask);
|
||||
for (int i = 0; i < primaryKeys.length; i++) {
|
||||
int fidx = primaryKeyIndices[i];
|
||||
fields[fidx].bindVariable(ustmt, obj, column+i+1);
|
||||
}
|
||||
nUpdated = ustmt.executeUpdate();
|
||||
ustmt.close();
|
||||
return nUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update set of records in the table using table's primary key to locate
|
||||
* record in the table and values of fields of objects from sepecifed array
|
||||
* <I>objects</I> to alter record fields.
|
||||
*
|
||||
* @param objects array of objects specifing primiray keys and and new
|
||||
* values of updated record fields
|
||||
*
|
||||
* @return number of objects actually updated
|
||||
*/
|
||||
public synchronized int update (Connection conn, T[] objects)
|
||||
throws SQLException
|
||||
{
|
||||
if (primaryKeys == null) {
|
||||
throw new IllegalStateException(
|
||||
"No primary key for table " + name + ".");
|
||||
}
|
||||
|
||||
int nUpdated = 0;
|
||||
String sql = "update " + name + " set " + listOfAssignments +
|
||||
buildUpdateWhere();
|
||||
PreparedStatement updateStmt = conn.prepareStatement(sql);
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
int column = bindUpdateVariables(updateStmt, objects[i], null);
|
||||
for (int j = 0; j < primaryKeys.length; j++) {
|
||||
int fidx = primaryKeyIndices[j];
|
||||
fields[fidx].bindVariable(
|
||||
updateStmt, objects[i], column+1+j);
|
||||
}
|
||||
updateStmt.addBatch();
|
||||
}
|
||||
int rc[] = updateStmt.executeBatch();
|
||||
for (int k = 0; k < rc.length; k++) {
|
||||
nUpdated += rc[k];
|
||||
}
|
||||
updateStmt.close();
|
||||
return nUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete record with specified value of primary key from the table.
|
||||
*
|
||||
* @param obj object containing value of primary key.
|
||||
*/
|
||||
public synchronized int delete (Connection conn, T obj)
|
||||
throws SQLException
|
||||
{
|
||||
if (primaryKeys == null) {
|
||||
throw new IllegalStateException(
|
||||
"No primary key for table " + name + ".");
|
||||
}
|
||||
int nDeleted = 0;
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"delete from " + name + " where " + primaryKeys[0] + " = ?");
|
||||
for (int i = 1; i < primaryKeys.length; i++) {
|
||||
sql.append(" and ").append(primaryKeys[i]).append(" = ?");
|
||||
}
|
||||
PreparedStatement deleteStmt = conn.prepareStatement(sql.toString());
|
||||
for (int i = 0; i < primaryKeys.length; i++) {
|
||||
fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1);
|
||||
}
|
||||
nDeleted = deleteStmt.executeUpdate();
|
||||
deleteStmt.close();
|
||||
return nDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete records with specified primary keys from the table.
|
||||
*
|
||||
* @param objects array of objects containing values of primary key.
|
||||
*
|
||||
* @return number of objects actually deleted
|
||||
*/
|
||||
public synchronized int delete (Connection conn, T[] objects)
|
||||
throws SQLException
|
||||
{
|
||||
if (primaryKeys == null) {
|
||||
throw new IllegalStateException(
|
||||
"No primary key for table " + name + ".");
|
||||
}
|
||||
int nDeleted = 0;
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"delete from " + name + " where " + primaryKeys[0] + " = ?");
|
||||
for (int i = 1; i < primaryKeys.length; i++) {
|
||||
sql.append(" and ").append(primaryKeys[i]).append(" = ?");
|
||||
}
|
||||
PreparedStatement deleteStmt = conn.prepareStatement(sql.toString());
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
for (int j = 0; j < primaryKeys.length; j++) {
|
||||
fields[primaryKeyIndices[j]].bindVariable(
|
||||
deleteStmt, objects[i], j+1);
|
||||
}
|
||||
deleteStmt.addBatch();
|
||||
}
|
||||
int rc[] = deleteStmt.executeBatch();
|
||||
for (int k = 0; k < rc.length; k++) {
|
||||
nDeleted += rc[k];
|
||||
}
|
||||
deleteStmt.close();
|
||||
return nDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "[name=" + name +
|
||||
", primaryKeys=" + StringUtil.toString(primaryKeys) + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Spearator of name components of compound field. For example, if Java
|
||||
* class constains component "location" of Point class, which has two
|
||||
* components "x" and "y", then database table should have columns
|
||||
* "location_x" and "location_y" (if '_' is used as separator).
|
||||
*/
|
||||
public static final String fieldSeparator = "_";
|
||||
|
||||
protected final void init (Class<T> clazz, String tableName, String[] keys,
|
||||
boolean mixedCaseConvert)
|
||||
{
|
||||
name = tableName;
|
||||
this.mixedCaseConvert = mixedCaseConvert;
|
||||
_rowClass = clazz;
|
||||
primaryKeys = keys;
|
||||
listOfFields = "";
|
||||
qualifiedListOfFields = "";
|
||||
listOfAssignments = "";
|
||||
ArrayList<FieldDescriptor> fieldsVector =
|
||||
new ArrayList<FieldDescriptor>();
|
||||
nFields = buildFieldsList(fieldsVector, _rowClass, "");
|
||||
fields = fieldsVector.toArray(new FieldDescriptor[nFields]);
|
||||
fMask = new FieldMask(fields);
|
||||
|
||||
try {
|
||||
constructor = _rowClass.getDeclaredConstructor(new Class<?>[0]);
|
||||
setBypass.invoke(constructor, bypassFlag);
|
||||
} catch(Exception ex) {}
|
||||
|
||||
if (keys != null && keys.length > 0) {
|
||||
primaryKeyIndices = new int[keys.length];
|
||||
for (int j = keys.length; --j >= 0;) {
|
||||
int i = nFields;
|
||||
while (--i >= 0) {
|
||||
if (fields[i].name.equals(keys[j])) {
|
||||
if (!fields[i].isAtomic()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Non-atomic primary key provided");
|
||||
}
|
||||
primaryKeyIndices[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < 0) {
|
||||
throw new NoSuchFieldError("No such field '" + keys[j]
|
||||
+ "' in table " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final String convertName (String name)
|
||||
{
|
||||
if (mixedCaseConvert) {
|
||||
return StringUtil.unStudlyName(name);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
protected final int buildFieldsList (ArrayList<FieldDescriptor> buf,
|
||||
Class<?> _rowClass, String prefix)
|
||||
{
|
||||
Field[] f = _rowClass.getDeclaredFields();
|
||||
|
||||
Class<?> superclass = _rowClass;
|
||||
while ((superclass = superclass.getSuperclass()) != null) {
|
||||
Field[] inheritedFields = superclass.getDeclaredFields();
|
||||
Field[] allFields = new Field[inheritedFields.length + f.length];
|
||||
System.arraycopy(inheritedFields, 0, allFields, 0,
|
||||
inheritedFields.length);
|
||||
System.arraycopy(f,0, allFields, inheritedFields.length, f.length);
|
||||
f = allFields;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = f.length; --i>= 0;) {
|
||||
setBypass.invoke(f[i], bypassFlag);
|
||||
}
|
||||
} catch (IllegalAccessException iae) {
|
||||
System.err.println("Failed to set bypass attribute: " + iae);
|
||||
} catch (InvocationTargetException ite) {
|
||||
System.err.println("Failed to set bypass attribute: " + ite);
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
for (int i = 0; i < f.length; i++) {
|
||||
if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0)
|
||||
{
|
||||
String name = f[i].getName();
|
||||
Class<?> fieldClass = f[i].getType();
|
||||
String fullName = prefix + convertName(name);
|
||||
FieldDescriptor fd = new FieldDescriptor(f[i], fullName);
|
||||
int type;
|
||||
|
||||
buf.add(fd);
|
||||
n += 1;
|
||||
|
||||
String c = fieldClass.getName();
|
||||
if (c.equals("byte")) type = FieldDescriptor.t_byte;
|
||||
else if (c.equals("short")) type = FieldDescriptor.t_short;
|
||||
else if (c.equals("int")) type = FieldDescriptor.t_int;
|
||||
else if (c.equals("long")) type = FieldDescriptor.t_long;
|
||||
else if (c.equals("float")) type = FieldDescriptor.t_float;
|
||||
else if (c.equals("double")) type = FieldDescriptor.t_double;
|
||||
else if (c.equals("boolean")) type = FieldDescriptor.t_boolean;
|
||||
else if (c.equals("java.lang.Byte"))
|
||||
type = FieldDescriptor.tByte;
|
||||
else if (c.equals("java.lang.Short"))
|
||||
type = FieldDescriptor.tShort;
|
||||
else if (c.equals("java.lang.Integer"))
|
||||
type = FieldDescriptor.tInteger;
|
||||
else if (c.equals("java.lang.Long"))
|
||||
type = FieldDescriptor.tLong;
|
||||
else if (c.equals("java.lang.Float"))
|
||||
type = FieldDescriptor.tFloat;
|
||||
else if (c.equals("java.lang.Double"))
|
||||
type = FieldDescriptor.tDouble;
|
||||
else if (c.equals("java.lang.Boolean"))
|
||||
type = FieldDescriptor.tBoolean;
|
||||
else if (c.equals("java.math.BigDecimal"))
|
||||
type = FieldDescriptor.tDecimal;
|
||||
else if (c.equals("java.lang.String"))
|
||||
type = FieldDescriptor.tString;
|
||||
else if (fieldClass.equals(BYTE_PROTO.getClass()))
|
||||
type = FieldDescriptor.tBytes;
|
||||
else if (c.equals("java.sql.Date"))
|
||||
type = FieldDescriptor.tDate;
|
||||
else if (c.equals("java.sql.Time"))
|
||||
type = FieldDescriptor.tTime;
|
||||
else if (c.equals("java.sql.Timestamp"))
|
||||
type = FieldDescriptor.tTimestamp;
|
||||
else if (c.equals("java.lang.InputStream"))
|
||||
type = FieldDescriptor.tStream;
|
||||
else if (c.equals("java.sql.BlobLocator"))
|
||||
type = FieldDescriptor.tBlob;
|
||||
else if (c.equals("java.sql.ClobLocator"))
|
||||
type = FieldDescriptor.tClob;
|
||||
else if (serializableClass.isAssignableFrom(fieldClass))
|
||||
type = FieldDescriptor.tClosure;
|
||||
else {
|
||||
int nComponents = buildFieldsList(buf, fieldClass,
|
||||
fd.name+fieldSeparator);
|
||||
fd.inType = fd.outType =
|
||||
FieldDescriptor.tCompound + nComponents;
|
||||
|
||||
try {
|
||||
fd.constructor =
|
||||
fieldClass.getDeclaredConstructor(new Class<?>[0]);
|
||||
setBypass.invoke(fd.constructor, bypassFlag);
|
||||
} catch(Exception ex) {}
|
||||
|
||||
n += nComponents;
|
||||
continue;
|
||||
}
|
||||
if (listOfFields.length() != 0) {
|
||||
listOfFields += ",";
|
||||
qualifiedListOfFields += ",";
|
||||
listOfAssignments += ",";
|
||||
}
|
||||
listOfFields += fullName;
|
||||
qualifiedListOfFields += this.name + "." + fullName;
|
||||
listOfAssignments += fullName + "=?";
|
||||
|
||||
fd.inType = fd.outType = type;
|
||||
nColumns += 1;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
protected final String buildListOfAssignments (FieldMask mask)
|
||||
{
|
||||
StringBuilder sql = new StringBuilder();
|
||||
int fcount = fields.length;
|
||||
for (int i = 0; i < fcount; i++) {
|
||||
// skip non-modified fields
|
||||
if (!mask.isModified(i)) {
|
||||
continue;
|
||||
}
|
||||
// separate fields by a comma
|
||||
if (sql.length() > 0) {
|
||||
sql.append(",");
|
||||
}
|
||||
// append the necessary SQL to update this column
|
||||
sql.append(fields[i].name).append("=?");
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
protected final T load (ResultSet result) throws SQLException
|
||||
{
|
||||
T obj;
|
||||
try {
|
||||
obj = constructor.newInstance(constructorArgs);
|
||||
}
|
||||
catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
catch(InstantiationException ex) { throw new InstantiationError(); }
|
||||
catch(Exception ex) {
|
||||
throw new InstantiationError("Exception was thrown by constructor");
|
||||
}
|
||||
load(obj, 0, nFields, 0, result);
|
||||
return obj;
|
||||
}
|
||||
|
||||
protected final int load (
|
||||
Object obj, int i, int end, int column, ResultSet result)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
while (i < end) {
|
||||
FieldDescriptor fd = fields[i++];
|
||||
if (!fd.loadVariable(result, obj, ++column)) {
|
||||
Object component =
|
||||
fd.constructor.newInstance(constructorArgs);
|
||||
fd.field.set(obj, component);
|
||||
int nComponents = fd.inType - FieldDescriptor.tCompound;
|
||||
column = load(component, i, i + nComponents,
|
||||
column-1, result);
|
||||
i += nComponents;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
catch(InstantiationException ex) { throw new InstantiationError(); }
|
||||
catch(InvocationTargetException ex) {
|
||||
throw new InstantiationError("Exception was thrown by constructor");
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
protected final int bindUpdateVariables(PreparedStatement pstmt,
|
||||
T obj,
|
||||
FieldMask mask)
|
||||
throws SQLException
|
||||
{
|
||||
return bindUpdateVariables(pstmt, obj, 0, nFields, 0, mask);
|
||||
}
|
||||
|
||||
protected final void bindQueryVariables(PreparedStatement pstmt,
|
||||
T obj,
|
||||
FieldMask mask)
|
||||
throws SQLException
|
||||
{
|
||||
bindQueryVariables(pstmt, obj, 0, nFields, 0, mask);
|
||||
}
|
||||
|
||||
protected final void updateVariables(ResultSet result, T obj)
|
||||
throws SQLException
|
||||
{
|
||||
updateVariables(result, obj, 0, nFields, 0);
|
||||
result.updateRow();
|
||||
}
|
||||
|
||||
protected final String buildUpdateWhere()
|
||||
{
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append(" where ").append(primaryKeys[0]).append(" = ?");
|
||||
for (int i = 1; i < primaryKeys.length; i++) {
|
||||
sql.append(" and ").append(primaryKeys[i]).append(" = ?");
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
protected final String buildQueryList(T qbe, FieldMask mask, boolean like)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buildQueryList(buf, qbe, 0, nFields, mask, like);
|
||||
if (buf.length() > 0) {
|
||||
buf.insert(0, " where ");
|
||||
}
|
||||
return "select " + listOfFields + " from " + name + buf;
|
||||
}
|
||||
|
||||
protected final int bindUpdateVariables (
|
||||
PreparedStatement pstmt, Object obj, int i, int end, int column,
|
||||
FieldMask mask)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
while (i < end) {
|
||||
FieldDescriptor fd = fields[i++];
|
||||
Object comp = null;
|
||||
// skip non-modified fields
|
||||
if (mask != null && !mask.isModified(i-1)) {
|
||||
continue;
|
||||
}
|
||||
if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) {
|
||||
if (fd.isCompound()) {
|
||||
int nComponents = fd.outType-FieldDescriptor.tCompound;
|
||||
while (--nComponents >= 0) {
|
||||
fd = fields[i++];
|
||||
if (!fd.isCompound()) {
|
||||
pstmt.setNull(++column,
|
||||
FieldDescriptor.sqlTypeMapping[fd.outType]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pstmt.setNull(
|
||||
++column,
|
||||
FieldDescriptor.sqlTypeMapping[fd.outType]);
|
||||
}
|
||||
} else {
|
||||
if (!fd.bindVariable(pstmt, obj, ++column)) {
|
||||
int nComponents = fd.outType-FieldDescriptor.tCompound;
|
||||
column = bindUpdateVariables(
|
||||
pstmt, comp, i, i+nComponents,column-1, mask);
|
||||
i += nComponents;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
return column;
|
||||
}
|
||||
|
||||
protected final int bindQueryVariables (
|
||||
PreparedStatement pstmt, Object obj, int i, int end, int column,
|
||||
FieldMask mask)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
while (i < end) {
|
||||
Object comp;
|
||||
FieldDescriptor fd = fields[i++];
|
||||
if (!fd.field.getDeclaringClass().isInstance(obj)) {
|
||||
return column;
|
||||
}
|
||||
int nComponents = fd.isCompound() ?
|
||||
fd.outType-FieldDescriptor.tCompound : 0;
|
||||
|
||||
try {
|
||||
// skip closure fields (because querying by them makes
|
||||
// no sense)
|
||||
if (fd.outType == FieldDescriptor.tClosure) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if a field mask is specified, use that to determine
|
||||
// whether or not the field should be skipped
|
||||
if (mask != null) {
|
||||
if (!mask.isModified(i-1)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// look up the value of the field
|
||||
comp = fd.field.get(obj);
|
||||
|
||||
// if no field mask was specified, ignore builtin
|
||||
// fields and those that are null
|
||||
if (mask == null && (fd.isBuiltin() || comp == null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fd.bindVariable(pstmt, obj, ++column)) {
|
||||
column = bindQueryVariables(
|
||||
pstmt, comp, i, i+nComponents, column-1, mask);
|
||||
}
|
||||
|
||||
} finally {
|
||||
i += nComponents;
|
||||
}
|
||||
}
|
||||
} catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
return column;
|
||||
}
|
||||
|
||||
protected final void buildQueryList (
|
||||
StringBuilder buf, Object qbe, int i, int end, FieldMask mask,
|
||||
boolean like)
|
||||
{
|
||||
try {
|
||||
while (i < end) {
|
||||
Object comp;
|
||||
FieldDescriptor fd = fields[i++];
|
||||
int nComponents =
|
||||
fd.isCompound() ? fd.outType-FieldDescriptor.tCompound : 0;
|
||||
|
||||
try {
|
||||
// skip closure fields (because querying by them makes
|
||||
// no sense)
|
||||
if (fd.outType == FieldDescriptor.tClosure) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if a field mask is specified, use that to determine
|
||||
// whether or not the field should be skipped
|
||||
if (mask != null) {
|
||||
if (!mask.isModified(i-1)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// look up the value of the field
|
||||
comp = fd.field.get(qbe);
|
||||
|
||||
// if no field mask was specified, ignore builtin
|
||||
// fields and those that are null
|
||||
if (mask == null && (fd.isBuiltin() || comp == null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nComponents != 0) {
|
||||
buildQueryList(buf, comp, i, i+nComponents, mask, like);
|
||||
} else {
|
||||
if (buf.length() != 0) {
|
||||
buf.append(" AND ");
|
||||
}
|
||||
buf.append(fd.name);
|
||||
if (like && (comp instanceof String)) {
|
||||
buf.append(" like?");
|
||||
} else {
|
||||
buf.append("=?");
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
i += nComponents;
|
||||
}
|
||||
}
|
||||
} catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
}
|
||||
|
||||
protected final int updateVariables (
|
||||
ResultSet result, Object obj, int i, int end, int column)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
while (i < end) {
|
||||
FieldDescriptor fd = fields[i++];
|
||||
Object comp = null;
|
||||
if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) {
|
||||
if (fd.isCompound()) {
|
||||
int nComponents = fd.outType-FieldDescriptor.tCompound;
|
||||
while (--nComponents >= 0) {
|
||||
fd = fields[i++];
|
||||
if (!fd.isCompound()) {
|
||||
result.updateNull(++column);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.updateNull(++column);
|
||||
}
|
||||
} else {
|
||||
if (!fd.updateVariable(result, obj, ++column)) {
|
||||
int nComponents = fd.outType-FieldDescriptor.tCompound;
|
||||
column = updateVariables(result, comp,
|
||||
i, i+nComponents, column-1);
|
||||
i += nComponents;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IllegalAccessException ex) { throw new IllegalAccessError(); }
|
||||
return column;
|
||||
}
|
||||
|
||||
protected static Method getSetBypass ()
|
||||
{
|
||||
try {
|
||||
Class<?> c = Class.forName("java.lang.reflect.AccessibleObject");
|
||||
return c.getMethod("setAccessible", new Class<?>[] { Boolean.TYPE });
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Unable to reflect AccessibleObject.setAccessible: " + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected String name;
|
||||
protected String listOfFields;
|
||||
protected String qualifiedListOfFields;
|
||||
protected String listOfAssignments;
|
||||
protected Class<T> _rowClass;
|
||||
|
||||
protected boolean mixedCaseConvert = false;
|
||||
|
||||
protected FieldDescriptor[] fields;
|
||||
protected FieldMask fMask;
|
||||
|
||||
protected int nFields; // length of "fields" array
|
||||
protected int nColumns; // number of atomic fields in "fields" array
|
||||
|
||||
protected String primaryKeys[];
|
||||
protected int primaryKeyIndices[];
|
||||
|
||||
protected Constructor<T> constructor;
|
||||
|
||||
protected static final Method setBypass = getSetBypass();
|
||||
protected static final Class<Serializable> serializableClass = Serializable.class;
|
||||
protected static final Object[] bypassFlag = { Boolean.TRUE };
|
||||
protected static final Object[] constructorArgs = {};
|
||||
|
||||
// used to identify byte[] fields
|
||||
protected static final byte[] BYTE_PROTO = new byte[0];
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides a mapping between JDBC tables and Java objects.
|
||||
|
||||
<p> This is a hacked version of JORA 2.06, a library for automatically
|
||||
mapping RDBMS database rows to Java objects and vice versa. It's
|
||||
simple, it uses reflection and doesn't require any classfile
|
||||
post-processing or external configuration information.
|
||||
|
||||
<p> "That's all great but why is it hacked?" wonders the astute
|
||||
reader. Well, I couldn't quite cope with the error handling paradigm
|
||||
that it used (catch all SQL exceptions internally and pass them to
|
||||
an application-wide error handler method) and a few seconds thought
|
||||
on the matter led me to believe that there is not a simple way to
|
||||
allow a choice of error handling paradigms (the methods have to
|
||||
throw SQLException or not). So I took the easy way out and forked
|
||||
the codebase.
|
||||
|
||||
<p> The code is pretty stable, so I don't expect to be merging in
|
||||
updates with any frequency. I promise to be a good monkey and submit
|
||||
any useful modifications that I make back to the original
|
||||
maintainer. Fortunately the license was unrestrictive enough to
|
||||
allow me to do this, otherwise I'd probably be stuck reinventing the
|
||||
wheel or using something that does less of what I want. Three cheers
|
||||
for free software.
|
||||
|
||||
<p> The JORA package was written by Konstantin Knizhnik and the
|
||||
original is available from
|
||||
<a href="http://www.ispras.ru/~knizhnik/">his web site.</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides support services to applications that access relational
|
||||
databases via JDBC.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Allows other entities in an application to register URLStreamHandler
|
||||
* classes for protocols of their own making.
|
||||
*/
|
||||
public class AttachableURLFactory implements URLStreamHandlerFactory
|
||||
{
|
||||
/**
|
||||
* Register a URL handler.
|
||||
*
|
||||
* @param protocol the protocol to register.
|
||||
* @param handlerClass a Class of type java.net.URLStreamHandler
|
||||
*/
|
||||
public static void attachHandler (
|
||||
String protocol, Class<? extends URLStreamHandler> handlerClass)
|
||||
{
|
||||
// set up the factory.
|
||||
if (_handlers == null) {
|
||||
_handlers = new HashMap<String,Class<? extends URLStreamHandler>>();
|
||||
|
||||
// There are two ways to do this.
|
||||
|
||||
// Method 1, which is the only one that seems to work under
|
||||
// Java Web Start, is to register a factory. This can throw an
|
||||
// Error if another factory is already registered. We let that
|
||||
// error bubble on back.
|
||||
URL.setURLStreamHandlerFactory(new AttachableURLFactory());
|
||||
|
||||
// Method 2 seems like a better idea but doesn't work under
|
||||
// Java Web Start. We add on a property that registers this
|
||||
// very class as the handler for the resource property. It
|
||||
// would be instantiated with Class.forName().
|
||||
// (And I did check, it's not dasho that is preventing this
|
||||
// from working under JWS, it's something else.)
|
||||
/*
|
||||
// dug up from java.net.URL
|
||||
String HANDLER_PROP = "java.protocol.handler.pkgs";
|
||||
|
||||
String prop = System.getProperty(HANDLER_PROP, "");
|
||||
if (!"".equals(prop)) {
|
||||
prop += "|";
|
||||
}
|
||||
prop += "com.threerings";
|
||||
System.setProperty(HANDLER_PROP, prop);
|
||||
*/
|
||||
}
|
||||
|
||||
_handlers.put(protocol.toLowerCase(), handlerClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not let others instantiate us.
|
||||
*/
|
||||
private AttachableURLFactory ()
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface URLStreamHandlerFactory
|
||||
public URLStreamHandler createURLStreamHandler (String protocol)
|
||||
{
|
||||
Class<? extends URLStreamHandler> handler = _handlers.get(protocol.toLowerCase());
|
||||
if (handler != null) {
|
||||
try {
|
||||
return handler.newInstance();
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to instantiate URLStreamHandler", "protocol", protocol,
|
||||
"cause", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** A mapping of protocol name to handler classes. */
|
||||
protected static HashMap<String,Class<? extends URLStreamHandler>> _handlers;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import com.samskivert.util.ServiceWaiter;
|
||||
|
||||
/**
|
||||
* Contains utility methods for doing a form post.
|
||||
*/
|
||||
public class HttpPostUtil
|
||||
{
|
||||
/**
|
||||
* Return the results of a form post. Note that the http request takes
|
||||
* place on another thread, but this thread blocks until the results
|
||||
* are returned or it times out.
|
||||
*
|
||||
* @param url from which to make the request.
|
||||
* @param submission the entire submission eg "foo=bar&baz=boo&futz=foo".
|
||||
* @param timeout time to wait for the response, in seconds, or -1
|
||||
* for forever.
|
||||
*/
|
||||
public static String httpPost (final URL url, final String submission,
|
||||
int timeout)
|
||||
throws IOException, ServiceWaiter.TimeoutException
|
||||
{
|
||||
final ServiceWaiter<String> waiter = new ServiceWaiter<String>(
|
||||
(timeout < 0) ? ServiceWaiter.NO_TIMEOUT : timeout);
|
||||
Thread tt = new Thread() {
|
||||
@Override public void run () {
|
||||
try {
|
||||
HttpURLConnection conn =
|
||||
(HttpURLConnection) url.openConnection();
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
conn.setRequestProperty(
|
||||
"Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
conn.getOutputStream());
|
||||
out.writeBytes(submission);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream()));
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String s; null != (s = reader.readLine()); ) {
|
||||
buf.append(s);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
waiter.postSuccess(buf.toString()); // yay
|
||||
|
||||
} catch (IOException e) {
|
||||
waiter.postFailure(e); // boo
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tt.start();
|
||||
|
||||
if (waiter.waitForResponse()) {
|
||||
return waiter.getArgument();
|
||||
} else {
|
||||
throw (IOException) waiter.getError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
|
||||
/**
|
||||
* Attempts to find all the MAC addresses on the machine.
|
||||
* This is accomplished by calling external programs specific
|
||||
* to the platform we are on, and parsing the results.
|
||||
*/
|
||||
public class MACUtil
|
||||
{
|
||||
/**
|
||||
public static void main (String[] args)
|
||||
{
|
||||
String testOutput = "YOUR TEST STRING GOES HERE";
|
||||
|
||||
String[] macs = parseMACs(testOutput);
|
||||
for (int ii = 0; ii < macs.length; ii++) {
|
||||
System.err.println("mac: " + macs[ii]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get all the MAC addresses of the hardware we are running on that we can find.
|
||||
*/
|
||||
public static String[] getMACAddresses ()
|
||||
{
|
||||
String [] cmds;
|
||||
if (RunAnywhere.isWindows()) {
|
||||
cmds = WINDOWS_CMDS;
|
||||
} else {
|
||||
cmds = UNIX_CMDS;
|
||||
}
|
||||
|
||||
return parseMACs(tryCommands(cmds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look through the text for all the MAC addresses we can find.
|
||||
*/
|
||||
protected static String[] parseMACs (String text)
|
||||
{
|
||||
Matcher m = MACRegex.matcher(text);
|
||||
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
while (m.find()) {
|
||||
String mac = m.group(1).toUpperCase();
|
||||
mac = mac.replace(':', '-');
|
||||
|
||||
// "Didn't you get that memo?" Apparently some people are not
|
||||
// up on MAC addresses actually being unique, so we will ignore those.
|
||||
//
|
||||
// 44-45-53-XX-XX-XX - PPP Adaptor
|
||||
// 00-53-45-XX-XX-XX - PPP Adaptor
|
||||
// 00-E0-06-09-55-66 - Some bogus run of ASUS motherboards
|
||||
// 00-04-4B-80-80-03 - Some nvidia built-in lan issues
|
||||
// 00-03-8A-XX-XX-XX - MiniWAN or AOL software
|
||||
// 02-03-8A-00-00-11 - Westell Dual (USB/Ethernet) modem
|
||||
// FF-FF-FF-FF-FF-FF - Tunnel adapter Teredo
|
||||
// 02-00-4C-4F-4F-50 - MSFT thinger, loopback of some sort
|
||||
if (mac.startsWith("44-45-53")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-53-45-00")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-E0-06-09-55-66")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-04-4B-80-80-03")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-03-8A")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("02-03-8A-00-00-11")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("FF-FF-FF-FF-FF-FF")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("02-00-4C-4F-4F-50")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list.add(mac);
|
||||
}
|
||||
|
||||
return list.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a lists of commands and tries to run each one till one works.
|
||||
* The idea being to be able to 'gracefully' cope with not knowing
|
||||
* where a command is installed on different installations.
|
||||
*/
|
||||
protected static String tryCommands (String[] cmds)
|
||||
{
|
||||
if (cmds == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String output;
|
||||
|
||||
for (int ii = 0; ii < cmds.length; ii++) {
|
||||
output = runCommand(cmds[ii]);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified command and return the output as a string.
|
||||
*/
|
||||
protected static String runCommand (String cmd)
|
||||
{
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec(cmd);
|
||||
BufferedReader cin = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
StringBuilder buffer= new StringBuilder();
|
||||
|
||||
String line = "";
|
||||
while (line != null)
|
||||
{
|
||||
buffer.append(line);
|
||||
line = cin.readLine();
|
||||
}
|
||||
cin.close();
|
||||
|
||||
return buffer.toString();
|
||||
} catch (IOException e) {
|
||||
// don't want to log anything for the client to know what we are doing.
|
||||
// This will almost always be thrown/caught when the cmd isn't there.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Look for 2 hex values in a row followed by a ':' or '-' repeated 5 times, followed by 2 hex
|
||||
* values. */
|
||||
protected static final Pattern MACRegex =
|
||||
Pattern.compile("((?:\\p{XDigit}{2}+[:\\-]){5}+\\p{XDigit}{2}+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// TODO maybe we should obfuscate these with rot13 or something so
|
||||
// that people can't run 'strings' on us and instantly see what we try
|
||||
protected static final String[] WINDOWS_CMDS = {"ipconfig /all"};
|
||||
protected static final String[] UNIX_CMDS = {"/sbin/ifconfig", "/etc/ifconfig"};
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The mail util class encapsulates some utility functions related to
|
||||
* sending emails.
|
||||
*/
|
||||
public class MailUtil
|
||||
{
|
||||
/**
|
||||
* Checks the supplied address against a regular expression that (for
|
||||
* the most part) matches only valid email addresses. It's not
|
||||
* perfect, but the omissions and inclusions are so obscure that we
|
||||
* can't be bothered to worry about them.
|
||||
*/
|
||||
public static boolean isValidAddress (String address)
|
||||
{
|
||||
return _emailre.matcher(address).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a normalized form of the specified email address
|
||||
* (everything after the @ sign is lowercased).
|
||||
*/
|
||||
public static String normalizeAddress (String address)
|
||||
{
|
||||
// this algorithm is tolerant of bogus input: if address has no
|
||||
// @ symbol it will cope.
|
||||
int afterAt = address.indexOf('@') + 1;
|
||||
return address.substring(0, afterAt) +
|
||||
address.substring(afterAt).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail message using the machine's local mail
|
||||
* SMTP server which must be listening on port 25.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs delivering the
|
||||
* email. See {@link Transport#send} for exceptions that can be thrown
|
||||
* due to response from the SMTP server.
|
||||
*/
|
||||
public static void deliverMail (String recipient, String sender,
|
||||
String subject, String body)
|
||||
throws IOException
|
||||
{
|
||||
deliverMail(new String[] { recipient }, sender, subject, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail message using the machine's local mail
|
||||
* SMTP server which must be listening on port 25. If the
|
||||
* <code>mail.smtp.host</code> system property is set, that will be
|
||||
* used instead of localhost.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs delivering the
|
||||
* email. See {@link Transport#send} for exceptions that can be thrown
|
||||
* due to response from the SMTP server.
|
||||
*/
|
||||
public static void deliverMail (String[] recipients, String sender,
|
||||
String subject, String body)
|
||||
throws IOException
|
||||
{
|
||||
deliverMail(recipients, sender, subject, body, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail, with the specified additional headers.
|
||||
*/
|
||||
public static void deliverMail (String[] recipients, String sender,
|
||||
String subject, String body,
|
||||
String[] headers, String[] values)
|
||||
throws IOException
|
||||
{
|
||||
if (recipients == null || recipients.length < 1) {
|
||||
throw new IOException("Must specify one or more recipients.");
|
||||
}
|
||||
|
||||
try {
|
||||
MimeMessage message = createEmptyMessage();
|
||||
int hcount = (headers == null) ? 0 : headers.length;
|
||||
for (int ii = 0; ii < hcount; ii++) {
|
||||
message.addHeader(headers[ii], values[ii]);
|
||||
}
|
||||
message.setText(body);
|
||||
deliverMail(recipients, sender, subject, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failure sending mail [from=" + sender +
|
||||
", to=" + StringUtil.toString(recipients) +
|
||||
", subject=" + subject + "]";
|
||||
IOException ioe = new IOException(errmsg);
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers an already-formed message to the specified recipients. This
|
||||
* message can be any mime type including multipart.
|
||||
*/
|
||||
public static void deliverMail (String[] recipients, String sender,
|
||||
String subject, MimeMessage message)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
message.setFrom(new InternetAddress(sender));
|
||||
for (String recipient : recipients) {
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient));
|
||||
}
|
||||
if (subject != null) {
|
||||
message.setSubject(subject);
|
||||
}
|
||||
Transport.send(message);
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failure sending mail [from=" + sender +
|
||||
", to=" + StringUtil.toString(recipients) +
|
||||
", subject=" + subject + "]";
|
||||
IOException ioe = new IOException(errmsg);
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an initialized, but empty message.
|
||||
*/
|
||||
public static final MimeMessage createEmptyMessage ()
|
||||
{
|
||||
Properties props = System.getProperties();
|
||||
if (props.getProperty("mail.smtp.host") == null) {
|
||||
props.put("mail.smtp.host", "localhost");
|
||||
}
|
||||
return new MimeMessage(Session.getDefaultInstance(props, null));
|
||||
}
|
||||
|
||||
/** Originally formulated by lambert@nas.nasa.gov. */
|
||||
protected static final String EMAIL_REGEX = "^([-A-Za-z0-9_.!%+]+@" +
|
||||
"[-a-zA-Z0-9]+(\\.[-a-zA-Z0-9]+)*\\.[-a-zA-Z0-9]+)$";
|
||||
|
||||
/** A compiled version of our email regular expression. */
|
||||
protected static final Pattern _emailre = Pattern.compile(EMAIL_REGEX);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Path related utility functions.
|
||||
*/
|
||||
public class PathUtil
|
||||
{
|
||||
/**
|
||||
* Replaces the final component in the supplied path with the
|
||||
* specified new component. For example, if <code>/foo/bar/baz</code>
|
||||
* was provided as the source path, <code>baz</code> would be replaced
|
||||
* with the supplied new path component. If no slashes occur in the
|
||||
* path, the entire path will be replaced. Note that this function is
|
||||
* intended for use on URLs rather than filesystem paths and thus
|
||||
* always uses forward slash rather than the platform defined path
|
||||
* separator.
|
||||
*/
|
||||
public static String replaceFinalComponent (String source,
|
||||
String newComponent)
|
||||
{
|
||||
int sidx = source.lastIndexOf("/");
|
||||
if (sidx != -1) {
|
||||
return source.substring(0, sidx+1) + newComponent;
|
||||
} else {
|
||||
return newComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the supplied affix to the specified source path, ensuring
|
||||
* that exactly one path separator (<code>/</code> as we are dealing
|
||||
* with URLs here not platform specific file-system paths) is used
|
||||
* between the two. <em>Note:</em> this means that the affix will be
|
||||
* made into a relative path regardless of whether or not it starts
|
||||
* with a <code>/</code>.
|
||||
*/
|
||||
public static String appendPath (String source, String affix)
|
||||
{
|
||||
if (source.endsWith("/")) {
|
||||
if (affix.startsWith("/")) {
|
||||
return source + affix.substring(1);
|
||||
} else {
|
||||
return source + affix;
|
||||
}
|
||||
} else if (affix.startsWith("/")) {
|
||||
return source + affix;
|
||||
} else {
|
||||
return source + "/" + affix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the individual path elements building up the canonical path to the given file.
|
||||
*/
|
||||
public static String[] getCanonicalPathElements (File file)
|
||||
throws IOException
|
||||
{
|
||||
file = file.getCanonicalFile();
|
||||
|
||||
// If we were a file, get its parent
|
||||
if (!file.isDirectory()) {
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
return file.getPath().split(File.separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a relative path between to Files
|
||||
*
|
||||
* @param file the file we're referencing
|
||||
* @param relativeTo the path from which we want to refer to the file
|
||||
*/
|
||||
public static String computeRelativePath (File file, File relativeTo)
|
||||
throws IOException
|
||||
{
|
||||
String[] realDirs = getCanonicalPathElements(file);
|
||||
String[] relativeToDirs = getCanonicalPathElements(relativeTo);
|
||||
|
||||
// Eliminate the common root
|
||||
int common = 0;
|
||||
for (; common < realDirs.length && common < relativeToDirs.length; common++) {
|
||||
if (!realDirs[common].equals(relativeToDirs[common])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String relativePath = "";
|
||||
|
||||
// For each remaining level in the file path, add a ..
|
||||
for (int ii = 0; ii < (realDirs.length - common); ii++) {
|
||||
relativePath += ".." + File.separator;
|
||||
}
|
||||
|
||||
// For each level in the resource path, add the path
|
||||
for (; common < relativeToDirs.length; common++) {
|
||||
relativePath += relativeToDirs[common] + File.separator;
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The CDDB class provides access to the information provided by servers compliant with the
|
||||
* <a href="http://www.freedb.org/software/old/CDDBPROTO">CDDB protocol</a>.
|
||||
*/
|
||||
public class CDDB
|
||||
{
|
||||
/**
|
||||
* The port on which CDDB servers are generally run.
|
||||
*/
|
||||
public static final int STANDARD_PORT = 888;
|
||||
|
||||
/**
|
||||
* The client name reported to the CDDB server.
|
||||
*/
|
||||
public static final String CLIENT_NAME = "TSP/CDDB_client";
|
||||
|
||||
/**
|
||||
* The client version reported to the CDDB server.
|
||||
*/
|
||||
public static final String CLIENT_VERSION = "1.0";
|
||||
|
||||
/**
|
||||
* This class encapsulates the information needed to look up a full CDDB record for a
|
||||
* particular disc. An array of them are returned in response to a CDDB query.
|
||||
*/
|
||||
public static class Entry
|
||||
{
|
||||
/** The category to which this entry belongs. */
|
||||
public String category;
|
||||
|
||||
/** The unique identifier for this entry. */
|
||||
public String cdid;
|
||||
|
||||
/** The human readable title of this entry. */
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* Parses values for this entry from the supplied source string. The source string should
|
||||
* contain an entry description as formatted by the CDDB server.
|
||||
*
|
||||
* @exception CDDBException Thrown if the entry is not properly formatted.
|
||||
*/
|
||||
public void parse (String source)
|
||||
throws CDDBException
|
||||
{
|
||||
int sidx1 = source.indexOf(" ");
|
||||
int sidx2 = source.indexOf(" ", sidx1+1);
|
||||
|
||||
if (sidx1 == -1 || sidx2 == -1) {
|
||||
throw new CDDBException(499, "Malformed entry '" + source + "'");
|
||||
}
|
||||
|
||||
category = source.substring(0, sidx1);
|
||||
cdid = source.substring(sidx1+1, sidx2);
|
||||
title = source.substring(sidx2+1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this CDDB instance to the CDDB server running on the supplied host using the
|
||||
* standard port.
|
||||
*
|
||||
* <p> <b>Note well:</b> you must close a CDDB connection once you are done with it, otherwise
|
||||
* the socket connection will remain open and pointlessly consume machine resources.
|
||||
*
|
||||
* @param hostname The host to which to connect.
|
||||
*
|
||||
* @exception IOException Thrown if a network error occurs attempting to connect to the host.
|
||||
* @exception CDDBException Thrown if an error occurs after identifying ourselves to the host.
|
||||
*
|
||||
* @return The message supplied with the succesful connection response.
|
||||
*
|
||||
* @see #STANDARD_PORT
|
||||
* @see #close
|
||||
*/
|
||||
public String connect (String hostname)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
return connect(hostname, STANDARD_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this CDDB instance to the CDDB server running on the supplied host using the
|
||||
* specified port.
|
||||
*
|
||||
* <p> <b>Note well:</b> you must close a CDDB connection once you are done with it, otherwise
|
||||
* the socket connection will remain open and pointlessly consume machine resources.
|
||||
*
|
||||
* @param hostname The host to which to connect.
|
||||
* @param port The port number on which to connect to the host.
|
||||
*
|
||||
* @exception IOException Thrown if a network error occurs attempting to connect to the host.
|
||||
* @exception CDDBException Thrown if an error occurs after identifying ourselves to the host.
|
||||
*
|
||||
* @return The message supplied with the succesful connection response.
|
||||
*
|
||||
* @see #close
|
||||
*/
|
||||
public String connect (String hostname, int port)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// obtain the necessary information we'll need to identify
|
||||
// ourselves to the CDDB server
|
||||
String localhost = InetAddress.getLocalHost().getHostName();
|
||||
String username = System.getProperty("user.name");
|
||||
if (username == null) {
|
||||
username = "anonymous";
|
||||
}
|
||||
|
||||
// establish our socket connection and IO streams
|
||||
InetAddress addr = InetAddress.getByName(hostname);
|
||||
_sock = new Socket(addr, port);
|
||||
_in = new BufferedReader(new InputStreamReader(_sock.getInputStream()));
|
||||
_out = new PrintStream(new BufferedOutputStream(_sock.getOutputStream()));
|
||||
|
||||
// first read (and discard) the banner string
|
||||
_in.readLine();
|
||||
|
||||
// send a hello request
|
||||
StringBuilder req = new StringBuilder("cddb hello ");
|
||||
req.append(username).append(" ");
|
||||
req.append(localhost).append(" ");
|
||||
req.append(CLIENT_NAME).append(" ");
|
||||
req.append(CLIENT_VERSION);
|
||||
|
||||
Response rsp = request(req.toString());
|
||||
|
||||
// confirm that the response was a successful one
|
||||
if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK &&
|
||||
rsp.code != 402 /* already shook hands */) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
return rsp.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the number of milliseconds that the client should wait for a response from the
|
||||
* server before aborting. This must be called <em>after</em> a successful call to connect,
|
||||
* otherwise the timeout will not be set.
|
||||
*/
|
||||
public void setTimeout (int timeout)
|
||||
throws SocketException
|
||||
{
|
||||
if (_sock != null) {
|
||||
_sock.setSoTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this CDDB instance is connected to a CDDB server.
|
||||
*/
|
||||
public boolean connected ()
|
||||
{
|
||||
return _sock != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the CDDB server previously opened with a call to {@link #connect}.
|
||||
* If the connection was not previously established, this member function does nothing.
|
||||
*/
|
||||
public void close ()
|
||||
throws IOException
|
||||
{
|
||||
if (_sock != null) {
|
||||
_sock.close();
|
||||
|
||||
// clear out our references
|
||||
_sock = null;
|
||||
_in = null;
|
||||
_out = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a query to the CDDB server using the supplied CD identifying information.
|
||||
*
|
||||
* @param discid The disc identifier (information on how to compute the disc ID is available <a
|
||||
* href="http://www.freedb.org/sections.php?op=viewarticle&artid=6"> here</a>.
|
||||
* @param frameOffsets The frame offset of each track. The length of this array is assumed to
|
||||
* be the number of tracks on the CD and is used in the query.
|
||||
* @param length The length (in seconds) of the CD.
|
||||
*
|
||||
* @return If no entry matches the query, null is returned. Otherwise one or more entries is
|
||||
* returned that matched the query parameters.
|
||||
*/
|
||||
public Entry[] query (String discid, int[] frameOffsets, int length)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// sanity check
|
||||
if (_sock == null) {
|
||||
throw new CDDBException(500, "Not connected");
|
||||
}
|
||||
|
||||
// construct the query parameter
|
||||
StringBuilder req = new StringBuilder("cddb query ");
|
||||
req.append(discid).append(" ");
|
||||
req.append(frameOffsets.length).append(" ");
|
||||
for (int frameOffset : frameOffsets) {
|
||||
req.append(frameOffset).append(" ");
|
||||
}
|
||||
req.append(length);
|
||||
|
||||
// make the request
|
||||
Response rsp = request(req.toString());
|
||||
Entry[] entries = null;
|
||||
|
||||
// if this is an exact match, parse the entry and return it
|
||||
if (rsp.code == 200 /* exact match */) {
|
||||
entries = new Entry[1];
|
||||
entries[0] = new Entry();
|
||||
entries[0].parse(rsp.message);
|
||||
|
||||
} else if (rsp.code == 211 /* inexact matches */) {
|
||||
// read the matches from the server
|
||||
ArrayList<Entry> list = new ArrayList<Entry>();
|
||||
String input = _in.readLine();
|
||||
while (input != null && !input.equals(CDDBProtocol.TERMINATOR)) {
|
||||
System.out.println("...: " + input);
|
||||
Entry e = new Entry();
|
||||
e.parse(input);
|
||||
list.add(e);
|
||||
input = _in.readLine();
|
||||
}
|
||||
entries = new Entry[list.size()];
|
||||
list.toArray(entries);
|
||||
|
||||
} else if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* A detail object contains all of the detailed information about a particular CD as retrieved
|
||||
* from the CDDB server.
|
||||
*/
|
||||
public static class Detail
|
||||
{
|
||||
/** The unique identifier for this CD. */
|
||||
public String discid;
|
||||
|
||||
/** The category to which this CD belongs. */
|
||||
public String category;
|
||||
|
||||
/** The title of this CD. */
|
||||
public String title;
|
||||
|
||||
/** The track names. */
|
||||
public String[] trackNames;
|
||||
|
||||
/** The extended data for the CD. */
|
||||
public String extendedData;
|
||||
|
||||
/** The extended data for each track. */
|
||||
public String[] extendedTrackData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the detail information for a particular disc in a particular category from the CDDB
|
||||
* server.
|
||||
*
|
||||
* @return A detail instance filled with the information for the requested CD.
|
||||
*/
|
||||
public Detail read (String category, String discid)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// sanity check
|
||||
if (_sock == null) {
|
||||
throw new CDDBException(500, "Not connected");
|
||||
}
|
||||
|
||||
// construct the query
|
||||
StringBuilder req = new StringBuilder("cddb read ");
|
||||
req.append(category).append(" ");
|
||||
req.append(discid);
|
||||
|
||||
// make the request
|
||||
Response rsp = request(req.toString());
|
||||
// anything other than an OK response earns an exception
|
||||
if (rsp.code != 210 /* OK, entry follows */) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
Detail detail = new Detail();
|
||||
|
||||
// parse the category and discid from the response string
|
||||
int sidx = rsp.message.indexOf(" ");
|
||||
if (sidx == -1) {
|
||||
throw new CDDBException(500, "Malformed read response: " +
|
||||
rsp.message);
|
||||
}
|
||||
detail.category = rsp.message.substring(0, sidx);
|
||||
detail.discid = rsp.message.substring(sidx+1);
|
||||
|
||||
ArrayList<String> tnames = new ArrayList<String>();
|
||||
ArrayList<String> texts = new ArrayList<String>();
|
||||
|
||||
// now parse the contents
|
||||
String input = _in.readLine();
|
||||
for (int lno = 0; !input.equals(CDDBProtocol.TERMINATOR); lno++) {
|
||||
if (input.startsWith("#")) {
|
||||
// skip comments
|
||||
|
||||
} else if (input.startsWith("DTITLE")) {
|
||||
if (detail.title == null) {
|
||||
detail.title = contents(input, lno);
|
||||
} else {
|
||||
detail.title += contents(input, lno);
|
||||
}
|
||||
|
||||
} else if (input.startsWith("EXTD")) {
|
||||
if (detail.extendedData == null) {
|
||||
detail.extendedData = contents(input, lno);
|
||||
} else {
|
||||
detail.extendedData += contents(input, lno);
|
||||
}
|
||||
|
||||
} else if (input.startsWith("TTITLE")) {
|
||||
append(tnames, index(input, "TTITLE", lno), contents(input, lno));
|
||||
|
||||
} else if (input.startsWith("EXTT")) {
|
||||
append(texts, index(input, "EXTT", lno), contents(input, lno));
|
||||
}
|
||||
|
||||
// read in the next line of input
|
||||
input = _in.readLine();
|
||||
}
|
||||
|
||||
// convert the lists into arrays
|
||||
detail.trackNames = new String[tnames.size()];
|
||||
tnames.toArray(detail.trackNames);
|
||||
detail.extendedTrackData = new String[texts.size()];
|
||||
texts.toArray(detail.extendedTrackData);
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the track index of the supplied line (the number immediately following the supplied
|
||||
* prefix and preceding the equals sign) or throws a CDDBException if the line contains no
|
||||
* equals sign or track index.
|
||||
*/
|
||||
protected final int index (String source, String prefix, int lineno)
|
||||
throws CDDBException
|
||||
{
|
||||
int eidx = source.indexOf("=", prefix.length());
|
||||
if (eidx == -1) {
|
||||
throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(source.substring(prefix.length(), eidx));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the contents of the supplied line (everything after the equals sign) or throws a
|
||||
* CDDBException if the line contains no equals sign.
|
||||
*/
|
||||
protected final String contents (String source, int lineno)
|
||||
throws CDDBException
|
||||
{
|
||||
int eidx = source.indexOf("=");
|
||||
if (eidx == -1) {
|
||||
throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
|
||||
}
|
||||
return source.substring(eidx+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the supplied string to the contents of the list at the supplied index. If the list
|
||||
* has no contents at the supplied index, the supplied value becomes the contents at that
|
||||
* index.
|
||||
*/
|
||||
protected final void append (
|
||||
ArrayList<String> list, int index, String value)
|
||||
{
|
||||
// expand the list as necessary
|
||||
while (list.size() <= index) {
|
||||
list.add("");
|
||||
}
|
||||
list.set(index, list.get(index) + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a request to the CDDB server and parses the response.
|
||||
*/
|
||||
protected Response request (String req)
|
||||
throws IOException
|
||||
{
|
||||
System.err.println("REQ:" + req);
|
||||
|
||||
// send the request to the server
|
||||
_out.println(req);
|
||||
_out.flush();
|
||||
|
||||
// now read the response
|
||||
String rspstr = _in.readLine();
|
||||
// if they closed the connection on us, we should deal
|
||||
if (rspstr == null) {
|
||||
throw new EOFException();
|
||||
}
|
||||
System.err.println("RSP:" + rspstr);
|
||||
|
||||
// parse the response
|
||||
Response rsp = new Response();
|
||||
String codestr = rspstr;
|
||||
|
||||
// assume the response is just a code unless we see a space
|
||||
int sidx = rspstr.indexOf(" ");
|
||||
if (sidx != -1) {
|
||||
codestr = rspstr.substring(0, sidx);
|
||||
rsp.message = rspstr.substring(sidx+1);
|
||||
}
|
||||
|
||||
try {
|
||||
rsp.code = Integer.parseInt(codestr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
rsp.code = 599;
|
||||
rsp.message = "Unparseable response";
|
||||
}
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class to encapsulate the response from the CDDB server.
|
||||
*/
|
||||
protected static class Response
|
||||
{
|
||||
public int code;
|
||||
public String message;
|
||||
}
|
||||
|
||||
protected Socket _sock;
|
||||
protected BufferedReader _in;
|
||||
protected PrintStream _out;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* This exception class encapsulates errors that may occur while
|
||||
* communicating to a CDDB server. It is not used to communicate IO errors
|
||||
* (an IOException is used for that), but it is used to communicate
|
||||
* failures communicated within the scope of the CDDB protocol.
|
||||
*
|
||||
* @see CDDB
|
||||
*/
|
||||
public class CDDBException extends Exception
|
||||
{
|
||||
public CDDBException (int code, String message)
|
||||
{
|
||||
super(message);
|
||||
_code = code;
|
||||
}
|
||||
|
||||
public int getCode ()
|
||||
{
|
||||
return _code;
|
||||
}
|
||||
|
||||
protected int _code;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* The CDDB protocol class provides constants related to the CDDB protocol
|
||||
* as well as utility routines useful in dealing with the protocol.
|
||||
*/
|
||||
public class CDDBProtocol
|
||||
{
|
||||
/**
|
||||
* The terminating marker which occurs alone on it's own line to
|
||||
* indicate the end of an extended command.
|
||||
*/
|
||||
public static final String TERMINATOR = ".";
|
||||
|
||||
/* Class constant offsets */
|
||||
public static final int INFORMATIONAL = 100;
|
||||
public static final int OK = 200;
|
||||
public static final int OK_WITH_CONTINUATION = 300;
|
||||
public static final int UNABLE_TO_PERFORM = 400;
|
||||
public static final int SERVER_ERROR = 500;
|
||||
|
||||
/**
|
||||
* @return The family to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeFamily (int code)
|
||||
{
|
||||
return (code / 100) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The genus to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeGenus (int code)
|
||||
{
|
||||
return ((code % 100) / 10) * 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The species to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeSpecies (int code)
|
||||
{
|
||||
return code % 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* Tests the CDDB code by connecting to a CDDB server and making some
|
||||
* requests.
|
||||
*/
|
||||
public class CDDBTest
|
||||
{
|
||||
public static void test (String hostname, String cdid)
|
||||
throws Exception
|
||||
{
|
||||
CDDB cddb = new CDDB();
|
||||
|
||||
try {
|
||||
/* String rsp = */ cddb.connect(hostname);
|
||||
|
||||
// set the timeout to 30 seconds
|
||||
cddb.setTimeout(30*1000);
|
||||
|
||||
// try a test query
|
||||
int[] offsets = { 150, 18130, 48615 };
|
||||
int length = 893;
|
||||
CDDB.Entry[] entries = cddb.query(cdid, offsets, length);
|
||||
|
||||
if (entries == null || entries.length == 0) {
|
||||
System.out.println("No match for " + cdid + ".");
|
||||
|
||||
} else {
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
System.out.println("Match " + entries[i].category + "/" +
|
||||
entries[i].cdid + "/" +
|
||||
entries[i].title);
|
||||
}
|
||||
|
||||
CDDB.Detail detail = cddb.read(entries[0].category,
|
||||
entries[0].cdid);
|
||||
System.out.println("Title: " + detail.title);
|
||||
for (int i = 0; i < detail.trackNames.length; i++) {
|
||||
System.out.println(pad(i) + ": " + detail.trackNames[i]);
|
||||
}
|
||||
System.out.println("Extended data: " + detail.extendedData);
|
||||
for (int i = 0; i < detail.extendedTrackData.length; i++) {
|
||||
System.out.println(pad(i) + ": " + detail.extendedTrackData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
cddb.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected static String pad (int value)
|
||||
{
|
||||
return ((value > 9) ? "" : " ") + value;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: CDDBTest cddbhost");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
test(args[0], "1b037b03");
|
||||
|
||||
} catch (CDDBException ce) {
|
||||
System.err.println("Protocol exception: " + ce.getCode() + ": " + ce.getMessage());
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Routines for accessing a CDDB server.
|
||||
|
||||
<p> The {@link com.samskivert.net.cddb.CDDB} class provides the
|
||||
primary interface to the CDDB services. This service does not
|
||||
provide a means to obtain the CD identification information that
|
||||
you'll need to perform a CDDB lookup. In the application that
|
||||
motivated the creation of these services, a <code>cdparanoia</code>
|
||||
process was spawned to read the disc info. Other mechanisms surely
|
||||
exist.
|
||||
|
||||
<p> Use of the CDDB class is fairly straightforward:
|
||||
|
||||
<pre>
|
||||
CDDB cddb = new CDDB();
|
||||
cddb.connect("www.freedb.org");
|
||||
CDDB.Entry[] entries = cddb.query(discid, frameOffsets, length);
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
CDDB.Detail detail = cddb.read(entries[i].category,
|
||||
entries[i].discid);
|
||||
// do your thang now...
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p> These services presently do not provide a mechanism for submitting
|
||||
a CDDB entry (which involves sending an email). It surely will in
|
||||
the future.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides network related services and patterns.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: overview.html,v 1.1 2001/08/12 01:45:46 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
The samskivert library (SL) aims to provide useful reusable Java
|
||||
routines that do things for which I've been unable to find useful
|
||||
reusable implementations on the net.
|
||||
|
||||
<p> Given the emphasis on reusability, SL attempts to closely adhere
|
||||
to the following principles:
|
||||
|
||||
<ul>
|
||||
<li><p> Each individual module should depend as little as possible
|
||||
on other SL modules. Obvious exceptions include modules that are
|
||||
a logical extension of other modules and modules that clearly
|
||||
require a service that is implemented by another SL module and
|
||||
would have to implement that service themselves in the absence
|
||||
of dependence on the other module.
|
||||
|
||||
<li><p> Modules should be both simple to use and as general purpose
|
||||
as possible. To meet these two competing requirements, a balance
|
||||
must be struck at that sweet spot where reusability is
|
||||
maximized.
|
||||
|
||||
<li><p> Code included in SL will freely depend on JDK packages
|
||||
available in the Java 2 platform and beyond. SL is initially a
|
||||
repository of software useful for server-side or stand alone
|
||||
applications and therefore need not make compromises to function
|
||||
in the jungle of JVMs in commonly available web browsers.
|
||||
|
||||
<li><p> We are not here to reinvent the wheel, nor to provide a
|
||||
uniform interface to every software service under the sun. If
|
||||
something is available in a freely redistributable and reusable
|
||||
form from someone else, it won't be found in SL. If SL depends
|
||||
on such software from another source, it will provide clear
|
||||
documentation on how to get that software and make use of it
|
||||
within the scope of SL's particular needs. Again a balance of
|
||||
reusability will be struck here and software that is
|
||||
sufficiently difficult to make usable in an arbitrary
|
||||
environment will not be used by SL and may be "reinvented".
|
||||
</ul>
|
||||
|
||||
<p> I and others who have contributed to the library hope that you
|
||||
find it useful, easy to use and time saving. Comments and
|
||||
contributions are always welcome.
|
||||
|
||||
<p> -- <a href="mailto:mdb@samskivert.com">mdb@samskivert.com</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Nothing lives here except the {@link com.samskivert.util.Log} wrapper
|
||||
that is used by the samskivert services to generate log messages.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* An http error exception is thrown by servlet services when they wish to fail
|
||||
* the request in some particular way (ie. {@link
|
||||
* HttpServletResponse#SC_FORBIDDEN}, etc.). It is expected that error handling
|
||||
* can be implemented in a single place such that servlets can simply allow
|
||||
* this exception to propagate up to the proper handler which will then issue
|
||||
* the appropriate failure header.
|
||||
*/
|
||||
public class HttpErrorException extends Exception
|
||||
{
|
||||
public HttpErrorException (int errorCode)
|
||||
{
|
||||
super(String.valueOf(errorCode));
|
||||
_errorCode = errorCode;
|
||||
}
|
||||
|
||||
public HttpErrorException (int errorCode, String errorMessage)
|
||||
{
|
||||
super(errorMessage);
|
||||
_errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP error code supplied with this exception.
|
||||
*/
|
||||
public int getErrorCode ()
|
||||
{
|
||||
return _errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual error message supplied with this exception or null
|
||||
* if only an error code was supplied.
|
||||
*/
|
||||
public String getErrorMessage ()
|
||||
{
|
||||
String msg = getMessage();
|
||||
return String.valueOf(_errorCode).equals(msg) ? null : msg;
|
||||
}
|
||||
|
||||
protected int _errorCode;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Used by default and for systems that have no need to discriminate between different sites in
|
||||
* their web applications.
|
||||
*/
|
||||
public class IndiscriminateSiteIdentifier implements SiteIdentifier
|
||||
{
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_ID} regardless of the information in the request.
|
||||
*/
|
||||
public int identifySite (HttpServletRequest req)
|
||||
{
|
||||
return DEFAULT_SITE_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_STRING} regardless of the value of the supplied
|
||||
* identifer.
|
||||
*/
|
||||
public String getSiteString (int siteId)
|
||||
{
|
||||
return DEFAULT_SITE_STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_ID} regardless of the value of the supplied string.
|
||||
*/
|
||||
public int getSiteId (String siteString)
|
||||
{
|
||||
return DEFAULT_SITE_ID;
|
||||
}
|
||||
|
||||
// documented inherited from interface
|
||||
public Iterator<Site> enumerateSites ()
|
||||
{
|
||||
return _sites.iterator();
|
||||
}
|
||||
|
||||
protected static ArrayList<Site> _sites = new ArrayList<Site>();
|
||||
static {
|
||||
_sites.add(new Site(DEFAULT_SITE_ID, DEFAULT_SITE_STRING));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.SimpleRepository;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Accomplishes the process of site identification based on a mapping from domains (e.g.
|
||||
* samskivert.com) to site identifiers that is maintained in a database table, accessible via JDBC
|
||||
* (hence the name).
|
||||
*
|
||||
* <p> There are two tables, one that maps domains to site identifiers and another that maps site
|
||||
* identifiers to site strings. These are both loaded at construct time and refreshed periodically
|
||||
* in the course of normal operation.
|
||||
*
|
||||
* <p> Note that any of the calls to identify, lookup or enumerate site information can result in
|
||||
* the sites table being refreshed from the database which will take relatively much longer than
|
||||
* the simple hashtable lookup that the operations normally require. However, this happens only
|
||||
* once every 15 minutes and the circumstances in which the site identifier are normally used can
|
||||
* generally accomodate the extra 100 milliseconds or so that it is likely to take to reload the
|
||||
* (tiny) sites and domains tables from the database.
|
||||
*/
|
||||
public class JDBCTableSiteIdentifier implements SiteIdentifier
|
||||
{
|
||||
/** The database identifier used to obtain a connection from our connection provider. The value
|
||||
* is <code>sitedb</code> which you'll probably need to know to provide the proper
|
||||
* configuration to your connection provider. */
|
||||
public static final String SITE_IDENTIFIER_IDENT = "sitedb";
|
||||
|
||||
/**
|
||||
* Constructs a JDBC table site identifier with the supplied connection provider from which to
|
||||
* obtain its database connection.
|
||||
*
|
||||
* @see #SITE_IDENTIFIER_IDENT
|
||||
*/
|
||||
public JDBCTableSiteIdentifier (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
this(conprov, DEFAULT_SITE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an identifier that will load data from the supplied connection provider and which
|
||||
* will use the supplied default site id instead of {@link #DEFAULT_SITE_ID}.
|
||||
*/
|
||||
public JDBCTableSiteIdentifier (ConnectionProvider conprov, int defaultSiteId)
|
||||
throws PersistenceException
|
||||
{
|
||||
_repo = new SiteIdentifierRepository(conprov);
|
||||
_repo.refreshSiteData();
|
||||
_defaultSiteId = defaultSiteId;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int identifySite (HttpServletRequest req)
|
||||
{
|
||||
checkReloadSites();
|
||||
String serverName = req.getServerName();
|
||||
|
||||
// scan for the mapping that matches the specified domain
|
||||
int msize = _mappings.size();
|
||||
for (int i = 0; i < msize; i++) {
|
||||
SiteMapping mapping = _mappings.get(i);
|
||||
if (serverName.endsWith(mapping.domain)) {
|
||||
return mapping.siteId;
|
||||
}
|
||||
}
|
||||
|
||||
// if we matched nothing, return the default id
|
||||
return _defaultSiteId;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public String getSiteString (int siteId)
|
||||
{
|
||||
checkReloadSites();
|
||||
Site site = _sitesById.get(siteId);
|
||||
if (site == null) {
|
||||
site = _sitesById.get(_defaultSiteId);
|
||||
}
|
||||
return (site == null) ? DEFAULT_SITE_STRING : site.siteString;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getSiteId (String siteString)
|
||||
{
|
||||
checkReloadSites();
|
||||
Site site = _sitesByString.get(siteString);
|
||||
return (site == null) ? _defaultSiteId : site.siteId;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Iterator<Site> enumerateSites ()
|
||||
{
|
||||
checkReloadSites();
|
||||
return _sitesById.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new site into the site table and into this mapping.
|
||||
*/
|
||||
public Site insertNewSite (String siteString)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (_sitesByString.containsKey(siteString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add it to the db
|
||||
Site site = new Site();
|
||||
site.siteString = siteString;
|
||||
_repo.insertNewSite(site);
|
||||
|
||||
// add it to our two mapping tables, taking care to avoid causing enumerateSites() to choke
|
||||
@SuppressWarnings("unchecked") HashMap<String,Site> newStrings =
|
||||
(HashMap<String,Site>)_sitesByString.clone();
|
||||
HashIntMap<Site> newIds = _sitesById.clone();
|
||||
newIds.put(site.siteId, site);
|
||||
newStrings.put(site.siteString, site);
|
||||
_sitesByString = newStrings;
|
||||
_sitesById = newIds;
|
||||
|
||||
return site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if we should reload our sites information from the sites table.
|
||||
*/
|
||||
protected void checkReloadSites ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
boolean reload = false;
|
||||
synchronized (this) {
|
||||
reload = (now - _lastReload > RELOAD_INTERVAL);
|
||||
if (reload) {
|
||||
_lastReload = now;
|
||||
}
|
||||
}
|
||||
if (reload) {
|
||||
try {
|
||||
_repo.refreshSiteData();
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Error refreshing site data.", pe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to load information from the site database.
|
||||
*/
|
||||
protected class SiteIdentifierRepository extends SimpleRepository
|
||||
implements SimpleRepository.Operation<Object>
|
||||
{
|
||||
public SiteIdentifierRepository (ConnectionProvider conprov)
|
||||
{
|
||||
super(conprov, SITE_IDENTIFIER_IDENT);
|
||||
}
|
||||
|
||||
public void refreshSiteData ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// we are the operation!
|
||||
execute(this);
|
||||
}
|
||||
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
// first load up the list of sites
|
||||
String query = "select siteId, siteString from sites";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
HashIntMap<Site> sites = new HashIntMap<Site>();
|
||||
HashMap<String,Site> strings = new HashMap<String,Site>();
|
||||
while (rs.next()) {
|
||||
Site site = new Site(rs.getInt(1), rs.getString(2));
|
||||
sites.put(site.siteId, site);
|
||||
strings.put(site.siteString, site);
|
||||
}
|
||||
_sitesById = sites;
|
||||
_sitesByString = strings;
|
||||
|
||||
// now load up the domain mappings
|
||||
query = "select domain, siteId from domains";
|
||||
rs = stmt.executeQuery(query);
|
||||
ArrayList<SiteMapping> mappings = new ArrayList<SiteMapping>();
|
||||
while (rs.next()) {
|
||||
mappings.add(new SiteMapping(rs.getInt(2), rs.getString(1)));
|
||||
}
|
||||
|
||||
// sort the mappings in order of specificity
|
||||
Collections.sort(mappings, SiteMapping.BY_SPECIFICITY);
|
||||
_mappings = mappings;
|
||||
// Log.info("Loaded site mappings " + StringUtil.toString(_mappings) + ".");
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new site to the database.
|
||||
*/
|
||||
public void insertNewSite (final Site site)
|
||||
throws PersistenceException
|
||||
{
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement("insert into sites (siteString) VALUES (?)");
|
||||
stmt.setString(1, site.siteString);
|
||||
if (1 != stmt.executeUpdate()) {
|
||||
throw new PersistenceException("Not inserted " + site);
|
||||
}
|
||||
site.siteId = liaison.lastInsertedId(conn, "sites", "siteId");
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to track domain to site identifier mappings.
|
||||
*/
|
||||
protected static class SiteMapping
|
||||
{
|
||||
/**
|
||||
* Sorts site mappings from most specific (www.yahoo.com) to least specific (yahoo.com).
|
||||
*/
|
||||
public static final Comparator<SiteMapping> BY_SPECIFICITY = new Comparator<SiteMapping>() {
|
||||
public int compare (SiteMapping one, SiteMapping two) {
|
||||
return one._rdomain.compareTo(two._rdomain);
|
||||
}
|
||||
};
|
||||
|
||||
/** The domain to match. */
|
||||
public String domain;
|
||||
|
||||
/** The site identifier for the associated domain. */
|
||||
public int siteId;
|
||||
|
||||
public SiteMapping (int siteId, String domain) {
|
||||
this.siteId = siteId;
|
||||
this.domain = domain;
|
||||
byte[] bytes = domain.getBytes();
|
||||
ArrayUtil.reverse(bytes);
|
||||
_rdomain = new String(bytes);
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public String toString () {
|
||||
return "[" + domain + " => " + siteId + "]";
|
||||
}
|
||||
|
||||
protected String _rdomain;
|
||||
}
|
||||
|
||||
/** The repository through which we load up site identifier information. */
|
||||
protected SiteIdentifierRepository _repo;
|
||||
|
||||
/** The site id to return if we cannot identify the site from our table data. */
|
||||
protected int _defaultSiteId;
|
||||
|
||||
/** The list of domain to site identifier mappings ordered from most specific domain to least
|
||||
* specific. */
|
||||
protected volatile ArrayList<SiteMapping> _mappings = new ArrayList<SiteMapping>();
|
||||
|
||||
/** The mapping from integer site identifiers to string site identifiers. */
|
||||
protected volatile HashIntMap<Site> _sitesById = new HashIntMap<Site>();
|
||||
|
||||
/** The mapping from string site identifiers to integer site identifiers. */
|
||||
protected volatile HashMap<String,Site> _sitesByString = new HashMap<String,Site>();
|
||||
|
||||
/** Used to periodically reload our site data. */
|
||||
protected long _lastReload;
|
||||
|
||||
/** Reload our site data every 15 minutes. */
|
||||
protected static final long RELOAD_INTERVAL = 15 * 60 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.text.MessageUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The message manager handles the translation messages for a web application. The webapp should
|
||||
* construct the message manager with the name of its message properties file and it can then make
|
||||
* use of the message manager to generate locale specific messages for a request.
|
||||
*/
|
||||
public class MessageManager
|
||||
{
|
||||
/**
|
||||
* Constructs a message manager with the specified bundle path. The message manager will be
|
||||
* instantiating a <code>ResourceBundle</code> with the supplied path, so it should conform to
|
||||
* the naming conventions defined by that class.
|
||||
*
|
||||
* @param siteIdent an optional site identifier that can be used to identify the site via which
|
||||
* an http request was made.
|
||||
*
|
||||
* @see java.util.ResourceBundle
|
||||
*/
|
||||
public MessageManager (String bundlePath, Locale deflocale, SiteIdentifier siteIdent)
|
||||
{
|
||||
// keep these for later
|
||||
_bundlePath = bundlePath;
|
||||
_deflocale = deflocale;
|
||||
_siteIdent = siteIdent;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the message manager is to be used in a multi-site environment, it can be configured to
|
||||
* load site-specific message resources in addition to the default application message
|
||||
* resources. It must be configured with the facilities to load site-specific resources by a
|
||||
* call to this function.
|
||||
*
|
||||
* @param siteBundlePath the path to the site-specific message resources.
|
||||
* @param siteLoader a site-specific resource loader, properly configured with a site
|
||||
* identifier.
|
||||
*/
|
||||
public void activateSiteSpecificMessages (String siteBundlePath, SiteResourceLoader siteLoader)
|
||||
{
|
||||
_siteBundlePath = siteBundlePath;
|
||||
_siteLoader = siteLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the specifed path exists in the resource bundle.
|
||||
*/
|
||||
public boolean exists (HttpServletRequest req, String path)
|
||||
{
|
||||
return (getMessage(req, path, false) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle most appropriate for the
|
||||
* locales described as preferred by the request. Always reports missing paths.
|
||||
*/
|
||||
public String getMessage (HttpServletRequest req, String path)
|
||||
{
|
||||
return getMessage(req, path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle most appropriate for the
|
||||
* locales described as preferred by the request, then substitutes the supplied arguments into
|
||||
* that message using a <code>MessageFormat</code> object.
|
||||
*
|
||||
* @see java.text.MessageFormat
|
||||
*/
|
||||
public String getMessage (HttpServletRequest req, String path, Object[] args)
|
||||
{
|
||||
String msg = getMessage(req, path, true);
|
||||
// we may cache message formatters later, but for now just use the static convenience
|
||||
// function
|
||||
return MessageFormat.format(MessageUtil.escape(msg), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle most appropriate for the
|
||||
* locales described as preferred by the request. If requested it will log a missing path and
|
||||
* return the path as the translation (which should make it obvious in the servlet that the
|
||||
* translation is missing) otherwise it returns null.
|
||||
*/
|
||||
protected String getMessage (HttpServletRequest req, String path, boolean reportMissing)
|
||||
{
|
||||
if (path == null) {
|
||||
return "[null message key]";
|
||||
}
|
||||
|
||||
// if the key is tainted, just return the untainted key
|
||||
if (MessageUtil.isTainted(path)) {
|
||||
return MessageUtil.untaint(path);
|
||||
}
|
||||
|
||||
// attempt to determine whether or not this is a compound key
|
||||
int tidx = path.indexOf("|");
|
||||
if (tidx != -1) {
|
||||
String key = path.substring(0, tidx);
|
||||
String argstr = path.substring(tidx+1);
|
||||
String[] args = StringUtil.split(argstr, "|");
|
||||
// unescape and translate the arguments
|
||||
for (int ii = 0; ii < args.length; ii++) {
|
||||
// if the argument is tainted, do no further translation (it might contain |s or
|
||||
// other fun stuff)
|
||||
if (MessageUtil.isTainted(args[ii])) {
|
||||
args[ii] = MessageUtil.unescape(MessageUtil.untaint(args[ii]));
|
||||
} else {
|
||||
args[ii] = getMessage(req, MessageUtil.unescape(args[ii]));
|
||||
}
|
||||
}
|
||||
return getMessage(req, key, args);
|
||||
}
|
||||
|
||||
// load up the matching resource bundles (the array will contain the site-specific
|
||||
// resources first and the application resources second); use the locale preferred by the
|
||||
// client if possible
|
||||
ResourceBundle[] bundles = resolveBundles(req);
|
||||
String message = null;
|
||||
if (bundles != null) {
|
||||
int blength = bundles.length;
|
||||
for (int i = 0; message == null && i < blength; i++) {
|
||||
try {
|
||||
if (bundles[i] != null) {
|
||||
message = bundles[i].getString(path);
|
||||
}
|
||||
} catch (MissingResourceException mre) {
|
||||
// no complaints, just try the bundle in the enclosing scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we found a message, check it for embedded message links
|
||||
if (message != null) {
|
||||
int oidx = -1;
|
||||
while ((oidx = message.indexOf("{", oidx+1)) != -1) {
|
||||
int cidx = message.indexOf("}", oidx+1);
|
||||
if (cidx == -1) {
|
||||
// something's funny, just stop fiddling
|
||||
break;
|
||||
}
|
||||
String ref = message.substring(oidx+1, cidx);
|
||||
// avoid trivial infinite recursion
|
||||
if (ref.equals(path)) {
|
||||
throw new IllegalStateException(
|
||||
"Illegal self-referential message " + path + " = " + message + ".");
|
||||
}
|
||||
if (ref.length() > 0 && !Character.isDigit(ref.charAt(0))) {
|
||||
String refmsg = getMessage(req, ref, true);
|
||||
message = message.substring(0, oidx) + refmsg + message.substring(cidx+1);
|
||||
oidx += refmsg.length();
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
if (reportMissing) {
|
||||
// if there's no translation for this path, complain about it
|
||||
log.warning("Missing translation message", "path", path, "url", getURL(req));
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest matching resource bundle for the locales specified as preferred by the
|
||||
* client in the supplied http request.
|
||||
*/
|
||||
protected ResourceBundle[] resolveBundles (HttpServletRequest req)
|
||||
{
|
||||
// first look to see if we've cached the bundles for this request in the request object
|
||||
ResourceBundle[] bundles = null;
|
||||
if (req != null) {
|
||||
bundles = (ResourceBundle[])req.getAttribute(getBundleCacheName());
|
||||
}
|
||||
if (bundles != null) {
|
||||
return bundles;
|
||||
}
|
||||
|
||||
ClassLoader siteLoader = null;
|
||||
String siteString = null;
|
||||
if (_siteIdent != null) {
|
||||
int siteId = _siteIdent.identifySite(req);
|
||||
siteString = _siteIdent.getSiteString(siteId);
|
||||
|
||||
// grab our site-specific class loader if we have one
|
||||
if (_siteLoader != null) {
|
||||
try {
|
||||
siteLoader = _siteLoader.getSiteClassLoader(siteId);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Unable to fetch site-specific classloader", "siteId", siteId,
|
||||
"error", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try looking up the appropriate bundles
|
||||
bundles = new ResourceBundle[2];
|
||||
|
||||
// first from the site-specific classloader
|
||||
if (siteLoader != null) {
|
||||
bundles[0] = resolveBundle(req, _siteBundlePath, siteLoader, false);
|
||||
} else if (siteString != null) {
|
||||
// or just try prefixing the site string to the normal bundle path and see if that
|
||||
// turns something up
|
||||
bundles[0] = resolveBundle(req, siteString + "_" + _bundlePath,
|
||||
getClass().getClassLoader(), true);
|
||||
}
|
||||
|
||||
// then from the default classloader
|
||||
bundles[1] = resolveBundle(req, _bundlePath, getClass().getClassLoader(), false);
|
||||
|
||||
// if we found either or both bundles, cache 'em
|
||||
if (bundles[0] != null || bundles[1] != null && req != null) {
|
||||
req.setAttribute(getBundleCacheName(), bundles);
|
||||
}
|
||||
|
||||
return bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the default resource bundle based on the locale information provided in the
|
||||
* supplied http request object.
|
||||
*/
|
||||
protected ResourceBundle resolveBundle (HttpServletRequest req, String bundlePath,
|
||||
ClassLoader loader, boolean silent)
|
||||
{
|
||||
ResourceBundle bundle = null;
|
||||
|
||||
if (req != null) {
|
||||
Enumeration<?> locales = req.getLocales();
|
||||
while (locales.hasMoreElements()) {
|
||||
Locale locale = (Locale)locales.nextElement();
|
||||
|
||||
try {
|
||||
// java caches resource bundles, so we don't need to reinvent the wheel here.
|
||||
// however, java also falls back from a specific bundle to a more general one
|
||||
// if it can't find a specific bundle. that's real nice of it, but we want
|
||||
// first to see whether or not we have exact matches on any of the preferred
|
||||
// locales specified by the client. if we don't, then we can rely on java's
|
||||
// fallback mechanisms
|
||||
bundle = ResourceBundle.getBundle(bundlePath, locale, loader);
|
||||
|
||||
// if it's an exact match, off we go
|
||||
if (bundle.getLocale().equals(locale)) {
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (MissingResourceException mre) {
|
||||
// no need to freak out quite yet, see if we have something for one of the
|
||||
// other preferred locales
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we were unable to find an exact match for any of the user's preferred locales, take
|
||||
// their most preferred and let java perform it's fallback logic on that one
|
||||
if (bundle == null) {
|
||||
Locale locale = (req == null) ? _deflocale : req.getLocale();
|
||||
try {
|
||||
bundle = ResourceBundle.getBundle(bundlePath, locale, loader);
|
||||
} catch (MissingResourceException mre) {
|
||||
if (!silent) {
|
||||
// if we were unable even to find a default bundle, we may want to log a
|
||||
// warning
|
||||
log.warning("Unable to resolve any message bundle", "req", getURL(req),
|
||||
"locale", locale, "bundlePath", bundlePath, "classLoader", loader,
|
||||
"siteBundlePath", _siteBundlePath, "siteLoader", _siteLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected String getURL (HttpServletRequest req)
|
||||
{
|
||||
return (req == null) ? "<none>" : req.getRequestURL().toString();
|
||||
}
|
||||
|
||||
/** Used to cache resolved bundles in a request. */
|
||||
protected String getBundleCacheName ()
|
||||
{
|
||||
return BUNDLE_CACHE_PREFIX + _bundlePath;
|
||||
}
|
||||
|
||||
/** The path, relative to the classpath, to our resource bundles. */
|
||||
protected String _bundlePath;
|
||||
|
||||
/** The path to the site-specific message bundles, fetched via the site-specific resource
|
||||
* loader. */
|
||||
protected String _siteBundlePath;
|
||||
|
||||
/** The resource loader with which to fetch our site-specific message bundles. */
|
||||
protected SiteResourceLoader _siteLoader;
|
||||
|
||||
/** The site identifier we use to determine through which site a request was made. */
|
||||
protected SiteIdentifier _siteIdent;
|
||||
|
||||
/** The locale to use if we are accessed without an HTTP request. */
|
||||
protected Locale _deflocale;
|
||||
|
||||
/** The attribute name that we use for caching resource bundles in request objects. */
|
||||
protected static final String BUNDLE_CACHE_PREFIX =
|
||||
"com.samskivert.servlet.MessageManager:CachedResourceBundle:";
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
/**
|
||||
* A redirect exception is thrown by servlet services when they require
|
||||
* that the user be redirected to a different URL rather than continue
|
||||
* processing this request. It is expected that redirect handling can be
|
||||
* implemented in a single place such that servlets can simply allow this
|
||||
* exception to propagate up to the proper handler which will then issue
|
||||
* the appropriate redirect header.
|
||||
*/
|
||||
public class RedirectException extends Exception
|
||||
{
|
||||
public RedirectException (String url)
|
||||
{
|
||||
super(url);
|
||||
}
|
||||
|
||||
public String getRedirectURL ()
|
||||
{
|
||||
return getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Represents a site mapping known to a {@link SiteIdentifier}.
|
||||
*
|
||||
* @see SiteIdentifier#enumerateSites
|
||||
*/
|
||||
public class Site
|
||||
{
|
||||
/** The site's unqiue identifier. */
|
||||
public int siteId;
|
||||
|
||||
/** The site's human readable identifier (i.e., "monkeybutter"). */
|
||||
public String siteString;
|
||||
|
||||
/** Constructs a site record with the specified id and string. */
|
||||
public Site (int siteId, String siteString)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
this.siteString = siteString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a blank record for unserialization from the repository.
|
||||
*/
|
||||
public Site ()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.util.Iterator;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Responsible for determining the unique site identifier based on information available in the
|
||||
* HTTP request. Site identifiers are integers ranging from 1 to {@link Integer#MAX_VALUE}. Because
|
||||
* the site identifier implementation is likely to have access to the site classification metadata,
|
||||
* this interface is also used to map integer site identifiers to string site identifiers.
|
||||
*/
|
||||
public interface SiteIdentifier
|
||||
{
|
||||
/** The default site identifier, to be used when a site cannot be identified or for site
|
||||
* identifiers that don't wish to distinguish between sites. */
|
||||
public static final int DEFAULT_SITE_ID = -1;
|
||||
|
||||
/** The string identifier for the default site. */
|
||||
public static final String DEFAULT_SITE_STRING = "default";
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for the site on which this request originated. That may be
|
||||
* divined by looking at the server name, or perhaps a request parameter, or part of the path
|
||||
* info. The mechanism (or mechanisms) are up to the implementation.
|
||||
*
|
||||
* @param req the http servlet request the site for which we are trying to identify.
|
||||
*
|
||||
* @return the unique site identifier requestsed or {@link #DEFAULT_SITE_ID} if the site could
|
||||
* not be identified. No site should ever have a site id value of 0.
|
||||
*/
|
||||
public int identifySite (HttpServletRequest req);
|
||||
|
||||
/**
|
||||
* Returns a string representation of the site identifier. The SiteIdentifier in use can map
|
||||
* the site ids to strings however it likes as long as it consistently maps the same identifier
|
||||
* to the same string and vice versa. Presumably these strings would be human readable and
|
||||
* meaningful.
|
||||
*
|
||||
* @param siteId the unique integer identifier for the site that we wish to be identified by a
|
||||
* string.
|
||||
*
|
||||
* @return the string identifier for this site.
|
||||
*/
|
||||
public String getSiteString (int siteId);
|
||||
|
||||
/**
|
||||
* Returns the site identifier for the site associated with the supplied site string. The
|
||||
* SiteIdentifier in use can map the site ids to strings however it likes as long as it
|
||||
* consistently maps the same string to the same identifier and vice versa.
|
||||
*
|
||||
* @param siteString the string to be converted into a site identifer.
|
||||
*
|
||||
* @return the integer identifier for this site.
|
||||
*/
|
||||
public int getSiteId (String siteString);
|
||||
|
||||
/**
|
||||
* Returns an enumerator over all {@link Site} mappings known to this SiteIdentifier.
|
||||
*/
|
||||
public Iterator<Site> enumerateSites ();
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Web applications may wish to load resources in such a way that the site
|
||||
* on which they are running is allowed to override a resource with a
|
||||
* site-specific version (a header, footer or navigation template for
|
||||
* example). The site resource loader provides this capability by loading
|
||||
* resources from a site-specific jar file.
|
||||
*
|
||||
* <p> The site resource loader must be configured with the path to the
|
||||
* site-specific jar files, the names of which are dictated by the string
|
||||
* identifiers returned by the {@link SiteIdentifier} provided to the
|
||||
* resource loader at construct time. For example, if the configuration
|
||||
* dictates that site-specific jar files are located in
|
||||
* <code>/usr/share/java/webapps/site-data</code> and the site identifier
|
||||
* returns <code>samskivert</code> as the site identifier for a particular
|
||||
* request, site-specific resources will be loaded from
|
||||
* <code>/usr/share/java/webapps/site-data/samskivert.jar</code>.
|
||||
*/
|
||||
public class SiteResourceLoader
|
||||
{
|
||||
/**
|
||||
* Constructs a new resource loader.
|
||||
*
|
||||
* @param siteIdent the site identifier to be used to identify which
|
||||
* site through which a request was made when loading resources.
|
||||
* @param siteJarPath the path to the site-specific jar files.
|
||||
*/
|
||||
public SiteResourceLoader (
|
||||
SiteIdentifier siteIdent, String siteJarPath)
|
||||
{
|
||||
// keep this stuff around
|
||||
_siteIdent = siteIdent;
|
||||
_jarPath = siteJarPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specific resource, from the site-specific jar file if one
|
||||
* exists and contains the specified resource. If no resource exists
|
||||
* with that path, null will be returned.
|
||||
*
|
||||
* @param req the http request for which we are loading a resource
|
||||
* (this will be used to determine for which site the resource will be
|
||||
* loaded).
|
||||
* @param path the path to the desired resource.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or null
|
||||
* if no resource could be located with the specified path.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while loading
|
||||
* a resource.
|
||||
*/
|
||||
public InputStream getResourceAsStream (
|
||||
HttpServletRequest req, String path)
|
||||
throws IOException
|
||||
{
|
||||
return getResourceAsStream(_siteIdent.identifySite(req), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specific resource, from the site-specific jar file if one
|
||||
* exists and contains the specified resource. If no resource exists
|
||||
* with that path, null will be returned.
|
||||
*
|
||||
* @param siteId the unique identifer for the site for which we are
|
||||
* loading the resource.
|
||||
* @param path the path to the desired resource.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or null
|
||||
* if no resource could be located with the specified path.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while loading
|
||||
* a resource.
|
||||
*/
|
||||
public InputStream getResourceAsStream (int siteId, String path)
|
||||
throws IOException
|
||||
{
|
||||
// Log.info("Loading site resource [siteId=" + siteId +
|
||||
// ", path=" + path + "].");
|
||||
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
SiteResourceBundle bundle = getBundle(siteId);
|
||||
|
||||
// make sure the path has no leading slash
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// obtain our resource from the bundle
|
||||
return bundle.getResourceAsStream(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification time of the site-specific jar file
|
||||
* for the specified site.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs accessing the
|
||||
* site-specific jar file (like it doesn't exist).
|
||||
*/
|
||||
public long getLastModified (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
return getBundle(siteId).getLastModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a class loader that loads resources from the site-specific
|
||||
* jar file for the specified site. If no site-specific jar file
|
||||
* exists for the specified site, null will be returned.
|
||||
*/
|
||||
public ClassLoader getSiteClassLoader (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
// see if we've already got one
|
||||
ClassLoader loader = _loaders.get(siteId);
|
||||
|
||||
// create one if we've not
|
||||
if (loader == null) {
|
||||
final SiteResourceBundle bundle = getBundle(siteId);
|
||||
if (bundle == null) {
|
||||
// no bundle... no classloader.
|
||||
return null;
|
||||
}
|
||||
|
||||
loader = AccessController.doPrivileged(new PrivilegedAction<SiteClassLoader>() {
|
||||
public SiteClassLoader run () {
|
||||
return new SiteClassLoader(bundle);
|
||||
}
|
||||
});
|
||||
_loaders.put(siteId, loader);
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "[jarPath=" + _jarPath + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* We synchronize on a per-site basis, but we use a separate lock
|
||||
* object for each site so that the process of loading a bundle for
|
||||
* the first time does not require blocking access to resources from
|
||||
* other sites.
|
||||
*/
|
||||
protected Object getLock (int siteId)
|
||||
{
|
||||
Object lock = null;
|
||||
|
||||
synchronized (_locks) {
|
||||
lock = _locks.get(siteId);
|
||||
|
||||
// create a lock object if we haven't one already
|
||||
if (lock == null) {
|
||||
_locks.put(siteId, lock = new Object());
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the site-specific jar file for the specified site. This
|
||||
* should only be called when the lock for this site is held.
|
||||
*/
|
||||
protected SiteResourceBundle getBundle (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// look up the site resource bundle for this site
|
||||
SiteResourceBundle bundle = _bundles.get(siteId);
|
||||
|
||||
// if we haven't got one, create one
|
||||
if (bundle == null) {
|
||||
// obtain the string identifier for this site
|
||||
String ident = _siteIdent.getSiteString(siteId);
|
||||
// compose that with the jar file directory to obtain the
|
||||
// path to the site-specific jar file
|
||||
File file = new File(_jarPath, ident + JAR_EXTENSION);
|
||||
// create a handle for this site-specific jar file
|
||||
bundle = new SiteResourceBundle(file);
|
||||
// cache our new bundle
|
||||
_bundles.put(siteId, bundle);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the information we need to load data from a site
|
||||
* resource bundle as well as to determine whether the loaded bundle
|
||||
* is up to date.
|
||||
*/
|
||||
public static class SiteResourceBundle
|
||||
{
|
||||
/** The object through which we load resources from the
|
||||
* site-specific jar file. */
|
||||
public JarFile jarFile;
|
||||
|
||||
/** A handle on the site-specific jar file. */
|
||||
public File file;
|
||||
|
||||
/**
|
||||
* Constructs a new site resource bundle. The associated jar file
|
||||
* will be opened the first time a resource is read.
|
||||
*/
|
||||
public SiteResourceBundle (File file)
|
||||
throws IOException
|
||||
{
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the specified resource from our site-specific jar file.
|
||||
* The last modified time of the underlying jar file may be
|
||||
* checked to determine whether or not it needs to be reloaded.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or
|
||||
* null if no resource exists with the specified path.
|
||||
*/
|
||||
public InputStream getResourceAsStream (String path)
|
||||
throws IOException
|
||||
{
|
||||
// open or reopen our underlying jar file as necessary
|
||||
refreshJarFile();
|
||||
|
||||
// now load up the resource
|
||||
JarEntry entry = jarFile.getJarEntry(path);
|
||||
return (entry == null) ? null : jarFile.getInputStream(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified time of the underlying jar file.
|
||||
*/
|
||||
public long getLastModified ()
|
||||
throws IOException
|
||||
{
|
||||
// open or reopen our underlying jar file as necessary
|
||||
refreshJarFile();
|
||||
|
||||
return _lastModified;
|
||||
}
|
||||
|
||||
@Override public String toString ()
|
||||
{
|
||||
return "[bundle=" + file + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopens our site-specific jar file if it has been modified
|
||||
* since it was last opened.
|
||||
*/
|
||||
protected void refreshJarFile ()
|
||||
throws IOException
|
||||
{
|
||||
// ensure that the file exists
|
||||
if (!file.exists()) {
|
||||
String errmsg = "No site-specific jar file " +
|
||||
"[path=" + file.getPath() + "].";
|
||||
throw new FileNotFoundException(errmsg);
|
||||
}
|
||||
|
||||
// determine whether or not we need to create a new jarfile
|
||||
// instance
|
||||
if (file.lastModified() > _lastModified) {
|
||||
// make a note of the last modified time
|
||||
_lastModified = file.lastModified();
|
||||
|
||||
// close our old jar file if we've got one
|
||||
if (jarFile != null) {
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
log.info("Opened site bundle", "path", file.getPath());
|
||||
|
||||
// and open a new one
|
||||
jarFile = new JarFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/** The last modified time of the jar file at the time that we
|
||||
* opened it for reading. */
|
||||
protected long _lastModified;
|
||||
}
|
||||
|
||||
protected static class SiteClassLoader extends ClassLoader
|
||||
{
|
||||
public SiteClassLoader (SiteResourceBundle bundle)
|
||||
{
|
||||
_bundle = bundle;
|
||||
}
|
||||
|
||||
@Override public InputStream getResourceAsStream (String path)
|
||||
{
|
||||
try {
|
||||
return _bundle.getResourceAsStream(path);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error loading resource from jarfile", "bundle", _bundle, "path", path,
|
||||
"error", ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString ()
|
||||
{
|
||||
return _bundle.toString();
|
||||
}
|
||||
|
||||
protected SiteResourceBundle _bundle;
|
||||
}
|
||||
|
||||
/** The site identifier we use to identify requests. */
|
||||
protected SiteIdentifier _siteIdent;
|
||||
|
||||
/** The path to our site-specific jar files. */
|
||||
protected String _jarPath;
|
||||
|
||||
/** We synchronize on a per-site basis. */
|
||||
protected HashIntMap<Object> _locks = new HashIntMap<Object>();
|
||||
|
||||
/** The table of site-specific jar file information. */
|
||||
protected HashIntMap<SiteResourceBundle> _bundles =
|
||||
new HashIntMap<SiteResourceBundle>();
|
||||
|
||||
/** The table of site-specific class loaders. */
|
||||
protected HashIntMap<ClassLoader> _loaders = new HashIntMap<ClassLoader>();
|
||||
|
||||
/** The default path to the site-specific jar files. This won't be
|
||||
* used without logging a complaint first. */
|
||||
protected static final String DEFAULT_SITE_JAR_PATH =
|
||||
"/usr/share/java/webapps/site-data";
|
||||
|
||||
/** The file extension to be appended to the string site identifier to
|
||||
* obtain the file name of the site-specific jar file. */
|
||||
protected static final String JAR_EXTENSION = ".jar";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides services and patterns that are useful in developing web
|
||||
applications.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown when a user authentication attempt failed.
|
||||
*/
|
||||
public class AuthenticationFailedException extends Exception
|
||||
{
|
||||
public AuthenticationFailedException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Provides a means for applications to use their own application-specific
|
||||
* authentication schemes for validating a user by constructing their own
|
||||
* authenticator and passing it to {@link UserManager#login}.
|
||||
*/
|
||||
public interface Authenticator
|
||||
{
|
||||
/**
|
||||
* Checks whether the user should be authenticated based on the
|
||||
* supplied user record and the specified user information. Throws an
|
||||
* {@link AuthenticationFailedException} if the user fails to pass the
|
||||
* authentication check for some reason.
|
||||
*
|
||||
* @param user the definitive user record loaded from the persistent
|
||||
* repository against which the user-supplied data is to be checked.
|
||||
* @param username the username supplied by the user.
|
||||
* @param password the plaintext password supplied by the user.
|
||||
*
|
||||
* @throws AuthenticationFailedException if the user failed to pass
|
||||
* the authentication check.
|
||||
*/
|
||||
public void authenticateUser (User user, String username, Password password)
|
||||
throws AuthenticationFailedException;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown during authentication when an invalid password is supplied.
|
||||
*/
|
||||
public class InvalidPasswordException extends AuthenticationFailedException
|
||||
{
|
||||
public InvalidPasswordException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown during user account creation if an invalid username is supplied.
|
||||
*/
|
||||
public class InvalidUsernameException extends Exception
|
||||
{
|
||||
public InvalidUsernameException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown when a user cannot be located in the user database.
|
||||
*/
|
||||
public class NoSuchUserException extends AuthenticationFailedException
|
||||
{
|
||||
public NoSuchUserException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Represents an encrypted password. Currently only used when creating
|
||||
* user accounts.
|
||||
*/
|
||||
public class Password
|
||||
{
|
||||
/**
|
||||
* Returns the clear password text. This method may return null if
|
||||
* none was provided when creating the password.
|
||||
*/
|
||||
public String getCleartext ()
|
||||
{
|
||||
return _cleartext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encrypted password text.
|
||||
*/
|
||||
public String getEncrypted ()
|
||||
{
|
||||
return _encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a password instance from the supplied plaintext.
|
||||
*/
|
||||
public static Password makeFromClear (String password)
|
||||
{
|
||||
return new Password(password, UserUtil.encryptPassword(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a password instance from the supplied already encrypted
|
||||
* text. <em>Note:</em> the encrypted text must be obtained from
|
||||
* {@link UserUtil#encryptPassword}.
|
||||
*/
|
||||
public static Password makeFromCrypto (String encrypted)
|
||||
{
|
||||
return new Password(null, encrypted);
|
||||
}
|
||||
|
||||
/** Creates a password instance. */
|
||||
protected Password (String cleartext, String encrypted)
|
||||
{
|
||||
_cleartext = cleartext;
|
||||
_encrypted = encrypted;
|
||||
}
|
||||
|
||||
protected String _cleartext;
|
||||
protected String _encrypted;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
import com.samskivert.jdbc.jora.FieldMask;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A user object contains information about a registered user in our web application environment.
|
||||
* Users are stored in the user repository (a database table) and loaded during a request based on
|
||||
* authentication information provided with the request headers.
|
||||
*
|
||||
* <p><b>Note:</b> Do not modify any of the fields of this object directly. Use the
|
||||
* <code>set</code> methods to make updates. If no set methods exist, you shouldn't be modifying
|
||||
* that field.
|
||||
*/
|
||||
public class User
|
||||
{
|
||||
/** The user's assigned integer userid. */
|
||||
public int userId;
|
||||
|
||||
/** The user's chosen username. */
|
||||
public String username;
|
||||
|
||||
/** The date this record was created. */
|
||||
public Date created;
|
||||
|
||||
/** The user's real name (first, last and whatever else they opt to provide). */
|
||||
public String realname;
|
||||
|
||||
/** The user's chosen password (encrypted). */
|
||||
public String password;
|
||||
|
||||
/** The user's email address. */
|
||||
public String email;
|
||||
|
||||
/** The site identifier of the site through which the user created their account. (Their
|
||||
* affiliation, if you will.) */
|
||||
public int siteId;
|
||||
|
||||
/**
|
||||
* Updates the user's username.
|
||||
*/
|
||||
public void setUsername (Username username)
|
||||
{
|
||||
this.username = username.getUsername();
|
||||
setModified("username");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's real name.
|
||||
*/
|
||||
public void setRealName (String realname)
|
||||
{
|
||||
this.realname = realname;
|
||||
setModified("realname");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's password.
|
||||
*
|
||||
* @param password The user's new (unencrypted) password.
|
||||
*/
|
||||
public void setPassword (String password)
|
||||
{
|
||||
setPassword(Password.makeFromClear(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's password.
|
||||
*
|
||||
* @param password The user's new password.
|
||||
*/
|
||||
public void setPassword (Password password)
|
||||
{
|
||||
this.password = password.getEncrypted();
|
||||
setModified("password");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's email address.
|
||||
*/
|
||||
public void setEmail (String email)
|
||||
{
|
||||
this.email = email;
|
||||
setModified("email");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's site id.
|
||||
*/
|
||||
public void setSiteId (int siteId)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
setModified("siteId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the supplied password with the password associated with this user record.
|
||||
*
|
||||
* @return true if the passwords match, false if they do not.
|
||||
*/
|
||||
public boolean passwordsMatch (Password password)
|
||||
{
|
||||
return this.password.equals(password.getEncrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this User has been "deleted".
|
||||
*/
|
||||
public boolean isDeleted ()
|
||||
{
|
||||
// a deleted account has an "=" in the username
|
||||
return (-1 != username.indexOf('='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this user is an admin, false otherwise. The default implementation does not
|
||||
* track admin status and always returns false.
|
||||
*/
|
||||
public boolean isAdmin ()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the repository to find out which fields have been modified since the object was
|
||||
* loaded.
|
||||
*/
|
||||
protected FieldMask getDirtyMask ()
|
||||
{
|
||||
return _dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the repository to configure this user record with a field mask that it can use to
|
||||
* track modified fields.
|
||||
*/
|
||||
protected void setDirtyMask (FieldMask dirty)
|
||||
{
|
||||
_dirty = dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the supplied field as dirty.
|
||||
*/
|
||||
protected void setModified (String field)
|
||||
{
|
||||
_dirty.setModified(field);
|
||||
}
|
||||
|
||||
/** Our dirty field mask. */
|
||||
protected transient FieldMask _dirty;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* Thrown during user account creation when a user with the requested
|
||||
* username already exists.
|
||||
*/
|
||||
public class UserExistsException extends PersistenceException
|
||||
{
|
||||
public UserExistsException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.util.Properties;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.servlet.RedirectException;
|
||||
import com.samskivert.servlet.util.CookieUtil;
|
||||
import com.samskivert.servlet.util.RequestUtils;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.RunQueue;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The user manager provides easy access to user objects for servlets. It takes care of cookie
|
||||
* management involved in login, logout and loading a user record during an authenticated session.
|
||||
*/
|
||||
public class UserManager
|
||||
{
|
||||
/** An instance of the insecure authenticator for general-purpose use. */
|
||||
public static final Authenticator AUTH_INSECURE = new InsecureAuthenticator();
|
||||
|
||||
/** An instance of the password authenticator for general-purpose use. */
|
||||
public static final Authenticator AUTH_PASSWORD = new PasswordAuthenticator();
|
||||
|
||||
/**
|
||||
* A totally insecure authenticator that authenticates any user. <em>Note:</em> Applications
|
||||
* that make use of this authenticator should make sure the user has already been authenticated
|
||||
* through some other means.
|
||||
*/
|
||||
public static class InsecureAuthenticator implements Authenticator
|
||||
{
|
||||
// documentation inherited
|
||||
public void authenticateUser (User user, String username, Password password)
|
||||
throws InvalidPasswordException
|
||||
{
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An authenticator that requires that the user-supplied password match the actual user
|
||||
* password.
|
||||
*/
|
||||
public static class PasswordAuthenticator implements Authenticator
|
||||
{
|
||||
// documentation inherited
|
||||
public void authenticateUser (User user, String username, Password password)
|
||||
throws AuthenticationFailedException
|
||||
{
|
||||
if (!user.passwordsMatch(password)) {
|
||||
throw new InvalidPasswordException("error.invalid_password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this user manager for operation. Presently the user manager requires the
|
||||
* following configuration information:
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>login_url</code>: Should be set to the URL to which to redirect a requester if
|
||||
* they are required to login before accessing the requested page. For example:
|
||||
*
|
||||
* <pre>
|
||||
* login_url = /usermgmt/login.ajsp?return=%R
|
||||
* </pre>
|
||||
*
|
||||
* The <code>%R</code> will be replaced with the URL encoded URL the user is currently
|
||||
* requesting (complete with query parameters) so that the login code can redirect the user
|
||||
* back to this request once they are authenticated.
|
||||
* </ul>
|
||||
*
|
||||
* @param config the user manager configuration properties.
|
||||
* @param conprov the database connection provider that will be used to obtain a connection to
|
||||
* the user database.
|
||||
*/
|
||||
public void init (Properties config, ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
init(config, conprov, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this user manager for operation. See {@link #init(Properties,ConnectionProvider)}.
|
||||
*
|
||||
* @param pruneQueue an optional run queue on which to run our periodic session pruning task.
|
||||
*/
|
||||
public void init (Properties config, ConnectionProvider conprov, RunQueue pruneQueue)
|
||||
throws PersistenceException
|
||||
{
|
||||
// save this for later
|
||||
_config = config;
|
||||
|
||||
// create the user repository
|
||||
_repository = createRepository(conprov);
|
||||
|
||||
// fetch the login URL from the properties
|
||||
_loginURL = config.getProperty("login_url");
|
||||
if (_loginURL == null) {
|
||||
log.warning("No login_url supplied in user manager config. Authentication won't work.");
|
||||
_loginURL = "/missing_login_url";
|
||||
}
|
||||
|
||||
// look up any override to our user auth cookie
|
||||
String authCook = config.getProperty("auth_cookie.name");
|
||||
if (!StringUtil.isBlank(authCook)) {
|
||||
_userAuthCookie = authCook;
|
||||
}
|
||||
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("UserManager initialized", "acook", _userAuthCookie, "login", _loginURL);
|
||||
}
|
||||
|
||||
// register a cron job to prune the session table every hour
|
||||
_pruner = new Interval() {
|
||||
@Override public void expired () {
|
||||
try {
|
||||
_repository.pruneSessions();
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Error pruning session table.", pe);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (pruneQueue != null) {
|
||||
_pruner.setRunQueue(pruneQueue);
|
||||
}
|
||||
_pruner.schedule(SESSION_PRUNE_INTERVAL, true);
|
||||
}
|
||||
|
||||
public void shutdown ()
|
||||
{
|
||||
// cancel our session table pruning thread
|
||||
_pruner.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the repository in use by this user manager.
|
||||
*/
|
||||
public UserRepository getRepository ()
|
||||
{
|
||||
return _repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the necessary authentication information from the http request and loads the user
|
||||
* identified by that information.
|
||||
*
|
||||
* @return the user associated with the request or null if no user was associated with the
|
||||
* request or if the authentication information is bogus.
|
||||
*/
|
||||
public User loadUser (HttpServletRequest req)
|
||||
throws PersistenceException
|
||||
{
|
||||
String authcook = CookieUtil.getCookieValue(req, _userAuthCookie);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("Loading user by cookie", _userAuthCookie, authcook);
|
||||
}
|
||||
return loadUser(authcook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a user based on the supplied session authentication token.
|
||||
*/
|
||||
public User loadUser (String authcode)
|
||||
throws PersistenceException
|
||||
{
|
||||
User user = (authcode == null) ? null : _repository.loadUserBySession(authcode);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("Loaded user by authcode", "code", authcode, "user", user);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the necessary authentication information from the http request and loads the user
|
||||
* identified by that information. If no user could be loaded (because the requester is not
|
||||
* authenticated), a redirect exception will be thrown to redirect the user to the login page
|
||||
* specified in the user manager configuration.
|
||||
*
|
||||
* @return the user associated with the request.
|
||||
*/
|
||||
public User requireUser (HttpServletRequest req)
|
||||
throws PersistenceException, RedirectException
|
||||
{
|
||||
User user = loadUser(req);
|
||||
// if no user was loaded, we need to redirect these fine people to the login page
|
||||
if (user == null) {
|
||||
// first construct the redirect URL
|
||||
String eurl = RequestUtils.getLocationEncoded(req);
|
||||
String target = _loginURL.replace("%R", eurl);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("No user found in require, redirecting", "to", target);
|
||||
}
|
||||
throw new RedirectException(target);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to authenticate the requester and initiate an authenticated session for them. An
|
||||
* authenticated session involves their receiving a cookie that proves them to be authenticated
|
||||
* and an entry in the session database being created that maps their information to their
|
||||
* userid. If this call completes, the session was established and the proper cookies were set
|
||||
* in the supplied response object. If invalid authentication information is provided or some
|
||||
* other error occurs, an exception will be thrown.
|
||||
*
|
||||
* @param username The username supplied by the user.
|
||||
* @param password The password supplied by the user.
|
||||
* @param persist If true, the cookie will expire in one month, if false, the cookie will
|
||||
* expire at the end of the user's browser session.
|
||||
* @param req The request via which the login page was loaded.
|
||||
* @param rsp The response in which the cookie is to be set.
|
||||
* @param auth The authenticator used to check whether the user should be authenticated.
|
||||
*
|
||||
* @return the user object of the authenticated user.
|
||||
*/
|
||||
public User login (String username, Password password, boolean persist,
|
||||
HttpServletRequest req, HttpServletResponse rsp, Authenticator auth)
|
||||
throws PersistenceException, AuthenticationFailedException
|
||||
{
|
||||
// load up the requested user
|
||||
User user = _repository.loadUser(username);
|
||||
if (user == null) {
|
||||
throw new NoSuchUserException("error.no_such_user");
|
||||
}
|
||||
|
||||
// run the user through the authentication gamut
|
||||
auth.authenticateUser(user, username, password);
|
||||
|
||||
// give them the necessary cookies and business
|
||||
effectLogin(user, persist ? PERSIST_EXPIRE_DAYS : NON_PERSIST_EXPIRE_DAYS, req, rsp);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to authenticate the requester and initiate an authenticated session for them. A
|
||||
* session token will be assigned to the user and returned along with the associated {@link
|
||||
* User} record. It is assumed that the client will maintain the session token via its own
|
||||
* means.
|
||||
*
|
||||
* @param username the username supplied by the user.
|
||||
* @param password the password supplied by the user.
|
||||
* @param expires the number of days in which this session should expire.
|
||||
* @param auth the authenticator used to check whether the user should be authenticated.
|
||||
*
|
||||
* @return the user object of the authenticated user.
|
||||
*/
|
||||
public Tuple<User,String> login (
|
||||
String username, Password password, int expires, Authenticator auth)
|
||||
throws PersistenceException, AuthenticationFailedException
|
||||
{
|
||||
// load up the requested user
|
||||
User user = _repository.loadUser(username);
|
||||
if (user == null) {
|
||||
throw new NoSuchUserException("error.no_such_user");
|
||||
}
|
||||
|
||||
// run the user through the authentication gamut
|
||||
auth.authenticateUser(user, username, password);
|
||||
|
||||
// register a session for this user
|
||||
String authcode = _repository.registerSession(user, expires);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("Session started", "user", username, "code", authcode);
|
||||
}
|
||||
return new Tuple<User,String>(user, authcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a user is already known to be authenticated for one reason or other, this method can be
|
||||
* used to give them the appropriate authentication cookies to effect their login.
|
||||
*
|
||||
* @param expires the number of days in which to expire the session cookie, 0 means expire at
|
||||
* the end of the browser session.
|
||||
*/
|
||||
public void effectLogin (
|
||||
User user, int expires, HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws PersistenceException
|
||||
{
|
||||
String authcode = _repository.registerSession(user, Math.max(expires, 1));
|
||||
Cookie acookie = new Cookie(_userAuthCookie, authcode);
|
||||
// strip the hostname from the server and use that as the domain unless configured not to
|
||||
if (!"false".equalsIgnoreCase(_config.getProperty("auth_cookie.strip_hostname"))) {
|
||||
CookieUtil.widenDomain(req, acookie);
|
||||
}
|
||||
acookie.setPath("/");
|
||||
acookie.setMaxAge((expires > 0) ? (expires*24*60*60) : -1);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("Setting cookie " + acookie + ".");
|
||||
}
|
||||
rsp.addCookie(acookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out.
|
||||
*/
|
||||
public void logout (HttpServletRequest req, HttpServletResponse rsp)
|
||||
{
|
||||
// nothing to do if they don't already have an auth cookie
|
||||
String authcode = CookieUtil.getCookieValue(req, _userAuthCookie);
|
||||
if (authcode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set them up the bomb
|
||||
Cookie c = new Cookie(_userAuthCookie, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
CookieUtil.widenDomain(req, c);
|
||||
if (USERMGR_DEBUG) {
|
||||
log.info("Clearing cookie " + c + ".");
|
||||
}
|
||||
rsp.addCookie(c);
|
||||
|
||||
// we need an unwidened one to ensure that old-style cookies are wiped as well
|
||||
c = new Cookie(_userAuthCookie, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
rsp.addCookie(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the supplied session key is still valid and if so, refreshes it for the
|
||||
* specified number of days.
|
||||
*
|
||||
* @return true if the session was located and refreshed, false otherwise.
|
||||
*/
|
||||
public boolean refreshSession (String sessionKey, int expireDays)
|
||||
throws PersistenceException
|
||||
{
|
||||
return _repository.refreshSession(sessionKey, expireDays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the user manager to create the user repository. Derived classes can override this
|
||||
* and create a specialized repository if they so desire.
|
||||
*/
|
||||
protected UserRepository createRepository (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
return new UserRepository(conprov);
|
||||
}
|
||||
|
||||
/** Our user manager configuration. */
|
||||
protected Properties _config;
|
||||
|
||||
/** The user repository. */
|
||||
protected UserRepository _repository;
|
||||
|
||||
/** The interval for user session pruning. */
|
||||
protected Interval _pruner;
|
||||
|
||||
/** The URL for the user login page. */
|
||||
protected String _loginURL;
|
||||
|
||||
/** The name of our user authentication cookie. */
|
||||
protected String _userAuthCookie = USERAUTH_COOKIE;
|
||||
|
||||
/** The user authentication cookie name. */
|
||||
protected static final String USERAUTH_COOKIE = "id_";
|
||||
|
||||
/** Prune the session table every hour. */
|
||||
protected static final long SESSION_PRUNE_INTERVAL = 60L * 60L * 1000L;
|
||||
|
||||
/** Indicates how long (in days) that a "persisting" session token should last. */
|
||||
protected static final int PERSIST_EXPIRE_DAYS = 30;
|
||||
|
||||
/** Indicates how long (in days) that a "non-persisting" session token should last. */
|
||||
protected static final int NON_PERSIST_EXPIRE_DAYS = 1;
|
||||
|
||||
/** Change this to true and recompile to debug cookie handling. */
|
||||
protected static final boolean USERMGR_DEBUG = false;
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.JORARepository;
|
||||
import com.samskivert.jdbc.jora.FieldMask;
|
||||
import com.samskivert.jdbc.jora.Table;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.servlet.SiteIdentifier;
|
||||
|
||||
/**
|
||||
* Interfaces with the RDBMS in which the user information is stored. The user repository
|
||||
* encapsulates the creating, loading and management of users and sessions.
|
||||
*/
|
||||
public class UserRepository extends JORARepository
|
||||
{
|
||||
/**
|
||||
* The database identifier used to obtain a connection from our connection provider. The value
|
||||
* is <code>userdb</code> which you'll probably need to know to provide the proper
|
||||
* configuration to your connection provider.
|
||||
*/
|
||||
public static final String USER_REPOSITORY_IDENT = "userdb";
|
||||
|
||||
/**
|
||||
* Creates the repository and opens the user database. The database identifier used to fetch
|
||||
* our database connection is documented by {@link #USER_REPOSITORY_IDENT}.
|
||||
*
|
||||
* @param provider the database connection provider.
|
||||
*/
|
||||
public UserRepository (ConnectionProvider provider)
|
||||
{
|
||||
super(provider, USER_REPOSITORY_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that a new user be created in the repository.
|
||||
*
|
||||
* @param username the username of the new user to create.
|
||||
* @param password the password for the new user.
|
||||
* @param realname the user's real name.
|
||||
* @param email the user's email address.
|
||||
* @param siteId the unique identifier of the site through which this account is being
|
||||
* created. The resulting user will be tracked as originating from this site for accounting
|
||||
* purposes ({@link SiteIdentifier#DEFAULT_SITE_ID} can be used by systems that don't desire to
|
||||
* perform site tracking.
|
||||
*
|
||||
* @return The userid of the newly created user.
|
||||
*/
|
||||
public int createUser (
|
||||
Username username, Password password, String realname, String email, int siteId)
|
||||
throws UserExistsException, PersistenceException
|
||||
{
|
||||
// create a new user object...
|
||||
User user = new User();
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
|
||||
// ...configure it...
|
||||
populateUser(user, username, password, realname, email, siteId);
|
||||
// ...and stick it into the database
|
||||
return insertUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by username.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with that id exists.
|
||||
*/
|
||||
public User loadUser (String username)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadUserWhere("where username = " + JDBCUtil.escape(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by userid.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with that id exists.
|
||||
*/
|
||||
public User loadUser (int userId)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadUserWhere("where userId = " + userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by a session identifier.
|
||||
*
|
||||
* @return the user associated with the specified session or null of no session exists with the
|
||||
* supplied identifier.
|
||||
*/
|
||||
public User loadUserBySession (String sessionKey)
|
||||
throws PersistenceException
|
||||
{
|
||||
User user = load(_utable, "sessions", "where authcode = '" + sessionKey + "' " +
|
||||
"AND sessions.userId = users.userId");
|
||||
if (user != null) {
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up users by userid
|
||||
*
|
||||
* @return the users whom have a user id in the userIds array.
|
||||
*/
|
||||
public HashIntMap<User> loadUsersFromId (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
HashIntMap<User> data = new HashIntMap<User>();
|
||||
if (userIds.length > 0) {
|
||||
String query = "where userId in (" + genIdString(userIds) + ")";
|
||||
for (User user : loadAll(_utable, query)) {
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
data.put(user.userId, user);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a user by email address, something that is not efficient and should really only be
|
||||
* done by site admins attempting to look up a user record.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with that id exists.
|
||||
*/
|
||||
public ArrayList<User> lookupUsersByEmail (String email)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadAll(_utable, "where email = " + JDBCUtil.escape(email));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a list of users that match an arbitrary query. Care should be taken in constructing
|
||||
* these queries as the user table is likely to be large and a query that does not make use of
|
||||
* indices could be very slow.
|
||||
*
|
||||
* @return the users matching the specified query or an empty list if there are no matches.
|
||||
*/
|
||||
public ArrayList<User> lookupUsersWhere (final String where)
|
||||
throws PersistenceException
|
||||
{
|
||||
ArrayList<User> users = loadAll(_utable, where);
|
||||
for (User user : users) {
|
||||
// configure the user record with its field mask
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user that was previously fetched from the repository. Only fields that have been
|
||||
* modified since it was loaded will be written to the database and those fields will
|
||||
* subsequently be marked clean once again.
|
||||
*
|
||||
* @return true if the record was updated, false if the update was skipped because no fields in
|
||||
* the user record were modified.
|
||||
*/
|
||||
public boolean updateUser (final User user)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (!user.getDirtyMask().isModified()) {
|
||||
// nothing doing!
|
||||
return false;
|
||||
}
|
||||
update(_utable, user, user.getDirtyMask());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Delete' the users account such that they can no longer access it, however we do not delete
|
||||
* the record from the db. The name is changed such that the original name has XX=FOO if the
|
||||
* name were FOO originally. If we have to lop off any of the name to get our prefix to fit we
|
||||
* use a minus sign instead of a equals side. The password field is set to be the empty string
|
||||
* so that no one can log in (since nothing hashes to the empty string. We also make sure
|
||||
* their email address no longer works, so in case we don't ignore 'deleted' users when we do
|
||||
* the sql to get emailaddresses for the mass mailings we still won't spam delete folk. We
|
||||
* leave the emailaddress intact exect for the @ sign which gets turned to a #, so that we can
|
||||
* see what their email was incase it was an accidently deletion and we have to verify through
|
||||
* email.
|
||||
*/
|
||||
public void deleteUser (final User user)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (user.isDeleted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
// create our modified fields mask
|
||||
FieldMask mask = _utable.getFieldMask();
|
||||
mask.setModified("username");
|
||||
mask.setModified("password");
|
||||
mask.setModified("email");
|
||||
|
||||
// set the password to unusable
|
||||
user.password = "";
|
||||
|
||||
// 'disable' their email address
|
||||
String newEmail = user.email.replace('@','#');
|
||||
user.email = newEmail;
|
||||
|
||||
String oldName = user.username;
|
||||
for (int ii = 0; ii < 100; ii++) {
|
||||
try {
|
||||
user.username = StringUtil.truncate(ii + "=" + oldName, 24);
|
||||
_utable.update(conn, user, mask);
|
||||
return null; // nothing to return
|
||||
} catch (SQLException se) {
|
||||
if (!liaison.isDuplicateRowException(se)) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ok we failed to rename the user, lets bust an error
|
||||
throw new PersistenceException("Failed to 'delete' the user");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session for the specified user and returns the randomly generated session
|
||||
* identifier for that session. If a session entry already exists for the specified user it
|
||||
* will be reused.
|
||||
*
|
||||
* @param expireDays the number of days in which the session token should expire.
|
||||
*/
|
||||
public String registerSession (User user, int expireDays)
|
||||
throws PersistenceException
|
||||
{
|
||||
// look for an existing session for this user
|
||||
final String query = "select authcode from sessions where userId = " + user.userId;
|
||||
String authcode = execute(new Operation<String>() {
|
||||
public String invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
return rs.getString(1);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// figure out when to expire the session
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DATE, expireDays);
|
||||
Date expires = new Date(cal.getTime().getTime());
|
||||
|
||||
// if we found one, update its expires time and reuse it
|
||||
if (authcode != null) {
|
||||
update("update sessions set expires = '" + expires + "' where " +
|
||||
"authcode = '" + authcode + "'");
|
||||
} else {
|
||||
// otherwise create a new one and insert it into the table
|
||||
authcode = UserUtil.genAuthCode(user);
|
||||
update("insert into sessions (authcode, userId, expires) values('" + authcode + "', " +
|
||||
user.userId + ", '" + expires + "')");
|
||||
}
|
||||
|
||||
return authcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the supplied session key is still valid and if so, refreshes it for the
|
||||
* specified number of days.
|
||||
*
|
||||
* @return true if the session was located and refreshed, false if it no longer exists.
|
||||
*/
|
||||
public boolean refreshSession (String sessionKey, int expireDays)
|
||||
throws PersistenceException
|
||||
{
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DATE, expireDays);
|
||||
Date expires = new Date(cal.getTime().getTime());
|
||||
|
||||
// attempt to update an existing session row, returning true if we found and updated it
|
||||
return (update("update sessions set expires = '" + expires + "' " +
|
||||
"where authcode = " + JDBCUtil.escape(sessionKey)) == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes any expired sessions from the sessions table.
|
||||
*/
|
||||
public void pruneSessions ()
|
||||
throws PersistenceException
|
||||
{
|
||||
update("delete from sessions where expires <= CURRENT_DATE()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the usernames of the users identified by the supplied user ids and returns them in an
|
||||
* array. If any users do not exist, their slot in the array will contain a null.
|
||||
*/
|
||||
public String[] loadUserNames (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadNames(userIds, "username");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the real names of the users identified by the supplied user ids and returns them in an
|
||||
* array. If any users do not exist, their slot in the array will contain a null.
|
||||
*/
|
||||
public String[] loadRealNames (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadNames(userIds, "realname");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the real names of every user in the system.
|
||||
*/
|
||||
public String[] loadAllRealNames ()
|
||||
throws PersistenceException
|
||||
{
|
||||
final ArrayList<String> names = new ArrayList<String>();
|
||||
|
||||
// do the query
|
||||
execute(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
String query = "select realname from users";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
names.add(rs.getString(1));
|
||||
}
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// finally construct our result
|
||||
return names.toArray(new String[names.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the supplied user record with the provided information in preparation for
|
||||
* inserting the record into the database for the first time.
|
||||
*/
|
||||
protected void populateUser (
|
||||
User user, Username name, Password pass, String realname, String email, int siteId)
|
||||
{
|
||||
user.username = name.getUsername();
|
||||
user.setPassword(pass);
|
||||
user.setRealName(realname);
|
||||
user.setEmail(email);
|
||||
user.created = new Date(System.currentTimeMillis());
|
||||
user.setSiteId(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the supplied user record into the user database, assigning it a userid in the
|
||||
* process, which is returned.
|
||||
*/
|
||||
protected int insertUser (final User user)
|
||||
throws UserExistsException, PersistenceException
|
||||
{
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
try {
|
||||
_utable.insert(conn, user);
|
||||
// update the userid now that it's known
|
||||
user.userId = liaison.lastInsertedId(conn, _utable.getName(), "userId");
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (liaison.isDuplicateRowException(sqe)) {
|
||||
throw new UserExistsException("error.user_exists");
|
||||
} else {
|
||||
throw sqe;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return user.userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a user record that matches the specified where clause. Returns null if no record
|
||||
* matches.
|
||||
*/
|
||||
protected User loadUserWhere (String where)
|
||||
throws PersistenceException
|
||||
{
|
||||
User user = load(_utable, where);
|
||||
if (user != null) {
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
protected String[] loadNames (int[] userIds, final String column)
|
||||
throws PersistenceException
|
||||
{
|
||||
// if userids is zero length, we've got no work to do
|
||||
if (userIds.length == 0) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// do the query
|
||||
final String ids = genIdString(userIds);
|
||||
final HashIntMap<String> map = new HashIntMap<String>();
|
||||
execute(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
String query = "select userId, " + column + " from users " +
|
||||
"where userId in (" + ids + ")";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
int userId = rs.getInt(1);
|
||||
String name = rs.getString(2);
|
||||
map.put(userId, name);
|
||||
}
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// finally construct our result
|
||||
String[] result = new String[userIds.length];
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
result[i] = map.get(userIds[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the passed in int array and create the a string suitable for using in a SQL set query
|
||||
* (I.e., "select foo, from bar where userId in (genIdString(userIds))"; )
|
||||
*/
|
||||
protected String genIdString (int[] userIds)
|
||||
{
|
||||
// build up the string we need for the query
|
||||
StringBuilder ids = new StringBuilder();
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
if (ids.length() > 0) {
|
||||
ids.append(",");
|
||||
}
|
||||
ids.append(userIds[i]);
|
||||
}
|
||||
|
||||
return ids.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createTables ()
|
||||
{
|
||||
// create our table object
|
||||
_utable = new Table<User>(User.class, "users", "userId");
|
||||
}
|
||||
|
||||
/** A wrapper that provides access to the userstable. */
|
||||
protected Table<User> _utable;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import com.samskivert.util.Crypt;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* User related utility functions.
|
||||
*/
|
||||
public class UserUtil
|
||||
{
|
||||
/**
|
||||
* Generates a new random session identifier for the supplied user.
|
||||
*/
|
||||
public static String genAuthCode (User user)
|
||||
{
|
||||
// concatenate a bunch of secret stuff together
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(user.password);
|
||||
buf.append(System.currentTimeMillis());
|
||||
buf.append(Math.random());
|
||||
|
||||
// and MD5 hash it
|
||||
return StringUtil.md5hex(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the supplied username and password and returns the value
|
||||
* that would be stored in the user record were the password to be
|
||||
* updated via {@link User#setPassword}.
|
||||
*/
|
||||
public static String encryptPassword (String password)
|
||||
{
|
||||
return StringUtil.md5hex(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts passwords the way we used to.
|
||||
*/
|
||||
public static String legacyEncrypt (String username, String password,
|
||||
boolean ignoreUserCase)
|
||||
{
|
||||
if (ignoreUserCase) {
|
||||
username = username.toLowerCase();
|
||||
}
|
||||
return Crypt.crypt(StringUtil.truncate(username, 2), password);
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: UserUtil password");
|
||||
System.exit(-1);
|
||||
}
|
||||
System.out.println("Encrypted password: " + encryptPassword(args[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Allows us to require a valid username as a parameter without having to
|
||||
* do the checking ourselves.
|
||||
*/
|
||||
public class Username
|
||||
{
|
||||
/** The minimum allowable length of a username. */
|
||||
public static final int MINIMUM_USERNAME_LENGTH = 3;
|
||||
|
||||
/** The maximum allowable length of a username. */
|
||||
public static final int MAXIMUM_USERNAME_LENGTH = 12;
|
||||
|
||||
/** The regular expression defining valid names. */
|
||||
public static final String NAME_REGEX = "^[_A-Za-z0-9]*$";
|
||||
|
||||
/**
|
||||
* Creates a username instance.
|
||||
*/
|
||||
public Username (String username)
|
||||
throws InvalidUsernameException
|
||||
{
|
||||
validateName(username);
|
||||
_username = username;
|
||||
}
|
||||
|
||||
/** Returns the text of this username. */
|
||||
public String getUsername ()
|
||||
{
|
||||
return _username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return _username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates our username. The default implementation requires that usernames consist only of
|
||||
* characters that match the {@link #NAME_REGEX} regular expression and be between {@link
|
||||
* #MINIMUM_USERNAME_LENGTH} and {@link #MAXIMUM_USERNAME_LENGTH} characters.
|
||||
*/
|
||||
protected void validateName (String username)
|
||||
throws InvalidUsernameException
|
||||
{
|
||||
// check length
|
||||
if (username.length() < MINIMUM_USERNAME_LENGTH) {
|
||||
throw new InvalidUsernameException("error.username_too_short");
|
||||
}
|
||||
if (username.length() > MAXIMUM_USERNAME_LENGTH) {
|
||||
throw new InvalidUsernameException("error.username_too_long");
|
||||
}
|
||||
|
||||
// check that it's only valid characters
|
||||
if (!username.matches(NAME_REGEX)) {
|
||||
throw new InvalidUsernameException("error.invalid_username");
|
||||
}
|
||||
}
|
||||
|
||||
protected String _username;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides services for managing a database of users in a web
|
||||
application or suite of web applications.
|
||||
|
||||
<p> This is accomplished through the
|
||||
{@link com.samskivert.servlet.user.UserManager} which can be used to
|
||||
load user objects, create new users, update existing users and
|
||||
generally all of the user-related things that you'd like to do. The
|
||||
user manager manages cookies for persistent or session-only
|
||||
authentication and provides support for requiring that a user be
|
||||
authenticated in order to access a page.
|
||||
|
||||
<p> The user manager stores user information in an SQL database which
|
||||
is accomplished by the
|
||||
{@link com.samskivert.servlet.user.UserRepository} class. Presently,
|
||||
it assumes that it is making use of a MySQL database, but the
|
||||
dependencies are minimal. Eventually I'll modify the repository
|
||||
abstraction so that the database-specific support code can be chosen
|
||||
at runtime rather than compile time. Why I designed it the way it is
|
||||
in the first place, I can't imagine. I'm a bad monkey.
|
||||
|
||||
<p> The user table (and associated
|
||||
{@link com.samskivert.servlet.user.User} object) are inentionally
|
||||
minimal. The expectation is that application specific data will be
|
||||
stored and accessed via some other table that is keyed on the userid
|
||||
provided by these services.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Utility methods for dealing with cookies.
|
||||
*/
|
||||
public class CookieUtil
|
||||
{
|
||||
/**
|
||||
* Get the cookie of the specified name, or null if not found.
|
||||
*/
|
||||
public static Cookie getCookie (HttpServletRequest req, String name)
|
||||
{
|
||||
Cookie[] cookies = req.getCookies();
|
||||
if (cookies != null) {
|
||||
for (int ii=0, nn=cookies.length; ii < nn; ii++) {
|
||||
if (cookies[ii].getName().equals(name)) {
|
||||
return cookies[ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // not found
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the cookie for the cookie of the specified name, or null if not found.
|
||||
*/
|
||||
public static String getCookieValue (HttpServletRequest req, String name)
|
||||
{
|
||||
Cookie c = getCookie(req, name);
|
||||
return (c == null) ? null : c.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cookie with the specified name.
|
||||
*/
|
||||
public static void clearCookie (HttpServletResponse rsp, String name)
|
||||
{
|
||||
Cookie c = new Cookie(name, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
rsp.addCookie(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the domain of the specified cookie to the server name associated with the supplied
|
||||
* request minus the hostname (ie. <code>www.samskivert.com</code> becomes
|
||||
* <code>.samskivert.com</code>).
|
||||
*/
|
||||
public static void widenDomain (HttpServletRequest req, Cookie cookie)
|
||||
{
|
||||
String server = req.getServerName();
|
||||
int didx = server.indexOf(".");
|
||||
// if no period was found (e.g. localhost) don't set the domain
|
||||
if (didx == -1) {
|
||||
return;
|
||||
}
|
||||
// if two or more periods are found (e.g. www.domain.com) strip up to the first one
|
||||
if (server.indexOf(".", didx+1) != -1) {
|
||||
cookie.setDomain(server.substring(didx));
|
||||
} else {
|
||||
// ...otherwise prepend a "." because we're seeing something like "domain.com"
|
||||
cookie.setDomain("." + server);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* A data validation exception is thrown when a value supplied in a form
|
||||
* element is not valid.
|
||||
*
|
||||
* @see ParameterUtil
|
||||
*/
|
||||
public class DataValidationException extends FriendlyException
|
||||
{
|
||||
public DataValidationException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The exception map is used to map exceptions to error messages based on
|
||||
* a static, server-wide configuration.
|
||||
*
|
||||
* <p>The configuration file is loaded via the classpath. The file should
|
||||
* be named <code>exceptionmap.properties</code> and placed in the
|
||||
* classpath of the JVM in which the servlet is executed. The file should
|
||||
* contain colon-separated mappings from exception classes to friendly
|
||||
* error messages. For example:
|
||||
*
|
||||
* <pre>
|
||||
* # Exception mappings (lines beginning with # are ignored)
|
||||
* com.samskivert.servlet.util.FriendlyException: An error occurred while \
|
||||
* processing your request: {m}
|
||||
*
|
||||
* # lines ending with \ are continued on the next line
|
||||
* java.sql.SQLException: The database is currently unavailable. Please \
|
||||
* try your request again later.
|
||||
*
|
||||
* java.lang.Exception: An unexpected error occurred while processing \
|
||||
* your request. Please try again later.
|
||||
* </pre>
|
||||
*
|
||||
* The message associated with the exception will be substituted into the
|
||||
* error string in place of <code>{m}</code>. The exceptions should be
|
||||
* listed in order of most to least specific, as the first mapping for
|
||||
* which the exception to report is an instance of the listed exception
|
||||
* will be used.
|
||||
*
|
||||
* <p><em>Note:</em> These exception mappings will generally be used for
|
||||
* all requests (perhaps some day only for requests associated with a
|
||||
* particular application). Regardless, this error handling mechanism
|
||||
* should not be used for request specific errors. For example, an SQL
|
||||
* exception reporting a duplicate key should probably be caught and
|
||||
* reported specifically by the appropriate populator (it can still
|
||||
* leverage the pattern of inserting the error message into the context as
|
||||
* <code>"error"</code>) rather than relying on the default SQL exception
|
||||
* error message which is not likely to be meaningful for such a
|
||||
* situation.
|
||||
*/
|
||||
public class ExceptionMap
|
||||
{
|
||||
/**
|
||||
* Searches for the <code>exceptionmap.properties</code> file in the
|
||||
* classpath and loads it. If the file could not be found, an error is
|
||||
* reported and a default set of mappings is used.
|
||||
*/
|
||||
public static synchronized void init ()
|
||||
{
|
||||
// only initialize ourselves once
|
||||
if (_keys != null) {
|
||||
return;
|
||||
} else {
|
||||
_keys = new ArrayList<Class<?>>();
|
||||
_values = new ArrayList<String>();
|
||||
}
|
||||
|
||||
// first try loading the properties file without a leading slash
|
||||
ClassLoader cld = ExceptionMap.class.getClassLoader();
|
||||
InputStream config = ConfigUtil.getStream(PROPS_NAME, cld);
|
||||
if (config == null) {
|
||||
log.warning("Unable to load " + PROPS_NAME + " from CLASSPATH.");
|
||||
|
||||
} else {
|
||||
// otherwise process ye old config file.
|
||||
try {
|
||||
// we'll do some serious jiggery pokery to leverage the parsing
|
||||
// implementation provided by java.util.Properties. god bless
|
||||
// method overloading
|
||||
final ArrayList<String> classes = new ArrayList<String>();
|
||||
Properties loader = new Properties() {
|
||||
@Override public Object put (Object key, Object value) {
|
||||
classes.add((String)key);
|
||||
_values.add((String)value);
|
||||
return key;
|
||||
}
|
||||
};
|
||||
loader.load(config);
|
||||
|
||||
// now cruise through and resolve the exceptions named as
|
||||
// keys and throw out any that don't appear to exist
|
||||
for (int i = 0; i < classes.size(); i++) {
|
||||
String exclass = classes.get(i);
|
||||
try {
|
||||
Class<?> cl = Class.forName(exclass);
|
||||
// replace the string with the class object
|
||||
_keys.add(cl);
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.warning("Unable to resolve exception class.", "class", exclass,
|
||||
"error", t);
|
||||
_values.remove(i);
|
||||
i--; // back on up a notch
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error reading exception mapping file: " + ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the supplied exception in the map and returns the most
|
||||
* specific error message available for exceptions of that class.
|
||||
*
|
||||
* @param ex The exception to resolve into an error message.
|
||||
*
|
||||
* @return The error message to which this exception maps (properly
|
||||
* populated with the message associated with this exception
|
||||
* instance).
|
||||
*/
|
||||
public static String getMessage (Throwable ex)
|
||||
{
|
||||
String msg = DEFAULT_ERROR_MSG;
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
Class<?> cl = _keys.get(i);
|
||||
if (cl.isInstance(ex)) {
|
||||
msg = _values.get(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg.replace(MESSAGE_MARKER, ex.getMessage());
|
||||
}
|
||||
|
||||
protected static List<Class<?>> _keys;
|
||||
protected static List<String> _values;
|
||||
|
||||
// initialize ourselves
|
||||
static { init(); }
|
||||
|
||||
protected static final String PROPS_NAME = "exceptionmap.properties";
|
||||
protected static final String DEFAULT_ERROR_MSG = "Error: {m}";
|
||||
protected static final String MESSAGE_MARKER = "{m}";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* The friendly exception provides a mechanism by which a servlet or
|
||||
* underlying code can abort its processing and report a human readable
|
||||
* error to the servlet framework.
|
||||
*/
|
||||
public class FriendlyException extends Exception
|
||||
{
|
||||
public FriendlyException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* HTML related utility functions.
|
||||
*/
|
||||
public class HTMLUtil
|
||||
{
|
||||
/**
|
||||
* Converts instances of <code><, >, & and "</code> into their
|
||||
* entified equivalents: <code><, >, & and "</code>.
|
||||
* These characters are mentioned in the HTML spec as being common
|
||||
* candidates for entification.
|
||||
*
|
||||
* @return the entified string.
|
||||
*/
|
||||
public static String entify (String text)
|
||||
{
|
||||
// this could perhaps be done more efficiently, but this function
|
||||
// is not likely to be called on large quantities of text
|
||||
// (first we turn the entified versions normal so that if text is
|
||||
// repeatedly run through this it doesn't keep changing successive
|
||||
// &'s into "&".
|
||||
text = text.replace(""", "\"");
|
||||
text = text.replace(">", ">");
|
||||
text = text.replace("<", "<");
|
||||
text = text.replace("&", "&");
|
||||
text = text.replace("&", "&");
|
||||
text = text.replace("<", "<");
|
||||
text = text.replace(">", ">");
|
||||
text = text.replace("\"", """);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a <p> tag between every two consecutive newlines.
|
||||
*/
|
||||
public static String makeParagraphs (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
// handle both line ending formats
|
||||
text = text.replace("\n\n", "\n<p>\n");
|
||||
text = text.replace("\r\n\r\n", "\r\n<p>\r\n");
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a <br> tag before every newline.
|
||||
*/
|
||||
public static String makeLinear (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
// handle both line ending formats
|
||||
text = text.replace("\n", "<br>\n");
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does some simple HTML markup, matching bare image URLs and wrapping
|
||||
* them in image tags, matching other URLs and wrapping them in href
|
||||
* tags, and wrapping * prefixed lists into ul-style HTML lists.
|
||||
*/
|
||||
public static String simpleFormat (String text)
|
||||
{
|
||||
// first replace the image and other URLs
|
||||
Matcher m = URL_PAT.matcher(text);
|
||||
StringBuffer tbuf = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String match = m.group();
|
||||
String lmatch = match.toLowerCase();
|
||||
if (lmatch.endsWith(".png") ||
|
||||
lmatch.endsWith(".jpg") ||
|
||||
lmatch.endsWith(".gif")) {
|
||||
match = "<img src=\"" + match + "\">";
|
||||
} else {
|
||||
match = "<a href=\"" + match + "\">" + match + "</a>";
|
||||
}
|
||||
m.appendReplacement(tbuf, match);
|
||||
}
|
||||
m.appendTail(tbuf);
|
||||
|
||||
// then do our paragraph and list processing
|
||||
String[] lines = StringUtil.split(tbuf.toString(), "\n");
|
||||
StringBuilder lbuf = new StringBuilder();
|
||||
|
||||
boolean inpara = false, inlist = false;
|
||||
for (String line2 : lines) {
|
||||
String line = line2;
|
||||
if (StringUtil.isBlank(line2)) {
|
||||
if (inlist) {
|
||||
lbuf.append("</ul>");
|
||||
inlist = false;
|
||||
}
|
||||
if (inpara) {
|
||||
lbuf.append("</p>\n");
|
||||
inpara = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inpara) {
|
||||
inpara = true;
|
||||
lbuf.append("<p> ");
|
||||
}
|
||||
|
||||
if (line.startsWith("*")) {
|
||||
if (inlist) {
|
||||
lbuf.append("<li>");
|
||||
} else {
|
||||
lbuf.append("<ul><li>");
|
||||
inlist = true;
|
||||
}
|
||||
line = line.substring(1);
|
||||
}
|
||||
|
||||
lbuf.append(line).append("\n");
|
||||
}
|
||||
|
||||
if (inlist) {
|
||||
lbuf.append("</ul>");
|
||||
}
|
||||
if (inpara) {
|
||||
lbuf.append("</p>\n");
|
||||
}
|
||||
|
||||
return lbuf.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict all HTML from the specified String.
|
||||
*/
|
||||
public static String restrictHTML (String src)
|
||||
{
|
||||
return restrictHTML(src, new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict HTML except for the specified tags.
|
||||
*
|
||||
* @param allowFormatting enables <i>, <b>, <u>,
|
||||
* <font>, <br>, <p>, and
|
||||
* <hr>.
|
||||
* @param allowImages enabled <img ...>.
|
||||
* @param allowLinks enabled <a href ...>.
|
||||
*/
|
||||
public static String restrictHTML (String src, boolean allowFormatting,
|
||||
boolean allowImages, boolean allowLinks)
|
||||
{
|
||||
// TODO: these regexes should probably be checked to make
|
||||
// sure that javascript can't live inside a link
|
||||
ArrayList<String> allow = new ArrayList<String>();
|
||||
if (allowFormatting) {
|
||||
allow.add("<b>"); allow.add("</b>");
|
||||
allow.add("<i>"); allow.add("</i>");
|
||||
allow.add("<u>"); allow.add("</u>");
|
||||
allow.add("<font [^\"<>!-]*(\"[^\"<>!-]*\"[^\"<>!-]*)*>");
|
||||
allow.add("</font>");
|
||||
allow.add("<br>"); allow.add("</br>"); allow.add("<br/>");
|
||||
allow.add("<p>"); allow.add("</p>");
|
||||
allow.add("<hr>"); allow.add("</hr>"); allow.add("<hr/>");
|
||||
}
|
||||
if (allowImages) {
|
||||
// Until I find a way to disallow "---", no - can be in a url
|
||||
allow.add("<img [^\"<>!-]*(\"[^\"<>!-]*\"[^\"<>!-]*)*>");
|
||||
allow.add("</img>");
|
||||
}
|
||||
if (allowLinks) {
|
||||
allow.add("<a href=[^\"<>!-]*(\"[^\"<>!-]*\"[^\"<>!-]*)*>");
|
||||
allow.add("</a>");
|
||||
}
|
||||
return restrictHTML(src, allow.toArray(new String[allow.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict HTML from the specified string except for the specified
|
||||
* regular expressions.
|
||||
*/
|
||||
public static String restrictHTML (String src, String[] regexes)
|
||||
{
|
||||
if (StringUtil.isBlank(src)) {
|
||||
return src;
|
||||
}
|
||||
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
list.add(src);
|
||||
for (String regexe : regexes) {
|
||||
Pattern p = Pattern.compile(regexe, Pattern.CASE_INSENSITIVE);
|
||||
for (int jj=0; jj < list.size(); jj += 2) {
|
||||
String piece = list.get(jj);
|
||||
Matcher m = p.matcher(piece);
|
||||
if (m.find()) {
|
||||
list.set(jj, piece.substring(0, m.start()));
|
||||
list.add(jj + 1, piece.substring(m.start(), m.end()));
|
||||
list.add(jj + 2, piece.substring(m.end()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now, the even elements of list contain untrusted text, the
|
||||
// odd elements contain stuff that matched a regex
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int jj=0, nn = list.size(); jj < nn; jj++) {
|
||||
String s = list.get(jj);
|
||||
if (jj % 2 == 0) {
|
||||
s = s.replace("<", "<");
|
||||
s = s.replace(">", ">");
|
||||
}
|
||||
buf.append(s);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected static final Pattern URL_PAT = Pattern.compile("^http://\\S+", Pattern.MULTILINE);
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.IntSet;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Utility functions for fetching and manipulating request parameters (form fields).
|
||||
*/
|
||||
public class ParameterUtil
|
||||
{
|
||||
/** A default date that can be placed in fields to communicate the appropriate format. Is
|
||||
* treated the same as the empty string by {@link #getDateParameter}. */
|
||||
public static final String DATE_TEMPLATE = "YYYY-MM-DD";
|
||||
|
||||
/**
|
||||
* An interface for validating form parameters.
|
||||
*/
|
||||
public static interface ParameterValidator
|
||||
{
|
||||
public void validateParameter (String name, String value)
|
||||
throws DataValidationException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the parameters of the supplied request in a civilized format.
|
||||
*/
|
||||
public static Iterable<String> getParameterNames (HttpServletRequest req)
|
||||
{
|
||||
List<String> params = new ArrayList<String>();
|
||||
Enumeration<?> iter = req.getParameterNames();
|
||||
while (iter.hasMoreElements()) {
|
||||
params.add((String)iter.nextElement());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter does not exist, either
|
||||
* null or the empty string will be returned depending on the value of the
|
||||
* <code>returnNull</code> parameter.
|
||||
*/
|
||||
public static String getParameter (HttpServletRequest req, String name, boolean returnNull)
|
||||
{
|
||||
String value = req.getParameter(name);
|
||||
if (value == null) {
|
||||
return returnNull ? null : "";
|
||||
} else {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to a float. If the parameter
|
||||
* does not exist or is not a well-formed float, a data validation exception is thrown with the
|
||||
* supplied message.
|
||||
*/
|
||||
public static float requireFloatParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseFloatParameter(getParameter(req, name, false), invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to an integer. If the
|
||||
* parameter does not exist or is not a well-formed integer, a data validation exception is
|
||||
* thrown with the supplied message.
|
||||
*/
|
||||
public static int requireIntParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseIntParameter(getParameter(req, name, false), invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to an integer. If the
|
||||
* parameter does not exist or is not a well-formed integer, a data validation exception is
|
||||
* thrown with the supplied message.
|
||||
*/
|
||||
public static int requireIntParameter (HttpServletRequest req, String name,
|
||||
String invalidDataMessage, ParameterValidator validator)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
validator.validateParameter(name, value);
|
||||
return parseIntParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to an integer. If the
|
||||
* parameter does not exist or is not a well-formed integer, a data validation exception is
|
||||
* thrown with the supplied message.
|
||||
*/
|
||||
public static int requireIntParameter (HttpServletRequest req, String name, int low, int high,
|
||||
String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return requireIntParameter(
|
||||
req, name, invalidDataMessage, new IntRangeValidator(low, high, invalidDataMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the values from the request with the specified name and converts them to an
|
||||
* IntSet. If the parameter does not exist or is not a well-formed integer, a data validation
|
||||
* exception is thrown with the supplied message.
|
||||
*/
|
||||
public static IntSet getIntParameters (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
IntSet ints = new ArrayIntSet();
|
||||
String[] values = req.getParameterValues(name);
|
||||
if (values != null) {
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (!StringUtil.isBlank(values[ii])) {
|
||||
ints.add(parseIntParameter(values[ii], invalidDataMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the values from the request with the specified name and converts them to a Set.
|
||||
*/
|
||||
public static Set<String> getParameters (HttpServletRequest req, String name)
|
||||
{
|
||||
Set<String> set = new HashSet<String>();
|
||||
String[] values = req.getParameterValues(name);
|
||||
if (values != null) {
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (!StringUtil.isBlank(values[ii])) {
|
||||
set.add(values[ii]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter does not exist,
|
||||
* <code>defval</code> is returned.
|
||||
*/
|
||||
public static String getParameter (HttpServletRequest req, String name, String defval)
|
||||
{
|
||||
String value = req.getParameter(name);
|
||||
return StringUtil.isBlank(value) ? defval : value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to an integer. If the
|
||||
* parameter does not exist, <code>defval</code> is returned. If the parameter is not a
|
||||
* well-formed integer, a data validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static int getIntParameter (
|
||||
HttpServletRequest req, String name, int defval, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return defval;
|
||||
}
|
||||
return parseIntParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to a long. If the parameter
|
||||
* does not exist, <code>defval</code> is returned. If the parameter is not a well-formed
|
||||
* integer, a data validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static long getLongParameter (
|
||||
HttpServletRequest req, String name, long defval, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return defval;
|
||||
}
|
||||
return parseLongParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter does not exist, a data
|
||||
* validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static String requireParameter (
|
||||
HttpServletRequest req, String name, String missingDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, true);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
throw new DataValidationException(missingDataMessage);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and ensures that it is no longer than
|
||||
* maxLength.
|
||||
*
|
||||
* Note that use of this method could be dangerous. If the specified HttpServletRequest is used
|
||||
* to pre-fill in values on a form, it will not know to use the truncated version. A user may
|
||||
* enter a version that is too long, your code will see a truncated version, but then the user
|
||||
* will see on the page again the full-length reproduction of what they typed in. Be careful.
|
||||
*/
|
||||
public static String requireParameter (
|
||||
HttpServletRequest req, String name, String missingDataMessage, int maxLength)
|
||||
throws DataValidationException
|
||||
{
|
||||
return StringUtil.truncate(
|
||||
requireParameter(req, name, missingDataMessage), maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to a date. The value of the
|
||||
* parameter should be a date formatted like so: 2001-12-25. If the parameter does not exist or
|
||||
* is not a well-formed date, a data validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static Date requireDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseDateParameter(getParameter(req, name, false), invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #requireDateParameter} but returns a SQL date.
|
||||
*/
|
||||
public static java.sql.Date requireSQLDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return new java.sql.Date(requireDateParameter(req, name, invalidDataMessage).getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to a date. The value of the
|
||||
* parameter should be a date formatted like so: 2001-12-25. If the parameter does not exist,
|
||||
* null is returned. If the parameter is not a well-formed date, a data validation exception is
|
||||
* thrown with the supplied message.
|
||||
*/
|
||||
public static Date getDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value) ||
|
||||
DATE_TEMPLATE.equalsIgnoreCase(value)) {
|
||||
return null;
|
||||
}
|
||||
return parseDateParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #getDateParameter} but returns a SQL date.
|
||||
*/
|
||||
public static java.sql.Date getSQLDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
Date when = getDateParameter(req, name, invalidDataMessage);
|
||||
return (when == null) ? null : new java.sql.Date(when.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is set in the request.
|
||||
*
|
||||
* @return true if the specified parameter is set in the request context, false otherwise.
|
||||
*/
|
||||
public static boolean isSet (HttpServletRequest req, String name)
|
||||
{
|
||||
return isSet(req, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is set to a non-blank value in the request, false if
|
||||
* the value is blank, or <code>defaultValue</code> if the parameter is unspecified.
|
||||
*
|
||||
* @return true if the specified parameter is set to a non-blank value in the request, false if
|
||||
* the value is blank, or <code>defaultValue</code> if the parameter is unspecified.
|
||||
*/
|
||||
public static boolean isSet (HttpServletRequest req, String name, boolean defaultValue)
|
||||
{
|
||||
String value = getParameter(req, name, true);
|
||||
return (value == null) ? defaultValue : !StringUtil.isBlank(req.getParameter(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is equal to the supplied value. If the parameter is
|
||||
* not set in the request, false is returned.
|
||||
*
|
||||
* @param name The parameter whose value should be compared with the supplied value.
|
||||
* @param value The value to which the parameter may be equal. This should not be null.
|
||||
*
|
||||
* @return true if the specified parameter is equal to the supplied parameter, false otherwise.
|
||||
*/
|
||||
public static boolean parameterEquals (HttpServletRequest req, String name, String value)
|
||||
{
|
||||
return value.equals(getParameter(req, name, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse integer values.
|
||||
*/
|
||||
protected static int parseIntParameter (String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse long values.
|
||||
*/
|
||||
protected static long parseLongParameter (String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse a float value.
|
||||
*/
|
||||
protected static float parseFloatParameter (String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse a date.
|
||||
*/
|
||||
protected static Date parseDateParameter (String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
synchronized (_dparser) {
|
||||
return _dparser.parse(value);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes sure integers are within a range. */
|
||||
protected static class IntRangeValidator implements ParameterValidator
|
||||
{
|
||||
public IntRangeValidator (int low, int high, String outOfRangeError) {
|
||||
_low = low;
|
||||
_high = high;
|
||||
_err = outOfRangeError;
|
||||
}
|
||||
|
||||
public void validateParameter (String name, String value) throws DataValidationException {
|
||||
try {
|
||||
int ivalue = Integer.parseInt(value);
|
||||
if ((ivalue >= _low) && (ivalue <= _high)) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// fall through
|
||||
}
|
||||
throw new DataValidationException(_err);
|
||||
}
|
||||
|
||||
protected int _low, _high;
|
||||
protected String _err;
|
||||
}
|
||||
|
||||
/** We use this to parse dates in requireDateParameter(). */
|
||||
protected static final SimpleDateFormat _dparser = new SimpleDateFormat("yyyy-MM-dd");
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A repository of utility functions related to HTTP servlet stuff.
|
||||
*/
|
||||
public class RequestUtils
|
||||
{
|
||||
/**
|
||||
* Reconstructs the current location (URL) from the request and
|
||||
* servlet configuration (which is the only way to know which server
|
||||
* we're on because that's not provided in the request) and returns it
|
||||
* URL encoded so that it can be substituted into another URL.
|
||||
*
|
||||
* @return The URL encoded URL that represents our current location.
|
||||
*/
|
||||
public static String getLocationEncoded (HttpServletRequest req)
|
||||
{
|
||||
return StringUtil.encode(getLocation(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the current location (URL) from the request and
|
||||
* servlet configuration (which is the only way to know which server
|
||||
* we're on because that's not provided in the request) and returns
|
||||
* it.
|
||||
*
|
||||
* @return The URL that represents our current location.
|
||||
*/
|
||||
public static String getLocation (HttpServletRequest req)
|
||||
{
|
||||
StringBuffer rurl = req.getRequestURL();
|
||||
String qs = req.getQueryString();
|
||||
if (qs != null) {
|
||||
rurl.append("?").append(qs);
|
||||
}
|
||||
return rurl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreates the URL used to make the supplied request, replacing the
|
||||
* server part of the URL with the supplied server name.
|
||||
*/
|
||||
public static String rehostLocation (
|
||||
HttpServletRequest req, String servername)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
String csname = req.getServerName();
|
||||
int csidx = buf.indexOf(csname);
|
||||
if (csidx != -1) {
|
||||
buf.delete(csidx, csidx + csname.length());
|
||||
buf.insert(csidx, servername);
|
||||
}
|
||||
String query = req.getQueryString();
|
||||
if (!StringUtil.isBlank(query)) {
|
||||
buf.append("?").append(query);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the server, port and servlet context path to the supplied
|
||||
* path, resulting in a fully-formed URL for requesting a servlet.
|
||||
*/
|
||||
public static String getServletURL (HttpServletRequest req, String path)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
String sname = req.getServletPath();
|
||||
buf.delete(buf.length() - sname.length(), buf.length());
|
||||
if (!path.startsWith("/")) {
|
||||
buf.append("/");
|
||||
}
|
||||
buf.append(path);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the request URL including query parameters. <em>Note:</em>
|
||||
* the output of this method is purely for logging purposes only, thus POST
|
||||
* parameters are shown as if they were GET parameters and parameters are
|
||||
* <em>not</em> URL encoded.
|
||||
*/
|
||||
public static String reconstructURL (HttpServletRequest req)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
@SuppressWarnings("unchecked") Map<String, String[]> map = req.getParameterMap();
|
||||
if (map.size() > 0) {
|
||||
buf.append("?");
|
||||
for (Map.Entry<String, String[]> entry : map.entrySet()) {
|
||||
if (buf.charAt(buf.length()-1) != '?') {
|
||||
buf.append("&");
|
||||
}
|
||||
buf.append(entry.getKey()).append("=");
|
||||
String[] values = entry.getValue();
|
||||
if (values.length == 1) {
|
||||
buf.append(values[0]);
|
||||
} else {
|
||||
buf.append("(");
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(values[ii]);
|
||||
}
|
||||
buf.append(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* Extends the basic ServiceWaiter to be useful for servlets.
|
||||
*/
|
||||
public class ServiceWaiter<T> extends com.samskivert.util.ServiceWaiter<T>
|
||||
{
|
||||
/** Timeout to specify when you don't want a timeout. Use at your own
|
||||
* risk. */
|
||||
public static final int NO_TIMEOUT = -1;
|
||||
|
||||
/**
|
||||
* Construct a ServiceWaiter with the default (30 second) timeout.
|
||||
*/
|
||||
public ServiceWaiter ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ServiceWaiter with the specified timeout.
|
||||
*
|
||||
* @param timeout the timeout, in seconds.
|
||||
*/
|
||||
public ServiceWaiter (int timeout)
|
||||
{
|
||||
super(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks waiting for the response.
|
||||
*
|
||||
* @return true if a success response was posted, false if a failure
|
||||
* repsonse was posted.
|
||||
*/
|
||||
public boolean awaitFriendlyResponse (String friendlyText)
|
||||
throws FriendlyException
|
||||
{
|
||||
try {
|
||||
return waitForResponse();
|
||||
} catch (TimeoutException te) {
|
||||
throw new FriendlyException(friendlyText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides servlet-related utilities.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
/**
|
||||
* Dispatches a {@link ResultListener}'s callbacks on the AWT thread
|
||||
* regardless of what thread on which they were originally dispatched.
|
||||
*/
|
||||
public class AWTResultListener<T> implements ResultListener<T>
|
||||
{
|
||||
/**
|
||||
* Creates an AWT result listener that will dispatch results to the
|
||||
* supplied target.
|
||||
*/
|
||||
public AWTResultListener (ResultListener<T> target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestCompleted (final T result)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
_target.requestCompleted(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestFailed (final Exception cause)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
_target.requestFailed(cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The result listener for which we are proxying. */
|
||||
protected ResultListener<T> _target;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Insets;
|
||||
import java.awt.LayoutManager2;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Used to lay out components at absolute coordinates. This layout manager
|
||||
* will not gracefully deal with not having room to layout the components
|
||||
* at the coordinates specified, so be sure only to use it in very
|
||||
* controlled circumstances.
|
||||
*
|
||||
* <p> Components should be added with a {@link Point} or {@link
|
||||
* Rectangle} object as their constraints. If their constraints are a
|
||||
* {@link Point} they will be laid out at those coordinates with their
|
||||
* preferred size. If it is a {@link Rectangle} they will be laid out in
|
||||
* those exact bounds.
|
||||
*/
|
||||
public class AbsoluteLayout
|
||||
implements LayoutManager2
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public void addLayoutComponent (String name, Component comp)
|
||||
{
|
||||
throw new RuntimeException("You must use " +
|
||||
"addLayoutComponent(Component, Object)");
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void addLayoutComponent (Component comp, Object constraints)
|
||||
{
|
||||
if (!(constraints instanceof Point ||
|
||||
constraints instanceof Rectangle)) {
|
||||
throw new IllegalArgumentException("Constraints must be " +
|
||||
"Point or Rectangle");
|
||||
}
|
||||
_constraints.put(comp, constraints);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void removeLayoutComponent (Component comp)
|
||||
{
|
||||
_constraints.remove(comp);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension preferredLayoutSize (Container parent)
|
||||
{
|
||||
Rectangle rect = new Rectangle();
|
||||
Rectangle temp = new Rectangle();
|
||||
int pcount = parent.getComponentCount();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
Component comp = parent.getComponent(ii);
|
||||
if (!comp.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object constr = _constraints.get(comp);
|
||||
if (constr == null) {
|
||||
log.warning("No constraints for child!?", "cont", parent, "comp", comp);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constr instanceof Rectangle) {
|
||||
rect.add((Rectangle)constr);
|
||||
} else {
|
||||
Point p = (Point)constr;
|
||||
Dimension d = comp.getPreferredSize();
|
||||
temp.setBounds(p.x, p.y, d.width, d.height);
|
||||
rect.add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
Dimension dims = new Dimension(rect.width, rect.height);
|
||||
|
||||
// account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
dims.width += insets.left + insets.right;
|
||||
dims.height += insets.top + insets.bottom;
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension minimumLayoutSize (Container parent)
|
||||
{
|
||||
// we don't do no fancy business
|
||||
return preferredLayoutSize(parent);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension maximumLayoutSize (Container parent)
|
||||
{
|
||||
// we don't do no fancy business
|
||||
return preferredLayoutSize(parent);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void layoutContainer (Container parent)
|
||||
{
|
||||
Insets insets = parent.getInsets();
|
||||
int pcount = parent.getComponentCount();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
Component comp = parent.getComponent(ii);
|
||||
if (!comp.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object constr = _constraints.get(comp);
|
||||
if (constr == null) {
|
||||
log.warning("No constraints for child!?", "cont", parent, "comp", comp);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constr instanceof Rectangle) {
|
||||
Rectangle r = (Rectangle)constr;
|
||||
comp.setBounds(insets.left + r.x, insets.top + r.y,
|
||||
r.width, r.height);
|
||||
|
||||
} else {
|
||||
Point p = (Point)constr;
|
||||
Dimension d = comp.getPreferredSize();
|
||||
comp.setBounds(insets.left + p.x, insets.top + p.y,
|
||||
d.width, d.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void invalidateLayout (Container target)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public float getLayoutAlignmentX (Container target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public float getLayoutAlignmentY (Container target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected HashMap<Component,Object> _constraints =
|
||||
new HashMap<Component,Object>();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.ListModel;
|
||||
|
||||
/**
|
||||
* Displays a list of components in sections (with section headers) that
|
||||
* can be collapsed. Each section of a list uses a {@link JList} instance
|
||||
* to render the section elements.
|
||||
*/
|
||||
public class CollapsibleList extends JPanel
|
||||
{
|
||||
/**
|
||||
* Constructs an empty collapsible list.
|
||||
*/
|
||||
public CollapsibleList ()
|
||||
{
|
||||
// set up our layout manager
|
||||
VGroupLayout gl = new VGroupLayout(VGroupLayout.NONE);
|
||||
gl.setOffAxisPolicy(VGroupLayout.STRETCH);
|
||||
gl.setJustification(VGroupLayout.TOP);
|
||||
gl.setOffAxisJustification(VGroupLayout.LEFT);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a collapsible list with the supplied section labels and
|
||||
* models.
|
||||
*/
|
||||
public CollapsibleList (String[] sections, ListModel[] models)
|
||||
{
|
||||
this(); // set up our layout manager
|
||||
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
addSection(sections[i], models[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of sections.
|
||||
*/
|
||||
public int getSectionCount ()
|
||||
{
|
||||
return getComponentCount()/2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a section to this collapsible list.
|
||||
*
|
||||
* @param label the title of the section.
|
||||
* @param model the list model to use for the new section.
|
||||
*
|
||||
* @return the index of the newly added section.
|
||||
*/
|
||||
public int addSection (String label, ListModel model)
|
||||
{
|
||||
add(new JLabel(label));
|
||||
add(new JList(model));
|
||||
return getSectionCount()-1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label object associated with the title of the specified
|
||||
* section.
|
||||
*/
|
||||
public JLabel getSectionLabel (int index)
|
||||
{
|
||||
return (JLabel)getComponent(index*2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list object associated with the specified section.
|
||||
*/
|
||||
public JList getSectionList (int index)
|
||||
{
|
||||
return (JList)getComponent(index*2+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the collapsed state of the specified section.
|
||||
*/
|
||||
public void toggleCollapsed (int index)
|
||||
{
|
||||
JList list = getSectionList(index);
|
||||
list.setVisible(!list.isVisible());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.LayoutManager;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* A panel that contains a button which will collapse the rest of the content.
|
||||
*/
|
||||
public class CollapsiblePanel extends JPanel
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* Construct a collapsible panel with the specified button as the
|
||||
* trigger. The text of the button will be used as the triggertext.
|
||||
*/
|
||||
public CollapsiblePanel (JButton trigger)
|
||||
{
|
||||
setTrigger(trigger, null, null);
|
||||
setTriggerContainer(trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a collapsible panel with the specified button text.
|
||||
*/
|
||||
public CollapsiblePanel (String triggertext)
|
||||
{
|
||||
this(new JButton(triggertext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collapsible panel to which the trigger button will be
|
||||
* added later.
|
||||
*/
|
||||
public CollapsiblePanel (LayoutManager layout)
|
||||
{
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collapsible panel to which the trigger button will be
|
||||
* added later.
|
||||
*/
|
||||
public CollapsiblePanel ()
|
||||
{
|
||||
VGroupLayout gl = new VGroupLayout(VGroupLayout.NONE);
|
||||
gl.setOffAxisPolicy(VGroupLayout.STRETCH);
|
||||
gl.setGap(0);
|
||||
gl.setJustification(VGroupLayout.TOP);
|
||||
gl.setOffAxisJustification(VGroupLayout.LEFT);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a component which contains the trigger button. The simple case
|
||||
* is to just set the trigger button as this component.
|
||||
*/
|
||||
public void setTriggerContainer (JComponent comp)
|
||||
{
|
||||
setTriggerContainer(comp, new JPanel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a component which contains the trigger button.
|
||||
*/
|
||||
public void setTriggerContainer (JComponent comp, JPanel content)
|
||||
{
|
||||
setTriggerContainer(comp, content, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a component which contains the trigger button.
|
||||
*/
|
||||
public void setTriggerContainer (JComponent comp, JPanel content, boolean collapsed)
|
||||
{
|
||||
// these are our only two components.
|
||||
add(comp);
|
||||
add(_content = content);
|
||||
|
||||
// When the content is shown, make sure it's scrolled visible
|
||||
_content.addComponentListener(new ComponentAdapter() {
|
||||
@Override public void componentShown (ComponentEvent event)
|
||||
{
|
||||
// we can't do it just yet, the content doesn't know its size
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
// The content is offset a bit from the trigger
|
||||
// but we want the trigger to show up, so we add
|
||||
// in point 0,0
|
||||
Rectangle r = _content.getBounds();
|
||||
r.add(0, 0);
|
||||
scrollRectToVisible(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// and start us out not showing
|
||||
setCollapsed(collapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the trigger button.
|
||||
*/
|
||||
public void setTrigger (AbstractButton trigger,
|
||||
Icon collapsed, Icon uncollapsed)
|
||||
{
|
||||
_trigger = trigger;
|
||||
_trigger.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
_trigger.setHorizontalTextPosition(SwingConstants.RIGHT);
|
||||
_downIcon = collapsed;
|
||||
_upIcon = uncollapsed;
|
||||
_trigger.addActionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gap between the trigger button and the rest of the content.
|
||||
* Can be negative for an overlapping effect.
|
||||
*/
|
||||
public void setGap (int gap)
|
||||
{
|
||||
((VGroupLayout) getLayout()).setGap(gap);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content panel for filling in with sweet content goodness.
|
||||
*/
|
||||
public JPanel getContent ()
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
// documentation from interface ActionListener
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
if (e.getSource() == _trigger) {
|
||||
setCollapsed(!isCollapsed());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the panel collapsed?
|
||||
*/
|
||||
public boolean isCollapsed ()
|
||||
{
|
||||
return !_content.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collapsion state.
|
||||
*/
|
||||
public void setCollapsed (boolean collapse)
|
||||
{
|
||||
if (collapse) {
|
||||
_content.setVisible(false);
|
||||
_trigger.setIcon(_downIcon);
|
||||
|
||||
} else {
|
||||
_content.setVisible(true);
|
||||
_trigger.setIcon(_upIcon);
|
||||
}
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
/** The button that triggers collapsion. */
|
||||
protected AbstractButton _trigger;
|
||||
|
||||
/** The icons for collapsed and uncollapsed. */
|
||||
protected Icon _upIcon, _downIcon;
|
||||
|
||||
/** The who in the what now? */
|
||||
protected JPanel _content;
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ComboBoxModel;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* Used to display a horizontal or vertical array of buttons, out of which
|
||||
* only one is selectable at a time (which will be represented by
|
||||
* rendering it with an indented border, whereas the other buttons will
|
||||
* render with an extruded border.
|
||||
*/
|
||||
public class ComboButtonBox extends JPanel
|
||||
implements SwingConstants, ListDataListener, MouseListener
|
||||
{
|
||||
/**
|
||||
* Constructs a button box with the specified orientation (either
|
||||
* {@link #HORIZONTAL} or {@link #VERTICAL}.
|
||||
*/
|
||||
public ComboButtonBox (int orientation)
|
||||
{
|
||||
this(orientation, new DefaultComboBoxModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a button box with the specified orientation (either
|
||||
* {@link #HORIZONTAL} or {@link #VERTICAL}. The supplied model will
|
||||
* be used to populate the buttons (see {@link #setModel} for more
|
||||
* details).
|
||||
*/
|
||||
public ComboButtonBox (int orientation, ComboBoxModel model)
|
||||
{
|
||||
// set up our layout
|
||||
setOrientation(orientation);
|
||||
|
||||
// set up our contents
|
||||
setModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation of the box (either {@link #HORIZONTAL} or
|
||||
* {@link #VERTICAL}.
|
||||
*/
|
||||
public void setOrientation (int orientation)
|
||||
{
|
||||
GroupLayout gl = (orientation == HORIZONTAL) ?
|
||||
(GroupLayout)new HGroupLayout() : new VGroupLayout();
|
||||
gl.setPolicy(GroupLayout.EQUALIZE);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the button box with a data model which it will display. If
|
||||
* the model contains {@link Image} objects, they will be used to make
|
||||
* icons for the buttons. Otherwise the button text will contain the
|
||||
* string representation of the elements in the model.
|
||||
*/
|
||||
public void setModel (ComboBoxModel model)
|
||||
{
|
||||
// if we had a previous model, unregister ourselves from it
|
||||
if (_model != null) {
|
||||
_model.removeListDataListener(this);
|
||||
}
|
||||
|
||||
// subscribe to our new model
|
||||
_model = model;
|
||||
_model.addListDataListener(this);
|
||||
|
||||
// rebuild the list
|
||||
removeAll();
|
||||
addButtons(0, _model.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled (boolean enabled)
|
||||
{
|
||||
super.setEnabled(enabled);
|
||||
|
||||
int ccount = getComponentCount();
|
||||
for (int i = 0; i < ccount; i++) {
|
||||
getComponent(i).setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model in use by the button box.
|
||||
*/
|
||||
public ComboBoxModel getModel ()
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the selected component. A value of -1 will clear
|
||||
* the selection.
|
||||
*/
|
||||
public void setSelectedIndex (int selidx)
|
||||
{
|
||||
// update the display
|
||||
updateSelection(selidx);
|
||||
|
||||
// let the model know what's up
|
||||
Object item = (selidx == -1) ? null : _model.getElementAt(selidx);
|
||||
_model.setSelectedItem(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the selected item.
|
||||
*/
|
||||
public int getSelectedIndex ()
|
||||
{
|
||||
return _selectedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the command that will be used when generating action
|
||||
* events (which is done when the selection changes).
|
||||
*/
|
||||
public void setActionCommand (String actionCommand)
|
||||
{
|
||||
_actionCommand = actionCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to our list of entities to be notified when the
|
||||
* selection changes.
|
||||
*/
|
||||
public void addActionListener (ActionListener l)
|
||||
{
|
||||
listenerList.add(ActionListener.class, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from the list.
|
||||
*/
|
||||
public void removeActionListener (ActionListener l)
|
||||
{
|
||||
listenerList.remove(ActionListener.class, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies our listeners when the selection changed.
|
||||
*/
|
||||
protected void fireActionPerformed ()
|
||||
{
|
||||
// guaranteed to return a non-null array
|
||||
Object[] listeners = listenerList.getListenerList();
|
||||
// process the listeners last to first, notifying those that are
|
||||
// interested in this event
|
||||
for (int i = listeners.length-2; i >= 0; i -= 2) {
|
||||
if (listeners[i] == ActionListener.class) {
|
||||
// lazily create the event:
|
||||
if (_actionEvent == null) {
|
||||
_actionEvent = new ActionEvent(
|
||||
this, ActionEvent.ACTION_PERFORMED, _actionCommand);
|
||||
}
|
||||
((ActionListener)listeners[i+1]).actionPerformed(_actionEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void contentsChanged (ListDataEvent e)
|
||||
{
|
||||
// if this update is informing us of a new selection, reflect that
|
||||
// in the UI
|
||||
int start = e.getIndex0(), count = start - e.getIndex1() + 1;
|
||||
if (start == -1) {
|
||||
// figure out the selected index
|
||||
int selidx = -1;
|
||||
Object eitem = _model.getSelectedItem();
|
||||
if (eitem != null) {
|
||||
int ecount = _model.getSize();
|
||||
for (int i = 0; i < ecount; i++) {
|
||||
if (eitem == _model.getElementAt(i)) {
|
||||
selidx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and update it
|
||||
updateSelection(selidx);
|
||||
|
||||
} else {
|
||||
// replace the buttons in this range
|
||||
removeButtons(start, count);
|
||||
addButtons(start, count);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void intervalAdded (ListDataEvent e)
|
||||
{
|
||||
int start = e.getIndex0(), count = e.getIndex1() - start + 1;
|
||||
|
||||
// adjust the selected index
|
||||
if (_selectedIndex >= start) {
|
||||
_selectedIndex += count;
|
||||
}
|
||||
|
||||
// add buttons for the new interval
|
||||
addButtons(start, count);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void intervalRemoved (ListDataEvent e)
|
||||
{
|
||||
// remove the buttons in the specified interval
|
||||
int start = e.getIndex0(), count = e.getIndex1() - start + 1;
|
||||
removeButtons(start, count);
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseClicked (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mousePressed (MouseEvent e)
|
||||
{
|
||||
// ignore if we're not enabled
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keep track of the selected button
|
||||
_selectedButton = (JLabel)e.getSource();
|
||||
// if the selected button is already selected, ignore the click
|
||||
if (_selectedButton.getBorder() == SELECTED_BORDER) {
|
||||
_selectedButton = null;
|
||||
} else {
|
||||
_selectedButton.setBorder(SELECTED_BORDER);
|
||||
_selectedButton.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseReleased (MouseEvent e)
|
||||
{
|
||||
// if the mouse was released within the bounds of the button, go
|
||||
// ahead and select it properly
|
||||
if (_selectedButton != null) {
|
||||
if (_selectedButton.contains(e.getX(), e.getY())) {
|
||||
// tell the model that the selection has changed (and
|
||||
// we'll respond and do our business
|
||||
Object elem = _selectedButton.getClientProperty("element");
|
||||
_model.setSelectedItem(elem);
|
||||
|
||||
} else {
|
||||
_selectedButton.setBorder(DESELECTED_BORDER);
|
||||
_selectedButton.repaint();
|
||||
}
|
||||
|
||||
// clear out the selected button indicator
|
||||
_selectedButton = null;
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseEntered (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseExited (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds buttons for the specified range of model elements.
|
||||
*/
|
||||
protected void addButtons (int start, int count)
|
||||
{
|
||||
Object selobj = _model.getSelectedItem();
|
||||
for (int i = start; i < count; i++) {
|
||||
Object elem = _model.getElementAt(i);
|
||||
if (selobj == elem) {
|
||||
_selectedIndex = i;
|
||||
}
|
||||
JLabel ibut = null;
|
||||
if (elem instanceof Image) {
|
||||
ibut = new JLabel(new ImageIcon((Image)elem));
|
||||
} else {
|
||||
ibut = new JLabel(elem.toString());
|
||||
}
|
||||
ibut.putClientProperty("element", elem);
|
||||
ibut.addMouseListener(this);
|
||||
ibut.setBorder((_selectedIndex == i) ?
|
||||
SELECTED_BORDER : DESELECTED_BORDER);
|
||||
add(ibut, i);
|
||||
}
|
||||
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the buttons in the specified interval.
|
||||
*/
|
||||
protected void removeButtons (int start, int count)
|
||||
{
|
||||
while (count-- > 0) {
|
||||
remove(start);
|
||||
}
|
||||
|
||||
// adjust the selected index
|
||||
if (_selectedIndex >= start) {
|
||||
if (start + count > _selectedIndex) {
|
||||
_selectedIndex = -1;
|
||||
} else {
|
||||
_selectedIndex -= count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection to the specified index and updates the buttons
|
||||
* to reflect the change. This does not update the model.
|
||||
*/
|
||||
protected void updateSelection (int selidx)
|
||||
{
|
||||
// do nothing if this element is already selected
|
||||
if (selidx == _selectedIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// unhighlight the old component
|
||||
if (_selectedIndex != -1) {
|
||||
JLabel but = (JLabel)getComponent(_selectedIndex);
|
||||
but.setBorder(DESELECTED_BORDER);
|
||||
}
|
||||
|
||||
// save the new selection
|
||||
_selectedIndex = selidx;
|
||||
|
||||
// if the selection is valid, highlight the new component
|
||||
if (_selectedIndex != -1) {
|
||||
JLabel but = (JLabel)getComponent(_selectedIndex);
|
||||
but.setBorder(SELECTED_BORDER);
|
||||
}
|
||||
|
||||
// fire an action performed to let listeners know about our
|
||||
// changed selection
|
||||
fireActionPerformed();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** The contents of the box. */
|
||||
protected ComboBoxModel _model;
|
||||
|
||||
/** The index of the selected button. */
|
||||
protected int _selectedIndex = -1;
|
||||
|
||||
/** The button over which the mouse was pressed. */
|
||||
protected JLabel _selectedButton;
|
||||
|
||||
/** Used when notifying our listeners. */
|
||||
protected ActionEvent _actionEvent;
|
||||
|
||||
/** The action command we generate when the selection changes. */
|
||||
protected String _actionCommand;
|
||||
|
||||
/** The border used for selected components. */
|
||||
protected static final Border SELECTED_BORDER =
|
||||
BorderFactory.createLoweredBevelBorder();
|
||||
|
||||
/** The border used for non-selected components. */
|
||||
protected static final Border DESELECTED_BORDER =
|
||||
BorderFactory.createRaisedBevelBorder();
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.JButton;
|
||||
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
/**
|
||||
* A button that fires CommandEvents when it is actioned.
|
||||
*/
|
||||
public class CommandButton extends JButton
|
||||
{
|
||||
/**
|
||||
* Set the argument of the CommandEvents which we generate.
|
||||
*/
|
||||
public void setActionArgument (Object arg)
|
||||
{
|
||||
_argument = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument that we'll use when we generate CommandEvents.
|
||||
*/
|
||||
public Object getActionArgument ()
|
||||
{
|
||||
return _argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireActionPerformed (ActionEvent event)
|
||||
{
|
||||
Object[] listeners = listenerList.getListenerList();
|
||||
ActionEvent e = null;
|
||||
// Process the listeners last to first, notfiying
|
||||
// those that are interested in this event
|
||||
for (int ii = listeners.length - 2; ii >= 0; ii -= 2) {
|
||||
if (listeners[ii] == ActionListener.class) {
|
||||
// Lazily create the event:
|
||||
if (e == null) {
|
||||
e = createEventToFire(event);
|
||||
}
|
||||
((ActionListener) listeners[ii + 1]).actionPerformed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the event to fire from the prototype.
|
||||
* If this method were broken out in AbstractButton, we wouldn't even
|
||||
* have had to override fireActionPerformed().
|
||||
*/
|
||||
protected ActionEvent createEventToFire (ActionEvent proto)
|
||||
{
|
||||
String actionCommand = proto.getActionCommand();
|
||||
if (actionCommand == null) {
|
||||
actionCommand = getActionCommand();
|
||||
}
|
||||
|
||||
Object arg = (proto instanceof CommandEvent)
|
||||
? ((CommandEvent) proto).getArgument() : null;
|
||||
|
||||
// if we were passed an action event, or if it was a command event
|
||||
// with a null arg...
|
||||
if (arg == null) {
|
||||
// ...use ours
|
||||
arg = getActionArgument();
|
||||
}
|
||||
|
||||
if (arg == null) {
|
||||
// just create a plain ActionEvent
|
||||
return new ActionEvent(CommandButton.this,
|
||||
ActionEvent.ACTION_PERFORMED, actionCommand,
|
||||
proto.getWhen(), proto.getModifiers());
|
||||
} else {
|
||||
// if we found an arg somewhere, create a CommandEvent
|
||||
return new CommandEvent(CommandButton.this, actionCommand, arg,
|
||||
proto.getWhen(), proto.getModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
/** The CommandEvent argument for our actions. */
|
||||
protected Object _argument;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* A controlled panel takes care of setting up its controller and
|
||||
* providing it. When operating in conjunction with a controlled panel,
|
||||
* the controller can automatically invoke {@link Controller#wasAdded} and
|
||||
* {@link Controller#wasRemoved}.
|
||||
*/
|
||||
public abstract class ControlledPanel extends JPanel
|
||||
implements ControllerProvider
|
||||
{
|
||||
/**
|
||||
* Creates a controlled panel and its associated controller.
|
||||
*/
|
||||
public ControlledPanel ()
|
||||
{
|
||||
_controller = createController();
|
||||
|
||||
// let the controller know about this panel
|
||||
if (_controller != null) {
|
||||
_controller.setControlledPanel(this);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Controller getController ()
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the controller associated with this controlled
|
||||
* panel. Derived classes should override this and instantiate the
|
||||
* appropriate controller derived class. Note that this will be called
|
||||
* very early in the construction process, before derived classes have
|
||||
* access to their member data and as such, the derived panel will not
|
||||
* be able to pass anything interesting to its associated controller
|
||||
* constructor. The expectation is that it will obtain a reference to
|
||||
* its controller later in its own constructor and supply any extra
|
||||
* initialization data that is needed at that time.
|
||||
*/
|
||||
protected abstract Controller createController ();
|
||||
|
||||
/** The controller with which we are working. */
|
||||
protected Controller _controller;
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.awt.event.HierarchyListener;
|
||||
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The controller class provides a basis for the separation of user interface
|
||||
* code into display code and control code. The display code lives in a panel
|
||||
* class (<code>javax.swing.JPanel</code> or something conceptually similar)
|
||||
* and the control code lives in an associated controller class.
|
||||
*
|
||||
* <p> The controller philosophy is thus: The panel class (and its UI
|
||||
* components) convert basic user interface actions into higher level actions
|
||||
* that more cleanly encapsulate the action desired by the user and they pass
|
||||
* those actions on to their controller. The controller then performs abstract
|
||||
* processing based on the users desires and the changing state of the
|
||||
* application and calls back to the panel to affect changes to the display.
|
||||
*
|
||||
* <p> Controllers also support the notion of scope. When a panel wishes to
|
||||
* post an action, it doesn't do it directly to the controller. Instead it does
|
||||
* it using a controller utility function called {@link #postAction}, which
|
||||
* searches up the user interface hierarchy looking for a component that
|
||||
* implements {@link ControllerProvider} which it will use to obtain the
|
||||
* controller "in scope" for that component. That controller is requested to
|
||||
* handle the action, but if it cannot handle the action, the next controller
|
||||
* up the chain is located and requested to process the action.
|
||||
*
|
||||
* <p> In this manner, a hierarchy of controllers (often just two: one
|
||||
* application wide and one for whatever particular mode the application is in
|
||||
* at the moment) can provide a set of services that are available to all user
|
||||
* interface elements in the entire application and in a way that doesn't
|
||||
* require tight connectedness between the UI elements and the controllers.
|
||||
*/
|
||||
public abstract class Controller
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* This action listener can be wired up to any action event generator
|
||||
* and it will take care of forwarding that event on to the controller
|
||||
* in scope for the component that generated the action event.
|
||||
*
|
||||
* <p> For example, wiring a button up to a dispatcher would look like
|
||||
* so:
|
||||
*
|
||||
* <pre>
|
||||
* JButton button = new JButton("Do thing");
|
||||
* button.setActionCommand("dothing");
|
||||
* button.addActionListener(Controller.DISPATCHER);
|
||||
* </pre>
|
||||
*
|
||||
* or, use the provided convenience function:
|
||||
*
|
||||
* <pre>
|
||||
* JButton button =
|
||||
* Controller.createActionButton("Do thing", "dothing");
|
||||
* </pre>
|
||||
*
|
||||
* The controllers in scope would then be requested (in order) to
|
||||
* process the <code>dothing</code> action whenever the button was
|
||||
* clicked.
|
||||
*/
|
||||
public static final ActionListener DISPATCHER = new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
Controller.postAction(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Posts the specified action to the nearest controller in scope. The
|
||||
* controller search begins with the source component of the action and
|
||||
* traverses up the component tree looking for a controller to handle the
|
||||
* action. The controller location and action event processing is
|
||||
* guaranteed to take place on the AWT thread regardless of what thread
|
||||
* calls <code>postAction</code> and that processing will not occur
|
||||
* immediately but is instead appended to the AWT event dispatch queue for
|
||||
* processing.
|
||||
*/
|
||||
public static void postAction (ActionEvent action)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
EventQueue.invokeLater(new ActionInvoker(action));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #postAction(ActionEvent)} except that it constructs the
|
||||
* action event for you with the supplied source component and string
|
||||
* command. The <code>id</code> of the event will always be set to
|
||||
* zero.
|
||||
*/
|
||||
public static void postAction (Component source, String command)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
ActionEvent event = new ActionEvent(source, 0, command);
|
||||
EventQueue.invokeLater(new ActionInvoker(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #postAction(ActionEvent)} except that it constructs a
|
||||
* {@link CommandEvent} with the supplied source component, string
|
||||
* command and argument.
|
||||
*/
|
||||
public static void postAction (
|
||||
Component source, String command, Object argument)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
CommandEvent event = new CommandEvent(source, command, argument);
|
||||
EventQueue.invokeLater(new ActionInvoker(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button and configures it with the specified label and action
|
||||
* command and adds {@link #DISPATCHER} as an action listener.
|
||||
*/
|
||||
public static JButton createActionButton (String label, String command)
|
||||
{
|
||||
JButton button = new JButton(label);
|
||||
configureAction(button, command);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the supplied button with the {@link #DISPATCHER} action
|
||||
* listener and the specified action command (which, if it is a method name
|
||||
* will be looked up dynamically on the matching controller).
|
||||
*/
|
||||
public static void configureAction (AbstractButton button, String action)
|
||||
{
|
||||
button.setActionCommand(action);
|
||||
button.addActionListener(DISPATCHER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets this controller know about the panel that it is controlling.
|
||||
*/
|
||||
public void setControlledPanel (final JComponent panel)
|
||||
{
|
||||
panel.addHierarchyListener(new HierarchyListener() {
|
||||
public void hierarchyChanged (HierarchyEvent e) {
|
||||
boolean nowShowing = panel.isDisplayable();
|
||||
//System.err.println("Controller." + Controller.this +
|
||||
// " nowShowing=" + nowShowing +
|
||||
// ", wasShowing=" + _showing);
|
||||
if (_showing != nowShowing) {
|
||||
_showing = nowShowing;
|
||||
if (_showing) {
|
||||
wasAdded();
|
||||
} else {
|
||||
wasRemoved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean _showing = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the panel controlled by this controller was added to
|
||||
* the user interface hierarchy. This assumes that the controlled
|
||||
* panel made itself known to the controller via {@link
|
||||
* #setControlledPanel} (which is done automatically by {@link
|
||||
* ControlledPanel} and derived classes).
|
||||
*/
|
||||
public void wasAdded ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the panel controlled by this controller was removed
|
||||
* from the user interface hierarchy. This assumes that the controlled
|
||||
* panel made itself known to the controller via {@link
|
||||
* #setControlledPanel} (which is done automatically by {@link
|
||||
* ControlledPanel} and derived classes).
|
||||
*/
|
||||
public void wasRemoved ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs this controller to process this action event. When an action
|
||||
* is posted by a user interface element, it will be posted to the
|
||||
* controller in closest scope for that element. If that controller handles
|
||||
* the event, it should return true from this method to indicate that
|
||||
* processing should stop. If it cannot handle the event, it can return
|
||||
* false to indicate that the event should be propagated to the next
|
||||
* controller up the chain.
|
||||
*
|
||||
* <p> This method will be called on the AWT thread, so the controller can
|
||||
* safely manipulate user interface components while handling an action.
|
||||
* However, this means that action handling cannot block and should not
|
||||
* take an undue amount of time. If the controller needs to perform
|
||||
* complicated, lengthy processing it should do so with a separate thread,
|
||||
* for example via {@link com.samskivert.swing.util.TaskMaster}.
|
||||
*
|
||||
* <p> The default implementation of this method will reflect on the
|
||||
* controller class, looking for a method that matches the name of the
|
||||
* action event. For example, if the action was "exit" a method named
|
||||
* "exit" would be sought. A handler method must provide one of three
|
||||
* signatures: one accepting no arguments, one including only a reference
|
||||
* to the source object, or one including the source object and an extra
|
||||
* argument (which can be used only if the action event is an instance of
|
||||
* {@link CommandEvent}). For example:
|
||||
*
|
||||
* <pre>
|
||||
* public void cancelClicked (Object source);
|
||||
* public void textEntered (Object source, String text);
|
||||
* </pre>
|
||||
*
|
||||
* The arguments to the method can be as specific or as generic as desired
|
||||
* and reflection will perform the appropriate conversions at runtime. For
|
||||
* example, a method could be declared like so:
|
||||
*
|
||||
* <pre>
|
||||
* public void cancelClicked (JButton source);
|
||||
* </pre>
|
||||
*
|
||||
* One would have to ensure that the only action events generated with the
|
||||
* action command string "cancelClicked" were generated by JButton
|
||||
* instances if such a signature were used.
|
||||
*
|
||||
* @param action the action to be processed.
|
||||
*
|
||||
* @return true if the action was processed, false if it should be
|
||||
* propagated up to the next controller in scope.
|
||||
*/
|
||||
public boolean handleAction (ActionEvent action)
|
||||
{
|
||||
Object arg = null;
|
||||
if (action instanceof CommandEvent) {
|
||||
arg = ((CommandEvent)action).getArgument();
|
||||
}
|
||||
return handleAction(action.getSource(), action.getActionCommand(), arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of {@link #handleAction(ActionEvent)} with the parameters
|
||||
* broken out so that it can be used by non-Swing interface toolkits.
|
||||
*/
|
||||
public boolean handleAction (Object source, String action, Object arg)
|
||||
{
|
||||
Method method = null;
|
||||
Object[] args = null;
|
||||
|
||||
try {
|
||||
// look for the appropriate method
|
||||
Method[] methods = getClass().getMethods();
|
||||
int mcount = methods.length;
|
||||
|
||||
for (int i = 0; i < mcount; i++) {
|
||||
if (methods[i].getName().equals(action) ||
|
||||
// handle our old style of prepending "handle"
|
||||
methods[i].getName().equals("handle" + action)) {
|
||||
// see if we can generate the appropriate arguments
|
||||
args = generateArguments(methods[i], source, arg);
|
||||
// if we were able to, go ahead and use this method
|
||||
if (args != null) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Error searching for action handler method", "controller", this,
|
||||
"action", action);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (method != null) {
|
||||
method.invoke(this, args);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Error invoking action handler", "controller", this, "action", action, e);
|
||||
// even though we choked, we still "handled" the action
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for constructing and immediately handling an
|
||||
* event on this controller (via {@link #handleAction(ActionEvent)}).
|
||||
*
|
||||
* @return true if the controller knew how to handle the action, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean handleAction (Component source, String command)
|
||||
{
|
||||
return handleAction(new ActionEvent(source, 0, command));
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for constructing and immediately handling an
|
||||
* event on this controller (via {@link #handleAction(ActionEvent)}).
|
||||
*
|
||||
* @return true if the controller knew how to handle the action, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean handleAction (Component source, String command, Object arg)
|
||||
{
|
||||
return handleAction(new CommandEvent(source, command, arg));
|
||||
}
|
||||
|
||||
// documentation inherited from interface ActionListener
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
handleAction(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link #handleAction} to generate arguments to the action
|
||||
* handler method.
|
||||
*/
|
||||
protected Object[] generateArguments (
|
||||
Method method, Object source, Object argument)
|
||||
{
|
||||
// figure out what sort of arguments are required by the method
|
||||
Class<?>[] atypes = method.getParameterTypes();
|
||||
|
||||
if (atypes == null || atypes.length == 0) {
|
||||
return new Object[0];
|
||||
|
||||
} else if (atypes.length == 1) {
|
||||
return new Object[] { source };
|
||||
|
||||
} else if (atypes.length == 2) {
|
||||
if (argument != null) {
|
||||
return new Object[] { source, argument };
|
||||
}
|
||||
log.warning("Unable to map argumentless event to handler method that requires an " +
|
||||
"argument", "controller", this, "method", method, "source", source);
|
||||
}
|
||||
|
||||
// we would have handled it, but we couldn't
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to dispatch action events to controllers within
|
||||
* the context of the AWT event dispatch mechanism.
|
||||
*/
|
||||
protected static class ActionInvoker implements Runnable
|
||||
{
|
||||
public ActionInvoker (ActionEvent action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void run ()
|
||||
{
|
||||
// do some sanity checking on the source
|
||||
Object src = _action.getSource();
|
||||
if (src == null || !(src instanceof Component)) {
|
||||
log.warning("Requested to dispatch action on non-component source", "source", src,
|
||||
"action", _action);
|
||||
return;
|
||||
}
|
||||
|
||||
// scan up the component hierarchy looking for a controller on
|
||||
// which to dispatch this action
|
||||
for (Component source = (Component)src; source != null;
|
||||
source = source.getParent()) {
|
||||
if (!(source instanceof ControllerProvider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Controller ctrl = ((ControllerProvider)source).getController();
|
||||
if (ctrl == null) {
|
||||
log.warning("Provider returned null controller", "provider", source);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// if the controller returns true, it handled the
|
||||
// action and we can call this business done
|
||||
if (ctrl.handleAction(_action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Controller choked on action", "ctrl", ctrl, "action", _action, e);
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we didn't find a controller
|
||||
log.warning("Unable to find a controller to process action", "action", _action);
|
||||
}
|
||||
|
||||
protected ActionEvent _action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
/**
|
||||
* The controller provider interface is implemented by user interface
|
||||
* elements that have an associated {@link
|
||||
* com.samskivert.swing.Controller}. The hierarchy of the user interface
|
||||
* elements defines the hierarchy of controllers and at any point in the
|
||||
* UI element hierarchy, an element can implement controller provider and
|
||||
* provide a controller that will process actions received at that scope
|
||||
* or below.
|
||||
*/
|
||||
public interface ControllerProvider
|
||||
{
|
||||
/**
|
||||
* Returns the controller to be used at the scope of the UI element
|
||||
* that implements controller provider.
|
||||
*/
|
||||
public Controller getController ();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Dimension;
|
||||
|
||||
/**
|
||||
* This record is used by the group layout managers to return a set of
|
||||
* statistics computed for their target widgets.
|
||||
*/
|
||||
public class DimenInfo
|
||||
{
|
||||
public int count;
|
||||
|
||||
public int totwid;
|
||||
public int tothei;
|
||||
|
||||
public int maxwid;
|
||||
public int maxhei;
|
||||
|
||||
public int numfix;
|
||||
public int fixwid;
|
||||
public int fixhei;
|
||||
|
||||
public int maxfreewid;
|
||||
public int maxfreehei;
|
||||
|
||||
public int totweight;
|
||||
|
||||
public Dimension[] dimens;
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[count=").append(count);
|
||||
buf.append(", totwid=").append(totwid);
|
||||
buf.append(", tothei=").append(tothei);
|
||||
buf.append(", maxwid=").append(maxwid);
|
||||
buf.append(", maxhei=").append(maxhei);
|
||||
buf.append(", numfix=").append(numfix);
|
||||
buf.append(", fixwid=").append(fixwid);
|
||||
buf.append(", fixhei=").append(fixhei);
|
||||
buf.append(", maxfreewid=").append(maxfreewid);
|
||||
buf.append(", maxfreehei=").append(maxfreehei);
|
||||
buf.append(", totweight=").append(totweight);
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Component;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
/**
|
||||
* Draws an icon with a specified alpha level.
|
||||
*/
|
||||
public class DimmedIcon implements Icon
|
||||
{
|
||||
/**
|
||||
* Construct a dimmed icon that is drawn at 50% normal.
|
||||
*/
|
||||
public DimmedIcon (Icon icon)
|
||||
{
|
||||
this(icon, .5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a dimmed icon that is drawn at the specified alpha level.
|
||||
*/
|
||||
public DimmedIcon (Icon icon, float alpha)
|
||||
{
|
||||
_alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public int getIconWidth ()
|
||||
{
|
||||
return _icon.getIconWidth();
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public int getIconHeight ()
|
||||
{
|
||||
return _icon.getIconHeight();
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public void paintIcon (Component c, Graphics g, int x, int y)
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D) g;
|
||||
Composite ocomp = gfx.getComposite();
|
||||
gfx.setComposite(_alpha);
|
||||
_icon.paintIcon(c, gfx, x, y);
|
||||
gfx.setComposite(ocomp);
|
||||
}
|
||||
|
||||
/** The icon we're actually drawing. */
|
||||
protected Icon _icon;
|
||||
|
||||
/** Our alpha composite. */
|
||||
protected Composite _alpha;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* Used to enable or disable a source component based on some
|
||||
* asynchronously changing state.
|
||||
*/
|
||||
public class EnablingAdapter
|
||||
{
|
||||
/**
|
||||
* Creates and returns an enabler that listens for changes in the
|
||||
* specified property (which must be a {@link Boolean} valued
|
||||
* property) and updates the target's enabled state accordingly.
|
||||
*/
|
||||
public static PropertyChangeListener getPropChangeEnabler (
|
||||
String property, JComponent target, boolean invert)
|
||||
{
|
||||
return new PropertyChangeEnabler(property, target, invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an enabling adapter with the specified target component.
|
||||
*
|
||||
* @param invert if true, the target component's enabled state is set
|
||||
* to the inverse of the monitored state.
|
||||
*/
|
||||
protected EnablingAdapter (JComponent target, boolean invert)
|
||||
{
|
||||
_target = target;
|
||||
_invert = invert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the appropriate derived adapter to adjust the target's
|
||||
* enabled state.
|
||||
*/
|
||||
protected void stateChanged (boolean newState)
|
||||
{
|
||||
_target.setEnabled(_invert ? !newState : newState);
|
||||
}
|
||||
|
||||
/** Used by {@link #getPropChangeEnabler}. */
|
||||
protected static class PropertyChangeEnabler extends EnablingAdapter
|
||||
implements PropertyChangeListener
|
||||
{
|
||||
public PropertyChangeEnabler (
|
||||
String property, JComponent target, boolean invert)
|
||||
{
|
||||
super(target, invert);
|
||||
_property = property;
|
||||
}
|
||||
|
||||
public void propertyChange (PropertyChangeEvent evt)
|
||||
{
|
||||
if (evt.getPropertyName().equals(_property)) {
|
||||
Object value = evt.getNewValue();
|
||||
if (value instanceof Boolean) {
|
||||
stateChanged(((Boolean)value).booleanValue());
|
||||
} else {
|
||||
log.warning("PropertyChangeEnabler connected to non-Boolean property",
|
||||
"got", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String _property;
|
||||
}
|
||||
|
||||
protected JComponent _target;
|
||||
protected boolean _invert;
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Group layout managers lay out widgets in horizontal or vertical groups.
|
||||
*/
|
||||
public abstract class GroupLayout
|
||||
implements LayoutManager2
|
||||
{
|
||||
/**
|
||||
* The group layout managers supports two constraints: fixedness
|
||||
* and weight. A fixed component will not be stretched along the major
|
||||
* axis of the group. Those components that are stretched will have
|
||||
* the extra space divided among them according to their weight
|
||||
* (specifically receiving the ratio of their weight to the total
|
||||
* weight of all of the free components in the container).
|
||||
*
|
||||
* To add a component with the fixed constraints, use the FIXED constant.
|
||||
*/
|
||||
public static class Constraints
|
||||
{
|
||||
/**
|
||||
* Constructs a new constraints object with the specified weight,
|
||||
* which is only applicable with the STRETCH policy.
|
||||
*/
|
||||
public Constraints (int weight) {
|
||||
_weight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this Constraints specifying fixed?
|
||||
*/
|
||||
public final boolean isFixed () {
|
||||
return (this == FIXED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weight.
|
||||
*/
|
||||
public final int getWeight () {
|
||||
return _weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* The weight of this component relative to the other components
|
||||
* in the container. Only valid if the layout policy is STRETCH.
|
||||
*/
|
||||
protected int _weight = 1;
|
||||
}
|
||||
|
||||
/** A class used to make our policy constants type-safe. */
|
||||
public static class Policy
|
||||
{
|
||||
}
|
||||
|
||||
/** A class used to make our policy constants type-safe. */
|
||||
public static class Justification
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* A constraints object that indicates that the component should be
|
||||
* fixed and have the default weight of one. This is so commonly used
|
||||
* that we create and make this object available here.
|
||||
*/
|
||||
public final static Constraints FIXED = new Constraints(Integer.MIN_VALUE);
|
||||
|
||||
/** Do not adjust the widgets on this axis. */
|
||||
public final static Policy NONE = new Policy();
|
||||
|
||||
/** Stretch all the widgets to their maximum possible size on this axis. */
|
||||
public final static Policy STRETCH = new Policy();
|
||||
|
||||
/** Stretch all the widgets to be equal to the size of the largest widget on this axis. */
|
||||
public final static Policy EQUALIZE = new Policy();
|
||||
|
||||
/** Only valid for off-axis policy, this leaves widgets alone unless they are larger in the
|
||||
* off-axis direction than their container, in which case it constrains them to fit on the
|
||||
* off-axis. */
|
||||
public final static Policy CONSTRAIN = new Policy();
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification CENTER = new Justification();
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification LEFT = new Justification();
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification RIGHT = new Justification();
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification TOP = new Justification();
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification BOTTOM = new Justification();
|
||||
|
||||
/** The default gap between components, in pixels. */
|
||||
public static final int DEFAULT_GAP = 5;
|
||||
|
||||
public void setPolicy (Policy policy)
|
||||
{
|
||||
_policy = policy;
|
||||
}
|
||||
|
||||
public Policy getPolicy ()
|
||||
{
|
||||
return _policy;
|
||||
}
|
||||
|
||||
public void setOffAxisPolicy (Policy offpolicy)
|
||||
{
|
||||
_offpolicy = offpolicy;
|
||||
}
|
||||
|
||||
public Policy getOffAxisPolicy ()
|
||||
{
|
||||
return _offpolicy;
|
||||
}
|
||||
|
||||
public void setGap (int gap)
|
||||
{
|
||||
_gap = gap;
|
||||
}
|
||||
|
||||
public int getGap ()
|
||||
{
|
||||
return _gap;
|
||||
}
|
||||
|
||||
public void setJustification (Justification justification)
|
||||
{
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public Justification getJustification ()
|
||||
{
|
||||
return _justification;
|
||||
}
|
||||
|
||||
public void setOffAxisJustification (Justification justification)
|
||||
{
|
||||
_offjust = justification;
|
||||
}
|
||||
|
||||
public Justification getOffAxisJustification ()
|
||||
{
|
||||
return _offjust;
|
||||
}
|
||||
|
||||
public void addLayoutComponent (String name, Component comp)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
public void removeLayoutComponent (Component comp)
|
||||
{
|
||||
if (_constraints != null) {
|
||||
_constraints.remove(comp);
|
||||
}
|
||||
}
|
||||
|
||||
public void addLayoutComponent (Component comp, Object constraints)
|
||||
{
|
||||
if (constraints != null) {
|
||||
if (constraints instanceof Constraints) {
|
||||
if (_constraints == null) {
|
||||
_constraints = new HashMap<Component,Constraints>();
|
||||
}
|
||||
_constraints.put(comp, (Constraints)constraints);
|
||||
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"GroupLayout constraints object must be of type GroupLayout.Constraints");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getLayoutAlignmentX (Container target)
|
||||
{
|
||||
// we don't support alignment like this
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public float getLayoutAlignmentY (Container target)
|
||||
{
|
||||
// we don't support alignment like this
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public Dimension minimumLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, MINIMUM);
|
||||
}
|
||||
|
||||
public Dimension preferredLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, PREFERRED);
|
||||
}
|
||||
|
||||
public Dimension maximumLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, MAXIMUM);
|
||||
}
|
||||
|
||||
protected abstract Dimension getLayoutSize (Container parent, int type);
|
||||
|
||||
public abstract void layoutContainer (Container parent);
|
||||
|
||||
public void invalidateLayout (Container target)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Constraints for the specified child component.
|
||||
*
|
||||
* @return a Constraints object, never null.
|
||||
*/
|
||||
protected Constraints getConstraints (Component child)
|
||||
{
|
||||
if (_constraints != null) {
|
||||
Constraints c = _constraints.get(child);
|
||||
if (c != null) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return DEFAULT_CONSTRAINTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes dimensions of the children widgets that are useful for the
|
||||
* group layout managers.
|
||||
*/
|
||||
protected DimenInfo computeDimens (Container parent, int type) {
|
||||
int count = parent.getComponentCount();
|
||||
DimenInfo info = new DimenInfo();
|
||||
info.dimens = new Dimension[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
Component child = parent.getComponent(i);
|
||||
if (!child.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Dimension csize;
|
||||
switch (type) {
|
||||
case MINIMUM:
|
||||
csize = child.getMinimumSize();
|
||||
break;
|
||||
|
||||
case MAXIMUM:
|
||||
csize = child.getMaximumSize();
|
||||
break;
|
||||
|
||||
default:
|
||||
csize = child.getPreferredSize();
|
||||
break;
|
||||
}
|
||||
|
||||
info.count++;
|
||||
info.totwid += csize.width;
|
||||
info.tothei += csize.height;
|
||||
|
||||
if (csize.width > info.maxwid) {
|
||||
info.maxwid = csize.width;
|
||||
}
|
||||
if (csize.height > info.maxhei) {
|
||||
info.maxhei = csize.height;
|
||||
}
|
||||
|
||||
Constraints c = getConstraints(child);
|
||||
if (c.isFixed()) {
|
||||
info.fixwid += csize.width;
|
||||
info.fixhei += csize.height;
|
||||
info.numfix++;
|
||||
|
||||
} else {
|
||||
info.totweight += c.getWeight();
|
||||
|
||||
if (csize.width > info.maxfreewid) {
|
||||
info.maxfreewid = csize.width;
|
||||
}
|
||||
if (csize.height > info.maxfreehei) {
|
||||
info.maxfreehei = csize.height;
|
||||
}
|
||||
}
|
||||
|
||||
info.dimens[i] = csize;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with a center-justified {@link HGroupLayout}
|
||||
* with a configuration conducive to containing a row of buttons. Any supplied buttons are
|
||||
* added to the box.
|
||||
*/
|
||||
public static JPanel makeButtonBox (Component... buttons)
|
||||
{
|
||||
return makeButtonBox(GroupLayout.CENTER, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link HGroupLayout} with a
|
||||
* configuration conducive to containing a row of buttons. Any supplied buttons are added to
|
||||
* the box.
|
||||
*/
|
||||
public static JPanel makeButtonBox (Justification justification, Component... buttons)
|
||||
{
|
||||
JPanel box = new JPanel(new HGroupLayout(NONE, justification));
|
||||
for (Component button : buttons) {
|
||||
box.add(button);
|
||||
box.setOpaque(false);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the default configuration.
|
||||
*/
|
||||
public static JPanel makeHBox ()
|
||||
{
|
||||
return new JPanel(new HGroupLayout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with a configuration that stretches in both
|
||||
* directions, with the specified gap.
|
||||
*/
|
||||
public static JPanel makeHStretchBox (int gap)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(STRETCH, STRETCH, gap, CENTER));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy (default
|
||||
* configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy and justification
|
||||
* (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy, Justification justification)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy, justification and
|
||||
* off-axis policy (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy, Justification justification,
|
||||
Policy offAxisPolicy)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy, offAxisPolicy,
|
||||
DEFAULT_GAP, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the default configuration.
|
||||
*/
|
||||
public static JPanel makeVBox ()
|
||||
{
|
||||
return new JPanel(new VGroupLayout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy (default
|
||||
* configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy and justification
|
||||
* (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy, Justification justification)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy, justification and
|
||||
* off-axis policy (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy, Justification justification,
|
||||
Policy offAxisPolicy)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy, offAxisPolicy,
|
||||
DEFAULT_GAP, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with a configuration that stretches in both
|
||||
* directions, with the specified gap.
|
||||
*/
|
||||
public static JPanel makeVStretchBox (int gap)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(STRETCH, STRETCH, gap, CENTER));
|
||||
}
|
||||
|
||||
protected Policy _policy = NONE;
|
||||
protected Policy _offpolicy = CONSTRAIN;
|
||||
protected int _gap = DEFAULT_GAP;
|
||||
protected Justification _justification = CENTER;
|
||||
protected Justification _offjust = CENTER;
|
||||
|
||||
protected HashMap<Component,Constraints> _constraints;
|
||||
|
||||
protected static final int MINIMUM = 0;
|
||||
protected static final int PREFERRED = 1;
|
||||
protected static final int MAXIMUM = 2;
|
||||
|
||||
/** All children added without a Constraints object are
|
||||
* constrained by this Constraints object. */
|
||||
protected static final Constraints DEFAULT_CONSTRAINTS = new Constraints(1);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
|
||||
public class GroupLayoutTest
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
JFrame frame = new JFrame("GroupLayoutTest");
|
||||
JPanel panel = new JPanel();
|
||||
GroupLayout layout = new HGroupLayout();
|
||||
layout.setJustification(GroupLayout.CENTER);
|
||||
layout.setPolicy(GroupLayout.STRETCH);
|
||||
layout.setJustification(GroupLayout.RIGHT);
|
||||
layout.setGap(15);
|
||||
panel.setLayout(layout);
|
||||
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
JButton butone = new JButton("One");
|
||||
panel.add(butone, GroupLayout.FIXED);
|
||||
JButton buttwo = new JButton("Two");
|
||||
panel.add(buttwo, GroupLayout.FIXED);
|
||||
JButton butthree = new JButton("Three to get ready");
|
||||
panel.add(butthree, GroupLayout.FIXED);
|
||||
|
||||
frame.addWindowListener(new WindowAdapter () {
|
||||
@Override public void windowClosing (WindowEvent e) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class HGroupLayout extends GroupLayout
|
||||
{
|
||||
public HGroupLayout (Policy policy, Policy offpolicy, int gap, Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_offpolicy = offpolicy;
|
||||
_gap = gap;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy, int gap, Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_gap = gap;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy, Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy)
|
||||
{
|
||||
_policy = policy;
|
||||
}
|
||||
|
||||
public HGroupLayout ()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dimension getLayoutSize (Container parent, int type)
|
||||
{
|
||||
DimenInfo info = computeDimens(parent, type);
|
||||
Dimension dims = new Dimension();
|
||||
|
||||
if (_policy == STRETCH) {
|
||||
dims.width = info.maxfreewid * (info.count - info.numfix) + info.fixwid;
|
||||
} else if (_policy == EQUALIZE) {
|
||||
dims.width = info.maxwid * info.count;
|
||||
} else { // NONE or CONSTRAIN
|
||||
dims.width = info.totwid;
|
||||
}
|
||||
|
||||
dims.width += (info.count - 1) * _gap;
|
||||
dims.height = info.maxhei;
|
||||
|
||||
// account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
dims.width += insets.left + insets.right;
|
||||
dims.height += insets.top + insets.bottom;
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutContainer (Container parent)
|
||||
{
|
||||
Rectangle b = parent.getBounds();
|
||||
DimenInfo info = computeDimens(parent, PREFERRED);
|
||||
|
||||
// adjust the bounds width and height to account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
b.width -= (insets.left + insets.right);
|
||||
b.height -= (insets.top + insets.bottom);
|
||||
|
||||
int nk = parent.getComponentCount();
|
||||
int sx, sy;
|
||||
int totwid, totgap = _gap * (info.count-1);
|
||||
int freecount = info.count - info.numfix;
|
||||
|
||||
// when stretching, there is the possibility that a pixel or more
|
||||
// will be lost to rounding error. we account for that here and
|
||||
// assign the extra space to the first free component
|
||||
int freefrac = 0;
|
||||
|
||||
// do the on-axis policy calculations
|
||||
int defwid = 0;
|
||||
if (_policy == STRETCH) {
|
||||
if (freecount > 0) {
|
||||
int freewid = b.width - info.fixwid - totgap;
|
||||
defwid = freewid / info.totweight;
|
||||
freefrac = freewid % info.totweight;
|
||||
totwid = b.width;
|
||||
} else {
|
||||
totwid = info.fixwid + totgap;
|
||||
}
|
||||
|
||||
} else if (_policy == EQUALIZE) {
|
||||
defwid = info.maxwid;
|
||||
totwid = info.fixwid + defwid * freecount + totgap;
|
||||
|
||||
} else { // NONE or CONSTRAIN
|
||||
totwid = info.totwid + totgap;
|
||||
}
|
||||
|
||||
// do the off-axis policy calculations
|
||||
int defhei = 0;
|
||||
if (_offpolicy == STRETCH) {
|
||||
defhei = b.height;
|
||||
} else if (_offpolicy == EQUALIZE) {
|
||||
defhei = info.maxhei;
|
||||
}
|
||||
|
||||
// do the justification-related calculations
|
||||
if (_justification == LEFT || _justification == TOP) {
|
||||
sx = insets.left;
|
||||
} else if (_justification == CENTER) {
|
||||
sx = insets.left + (b.width - totwid)/2;
|
||||
} else { // RIGHT or BOTTOM
|
||||
sx = insets.left + b.width - totwid;
|
||||
}
|
||||
|
||||
// do the layout
|
||||
for (int i = 0; i < nk; i++) {
|
||||
// skip non-visible kids
|
||||
if (info.dimens[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Component child = parent.getComponent(i);
|
||||
Constraints c = getConstraints(child);
|
||||
int newwid, newhei;
|
||||
|
||||
if (_policy == NONE || c.isFixed()) {
|
||||
newwid = info.dimens[i].width;
|
||||
} else {
|
||||
newwid = freefrac + ((_policy == STRETCH) ? defwid * c.getWeight() : defwid);
|
||||
// clear out the extra pixels the first time they're used
|
||||
freefrac = 0;
|
||||
}
|
||||
|
||||
if (_offpolicy == NONE) {
|
||||
newhei = info.dimens[i].height;
|
||||
} else if (_offpolicy == CONSTRAIN) {
|
||||
newhei = Math.min(info.dimens[i].height, b.height);
|
||||
} else {
|
||||
newhei = defhei;
|
||||
}
|
||||
|
||||
// determine our off-axis position
|
||||
if (_offjust == LEFT || _offjust == TOP) {
|
||||
sy = insets.top;
|
||||
} else if (_offjust == RIGHT || _offjust == BOTTOM) {
|
||||
sy = insets.top + b.height - newhei;
|
||||
} else { // CENTER
|
||||
sy = insets.top + (b.height - newhei)/2;
|
||||
}
|
||||
|
||||
child.setBounds(sx, sy, newwid, newhei);
|
||||
sx += child.getSize().width + _gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.DocumentFilter;
|
||||
import javax.swing.text.PlainDocument;
|
||||
|
||||
/**
|
||||
* A text field that will only allow editing of integer values within
|
||||
* the specified range.
|
||||
*/
|
||||
public class IntField extends JTextField
|
||||
{
|
||||
/**
|
||||
* Create an IntField with the range 0 - Integer.MAX_VALUE, with
|
||||
* 0 as the initial value.
|
||||
*/
|
||||
public IntField ()
|
||||
{
|
||||
this(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified maximum.
|
||||
*/
|
||||
public IntField (int maxValue)
|
||||
{
|
||||
this(0, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified minimum and maximum, with
|
||||
* the minimum value initially displayed.
|
||||
*/
|
||||
public IntField (int minValue, int maxValue)
|
||||
{
|
||||
this(minValue, minValue, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified initial, minimum, and
|
||||
* maximum values.
|
||||
*/
|
||||
public IntField (int initial, int minValue, int maxValue)
|
||||
{
|
||||
super(new IntDocument(), format(initial), 5);
|
||||
|
||||
validateMinMax(minValue, maxValue);
|
||||
if (initial > maxValue || initial < minValue) {
|
||||
throw new IllegalArgumentException("initial value not between " +
|
||||
"min and max");
|
||||
}
|
||||
_minValue = minValue;
|
||||
_maxValue = maxValue;
|
||||
|
||||
setHorizontalAlignment(JTextField.RIGHT);
|
||||
|
||||
// create a document filter which enforces the restrictions on
|
||||
// what text may be entered. This is similar to the filter
|
||||
// that is configured in SwingUtil.setDocumentHelpers, only the
|
||||
// remove operation is modified to do the sneaky highlighting we do.
|
||||
final IntDocument doc = (IntDocument) getDocument();
|
||||
doc.setDocumentFilter(new DocumentFilter() {
|
||||
@Override public void remove (FilterBypass fb, int offset, int length)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) +
|
||||
current.substring(offset + length);
|
||||
// if the bit to be removed is still a valid value, go ahead
|
||||
if (unformatSafe(potential) >= _minValue) {
|
||||
transform(fb, current, potential);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, we are going to highlight the bit instead
|
||||
// of removing it
|
||||
int selStart = getSelectionStart();
|
||||
if (selStart > 0) {
|
||||
// see if the next highlighted character is a digit
|
||||
char replace = current.charAt(selStart - 1);
|
||||
if (Character.isDigit(replace)) {
|
||||
// and can be zeroed out
|
||||
String zeroed = current.substring(0, selStart - 1) +
|
||||
"0" + current.substring(selStart);
|
||||
// if so, change it to a zero
|
||||
if (unformatSafe(zeroed) >= _minValue) {
|
||||
fb.replace(selStart - 1, 1, "0", null);
|
||||
|
||||
} else {
|
||||
// otherwise, re-set the entire field to
|
||||
// contain the minimum value, and highlight it
|
||||
String min = format(_minValue);
|
||||
fb.replace(0, current.length(), min, null);
|
||||
setSelectionStart(0);
|
||||
setSelectionEnd(min.length());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// grow the selection to encompass one more character
|
||||
setSelectionStart(selStart - 1);
|
||||
}
|
||||
|
||||
@Override public void insertString (FilterBypass fb, int offset,
|
||||
String s, AttributeSet attr)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) + s +
|
||||
current.substring(offset);
|
||||
transform(fb, current, potential);
|
||||
}
|
||||
|
||||
@Override public void replace (FilterBypass fb, int offset, int length,
|
||||
String text, AttributeSet attrs)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) + text +
|
||||
current.substring(offset + length);
|
||||
transform(fb, current, potential);
|
||||
}
|
||||
|
||||
protected void transform (FilterBypass fb,
|
||||
String current, String potential)
|
||||
throws BadLocationException
|
||||
{
|
||||
boolean wouldaBeenEqual = current.equals(potential);
|
||||
potential = transform(potential);
|
||||
boolean selection = (getSelectionEnd() != getSelectionStart());
|
||||
// we only change it if it needs changing
|
||||
if (!current.equals(potential) ||
|
||||
// or if it would have been the same pre-transforming
|
||||
// and there is a selection (IE undo the selection)
|
||||
(wouldaBeenEqual && selection)) {
|
||||
if (selection) {
|
||||
// undo the selection to not cause an exception
|
||||
setCaretPosition(0);
|
||||
}
|
||||
fb.replace(0, doc.getLength(), potential, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the specified text is formatted and within
|
||||
* the bounds.
|
||||
*/
|
||||
protected String transform (String text)
|
||||
{
|
||||
// if we're at least the minvalue, everything's ok
|
||||
int val = unformatSafe(text);
|
||||
if (val >= _minValue) {
|
||||
return format(Math.min(_maxValue, val));
|
||||
}
|
||||
|
||||
// otherwise, see if we can append zeros and make a valid value,
|
||||
// remembering how many digits we added
|
||||
int newVal = val;
|
||||
int digits = 0;
|
||||
if (newVal > 0) {
|
||||
while (newVal * 10 <= _maxValue) {
|
||||
newVal *= 10;
|
||||
digits++;
|
||||
if (newVal >= _minValue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if that didn't work, just set it to the min value and
|
||||
// highlight all the digits
|
||||
if (newVal < _minValue) {
|
||||
newVal = _minValue;
|
||||
digits = String.valueOf(newVal).length();
|
||||
}
|
||||
|
||||
// return the new value, but post an event to immediately
|
||||
// highlight the digits that the user did not enter, so
|
||||
// that they can type over them
|
||||
final String newText = format(newVal);
|
||||
final int fdigits = digits;
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
String text = getText();
|
||||
if (text.equals(newText)) {
|
||||
int len = text.length();
|
||||
setSelectionEnd(len);
|
||||
int digits = fdigits;
|
||||
for (int ii = len - 1; ii >= 0; ii--) {
|
||||
if (Character.isDigit(text.charAt(ii))) {
|
||||
--digits;
|
||||
if (digits == 0) {
|
||||
setSelectionStart(ii);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return newText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate min/max.
|
||||
*/
|
||||
protected void validateMinMax (int minValue, int maxValue)
|
||||
{
|
||||
if (minValue < 0) {
|
||||
throw new IllegalArgumentException("minValue must be at least 0");
|
||||
}
|
||||
if (maxValue < minValue) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxValue must be greater than minValue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the int that is represented by this field.
|
||||
*/
|
||||
public int getValue ()
|
||||
{
|
||||
return unformatSafe(getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text to the value specified.
|
||||
*/
|
||||
public void setValue (int value)
|
||||
{
|
||||
setText(format(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current min value.
|
||||
*/
|
||||
public void setMinValue (int minValue)
|
||||
{
|
||||
validateMinMax(minValue, _maxValue);
|
||||
_minValue = minValue;
|
||||
validateText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current max value.
|
||||
*/
|
||||
public void setMaxValue (int maxValue)
|
||||
{
|
||||
validateMinMax(_minValue, maxValue);
|
||||
_maxValue = maxValue;
|
||||
validateText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the value we're displaying is between the minimum and
|
||||
* the maximum.
|
||||
*/
|
||||
protected void validateText ()
|
||||
{
|
||||
setText(getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the specified monetary value into a string.
|
||||
* This just puts commas in.
|
||||
*/
|
||||
public static String format (int value)
|
||||
{
|
||||
return _formatter.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers, with commas being ok.
|
||||
*/
|
||||
public static int unformat (String text)
|
||||
throws ParseException
|
||||
{
|
||||
return _formatter.parse(text).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers, don't throw exceptions.
|
||||
*/
|
||||
public static int unformatSafe (String text)
|
||||
{
|
||||
try {
|
||||
return unformat(text);
|
||||
} catch (ParseException pe) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our own special Document class.
|
||||
*/
|
||||
protected static class IntDocument extends PlainDocument
|
||||
{
|
||||
@Override protected void fireRemoveUpdate (DocumentEvent e)
|
||||
{
|
||||
// suppress: the DocumentFilter implements replace by doing
|
||||
// a remove and then an insert. We do not want listeners to ever
|
||||
// be notified when the document contains invalid text.
|
||||
// (Gosh, it'd be nice if we didn't have to hack this, but
|
||||
// the standard implementation has a bunch of methods with
|
||||
// package-protected access. Thanks Sun!)
|
||||
}
|
||||
}
|
||||
|
||||
/** min/max */
|
||||
protected int _minValue, _maxValue;
|
||||
|
||||
/** Formats and parses numbers with commas in them. */
|
||||
protected static final NumberFormat _formatter = NumberFormat.getIntegerInstance();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
/**
|
||||
* A table cell renderer that that formats integers according to the
|
||||
* locale's desires.
|
||||
*/
|
||||
public class IntegerTableCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public IntegerTableCellRenderer ()
|
||||
{
|
||||
setHorizontalAlignment(RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setValue (Object value)
|
||||
{
|
||||
if ((value instanceof Integer) || (value instanceof Long)) {
|
||||
setText(_nfi.format(value) + " ");
|
||||
} else {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// our number formatter
|
||||
protected NumberFormat _nfi = NumberFormat.getIntegerInstance();
|
||||
|
||||
/**
|
||||
* A convenience method for installing this renderer.
|
||||
*/
|
||||
public static void install (JTable table)
|
||||
{
|
||||
table.setDefaultRenderer(Number.class, new IntegerTableCellRenderer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JInternalFrame;
|
||||
import javax.swing.JLayeredPane;
|
||||
|
||||
/**
|
||||
* Used for displaying dialogs internally. Be sure to use {@link
|
||||
* #showDialog} and {@link #dismissDialog} to show and hide these dialogs
|
||||
* because they need to do some extra fiddling that the regular show and
|
||||
* hide wouldn't do.
|
||||
*/
|
||||
public class JInternalDialog extends JInternalFrame
|
||||
{
|
||||
/**
|
||||
* Creates a dialog that will display itself in the layered pane of
|
||||
* the frame that contains the supplied component.
|
||||
*/
|
||||
public JInternalDialog (JComponent friend)
|
||||
{
|
||||
this(JLayeredPane.getLayeredPaneAbove(friend));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog that will display itself in the layered pane of
|
||||
* the supplied frame.
|
||||
*/
|
||||
public JInternalDialog (JFrame frame)
|
||||
{
|
||||
this(frame.getLayeredPane());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog that will display itself in the specified layered
|
||||
* pane.
|
||||
*/
|
||||
public JInternalDialog (JLayeredPane parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this dialog to its parent and shows it.
|
||||
*/
|
||||
public void showDialog ()
|
||||
{
|
||||
_parent.add(this, JLayeredPane.PALETTE_LAYER);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this dialog and removes it from its parent.
|
||||
*/
|
||||
public void dismissDialog ()
|
||||
{
|
||||
setVisible(false);
|
||||
_parent.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans up the interface hierarchy looking for the {@link
|
||||
* JInternalDialog} that contains the supplied child component and
|
||||
* dismisses it.
|
||||
*/
|
||||
public static void dismissDialog (Component child)
|
||||
{
|
||||
if (child == null) {
|
||||
return;
|
||||
} else if (child instanceof JInternalDialog) {
|
||||
((JInternalDialog)child).dismissDialog();
|
||||
} else {
|
||||
dismissDialog(child.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
/** Our parent. */
|
||||
protected JLayeredPane _parent;
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
|
||||
import java.awt.font.TextLayout;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.TextAttribute;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import java.text.AttributedString;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
import static com.samskivert.Log.log;
|
||||
|
||||
/**
|
||||
* The label is a multipurpose text display mechanism that can display small amounts of text
|
||||
* wrapped to fit into a variety of constrained spaces. It can be requested to conform to a
|
||||
* particular width or height and will expand into the other dimension in order to accomodate the
|
||||
* text at hand. It is not a component, but is intended for use by components and other more
|
||||
* heavyweight entities.
|
||||
*/
|
||||
public class Label implements SwingConstants, LabelStyleConstants
|
||||
{
|
||||
/** The pattern used to mark the start/end of color blocks. */
|
||||
public static final Pattern COLOR_PATTERN = Pattern.compile("#([Xx]|[0-9A-Fa-f]{6}+)");
|
||||
|
||||
/**
|
||||
* Filter out any color tags from the specified text.
|
||||
*/
|
||||
public static String filterColors (String txt)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
return COLOR_PATTERN.matcher(txt).replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape any special tags so that they won't be interpreted by the label.
|
||||
*/
|
||||
public static String escapeColors (String txt)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
return COLOR_PATTERN.matcher(txt).replaceAll("#''$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escape special tags so that they again look correct. Called by rendering components that
|
||||
* do not understand the color tags to filter colors and unescape any escaped colors.
|
||||
*/
|
||||
public static String unescapeColors (String txt)
|
||||
{
|
||||
return unescapeColors(filterColors(txt), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escape escaped tags so that they look as the users intended.
|
||||
*/
|
||||
private static String unescapeColors (String txt, boolean restore)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
String prefix = restore ? "#" : "%";
|
||||
return ESCAPED_PATTERN.matcher(txt).replaceAll(prefix + "$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a blank label.
|
||||
*/
|
||||
public Label ()
|
||||
{
|
||||
this("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text.
|
||||
*/
|
||||
public Label (String text)
|
||||
{
|
||||
this(text, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text and configuration parameters.
|
||||
*/
|
||||
public Label (String text, Color textColor, Font font)
|
||||
{
|
||||
this(text, NORMAL, textColor, null, font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text and configuration parameters.
|
||||
*/
|
||||
public Label (String text, int style, Color textColor, Color altColor, Font font)
|
||||
{
|
||||
setText(text);
|
||||
setStyle(style);
|
||||
setTextColor(textColor);
|
||||
setAlternateColor(altColor);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text displayed by this label.
|
||||
*/
|
||||
public String getText ()
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text to be displayed by this label.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*
|
||||
* @return true if the text changed as a result of being set, false if the label was already
|
||||
* displaying the requested text.
|
||||
*/
|
||||
public boolean setText (String text)
|
||||
{
|
||||
// the Java text stuff freaks out in a variety of ways if it is asked to deal with the
|
||||
// empty string, so we fake blank labels by just using a space
|
||||
if (StringUtil.isBlank(text)) {
|
||||
text = " ";
|
||||
}
|
||||
|
||||
// if there is no change then avoid doing anything
|
||||
if (text.equals((_rawText == null) ? _text : _rawText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// _text should contain the text without any tags
|
||||
_text = filterColors(text);
|
||||
// _rawText will be null if there are no tags
|
||||
_rawText = text.equals(_text) ? null : text;
|
||||
|
||||
// if what we were passed contains escaped color tags, unescape them
|
||||
_text = unescapeColors(_text, true);
|
||||
if (_rawText != null) {
|
||||
_rawText = unescapeColors(_rawText, false);
|
||||
}
|
||||
|
||||
invalidate("setText");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the font to be used by this label. If the font is not set, the current font of the
|
||||
* graphics context will be used.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setFont (Font font)
|
||||
{
|
||||
_font = font;
|
||||
invalidate("setFont");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to render the text.
|
||||
*/
|
||||
public Color getTextColor ()
|
||||
{
|
||||
return _textColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to render the text. Setting the text color to <code>null</code> will
|
||||
* render the label in the graphics context color (which is the default).
|
||||
*/
|
||||
public void setTextColor (Color color)
|
||||
{
|
||||
_textColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alternate color used to render the text's outline or shadow, if any.
|
||||
*/
|
||||
public Color getAlternateColor ()
|
||||
{
|
||||
return _alternateColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the label to render the text with the specified alternate color when
|
||||
* rendering. The text itself will be rendered in whatever color is currently set in the
|
||||
* graphics context, but the outline or shadow (if any) will always be in the specified color.
|
||||
*/
|
||||
public void setAlternateColor (Color color)
|
||||
{
|
||||
_alternateColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alignment of the text within the label.
|
||||
*/
|
||||
public int getAlignment ()
|
||||
{
|
||||
return _align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alignment of the text within the label to either {@link SwingConstants#LEFT},
|
||||
* {@link SwingConstants#RIGHT}, or {@link SwingConstants#CENTER}. The default alignment is
|
||||
* selected to be appropriate for the locale of the text being rendered.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setAlignment (int align)
|
||||
{
|
||||
_align = align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of the text within the label to one of the styles defined in {@link
|
||||
* LabelStyleConstants}. Some styles can be combined together into a mask,
|
||||
* ie. <code>BOLD|UNDERLINE</code>.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setStyle (int style)
|
||||
{
|
||||
_style = style;
|
||||
invalidate("setStyle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the label to attempt to achieve a balance between width and height that
|
||||
* approximates the golden ratio (width ~1.618 times height).
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setGoldenLayout ()
|
||||
{
|
||||
// use -1 as an indicator that we should be golden
|
||||
_constraints.width = -1;
|
||||
_constraints.height = -1;
|
||||
invalidate("setGoldenLayout");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target width for this label. Text will be wrapped to fit into this width, forcibly
|
||||
* breaking words on character boundaries if a single word is too long to fit into the target
|
||||
* width. Calling this method will annul any previously established target height as we must
|
||||
* have one degree of freedom in which to maneuver.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setTargetWidth (int targetWidth)
|
||||
{
|
||||
if (targetWidth <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid target width '" + targetWidth + "'");
|
||||
}
|
||||
_constraints.width = targetWidth;
|
||||
_constraints.height = 0;
|
||||
invalidate("setTargetWidth");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target height for this label. A simple algorithm will be used to balance the width
|
||||
* of the text in order that there are only as many lines of text as fit into the target
|
||||
* height. If rendering the label as a single line of text causes it to be taller than the
|
||||
* target height, we simply render ourselves anyway. Calling this method will annul any
|
||||
* previously established target width as we must have one degree of freedom in which to
|
||||
* maneuver.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a call is made to {@link
|
||||
* #render} as this method invalidates the layout information.
|
||||
*/
|
||||
public void setTargetHeight (int targetHeight)
|
||||
{
|
||||
if (targetHeight <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid target height '" + targetHeight + "'");
|
||||
}
|
||||
_constraints.width = 0;
|
||||
_constraints.height = targetHeight;
|
||||
invalidate("setTargetHeight");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out previously configured target dimensions for this label.
|
||||
*/
|
||||
public void clearTargetDimens ()
|
||||
{
|
||||
_constraints.width = 0;
|
||||
_constraints.height = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of lines used by this label.
|
||||
*/
|
||||
public int getLineCount ()
|
||||
{
|
||||
return _layouts.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns our computed dimensions. Only valid after a call to {@link #layout}.
|
||||
*/
|
||||
public Dimension getSize ()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this label has been laid out, false if not.
|
||||
*/
|
||||
public boolean isLaidOut ()
|
||||
{
|
||||
return (_layouts != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #layout(Graphics2D)} with the graphics context for the given component.
|
||||
*/
|
||||
public void layout (Component comp)
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D)comp.getGraphics();
|
||||
if (gfx != null) {
|
||||
layout(gfx);
|
||||
gfx.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this label lay out its text, obtaining information from the supplied graphics
|
||||
* context to do so. It is expected that the label will be subsequently rendered in the same
|
||||
* graphics context or at least one that is configured very similarly. If not, wackiness may
|
||||
* ensue.
|
||||
*/
|
||||
public void layout (Graphics2D gfx)
|
||||
{
|
||||
// if text antialiasing is enabled by default, honor that setting
|
||||
Object oalias = SwingUtil.getDefaultTextAntialiasing() ?
|
||||
SwingUtil.activateAntiAliasing(gfx) : null;
|
||||
|
||||
// now we can get our font render context (antialias settings are part of our context)
|
||||
FontRenderContext frc = gfx.getFontRenderContext();
|
||||
List<Tuple<TextLayout,Rectangle2D>> layouts = null;
|
||||
|
||||
// if we have a target height, do some processing and convert that into a target width
|
||||
if (_constraints.height > 0 || _constraints.width == -1) {
|
||||
int targetHeight = _constraints.height;
|
||||
|
||||
// if we're approximating the golden ratio, target a height that gets us near that
|
||||
// ratio, then we can err on the side of being a bit wider which is generally nicer
|
||||
// than being taller (for those of us that don't speak verticall written languages,
|
||||
// anyway)
|
||||
if (_constraints.width == -1) {
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
|
||||
int lines = 1;
|
||||
double width = getWidth(bounds)/lines;
|
||||
double height = getHeight(layout)*lines;
|
||||
double delta = Math.abs(width/height - GOLDEN_RATIO);
|
||||
|
||||
do {
|
||||
width = getWidth(bounds) / (lines+1);
|
||||
double nheight = getHeight(layout) * (lines+1);
|
||||
double ndelta = Math.abs(width/nheight - GOLDEN_RATIO);
|
||||
if (delta <= ndelta) {
|
||||
break;
|
||||
}
|
||||
delta = ndelta;
|
||||
height = nheight;
|
||||
} while (++lines < 200); // cap ourselves at 200 lines
|
||||
|
||||
targetHeight = (int)Math.ceil(height);
|
||||
}
|
||||
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
int lines = Math.round(targetHeight / getHeight(layout));
|
||||
if (lines > 1) {
|
||||
int targetWidth = (int)Math.round(getWidth(bounds) / lines);
|
||||
|
||||
// attempt to lay the text out in the specified width, incrementing by 10% each
|
||||
// time; limit our attempts to 10 expansions to avoid infinite loops if something
|
||||
// is fucked
|
||||
for (int i = 0; i < 10; i++) {
|
||||
LineBreakMeasurer measurer = new LineBreakMeasurer(textIterator(gfx), frc);
|
||||
layouts = computeLines(measurer, targetWidth, _size, true);
|
||||
if ((layouts != null) && (layouts.size() <= lines)) {
|
||||
break;
|
||||
}
|
||||
targetWidth = (int)Math.round(targetWidth * 1.1);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (_constraints.width > 0) {
|
||||
LineBreakMeasurer measurer =
|
||||
new LineBreakMeasurer(textIterator(gfx), frc);
|
||||
layouts = computeLines(measurer, _constraints.width, _size, false);
|
||||
}
|
||||
|
||||
// if no constraint, or our constraining height puts us on one line then layout on one line
|
||||
// and call it good
|
||||
if (layouts == null) {
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
// for some reason JDK1.3 on Linux chokes on setSize(double,double)
|
||||
_size.setSize(Math.ceil(getWidth(bounds)), Math.ceil(getHeight(layout)));
|
||||
layouts = new ArrayList<Tuple<TextLayout,Rectangle2D>>();
|
||||
layouts.add(new Tuple<TextLayout,Rectangle2D>(layout, bounds));
|
||||
}
|
||||
|
||||
// create our layouts array
|
||||
int lcount = layouts.size();
|
||||
_layouts = new TextLayout[lcount];
|
||||
_lbounds = new Rectangle2D[lcount];
|
||||
_leaders = new float[lcount];
|
||||
for (int ii = 0; ii < lcount; ii++) {
|
||||
Tuple<TextLayout,Rectangle2D> tup = layouts.get(ii);
|
||||
_layouts[ii] = tup.left;
|
||||
_lbounds[ii] = tup.right;
|
||||
// account for potential leaders
|
||||
if (_lbounds[ii].getX() < 0) {
|
||||
_leaders[ii] = (float)-_lbounds[ii].getX();
|
||||
}
|
||||
}
|
||||
|
||||
// finally restore our antialiasing state
|
||||
SwingUtil.restoreAntiAliasing(gfx, oalias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the lines of text for this label given the specified target width. The overall size
|
||||
* of the computed lines is stored into the <code>size</code> parameter.
|
||||
*
|
||||
* @return an {@link List} or null if <code>keepWordsWhole</code> was true and the lines could
|
||||
* not be layed out in the target width.
|
||||
*/
|
||||
protected List<Tuple<TextLayout,Rectangle2D>> computeLines (
|
||||
LineBreakMeasurer measurer, int targetWidth, Dimension size, boolean keepWordsWhole)
|
||||
{
|
||||
// start with a size of zero
|
||||
double width = 0, height = 0;
|
||||
List<Tuple<TextLayout,Rectangle2D>> layouts = new ArrayList<Tuple<TextLayout,Rectangle2D>>();
|
||||
|
||||
try {
|
||||
// obtain our new dimensions by using a line break iterator to lay out our text one
|
||||
// line at a time
|
||||
TextLayout layout;
|
||||
int lastposition = _text.length();
|
||||
while (true) {
|
||||
int nextret = _text.indexOf('\n', measurer.getPosition() + 1);
|
||||
if (nextret == -1) {
|
||||
nextret = lastposition;
|
||||
}
|
||||
layout = measurer.nextLayout(targetWidth, nextret, keepWordsWhole);
|
||||
if (layout == null) {
|
||||
break;
|
||||
}
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
width = Math.max(width, getWidth(bounds));
|
||||
height += getHeight(layout);
|
||||
layouts.add(new Tuple<TextLayout,Rectangle2D>(layout, bounds));
|
||||
}
|
||||
|
||||
// fill in the computed size; for some reason JDK1.3 on Linux chokes on
|
||||
// setSize(double,double)
|
||||
size.setSize(Math.ceil(width), Math.ceil(height));
|
||||
|
||||
// this can only happen if keepWordsWhole is true
|
||||
if (measurer.getPosition() < lastposition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.warning("Label layout failed", "text", _text, t);
|
||||
}
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the layout at the specified position in the supplied graphics context.
|
||||
*/
|
||||
public void render (Graphics2D gfx, float x, float y)
|
||||
{
|
||||
// nothing to do if we haven't been laid out
|
||||
if (_layouts == null) {
|
||||
log.warning(hashCode() + " Unlaid-out label asked to render", "text", _text
|
||||
/*, "last", _invalidator */);
|
||||
return;
|
||||
}
|
||||
|
||||
Color old = gfx.getColor();
|
||||
if (_textColor != null) {
|
||||
gfx.setColor(_textColor);
|
||||
}
|
||||
|
||||
// We're going to do a couple things differently if we're antialiased
|
||||
RenderingHints hints = gfx.getRenderingHints();
|
||||
boolean antialiased = hints.containsKey(RenderingHints.KEY_ANTIALIASING) ||
|
||||
hints.containsKey(RenderingHints.KEY_TEXT_ANTIALIASING);
|
||||
|
||||
// render our text
|
||||
for (int i = 0; i < _layouts.length; i++) {
|
||||
TextLayout layout = _layouts[i];
|
||||
Rectangle2D lbounds = _lbounds[i];
|
||||
y += layout.getAscent();
|
||||
|
||||
float extra = (float)Math.floor(_size.width - getWidth(lbounds));
|
||||
float rx;
|
||||
switch (_align) {
|
||||
case -1: rx = x + (layout.isLeftToRight() ? 0 : extra); break;
|
||||
default:
|
||||
case LEFT: rx = x; break;
|
||||
case RIGHT: rx = x + extra; break;
|
||||
case CENTER: rx = x + extra/2; break;
|
||||
}
|
||||
|
||||
// shift over any lines that start with a font that extends into negative x-land
|
||||
rx += _leaders[i];
|
||||
|
||||
// System.out.println(i + " x: " + x + " y: " + y + " rx: " + rx + " a: " + _align +
|
||||
// " width: " + _size.width + " lx: " + lbounds.getX() +
|
||||
// " lwidth: " + getWidth(lbounds) + " extra: " + extra);
|
||||
|
||||
Color textColor;
|
||||
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
// render the outline using the hacky, but much nicer than using "real" outlines
|
||||
// (via TextLayout.getOutline), method
|
||||
textColor = gfx.getColor();
|
||||
gfx.setColor(_alternateColor);
|
||||
_mainDraw = false;
|
||||
if (antialiased) {
|
||||
// We need to fill in the actual spot we'll be drawing on top of if antialiased
|
||||
layout.draw(gfx, rx + 1, y + 1);
|
||||
}
|
||||
layout.draw(gfx, rx, y);
|
||||
layout.draw(gfx, rx, y + 1);
|
||||
layout.draw(gfx, rx, y + 2);
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
layout.draw(gfx, rx + 1, y + 2);
|
||||
layout.draw(gfx, rx + 2, y);
|
||||
layout.draw(gfx, rx + 2, y + 1);
|
||||
layout.draw(gfx, rx + 2, y + 2);
|
||||
_mainDraw = true;
|
||||
gfx.setColor(textColor);
|
||||
layout.draw(gfx, rx + 1, y + 1);
|
||||
if (antialiased) {
|
||||
// If antialiased, draw a second time to make sure our letters are nice and
|
||||
// solid looking on top of the outline
|
||||
layout.draw(gfx, rx + 1, y + 1);
|
||||
}
|
||||
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
textColor = gfx.getColor();
|
||||
gfx.setColor(_alternateColor);
|
||||
_mainDraw = false;
|
||||
layout.draw(gfx, rx, y + 1);
|
||||
_mainDraw = true;
|
||||
gfx.setColor(textColor);
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
|
||||
} else if ((_style & BOLD) != 0) {
|
||||
_mainDraw = false;
|
||||
layout.draw(gfx, rx, y);
|
||||
_mainDraw = true;
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
|
||||
} else {
|
||||
layout.draw(gfx, rx, y);
|
||||
}
|
||||
|
||||
y += layout.getDescent() + layout.getLeading();
|
||||
}
|
||||
|
||||
gfx.setColor(old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an attributed character iterator with our text and the appropriate font.
|
||||
*/
|
||||
protected AttributedCharacterIterator textIterator (Graphics2D gfx)
|
||||
{
|
||||
// first set up any attributes that apply to the entire text
|
||||
Font font = (_font == null) ? gfx.getFont() : _font;
|
||||
HashMap<TextAttribute,Object> map = new HashMap<TextAttribute,Object>();
|
||||
map.put(TextAttribute.FONT, font);
|
||||
if ((_style & UNDERLINE) != 0) {
|
||||
map.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
|
||||
}
|
||||
|
||||
AttributedString text = new AttributedString(_text, map);
|
||||
addAttributes(text);
|
||||
return text.getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any attributes to the text.
|
||||
*/
|
||||
protected void addAttributes (AttributedString text)
|
||||
{
|
||||
// add any color attributes for specific segments
|
||||
if (_rawText != null) {
|
||||
Matcher m = COLOR_PATTERN.matcher(_rawText);
|
||||
int startSeg = 0, endSeg = 0;
|
||||
Color lastColor = null;
|
||||
while (m.find()) {
|
||||
// color the segment just passed
|
||||
endSeg += m.start();
|
||||
if (lastColor != null) {
|
||||
text.addAttribute(TextAttribute.FOREGROUND, lastColor, startSeg, endSeg);
|
||||
}
|
||||
|
||||
// parse the tag: start or end a color
|
||||
String group = m.group(1);
|
||||
if ("x".equalsIgnoreCase(group)) {
|
||||
lastColor = null;
|
||||
} else {
|
||||
lastColor = new Color(Integer.parseInt(group, 16));
|
||||
}
|
||||
|
||||
// prepare for the next segment
|
||||
startSeg = endSeg;
|
||||
// Subtract the end of the segment from endSeg so that when we add the start of the
|
||||
// next match we have actually added the length of the characters in between.
|
||||
endSeg -= m.end();
|
||||
}
|
||||
// apply any final color to the tail segment
|
||||
if (lastColor != null) {
|
||||
text.addAttribute(TextAttribute.FOREGROUND, lastColor, startSeg, _text.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the total width of a {@link TextLayout} given bounds returned from a call to {@link
|
||||
* TextLayout#getBounds}.
|
||||
*/
|
||||
protected double getWidth (Rectangle2D laybounds)
|
||||
{
|
||||
double width = Math.max(laybounds.getX(), 0) + laybounds.getWidth();
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
width += 2;
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
width += 1;
|
||||
} else if ((_style & BOLD) != 0) {
|
||||
width += 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounds of the supplied text layout in a way that works around the various
|
||||
* befuckeries that currently happen on the Mac.
|
||||
*/
|
||||
protected Rectangle2D getBounds (TextLayout layout)
|
||||
{
|
||||
if (RunAnywhere.isMacOS()) {
|
||||
return layout.getOutline(null).getBounds();
|
||||
} else {
|
||||
return layout.getBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the height based on the leading, ascent and descent rather than what the layout
|
||||
* reports via <code>getBounds()</code> which rarely seems to have any bearing on reality.
|
||||
*/
|
||||
protected float getHeight (TextLayout layout)
|
||||
{
|
||||
float height = layout.getLeading() + layout.getAscent() + layout.getDescent();
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
height += 2;
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
height += 1;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the label is changed in such a way that it must be relaid out before again being
|
||||
* rendered.
|
||||
*/
|
||||
protected void invalidate (String where)
|
||||
{
|
||||
_layouts = null;
|
||||
// _invalidator = where;
|
||||
}
|
||||
|
||||
/** The text of the label. */
|
||||
protected String _text;
|
||||
|
||||
/** The raw text, with color tags, or null if there are no color tags. */
|
||||
protected String _rawText;
|
||||
|
||||
/** The text style. */
|
||||
protected int _style;
|
||||
|
||||
/** The text alignment. */
|
||||
protected int _align = -1; // -1 means default according to locale
|
||||
|
||||
/** Our size constraints in either the x or y direction. */
|
||||
protected Dimension _constraints = new Dimension();
|
||||
|
||||
/** Our calculated size. */
|
||||
protected Dimension _size = new Dimension();
|
||||
|
||||
/** Some fonts (God bless 'em) extend to the left of the position at which you request that
|
||||
* they be rendered. We opt to push such lines to the right sufficiently that they line up with
|
||||
* the rest of the lines (perhaps not the typographically ideal thing to do, but we're in
|
||||
* computer land and when we say our bounds are (0, 0, width, height) we damned well better not
|
||||
* render outside those bounds, which these wonderful fonts are choosing to do). */
|
||||
protected float[] _leaders;
|
||||
|
||||
/** The font we use when laying out and rendering out text, or null if we're to use the default
|
||||
* font. */
|
||||
protected Font _font;
|
||||
|
||||
/** Formatted text layout instances that contain each line of text. */
|
||||
protected TextLayout[] _layouts;
|
||||
|
||||
/** Formatted text layout instances that contain each line of text. */
|
||||
protected Rectangle2D[] _lbounds;
|
||||
|
||||
/** The color in which to render the text outline or shadow if we're rendering in outline or
|
||||
* shadow mode. */
|
||||
protected Color _alternateColor = null;
|
||||
|
||||
/** The color in which to render the text or null if the text should be rendered with the
|
||||
* graphics context color. */
|
||||
protected Color _textColor = null;
|
||||
|
||||
/** Will be true only when we're drawing a textlayout for the "main" portion of the label. If
|
||||
* we are in OUTLINE mode, we draw each layout 9 times: the last one is the only main one. */
|
||||
protected boolean _mainDraw = true;
|
||||
|
||||
// /** Used for debugging. */
|
||||
// protected String _invalidator;
|
||||
|
||||
/** An approximation of the golden ratio. */
|
||||
protected static final double GOLDEN_RATIO = 1.618034;
|
||||
|
||||
/** Used by {@link #unescapeColors}. */
|
||||
protected static final Pattern ESCAPED_PATTERN = Pattern.compile("#''([Xx]|[0-9A-Fa-f]{6}+)");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user