samp.error
entry in a response map.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class ErrInfo extends SampMap {
/** Key for short description of what went wrong. */
public static final String ERRORTXT_KEY = "samp.errortxt";
/** Key for free-form text given more information about the error. */
public static final String USERTXT_KEY = "samp.usertxt";
/** Key for debugging information such as a stack trace. */
public static final String DEBUGTXT_KEY = "samp.debugtxt";
/** Key for a numeric or textual code identifying the error. */
public static final String CODE_KEY = "samp.code";
private static final String[] KNOWN_KEYS = new String[] {
ERRORTXT_KEY,
USERTXT_KEY,
DEBUGTXT_KEY,
CODE_KEY,
};
/**
* Constructs an empty ErrInfo.
*/
public ErrInfo() {
super( KNOWN_KEYS );
}
/**
* Constructs an ErrInfo based on a given Throwable.
*
* @param e error
*/
public ErrInfo( Throwable e ) {
this();
String txt = e.getMessage();
if ( txt == null || txt.trim().length() == 0 ) {
txt = e.getClass().getName();
}
put( ERRORTXT_KEY, txt );
put( USERTXT_KEY, e.toString() );
put( DEBUGTXT_KEY, getStackTrace( e ) );
put( CODE_KEY, e.getClass().getName() );
}
/**
* Constructs an ErrInfo based on an existing map.
*
* @param map map containing initial data for this object
*/
public ErrInfo( Map map ) {
this();
putAll( map );
}
/**
* Constructs an ErrInfo with a given {@link #ERRORTXT_KEY} value.
*
* @param errortxt short string describing what went wrong
*/
public ErrInfo( String errortxt ) {
this();
put( ERRORTXT_KEY, errortxt );
}
/**
* Sets the value for the {@link #ERRORTXT_KEY} key.
*
* @param errortxt short string describing what went wrong
*/
public void setErrortxt( String errortxt ) {
put( ERRORTXT_KEY, errortxt );
}
/**
* Returns the value for the {@link #ERRORTXT_KEY} key.
*
* @return short string describing what went wrong
*/
public String getErrortxt() {
return getString( ERRORTXT_KEY );
}
/**
* Sets the value for the {@link #USERTXT_KEY} key.
*
* @param usertxt free-form string giving more detail on the error
*/
public void setUsertxt( String usertxt ) {
put( USERTXT_KEY, usertxt );
}
/**
* Returns the value for the {@link #USERTXT_KEY} key.
*
* @return free-form string giving more detail on the error
*/
public String getUsertxt() {
return getString( USERTXT_KEY );
}
/**
* Sets the value for the {@link #DEBUGTXT_KEY} key.
*
* @param debugtxt string containing debugging information, such as a
* a stack trace
*/
public void setDebugtxt( String debugtxt ) {
put( DEBUGTXT_KEY, debugtxt );
}
/**
* Returns the value for the {@link #DEBUGTXT_KEY} key.
*
* @return string containing debugging information, such as a stack trace
*/
public String getDebugtxt() {
return getString( DEBUGTXT_KEY );
}
/**
* Sets the value for the {@link #CODE_KEY} key.
*
* @param code numeric or textual code identifying the error
*/
public void setCode( String code ) {
put( CODE_KEY, code );
}
/**
* Returns the value for the {@link #CODE_KEY} key.
*
* @return numeric or textual code identifying the error
*/
public String getCode() {
return getString( CODE_KEY );
}
public void check() {
super.check();
checkHasKeys( new String[] { ERRORTXT_KEY, } );
}
/**
* Returns a given map as an ErrInfo object.
*
* @param map map
* @return errInfo
*/
public static ErrInfo asErrInfo( Map map ) {
return ( map instanceof ErrInfo || map == null )
? (ErrInfo) map
: new ErrInfo( map );
}
/**
* Generates a string containing a stack trace for a given throwable.
*
* @param e error
* @return stacktrace
*/
private static String getStackTrace( Throwable e ) {
byte[] bbuf;
try {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
e.printStackTrace( new PrintStream( bOut ) );
bOut.close();
bbuf = bOut.toByteArray();
}
catch ( IOException ioex ) {
assert false;
return "error generating stacktrace";
}
StringBuffer sbuf = new StringBuffer( bbuf.length );
for ( int ic = 0; ic < bbuf.length; ic++ ) {
char c = (char) bbuf[ ic ];
if ( SampUtils.isStringChar( c ) ) {
sbuf.append( c );
}
}
return sbuf.toString();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JSamp.java 0000664 0000000 0000000 00000025024 13564500043 0023061 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.hub.Hub;
import org.astrogrid.samp.web.WebClientProfile;
import org.astrogrid.samp.xmlrpc.StandardClientProfile;
import org.astrogrid.samp.xmlrpc.XmlRpcKit;
/**
* Convenience class for invoking JSAMP command-line applications.
*
* @author Mark Taylor
* @since 23 Jul 2008
*/
public class JSamp {
/** Known command class names. */
static final String[] COMMAND_CLASSES = new String[] {
"org.astrogrid.samp.hub.Hub",
"org.astrogrid.samp.gui.HubMonitor",
"org.astrogrid.samp.util.SampLoad",
"org.astrogrid.samp.test.Snooper",
"org.astrogrid.samp.test.MessageSender",
"org.astrogrid.samp.test.HubTester",
"org.astrogrid.samp.test.CalcStorm",
"org.astrogrid.samp.bridge.Bridge",
};
/**
* Private sole constructor prevents instantiation.
*/
private JSamp() {
}
/**
* Does the work for the main method.
*/
public static int runMain( String[] args ) {
// Assemble usage message.
StringBuffer ubuf = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " )
.append( JSamp.class.getName() )
.append( " [-help]" )
.append( " [-version]" )
.append( " main(String[])
* method
* @param args arguments as if passed from the command line
*/
private static int runCommand( String className, String[] args ) {
Class clazz;
try {
clazz = Class.forName( className );
}
catch ( ClassNotFoundException e ) {
System.err.println( "Class " + className + " not available" );
return 1;
}
Object statusObj;
try {
getMainMethod( clazz ).invoke( null, new Object[] { args } );
return 0;
}
catch ( InvocationTargetException e ) {
e.getCause().printStackTrace();
return 1;
}
catch ( Exception e ) {
e.printStackTrace();
return 1;
}
}
/**
* Returns the main(String[])
method for a given class.
*/
static Method getMainMethod( Class clazz ) {
Method method;
try {
method = clazz.getMethod( "main",
new Class[] { String[].class } );
}
catch ( NoSuchMethodException e ) {
throw (IllegalArgumentException)
new AssertionError( "main() method missing for "
+ clazz.getName() )
.initCause( e );
}
int mods = method.getModifiers();
if ( Modifier.isStatic( mods ) &&
Modifier.isPublic( mods ) &&
method.getReturnType() == void.class ) {
return method;
}
else {
throw new IllegalArgumentException( "Wrong main() method signature"
+ " for " + clazz.getName() );
}
}
/**
* Returns the abbreviated form of a given class name.
*
* @param className class name
* @return abbreviation
*/
private static String abbrev( String className ) {
return className.substring( className.lastIndexOf( "." ) + 1 )
.toLowerCase();
}
private static String formatImpls( Object[] options, Class clazz ) {
StringBuffer sbuf = new StringBuffer();
if ( options != null ) {
for ( int i = 0; i < options.length; i++ ) {
if ( sbuf.length() > 0 ) {
sbuf.append( '|' );
}
sbuf.append( options[ i ] );
}
}
if ( clazz != null ) {
if ( sbuf.length() > 0 ) {
sbuf.append( '|' );
}
sbuf.append( '<' )
.append( clazz.getName().replaceFirst( "^.*\\.", "" )
.toLowerCase() )
.append( "-class" )
.append( '>' );
}
return sbuf.toString();
}
/**
* Returns a string giving version details for this package.
*
* @return version string
*/
private static String getVersionText() {
return new StringBuffer()
.append( " This is JSAMP.\n" )
.append( "\n " )
.append( "JSAMP toolkit version:" )
.append( "\n " )
.append( SampUtils.getSoftwareVersion() )
.append( "\n " )
.append( "SAMP standard version:" )
.append( "\n " )
.append( SampUtils.getSampVersion() )
.append( "\n " )
.append( "Author:" )
.append( "\n " )
.append( "Mark Taylor (m.b.taylor@bristol.ac.uk)" )
.append( "\n " )
.append( "WWW:" )
.append( "\n " )
.append( "http://www.star.bristol.ac.uk/~mbt/jsamp" )
.toString();
}
/**
* Sets a system property to a given value unless it has already been set.
* If it has a prior value, that is undisturbed.
* Potential security exceptions are caught and dealt with.
*
* @param key property name
* @param value suggested property value
*/
private static void setDefaultProperty( String key, String value ) {
String existingVal = System.getProperty( key );
if ( existingVal == null || existingVal.trim().length() == 0 ) {
try {
System.setProperty( key, value );
}
catch ( SecurityException e ) {
// never mind.
}
}
}
/**
* Main method.
* Use -help flag for documentation.
*/
public static void main( String[] args ) {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JsonReader.java 0000664 0000000 0000000 00000020031 13564500043 0024074 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2011 Miami-Dade County.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: this file incorporates source code from 3d party entities. Such code
* is copyrighted by those entities as indicated below.
*/
/*
* This code is based on the mjson library found here:
* http://www.sharegov.org/mjson/Json.java
* http://sharegov.blogspot.com/2011/06/json-library.html
*/
package org.astrogrid.samp;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Simple JSON parser which only copes with SAMP-friendly JSON,
* that is strings, lists and objects.
* This code is a stripped-down and somewhat fixed copy of the mjson
* libraray written by Borislav Iordanov, from
* http://www.sharegov.org/mjson/Json.java.
*
* @author Borislav Iordanov
* @author Mark Taylor
*/
class JsonReader {
private static final Object OBJECT_END = new Token("OBJECT_END");
private static final Object ARRAY_END = new Token("ARRAY_END");
private static final Object COLON = new Token("COLON");
private static final Object COMMA = new Token("COMMA");
public static final int FIRST = 0;
public static final int CURRENT = 1;
public static final int NEXT = 2;
private static Map escapes = new HashMap();
static
{
escapes.put(new Character('"'), new Character('"'));
escapes.put(new Character('\\'), new Character('\\'));
escapes.put(new Character('/'), new Character('/'));
escapes.put(new Character('b'), new Character('\b'));
escapes.put(new Character('f'), new Character('\f'));
escapes.put(new Character('n'), new Character('\n'));
escapes.put(new Character('r'), new Character('\r'));
escapes.put(new Character('t'), new Character('\t'));
}
private CharacterIterator it;
private char c;
private Object token;
private StringBuffer buf = new StringBuffer();
private char next()
{
if (it.getIndex() == it.getEndIndex())
throw new DataException("Reached end of input at the " +
it.getIndex() + "th character.");
c = it.next();
return c;
}
private char previous()
{
c = it.previous();
return c;
}
private void skipWhiteSpace()
{
do
{
if (Character.isWhitespace(c))
;
else if (c == '/')
{
next();
if (c == '*')
{
// skip multiline comments
while (c != CharacterIterator.DONE)
if (next() == '*' && next() == '/')
break;
if (c == CharacterIterator.DONE)
throw new DataException("Unterminated comment while parsing JSON string.");
}
else if (c == '/')
while (c != '\n' && c != CharacterIterator.DONE)
next();
else
{
previous();
break;
}
}
else
break;
} while (next() != CharacterIterator.DONE);
}
public Object read(CharacterIterator ci, int start)
{
it = ci;
switch (start)
{
case FIRST:
c = it.first();
break;
case CURRENT:
c = it.current();
break;
case NEXT:
c = it.next();
break;
}
return read();
}
public Object read(CharacterIterator it)
{
return read(it, NEXT);
}
public Object read(String string)
{
return read(new StringCharacterIterator(string), FIRST);
}
private Object read()
{
skipWhiteSpace();
char ch = c;
next();
switch (ch)
{
case '"': token = readString(); break;
case '[': token = readArray(); break;
case ']': token = ARRAY_END; break;
case ',': token = COMMA; break;
case '{': token = readObject(); break;
case '}': token = OBJECT_END; break;
case ':': token = COLON; break;
default: {
throw new DataException( "Unexpected character '" + ch + "'" );
}
}
return token;
}
private Map readObject()
{
Map ret = new LinkedHashMap();
read();
while (true) {
if (token == OBJECT_END) {
return ret;
}
if (!(token instanceof String)) {
throw new DataException("Missing/illegal object key");
}
String key = (String) token;
if (read() != COLON) {
throw new DataException("Missing colon in JSON object");
}
Object value = read();
ret.put(key, value);
read();
if (token == COMMA) {
read();
}
else if (token != OBJECT_END) {
throw new DataException("Unexpected token " + token);
}
}
}
private List readArray()
{
List ret = new ArrayList();
Object value = read();
while (token != ARRAY_END)
{
ret.add(value);
if (read() == COMMA)
value = read();
else if (token != ARRAY_END)
throw new DataException("Unexpected token in array " + token);
}
return ret;
}
private String readString()
{
buf.setLength(0);
while (c != '"')
{
if (c == '\\')
{
next();
if (c == 'u')
{
add(unicode());
}
else
{
Object value = escapes.get(new Character(c));
if (value != null)
{
add(((Character) value).charValue());
}
}
}
else
{
add();
}
}
next();
return buf.toString();
}
private void add(char cc)
{
buf.append(cc);
next();
}
private void add()
{
add(c);
}
private char unicode()
{
int value = 0;
for (int i = 0; i < 4; ++i)
{
switch (next())
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
value = (value << 4) + c - '0';
break;
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
value = (value << 4) + (c - 'a') + 10;
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
value = (value << 4) + (c - 'A') + 10;
break;
}
}
return (char) value;
}
/**
* Named object.
*/
private static class Token {
private final String name;
Token(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JsonWriter.java 0000664 0000000 0000000 00000013117 13564500043 0024155 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Outputs a SAMP object as JSON.
* Can do it formatted and reasonably compact.
*
* @author Mark Taylor
* @since 25 Jul 2011
*/
class JsonWriter {
private final int indent_;
private final String spc_;
/**
* Constructor with default properties.
*/
public JsonWriter() {
this( 2, true );
}
/**
* Custom constructor.
*
* @param indent number of characters indent per level
* @param spacer whether to put spaces inside brackets
*/
public JsonWriter( int indent, boolean spacer ) {
indent_ = indent;
spc_ = spacer ? " " : "";
}
/**
* Converts a SAMP data item to JSON.
*
* @param item SAMP-friendly object
* @return JSON representation
*/
public String toJson( Object item ) {
StringBuffer sbuf = new StringBuffer();
toJson( sbuf, item, 0, false );
if ( indent_ >= 0 ) {
assert sbuf.charAt( 0 ) == '\n';
return sbuf.substring( 1, sbuf.length() );
}
else {
return sbuf.toString();
}
}
/**
* Recursive method which does the work for conversion.
* If possible, call this method with isPositioned=false
.
*
* @param sbuf string buffer to append result to
* @param item object to convert
* @param level current indentation level
* @param isPositioned true if output should be direct to sbuf,
* false if it needs a newline plus indentation first
*/
private void toJson( StringBuffer sbuf, Object item, int level,
boolean isPositioned ) {
if ( item instanceof String ) {
if ( ! isPositioned ) {
sbuf.append( getIndent( level ) );
}
sbuf.append( '"' )
.append( (String) item )
.append( '"' );
}
else if ( item instanceof List ) {
List list = (List) item;
if ( list.isEmpty() ) {
if ( ! isPositioned ) {
sbuf.append( getIndent( level ) );
}
sbuf.append( "[]" );
}
else {
sbuf.append( getIntroIndent( level, '[', isPositioned ) );
boolean isPos = ! isPositioned;
for ( Iterator it = list.iterator(); it.hasNext(); ) {
toJson( sbuf, it.next(), level + 1, isPos );
if ( it.hasNext() ) {
sbuf.append( "," );
}
isPos = false;
}
sbuf.append( spc_ + "]" );
}
}
else if ( item instanceof Map ) {
Map map = (Map) item;
if ( map.isEmpty() ) {
if ( ! isPositioned ) {
sbuf.append( getIndent( level ) );
}
sbuf.append( "{}" );
}
else {
sbuf.append( getIntroIndent( level, '{', isPositioned ) );
boolean isPos = ! isPositioned;
for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
if ( ! ( key instanceof String ) ) {
throw new DataException( "Non-string key in map:"
+ key );
}
toJson( sbuf, key, level + 1, isPos );
sbuf.append( ":" + spc_ );
toJson( sbuf, entry.getValue(), level + 1, true );
if ( it.hasNext() ) {
sbuf.append( "," );
}
isPos = false;
}
sbuf.append( spc_ + "}" );
}
}
else {
throw new DataException( "Illegal data type " + item );
}
}
/**
* Returns prepended whitespace containing an opener character.
*
* @param level indentation level
* @param chr opener character
* @param isPositioned true if output should be direct to sbuf,
* false if it needs a newline plus indentation first
* @return string to prepend
*/
private String getIntroIndent( int level, char chr, boolean isPositioned ) {
if ( isPositioned ) {
return new StringBuffer().append( chr ).toString();
}
else {
StringBuffer sbuf = new StringBuffer();
sbuf.append( getIndent( level ) );
sbuf.append( chr );
for ( int ic = 0; ic < indent_ - 1; ic++ ) {
sbuf.append( ' ' );
}
return sbuf.toString();
}
}
/**
* Returns prepended whitespace.
*
* @param level indentation level
* @return string to prepend
*/
private String getIndent( int level ) {
if ( indent_ >= 0 ) {
int nc = level * indent_;
StringBuffer sbuf = new StringBuffer( nc + 1 );
sbuf.append( '\n' );
for ( int ic = 0; ic < nc; ic++ ) {
sbuf.append( ' ' );
}
return sbuf.toString();
}
else {
return "";
}
}
public static void main( String[] args ) {
String txt = args[ 0 ];
Object item = new JsonReader().read( txt );
System.out.println( new JsonWriter().toJson( item ) );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Message.java 0000664 0000000 0000000 00000007515 13564500043 0023440 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.HashMap;
import java.util.Map;
/**
* Represents an encoded SAMP Message.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class Message extends SampMap {
/** Key for message MType. */
public static final String MTYPE_KEY = "samp.mtype";
/** Key for map of parameters used by this message. */
public static final String PARAMS_KEY = "samp.params";
private static final String[] KNOWN_KEYS = new String[] {
MTYPE_KEY,
PARAMS_KEY,
};
/**
* Constructs an empty message.
*/
public Message() {
super( KNOWN_KEYS );
}
/**
* Constructs a message based on an existing map.
*
* @param map map containing initial data for this object
*/
public Message( Map map ) {
this();
putAll( map );
}
/**
* Constructs a message with a given MType and params map.
*
* @param mtype value for {@link #MTYPE_KEY} key
* @param params value for {@link #PARAMS_KEY} key
*/
public Message( String mtype, Map params ) {
this();
put( MTYPE_KEY, mtype );
put( PARAMS_KEY, params == null ? new HashMap() : params );
}
/**
* Constructs a message with a given MType.
* The parameters map will be mutable.
*
* @param mtype value for {@link #MTYPE_KEY} key
*/
public Message( String mtype ) {
this( mtype, null );
}
/**
* Returns this message's MType.
*
* @return value for {@link #MTYPE_KEY}
*/
public String getMType() {
return getString( MTYPE_KEY );
}
/**
* Sets this message's params map.
*
* @param params value for {@link #PARAMS_KEY}
*/
public void setParams( Map params ) {
put( PARAMS_KEY, params );
}
/**
* Returns this message's params map.
*
* @return value for {@link #PARAMS_KEY}
*/
public Map getParams() {
return getMap( PARAMS_KEY );
}
/**
* Sets the value for a single entry in this message's
* samp.params
map.
*
* @param name param name
* @param value param value
*/
public Message addParam( String name, Object value ) {
if ( ! containsKey( PARAMS_KEY ) ) {
put( PARAMS_KEY, new HashMap() );
}
getParams().put( name, value );
return this;
}
/**
* Returns the value of a single entry in this message's
* samp.params
map. Null is returned if the parameter
* does not appear.
*
* @param name param name
* @return param value, or null
*/
public Object getParam( String name ) {
Map params = getParams();
return params == null ? null
: params.get( name );
}
/**
* Returns the value of a single entry in this message's
* samp.params
map, throwing an exception
* if it is not present.
*
* @param name param name
* @return param value
* @throws DataException if no parameter name
is present
*/
public Object getRequiredParam( String name ) {
Object param = getParam( name );
if ( param != null ) {
return param;
}
else {
throw new DataException( "Required parameter \"" + name
+ "\" is missing" );
}
}
public void check() {
super.check();
checkHasKeys( new String[] { MTYPE_KEY } );
}
/**
* Returns a given map as a Message object.
*
* @param map map
* @return message
*/
public static Message asMessage( Map map ) {
return ( map instanceof Message || map == null )
? (Message) map
: new Message( map );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Metadata.java 0000664 0000000 0000000 00000007762 13564500043 0023600 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
* Represents the application metadata associated with a SAMP client.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class Metadata extends SampMap {
/** Key for application name. */
public static final String NAME_KEY = "samp.name";
/** Key for short description of the application in plain text. */
public static final String DESCTEXT_KEY = "samp.description.text";
/** Key for description of the application in HTML. */
public static final String DESCHTML_KEY = "samp.description.html";
/** Key for the URL of an icon in png, gif or jpeg format. */
public static final String ICONURL_KEY = "samp.icon.url";
/** Key for the URL of a documentation web page. */
public static final String DOCURL_KEY = "samp.documentation.url";
private static final String[] KNOWN_KEYS = new String[] {
NAME_KEY,
DESCTEXT_KEY,
DESCHTML_KEY,
ICONURL_KEY,
DOCURL_KEY,
};
/**
* Constructs an empty Metadata map.
*/
public Metadata() {
super( KNOWN_KEYS );
}
/**
* Constructs a Metadata map based on a given map.
*
* @param map map containing initial values for this object
*/
public Metadata( Map map ) {
this();
putAll( map );
}
/**
* Sets the value for the application's name.
*
* @param name value for {@link #NAME_KEY} key
*/
public void setName( String name ) {
put( NAME_KEY, name );
}
/**
* Returns the value for the application's name.
*
* @return value for {@link #NAME_KEY} key
*/
public String getName() {
return (String) get( NAME_KEY );
}
/**
* Sets a short description of the application.
*
* @param txt value for {@link #DESCTEXT_KEY} key
*/
public void setDescriptionText( String txt ) {
put( DESCTEXT_KEY, txt );
}
/**
* Returns a short description of the application.
*
* @return value for {@link #DESCTEXT_KEY} key
*/
public String getDescriptionText() {
return (String) get( DESCTEXT_KEY );
}
/**
* Sets an HTML description of the application.
*
* @param html value for {@link #DESCHTML_KEY} key
*/
public void setDescriptionHtml( String html ) {
put( DESCHTML_KEY, html );
}
/**
* Returns an HTML description of the application.
*
* @return value for {@link #DESCHTML_KEY} key
*/
public String getDescriptionHtml() {
return (String) get( DESCHTML_KEY );
}
/**
* Sets a URL for a gif, png or jpeg icon identifying the application.
*
* @param url value for {@link #ICONURL_KEY} key
*/
public void setIconUrl( String url ) {
put( ICONURL_KEY, url );
}
/**
* Returns a URL for a gif, png or jpeg icon identifying the application.
*
* @return value for {@link #ICONURL_KEY} key
*/
public URL getIconUrl() {
return getUrl( ICONURL_KEY );
}
/**
* Sets a URL for a documentation web page.
*
* @param url value for {@link #DOCURL_KEY} key
*/
public void setDocumentationUrl( String url ) {
put( DOCURL_KEY, url );
}
/**
* Returns a URL for a documentation web page.
*
* @return value for {@link #DOCURL_KEY} key
*/
public URL getDocumentationUrl() {
return getUrl( DOCURL_KEY );
}
public void check() {
super.check();
SampUtils.checkUrl( getString( DOCURL_KEY ) );
SampUtils.checkUrl( getString( ICONURL_KEY ) );
}
/**
* Returns a given map as a Metadata object.
*
* @param map map
* @return metadata
*/
public static Metadata asMetadata( Map map ) {
return ( map instanceof Metadata || map == null )
? (Metadata) map
: new Metadata( map );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Platform.java 0000664 0000000 0000000 00000025576 13564500043 0023647 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Platform-dependent features required by the SAMP implementation.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public abstract class Platform {
private static Platform instance_;
private final String name_;
private static final Logger logger_ =
Logger.getLogger( Platform.class.getName() );
/**
* Constructor.
*
* @param name platform name
*/
protected Platform( String name ) {
name_ = name;
}
/**
* Returns SAMP's definition of the "home" directory.
*
* @return directory containing SAMP lockfile
*/
public abstract File getHomeDirectory();
/**
* Returns the value of an environment variable.
* If it can't be done, null is returned.
*
* @param varname name of environment variable
* @return value of environment variable
*/
public String getEnv( String varname ) {
try {
return System.getenv( varname );
}
// System.getenv is unimplemented at 1.4, and throws an Error.
catch ( Throwable e ) {
String[] argv = getGetenvArgs( varname );
if ( argv == null ) {
return null;
}
else {
try {
String cmdout = exec( argv );
return cmdout.trim();
}
catch ( Throwable e2 ) {
return null;
}
}
}
}
/**
* Sets file permissions on a given file so that it cannot be read by
* anyone other than its owner.
*
* @param file file whose permissions are to be altered
* @throws IOException if permissions cannot be changed
*/
public void setPrivateRead( File file ) throws IOException {
if ( setPrivateReadReflect( file ) ) {
return;
}
else {
String[] privateReadArgs = getPrivateReadArgs( file );
if ( privateReadArgs != null ) {
exec( privateReadArgs );
}
else {
logger_.info( "No known way to set user-only read permissions"
+ "; possible security implications"
+ " on multi-user systems" );
}
}
}
/**
* Returns an array of words to pass to
* {@link java.lang.Runtime#exec(java.lang.String[])} in order
* to read an environment variable name.
* If null is returned, no way is known to do this with a system command.
*
* @param varname environment variable name to read
* @return exec args
*/
protected abstract String[] getGetenvArgs( String varname );
/**
* Returns an array of words to pass to
* {@link java.lang.Runtime#exec(java.lang.String[])} in order
* to set permissions on a given file so that it cannot be read by
* anyone other than its owner.
* If null is returned, no way is known to do this with a system command.
*
* @param file file to alter
* @return exec args
*/
protected abstract String[] getPrivateReadArgs( File file )
throws IOException;
/**
* Attempt to use the File.setReadable()
method to set
* permissions on a file so that it cannot be read by anyone other
* than its owner.
*
* @param file file to alter
* @return true if the attempt succeeded, false if it failed because
* we are running the wrong version of java
* @throws IOException if there was some I/O failure
*/
private static boolean setPrivateReadReflect( File file )
throws IOException {
try {
Method setReadableMethod =
File.class.getMethod( "setReadable",
new Class[] { boolean.class,
boolean.class, } );
boolean success =
( setReadableMethod.invoke( file,
new Object[] { Boolean.FALSE,
Boolean.FALSE } )
.equals( Boolean.TRUE ) ) &&
( setReadableMethod.invoke( file,
new Object[] { Boolean.TRUE,
Boolean.TRUE } )
.equals( Boolean.TRUE ) );
return success;
}
catch ( InvocationTargetException e1 ) {
Throwable e2 = e1.getCause();
if ( e2 instanceof IOException ) {
throw (IOException) e2;
}
else if ( e2 instanceof RuntimeException ) {
throw (RuntimeException) e2;
}
else {
throw (IOException) new IOException( e2.getMessage() )
.initCause( e2 );
}
}
catch ( NoSuchMethodException e ) {
// method only available at java 1.6+
return false;
}
catch ( IllegalAccessException e ) {
// not likely.
return false;
}
}
/**
* Attempts a {@java.lang.Runtime#exec(java.lang.String[])} with a given
* list of arguments. The output from stdout is returned as a string;
* in the case of error an IOException is thrown with a message giving
* the output from stderr.
*
* Note: do not use this for cases in which the
* output from stdout or stderr might be more than a few characters -
* blocking or deadlock is possible (see {@link java.lang.Process}).
*
* @param args array of words to pass to exec
* @return output from standard output
* @throws IOException with text from standard error if there is an error
*/
private static String exec( String[] args ) throws IOException {
String argv = Arrays.asList( args ).toString();
logger_.info( "System exec: " + argv );
final Process process;
final StreamReader outReader;
final StreamReader errReader;
try {
process = Runtime.getRuntime().exec( args );
outReader = new StreamReader( process.getInputStream() );
errReader = new StreamReader( process.getErrorStream() );
outReader.start();
errReader.start();
process.waitFor();
}
catch ( InterruptedException e ) {
throw new IOException( "Exec failed: " + argv );
}
catch ( IOException e ) {
throw (IOException)
new IOException( "Exec failed: " + argv ).initCause( e );
}
return process.exitValue() == 0 ? outReader.getContent()
: errReader.getContent();
}
/**
* Returns a Platform
instance for the current system.
*
* @return platform instance
*/
public static Platform getPlatform() {
if ( instance_ == null ) {
instance_ = createPlatform();
}
return instance_;
}
/**
* Constructs a Platform for the current system.
*
* @return new platform
*/
private static Platform createPlatform() {
// Is this reliable?
String osname = System.getProperty( "os.name" );
if ( osname.toLowerCase().startsWith( "windows" ) ||
osname.toLowerCase().indexOf( "microsoft" ) >= 0 ) {
return new WindowsPlatform();
}
else {
return new UnixPlatform();
}
}
/**
* Thread which reads the contents of a stream into a string buffer.
*/
private static class StreamReader extends Thread {
private final InputStream in_;
private final StringBuffer sbuf_;
/**
* Constructor.
*
* @param in input stream
*/
StreamReader( InputStream in ) {
super( "StreamReader" );
in_ = in;
sbuf_ = new StringBuffer();
setDaemon( true );
}
public void run() {
try {
for ( int c; ( c = in_.read() ) >= 0; ) {
sbuf_.append( (char) c );
}
in_.close();
}
catch ( IOException e ) {
}
}
/**
* Returns the content of the stream.
*
* @return content
*/
public String getContent() {
return sbuf_.toString();
}
}
/**
* Platform implementation for Un*x-like systems.
*/
private static class UnixPlatform extends Platform {
/**
* Constructor.
*/
UnixPlatform() {
super( "Un*x" );
}
public File getHomeDirectory() {
return new File( System.getProperty( "user.home" ) );
}
protected String[] getGetenvArgs( String varname ) {
return new String[] { "printenv", varname, };
}
protected String[] getPrivateReadArgs( File file ) {
return new String[] { "chmod", "600", file.toString(), };
}
}
/**
* Platform implementation for Microsoft Windows-like systems.
*/
private static class WindowsPlatform extends Platform {
/**
* Constructor.
*/
WindowsPlatform() {
super( "MS Windows" );
}
protected String[] getPrivateReadArgs( File file ) throws IOException {
// No good way known. For a while I was using "attrib -R file",
// but this wasn't doing what was wanted. Bruno Rino has
// suggested "CALCS file /G %USERNAME%:F". Sounds kind of
// sensible, but requires user input (doable, but fiddly),
// and from my experiments on NTFS doesn't seem to have any
// discernable effect. As I understand it, it's unlikely to do
// anything on FAT (no ACLs). Given my general ignorance of
// MS OSes and file systems, I'm inclined to leave this for
// fear of inadvertently doing something bad.
return null;
}
public File getHomeDirectory() {
String userprofile = getEnv( "USERPROFILE" );
if ( userprofile != null && userprofile.trim().length() > 0 ) {
return new File( userprofile );
}
else {
return new File( System.getProperty( "user.home" ) );
}
}
public String[] getGetenvArgs( String varname ) {
return new String[] { "cmd", "/c", "echo", "%" + varname + "%", };
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/RegInfo.java 0000664 0000000 0000000 00000004177 13564500043 0023406 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.Map;
/**
* Represents information provided to a client at registration by the hub.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class RegInfo extends SampMap {
/** Key for client public-id used by hub when sending messages itself. */
public static final String HUBID_KEY = "samp.hub-id";
/** Key for client public-id owned by the registering application. */
public static final String SELFID_KEY = "samp.self-id";
/** Key for private-key token used for communications between hub and
* registering client (Standard Profile). */
public static final String PRIVATEKEY_KEY = "samp.private-key";
private static final String[] KNOWN_KEYS = new String[] {
HUBID_KEY,
SELFID_KEY,
PRIVATEKEY_KEY,
};
/**
* Constructs an empty RegInfo.
*/
public RegInfo() {
super( KNOWN_KEYS );
}
/**
* Constructs a RegInfo based on an existing map.
*
* @param map map containing initial data for this object
*/
public RegInfo( Map map ) {
this();
putAll( map );
}
/**
* Returns the hub's own public client id.
*
* @return {@link #HUBID_KEY} value
*/
public String getHubId() {
return getString( HUBID_KEY );
}
/**
* Returns the registered client's public client id.
*
* @return {@link #SELFID_KEY} value
*/
public String getSelfId() {
return getString( SELFID_KEY );
}
/**
* Returns the registered client's private key (Standard Profile).
*
* @return {@link #PRIVATEKEY_KEY} value
*/
public String getPrivateKey() {
return getString( PRIVATEKEY_KEY );
}
public void check() {
super.check();
checkHasKeys( new String[] { HUBID_KEY, SELFID_KEY, } );
}
/**
* Returns a given map as a RegInfo.
*
* @param map map
* @return registration info
*/
public static RegInfo asRegInfo( Map map ) {
return map instanceof RegInfo
? (RegInfo) map
: new RegInfo( map );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Response.java 0000664 0000000 0000000 00000012633 13564500043 0023647 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.Map;
/**
* Represents an encoded SAMP response.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class Response extends SampMap {
/** Key for response status. May take one of the values
* {@link #OK_STATUS}, {@link #WARNING_STATUS} or {@link #ERROR_STATUS}.
*/
public static final String STATUS_KEY = "samp.status";
/** Key for result map. This is a map of key-value pairs with semantics
* defined by the original message's MType.
* Only present in case of success (or warning). */
public static final String RESULT_KEY = "samp.result";
/** Key for error map. Only present in case of failure (or warning). */
public static final String ERROR_KEY = "samp.error";
private static final String[] KNOWN_KEYS = new String[] {
STATUS_KEY,
RESULT_KEY,
ERROR_KEY,
};
/** {@link #STATUS_KEY} value indicating success. */
public static final String OK_STATUS = "samp.ok";
/** {@link #STATUS_KEY} value indicating partial success. */
public static final String WARNING_STATUS = "samp.warning";
/** {@link #STATUS_KEY} value indicating failure. */
public static final String ERROR_STATUS = "samp.error";
/**
* Constructs an empty response.
*/
public Response() {
super( KNOWN_KEYS );
}
/**
* Constructs a response based on an existing map.
*
* @param map map containing initial data for this object
*/
public Response( Map map ) {
this();
putAll( map );
}
/**
* Constructs a response with given status, result and error.
*
* @param status {@link #STATUS_KEY} value
* @param result {@link #RESULT_KEY} value
* @param errinfo {@link #ERROR_KEY} value
*/
public Response( String status, Map result, ErrInfo errinfo ) {
this();
put( STATUS_KEY, status );
if ( result != null ) {
put( RESULT_KEY, result );
}
if ( errinfo != null ) {
put( ERROR_KEY, errinfo );
}
}
/**
* Sets the status value.
*
* @param status {@link #STATUS_KEY} value
*/
public void setStatus( String status ) {
put( STATUS_KEY, status );
}
/**
* Returns the status value.
*
* @return {@link #STATUS_KEY} value
*/
public String getStatus() {
return getString( STATUS_KEY );
}
/**
* Sets the result map.
*
* @param result {@link #RESULT_KEY} value
*/
public void setResult( Map result ) {
put( RESULT_KEY, result );
}
/**
* Returns the result map.
*
* @return {@link #RESULT_KEY} value
*/
public Map getResult() {
return getMap( RESULT_KEY );
}
/**
* Sets the error object.
*
* @param errInfo {@link #ERROR_KEY} value
* @see ErrInfo
*/
public void setErrInfo( Map errInfo ) {
put( ERROR_KEY, errInfo );
}
/**
* Returns the error object.
*
* @return {@link #ERROR_KEY} value as an ErrInfo
*/
public ErrInfo getErrInfo() {
return ErrInfo.asErrInfo( getMap( ERROR_KEY ) );
}
/**
* Indicates whether the result was an unequivocal success.
*
* @return true iff getStatus()==OK_STATUS
*/
public boolean isOK() {
return OK_STATUS.equals( get( STATUS_KEY ) );
}
public void check() {
super.check();
checkHasKeys( new String[] { STATUS_KEY, } );
String status = getStatus();
if ( OK_STATUS.equals( status ) || WARNING_STATUS.equals( status ) ) {
if ( ! containsKey( RESULT_KEY ) ) {
throw new DataException( STATUS_KEY + "=" + status
+ " but no " + RESULT_KEY );
}
}
if ( ERROR_STATUS.equals( status ) ||
WARNING_STATUS.equals( status ) ) {
if ( ! containsKey( ERROR_KEY ) ) {
throw new DataException( STATUS_KEY + "=" + status
+ " but not " + ERROR_KEY );
}
}
if ( ! containsKey( RESULT_KEY ) && ! containsKey( ERROR_KEY ) ) {
throw new DataException( "Neither " + RESULT_KEY +
" nor " + ERROR_KEY +
" keys present" );
}
if ( containsKey( ERROR_KEY ) ) {
ErrInfo.asErrInfo( getMap( ERROR_KEY ) ).check();
}
}
/**
* Returns a new response which is a success.
*
* @param result key-value map representing results of successful call
* @return new success response
*/
public static Response createSuccessResponse( Map result ) {
return new Response( OK_STATUS, result, null );
}
/**
* Returns a new response which is an error.
*
* @param errinfo error information
* @return new error response
*/
public static Response createErrorResponse( ErrInfo errinfo ) {
return new Response( ERROR_STATUS, null, errinfo );
}
/**
* Returns a map as a Response object.
*
* @param map map
* @return response
*/
public static Response asResponse( Map map ) {
return ( map instanceof Response || map == null )
? (Response) map
: new Response( map );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/SampMap.java 0000664 0000000 0000000 00000014063 13564500043 0023406 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Abstract superclass for objects represented within the SAMP package as
* key-value maps. There are several of these, represented by subclasses
* of SampMap
, for instance {@link Message}, {@link Metadata} etc.
* A SampMap
is-a {@link java.util.Map}, but has some
* additional useful features:
*
asClass
* method to convert from a normal Map to the class in question
* In general
* any time a map-encoded object is required by a method in the toolkit,
* any Map
can be used. When the toolkit provides a map-encoded
* object however (as return value or callback method parameter), an object
* of the more specific SampMap
type is used.
* This allows maximum convenience for the application programmer, but
* means that you don't have to use these additional features if you
* don't want to, you can treat everything as a plain old Map
.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public abstract class SampMap extends AbstractMap {
private final Map baseMap_;
public static final Map EMPTY =
Collections.unmodifiableMap( new HashMap() );
/**
* Constructor.
* The given array of well-known keys will appear first in the list of
* entries when this map is iterated over. Other entries will appear in
* alphabetical order.
*
* @param knownKeys array of well-known keys for this class
*/
protected SampMap( String[] knownKeys ) {
super();
final List knownKeyList = Arrays.asList( (String[]) knownKeys.clone() );
baseMap_ = new TreeMap( new Comparator() {
public int compare( Object o1, Object o2 ) {
String s1 = o1.toString();
String s2 = o2.toString();
int k1 = knownKeyList.indexOf( s1 );
int k2 = knownKeyList.indexOf( s2 );
if ( k1 >= 0 ) {
if ( k2 >= 0 ) {
return k1 - k2;
}
else {
return -1;
}
}
else if ( k2 >= 0 ) {
assert k1 < 0;
return +1;
}
boolean f1 = s1.startsWith( "samp." );
boolean f2 = s2.startsWith( "samp." );
if ( f1 && ! f2 ) {
return -1;
}
else if ( ! f1 && f2 ) {
return +1;
}
boolean g1 = s1.startsWith( "x-samp." );
boolean g2 = s2.startsWith( "x-samp." );
if ( g1 && ! g2 ) {
return -1;
}
else if ( ! g1 && g2 ) {
return +1;
}
return s1.compareTo( s2 );
}
} );
}
public Object put( Object key, Object value ) {
return baseMap_.put( key, value );
}
public Set entrySet() {
return baseMap_.entrySet();
}
/**
* Checks that this object is ready for use with the SAMP toolkit.
* As well as calling {@link SampUtils#checkMap} (ensuring that all keys
* are Strings, and all values Strings, Lists or Maps), subclass-specific
* invariants may be checked. In the case that there's something wrong,
* an informative DataException
will be thrown.
*
* @throws DataException if this object's current state
* is not suitable for SAMP use
*/
public void check() {
SampUtils.checkMap( this );
}
/**
* Checks that this map contains at least the given set of keys.
* If any is absent, an informative DataException
will be
* thrown. Normally called by {@link #check}.
*
* @param keys array of required keys for this map
* @throws DataException if this object does not contain entries
* for all elements of the array keys
*/
public void checkHasKeys( String[] keys ) {
for ( int i = 0; i < keys.length; i++ ) {
String key = keys[ i ];
if ( ! containsKey( key ) ) {
throw new DataException( "Required key " + key
+ " not present" );
}
}
}
/**
* Returns the value for a given key in this map, cast to String.
*
* @return string value for key
*/
public String getString( String key ) {
return (String) get( key );
}
/**
* returns the value for a given key in this map, cast to Map.
*
* @return map value for key
*/
public Map getMap( String key ) {
return (Map) get( key );
}
/**
* Returns the value for a given key in this map, cast to List.
*
* @return list value for key
*/
public List getList( String key ) {
return (List) get( key );
}
/**
* Returns the value for a given key in this map as a URL.
*
* @return URL value for key
*/
public URL getUrl( String key ) {
String loc = getString( key );
if ( loc == null ) {
return null;
}
else {
try {
return new URL( loc );
}
catch ( MalformedURLException e ) {
return null;
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/SampUtils.java 0000664 0000000 0000000 00000053105 13564500043 0023771 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Contains static utility methods for use with the SAMP toolkit.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class SampUtils {
/**
* Property which can be used to set name used for localhost in server
* endpoints.
* Value is {@value}.
* @see #getLocalhost
*/
public static final String LOCALHOST_PROP = "jsamp.localhost";
private static final Logger logger_ =
Logger.getLogger( SampUtils.class.getName() );
private static String sampVersion_;
private static String softwareVersion_;
private static File lockFile_;
private static final String NEWLINE = getLineSeparator();
/**
* Private constructor prevents instantiation.
*/
private SampUtils() {
}
/**
* Returns a SAMP int string representation of an integer.
*
* @param i integer value
* @return SAMP int string
*/
public static String encodeInt( int i ) {
return Integer.toString( i );
}
/**
* Returns the integer value for a SAMP int string.
*
* @param s SAMP int string
* @return integer value
* @throws NumberFormatException if conversion fails
*/
public static int decodeInt( String s ) {
return Integer.parseInt( s );
}
/**
* Returns a SAMP int string representation of a long integer.
*
* @param i integer value
* @return SAMP int string
*/
public static String encodeLong( long i ) {
return Long.toString( i );
}
/**
* Returns the integer value as a long
for a SAMP int
* string.
*
* @param s SAMP int string
* @return long integer value
* @throws NumberFormatException if conversion fails
*/
public static long decodeLong( String s ) {
return Long.parseLong( s );
}
/**
* Returns a SAMP float string representation of a floating point
* value.
*
* @param d double value
* @return SAMP double string
* @throws IllegalArgumentException if d
is NaN or infinite
*/
public static String encodeFloat( double d ) {
if ( Double.isInfinite( d ) ) {
throw new IllegalArgumentException( "Infinite value "
+ "not permitted" );
}
if ( Double.isNaN( d ) ) {
throw new IllegalArgumentException( "NaN not permitted" );
}
return Double.toString( d );
}
/**
* Returns the double value for a SAMP float string.
*
* @param s SAMP float string
* @return double value
* @throws NumberFormatException if conversion fails
*/
public static double decodeFloat( String s ) {
return Double.parseDouble( s );
}
/**
* Returns a SAMP boolean string representation of a boolean value.
*
* @param b boolean value
* @return SAMP boolean string
*/
public static String encodeBoolean( boolean b ) {
return encodeInt( b ? 1 : 0 );
}
/**
* Returns the boolean value for a SAMP boolean string.
*
* @param s SAMP boolean string
* @return false iff s
is equal to zero
*/
public static boolean decodeBoolean( String s ) {
try {
return decodeInt( s ) != 0;
}
catch ( NumberFormatException e ) {
return false;
}
}
/**
* Checks that a given object is legal for use in a SAMP context.
* This checks that it is either a String, List or Map, that
* any Map keys are Strings, and that Map values and List elements are
* themselves legal (recursively).
*
* @param obj object to check
* @throws DataException in case of an error
*/
public static void checkObject( Object obj ) {
if ( obj instanceof Map ) {
checkMap( (Map) obj );
}
else if ( obj instanceof List ) {
checkList( (List) obj );
}
else if ( obj instanceof String ) {
checkString( (String) obj );
}
else if ( obj == null ) {
throw new DataException( "Bad SAMP object: contains a null" );
}
else {
throw new DataException( "Bad SAMP object: contains a "
+ obj.getClass().getName() );
}
}
/**
* Checks that a given Map is legal for use in a SAMP context.
* All its keys must be strings, and its values must be legal
* SAMP objects.
*
* @param map map to check
* @throws DataException in case of an error
* @see #checkObject
*/
public static void checkMap( Map map ) {
for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
if ( key instanceof String ) {
checkString( (String) key );
checkObject( entry.getValue() );
}
else if ( key == null ) {
throw new DataException( "Map key null" );
}
else {
throw new DataException( "Map key not a string ("
+ key.getClass().getName() + ")" );
}
}
}
/**
* Checks that a given List is legal for use in a SAMP context.
* All its elements must be legal SAMP objects.
*
* @param list list to check
* @throws DataException in case of error
* @see #checkObject
*/
public static void checkList( List list ) {
for ( Iterator it = list.iterator(); it.hasNext(); ) {
checkObject( it.next() );
}
}
/**
* Checks that a given String is legal for use in a SAMP context.
* All its characters must be in the range 0x01 - 0x7f.
*
* @param string string to check
* @throws DataException in case of error
*/
public static void checkString( String string ) {
int leng = string.length();
for ( int i = 0; i < leng; i++ ) {
char c = string.charAt( i );
if ( ! isStringChar( c ) ) {
throw new DataException( "Bad SAMP string; contains character "
+ "0x" + Integer.toHexString( c ) );
}
}
}
/**
* Indicates whether a given character is legal to include in a SAMP
* string.
*
* @return true iff c is 0x09, 0x0a, 0x0d or 0x20--0x7f
*/
public static boolean isStringChar( char c ) {
switch ( c ) {
case 0x09:
case 0x0a:
case 0x0d:
return true;
default:
return c >= 0x20 && c <= 0x7f;
}
}
/**
* Checks that a string is a legal URL.
*
* @param url string to check
* @throws DataException if url
is not a legal URL
*/
public static void checkUrl( String url ) {
if ( url != null ) {
try {
new URL( (String) url );
}
catch ( MalformedURLException e ) {
throw new DataException( "Bad URL " + url, e );
}
}
}
/**
* Returns a string representation of a client object.
* The name is used if present, otherwise the ID.
*
* @param client client object
* @return string
*/
public static String toString( Client client ) {
Metadata meta = client.getMetadata();
if ( meta != null ) {
String name = meta.getName();
if ( name != null && name.trim().length() > 0 ) {
return name;
}
}
return client.getId();
}
/**
* Pretty-prints a SAMP object.
*
* @param obj SAMP-friendly object
* @param indent base indent for text block
* @return string containing formatted object
*/
public static String formatObject( Object obj, int indent ) {
checkObject( obj );
return new JsonWriter( indent, true ).toJson( obj );
}
/**
* Parses a command-line string as a SAMP object.
* If it can be parsed as a SAMP-friendly JSON string, that interpretation
* will be used. Otherwise, the value is just the string as presented.
*
* @param str command-line argument
* @return SAMP object
*/
public static Object parseValue( String str ) {
if ( str == null || str.length() == 0 ) {
return null;
}
else {
try {
Object obj = fromJson( str );
checkObject( obj );
return obj;
}
catch ( RuntimeException e ) {
logger_.config( "String not JSON (" + e + ")" );
}
Object sval = str;
checkObject( sval );
return sval;
}
}
/**
* Returns a string denoting the local host to be used for communicating
* local server endpoints and so on.
*
*
The value returned by default is the loopback address, "127.0.0.1". * However this behaviour can be overridden by setting the * {@link #LOCALHOST_PROP} system property to the string which should * be returned instead. * This may be necessary if the loopback address is not appropriate, * for instance in the case of multiple configured loopback interfaces(?) * or where SAMP communication is required across different machines. * There are two special values which may be used for this property: *
[hostname]
:
* uses the fully qualified domain name of the host[hostnumber]
:
* uses the IP number of the hostIn JSAMP version 0.3-1 and prior versions, the [hostname] * behaviour was the default. * Although this might be seen as more correct, in practice it could cause * a lot of problems with DNS configurations which are incorrect or * unstable (common in laptops outside their usual networks). * See, for instance, AstroGrid bugzilla tickets * 1799, * 2151. * *
In JSAMP version 0.3-1 and prior versions, the property was
* named samp.localhost
rather than
* jsamp.localhost
. This name is still accepted for
* backwards compatibility.
*
* @return local host name
*/
public static String getLocalhost() {
final String defaultHost = "127.0.0.1";
String hostname =
System.getProperty( LOCALHOST_PROP,
System.getProperty( "samp.localhost", "" ) );
if ( hostname.length() == 0 ) {
hostname = defaultHost;
}
else if ( "[hostname]".equals( hostname ) ) {
try {
hostname = InetAddress.getLocalHost().getCanonicalHostName();
}
catch ( UnknownHostException e ) {
logger_.log( Level.WARNING,
"Local host determination failed - fall back to "
+ defaultHost, e );
hostname = defaultHost;
}
}
else if ( "[hostnumber]".equals( hostname ) ) {
try {
hostname = InetAddress.getLocalHost().getHostAddress();
}
catch ( UnknownHostException e ) {
logger_.log( Level.WARNING,
"Local host determination failed - fall back to "
+ defaultHost, e );
hostname = defaultHost;
}
}
logger_.config( "Local host is " + hostname );
return hostname;
}
/**
* Returns an unused port number on the local host.
*
* @param startPort suggested port number; may or may not be used
* @return unused port
*/
public static int getUnusedPort( int startPort ) throws IOException {
// Current implementation ignores the given startPort and uses
// findAnyPort.
return true ? findAnyPort()
: scanForPort( startPort, 20 );
}
/**
* Turns a File into a URL.
* Unlike Sun's J2SE, this gives you a URL which conforms to RFC1738 and
* looks like "file://localhost/abs-path
" rather than
* "file:abs-or-rel-path
".
* For non-ASCII characters, UTF-8 encoding is used.
*
* @param file file
* @return URL
* @see "RFC 1738"
* @see Sun Java bug 6356783
*/
public static URL fileToUrl( File file ) {
try {
String path = file.toURI().toURL().getPath();
StringBuffer pbuf = new StringBuffer();
for ( int ic = 0; ic < path.length(); ic++ ) {
char c = path.charAt( ic );
if ( c == '/' ) {
pbuf.append( c );
}
else {
pbuf.append( uriEncode( new String( new char[] { c } ) ) );
}
}
return new URL( "file", "localhost", pbuf.toString() );
}
catch ( MalformedURLException e ) {
throw new AssertionError();
}
}
/**
* Reverses URI-style character escaping (%xy) on a string.
* Note, unlike {@link java.net.URLDecoder},
* this does not turn "+" characters into spaces.
* For non-ASCII characters, UTF-8 encoding is used.
*
* @see "RFC 2396, Section 2.4"
* @param text escaped text
* @return unescaped text
*/
public static String uriDecode( String text ) {
try {
return URLDecoder.decode( replaceChar( text, '+', "%2B" ),
"UTF-8" );
}
catch ( UnsupportedEncodingException e ) {
throw new AssertionError( "UTF-8 unsupported??" );
}
}
/**
* Performs URI-style character escaping (%xy) on a string.
* Note, unlike {@link java.net.URLEncoder},
* this encodes spaces as "%20" and not "+".
* For non-ASCII characters, UTF-8 encoding is used.
*
* @see "RFC 2396, Section 2.4"
* @param text unescaped text
* @return escaped text
*/
public static String uriEncode( String text ) {
try {
return replaceChar( URLEncoder.encode( text, "UTF-8" ),
'+', "%20" );
}
catch ( UnsupportedEncodingException e ) {
throw new AssertionError( "UTF-8 unsupported??" );
}
}
/**
* Attempts to interpret a URL as a file.
* If the URL does not have the "file:" protocol, null is returned.
*
* @param url URL, may or may not be file: protocol
* @return file, or null
*/
public static File urlToFile( URL url ) {
if ( url.getProtocol().equals( "file" ) && url.getRef() == null
&& url.getQuery() == null ) {
String path = uriDecode( url.getPath() );
String filename = File.separatorChar == '/'
? path
: path.replace( '/', File.separatorChar );
return new File( filename );
}
else {
return null;
}
}
/**
* Parses JSON text to give a SAMP object.
* Note that double-quoted strings are the only legal scalars
* (no unquoted numbers or booleans).
*
* @param str string to parse
* @return SAMP object
*/
public static Object fromJson( String str ) {
return new JsonReader().read( str );
}
/**
* Serializes a SAMP object to a JSON string.
*
* @param item to serialize
* @param multiline true for formatted multiline output, false for a
* single line
*/
public static String toJson( Object item, boolean multiline ) {
checkObject( item );
return new JsonWriter( multiline ? 2 : -1, true ).toJson( item );
}
/**
* Returns a string giving the version of the SAMP standard which this
* software implements.
*
* @return SAMP standard version
*/
public static String getSampVersion() {
if ( sampVersion_ == null ) {
sampVersion_ = readResource( "samp.version" );
}
return sampVersion_;
}
/**
* Returns a string giving the version of this software package.
*
* @return JSAMP version
*/
public static String getSoftwareVersion() {
if ( softwareVersion_ == null ) {
softwareVersion_ = readResource( "jsamp.version" );
}
return softwareVersion_;
}
/**
* Returns the contents of a resource as a string.
*
* @param rname resource name
* (in the sense of {@link java.lang.Class#getResource})
*/
private static String readResource( String rname ) {
URL url = SampUtils.class.getResource( rname );
if ( url == null ) {
logger_.warning( "No such resource " + rname );
return "??";
}
else {
try {
InputStream in = url.openStream();
StringBuffer sbuf = new StringBuffer();
for ( int c; ( c = in.read() ) >= 0; ) {
sbuf.append( (char) c );
}
in.close();
return sbuf.toString().trim();
}
catch ( IOException e ) {
logger_.warning( "Failed to read resource " + url );
return "??";
}
}
}
/**
* Returns the system-dependent line separator sequence.
*
* @return line separator
*/
private static String getLineSeparator() {
try {
return System.getProperty( "line.separator", "\n" );
}
catch ( SecurityException e ) {
return "\n";
}
}
/**
* Replaces all occurrences of a single character with a given replacement
* string.
*
* @param in input string
* @param oldChar character to replace
* @param newText replacement string
* @return modified string
*/
private static String replaceChar( String in, char oldChar,
String newTxt ) {
int len = in.length();
StringBuffer sbuf = new StringBuffer( len );
for ( int i = 0; i < len; i++ ) {
char c = in.charAt( i );
if ( c == oldChar ) {
sbuf.append( newTxt );
}
else {
sbuf.append( c );
}
}
return sbuf.toString();
}
/**
* Locates an unused server port on the local host.
* Potential problem: between when this method completes and when
* the return value of this method is used by its caller, it's possible
* that the port will get used by somebody else.
* Probably this will not happen much in practice??
*
* @return unused server port
*/
private static int findAnyPort() throws IOException {
ServerSocket socket = new ServerSocket( 0 );
try {
return socket.getLocalPort();
}
finally {
try {
socket.close();
}
catch ( IOException e ) {
}
}
}
/**
* Two problems with this one - it may be a bit inefficient, and
* there's an annoying bug in the Apache XML-RPC WebServer class
* which causes it to print "java.util.NoSuchElementException" to
* the server's System.err for every port scanned by this routine
* that an org.apache.xmlrpc.WebServer server is listening on.
*
* @param startPort port to start scanning upwards from
* @param nTry number of ports in sequence to try before admitting defeat
* @return unused server port
*/
private static int scanForPort( int startPort, int nTry )
throws IOException {
for ( int iPort = startPort; iPort < startPort + nTry; iPort++ ) {
try {
Socket trySocket = new Socket( "localhost", iPort );
if ( ! trySocket.isClosed() ) {
trySocket.shutdownOutput();
trySocket.shutdownInput();
trySocket.close();
}
}
catch ( ConnectException e ) {
/* Can't connect - this hopefully means that the socket is
* unused. */
return iPort;
}
}
throw new IOException( "Can't locate an unused port in range " +
startPort + " ... " + ( startPort + nTry ) );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/ShutdownManager.java 0000664 0000000 0000000 00000011712 13564500043 0025154 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.Arrays;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handles ordered running of cleanup processes at JVM shutdown.
* This is a singleton class, use {@link #getInstance}.
*
* @author Sylvain Lafrasse
* @author Mark Taylor
* @since 12 Oct 2012
*/
public class ShutdownManager {
/** Shutdown sequence for preparatory hooks. */
public static final int PREPARE_SEQUENCE = 0;
/** Shutdown sequence value for client hooks. */
public static final int CLIENT_SEQUENCE = 100;
/** Shutdown sequence value for hub hooks. */
public static final int HUB_SEQUENCE = 200;
private static final ShutdownManager instance_ = new ShutdownManager();
private static final Logger logger_ =
Logger.getLogger( "org.astrogrid.samp" );
/** Maps Objects to Hooks. */
private final WeakHashMap hookMap_;
/**
* Private constructor prevents instantiation.
*/
private ShutdownManager() {
hookMap_ = new WeakHashMap();
try {
Runtime.getRuntime()
.addShutdownHook( new Thread( "SAMP Shutdown" ) {
public void run() {
doCleanup();
}
} );
}
catch ( SecurityException e ) {
logger_.log( Level.WARNING, "Can't add shutdown hook: " + e, e );
}
}
/**
* Register a runnable to be run on shutdown with a given key and sequence.
* Items with a smaller value of iseq
* are run earlier at shutdown.
* Suitable sequence values are given by {@link #HUB_SEQUENCE} and
* {@link #CLIENT_SEQUENCE}.
* The key
is kept in a WeakHashMap, so if it is GC'd,
* the runnable will never execute.
*
* @param key key which can be used to unregister the hook later
* @param iseq value indicating position in shutdown sequence
* @param runnable to be run on shutdown
*/
public synchronized void registerHook( Object key, int iseq,
Runnable runnable ) {
hookMap_.put( key, new Hook( runnable, iseq ) );
}
/**
* Unregisters a key earlier registered using {@link #registerHook}.
*
* @param key registration key
*/
public synchronized void unregisterHook( Object key ) {
hookMap_.remove( key );
}
/**
* Invoked on shutdown by runtime.
*/
private void doCleanup() {
Hook[] hooks;
synchronized ( this ) {
hooks = (Hook[]) hookMap_.values().toArray( new Hook[ 0 ] );
}
Arrays.sort( hooks );
logger_.info( "SAMP shutdown start" );
for ( int ih = 0; ih < hooks.length; ih++ ) {
try {
hooks[ ih ].runnable_.run();
}
catch ( RuntimeException e ) {
forceLog( logger_, Level.WARNING, "Shutdown hook failure: " + e,
e );
}
}
logger_.info( "SAMP shutdown end" );
}
/**
* Returns sole instance of this class.
*
* @return instance
*/
public static ShutdownManager getInstance() {
return instance_;
}
/**
* Writes a log-like message directly to standard error if it has
* an appropriate level.
* This method is only intended for use during the shutdown process,
* when the logging system may be turned off so that normal logging
* calls may get ignored (this behaviour is not as far as I know
* documented, but seems reliable in for example Oracle JRE1.5).
* There may be some good reason for logging services to be withdrawn
* during shutdown, so it's not clear that using this method is
* a good idea at all even apart from bypassing the logging system;
* therefore use it sparingly.
*
* @param logger logger
* @param level level of message to log
* @param msg text of logging message
* @param error associated throwable if any; may be null
*/
public static void forceLog( Logger logger, Level level, String msg,
Throwable error ) {
if ( logger.isLoggable( level ) ) {
System.err.println( level + ": " + msg );
if ( error != null ) {
error.printStackTrace( System.err );
}
}
}
/**
* Aggregates a runnable and an associated sequence value.
*/
private static class Hook implements Comparable {
final Runnable runnable_;
final int iseq_;
/**
* Constructor.
*
* @param runnable runnable
* @param iseq sequence value
*/
Hook( Runnable runnable, int iseq ) {
runnable_ = runnable;
iseq_ = iseq;
}
public int compareTo( Object other ) {
return this.iseq_ - ((Hook) other).iseq_;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Subscriptions.java 0000664 0000000 0000000 00000013312 13564500043 0024713 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents the set of subscribed messages for a SAMP client.
* This has the form of a Map in which each key is an MType (perhaps
* wildcarded) and the corresponding values are maps with keys which are
* so far undefined (thus typically empty).
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class Subscriptions extends SampMap {
private static final String ATOM_REGEX = "[0-9a-zA-Z\\-_]+";
private static String MTYPE_REGEX =
"(" + ATOM_REGEX + "\\.)*" + ATOM_REGEX;
private static String MSUB_REGEX =
"(" + MTYPE_REGEX + "|" + MTYPE_REGEX + "\\.\\*" + "|" + "\\*" + ")";
private static final Pattern MSUB_PATTERN = Pattern.compile( MSUB_REGEX );
/**
* Constructs an empty subscriptions object.
*/
public Subscriptions() {
super( new String[ 0 ] );
}
/**
* Constructs a subscriptions object based on an existing map.
*
* @param map map containing initial data for this object
*/
public Subscriptions( Map map ) {
this();
putAll( map );
}
/**
* Adds a subscription to a given MType. mtype
may include
* a wildcard according to the SAMP rules.
*
* @param mtype subscribed MType, possibly wildcarded
*/
public void addMType( String mtype ) {
put( mtype, new HashMap() );
}
/**
* Determines whether a given (non-wildcarded) MType is subscribed to
* by this object.
*
* @param mtype MType to test
*/
public boolean isSubscribed( String mtype ) {
if ( containsKey( mtype ) ) {
return true;
}
for ( Iterator it = keySet().iterator(); it.hasNext(); ) {
if ( matchLevel( (String) it.next(), mtype ) >= 0 ) {
return true;
}
}
return false;
}
/**
* Returns the map which forms the value for a given MType key.
* If a wildcarded subscription is recorded which matches
* mtype
, the corresponding value is returned.
* If mtype
is not subscribed to, null
* is returned.
*
* @param mtype MType to query
* @return map value corresponding to mtype
, or null
*/
public Map getSubscription( String mtype ) {
if ( containsKey( mtype ) ) {
Object value = get( mtype );
return value instanceof Map ? (Map) value
: (Map) new HashMap();
}
else {
int bestLevel = -1;
Map bestValue = null;
for ( Iterator it = entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
int level = matchLevel( (String) entry.getKey(), mtype );
if ( level > bestLevel ) {
bestLevel = level;
Object value = entry.getValue();
bestValue = value instanceof Map ? (Map) value
: (Map) new HashMap();
}
}
return bestValue;
}
}
public void check() {
super.check();
for ( Iterator it = entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
if ( ! MSUB_PATTERN.matcher( key ).matches() ) {
throw new DataException( "Illegal subscription key "
+ "\"" + key + "\"" );
}
if ( ! ( value instanceof Map ) ) {
throw new DataException( "Subscription values "
+ "are not all maps" );
}
}
}
/**
* Returns a given map in the form of a Subscriptions object.
*
* @param map map
* @return subscriptions
*/
public static Subscriptions asSubscriptions( Map map ) {
return ( map instanceof Subscriptions || map == null )
? (Subscriptions) map
: new Subscriptions( map );
}
/**
* Performs wildcard matching of MTypes. The result is the number of
* dot-separated "atoms" which match between the two.
*
* @param pattern MType pattern; may contain a wildcard
* @param mtype unwildcarded MType for comparison with
* pattern
* @return the number of atoms of pattern
which match
* mtype
; if pattern
="*" the result is
* 0, and if there is no match the result is -1
*/
public static int matchLevel( String pattern, String mtype ) {
if ( mtype.equals( pattern ) ) {
return countAtoms( pattern );
}
else if ( "*".equals( pattern ) ) {
return 0;
}
else if ( pattern.endsWith( ".*" ) ) {
String prefix = pattern.substring( 0, pattern.length() - 2 );
return mtype.startsWith( prefix ) ? countAtoms( prefix )
: -1;
}
else {
return -1;
}
}
/**
* Counts the number of dot-separated "atoms" in a string.
*
* @param text string to test
*/
private static int countAtoms( String text ) {
int leng = text.length();
int natom = 1;
for ( int i = 0; i < leng; i++ ) {
if ( text.charAt( i ) == '.' ) {
natom++;
}
}
return natom;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/ 0000775 0000000 0000000 00000000000 13564500043 0022435 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/Bridge.java 0000664 0000000 0000000 00000044101 13564500043 0024474 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.bridge;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.UtilServer;
import org.astrogrid.samp.xmlrpc.LockInfo;
import org.astrogrid.samp.xmlrpc.StandardClientProfile;
import org.astrogrid.samp.xmlrpc.XmlRpcKit;
/**
* Runs a bridging service between two or more hubs.
* For each client on one hub, a proxy client appears on all other
* participating hubs. These proxies can be treated in exactly the
* same way as normal clients by other registered clients; any
* messages sent to/from them will be marshalled over the bridge
* in a transparent way. One application for this is to allow
* collaboration between users who each have their own hub running.
*
*
A {@link java.lang.Object#notifyAll notifyAll} call is made on
* the Bridge object whenever the number of live hubs connected by
* the bridge changes.
*
* @author Mark Taylor
* @since 15 Jul 2009
*/
public class Bridge {
private final ProxyManager[] proxyManagers_;
private static final Logger logger_ =
Logger.getLogger( Bridge.class.getName() );
/**
* Constructor.
*
* @param profiles array of SAMP profile objects, one for each
* hub which is to participate in the bridge
*/
public Bridge( ClientProfile[] profiles ) throws IOException {
int nhub = profiles.length;
proxyManagers_ = new ProxyManager[ nhub ];
UtilServer server = UtilServer.getInstance();
for ( int ih = 0; ih < nhub; ih++ ) {
proxyManagers_[ ih ] = new ProxyManager( profiles[ ih ], server ) {
protected void managerConnectionChanged( boolean isConnected ) {
super.managerConnectionChanged( isConnected );
synchronized ( Bridge.this ) {
Bridge.this.notifyAll();
}
}
};
}
for ( int ih = 0; ih < nhub; ih++ ) {
proxyManagers_[ ih ].init( proxyManagers_ );
}
for ( int ih = 0; ih < nhub; ih++ ) {
proxyManagers_[ ih ].getManagerConnector().setAutoconnect( 0 );
}
}
/**
* Returns the client profiles which define the hubs this bridge links.
*
* @return profile array, one for each connected hub
*/
public ClientProfile[] getProfiles() {
int nhub = proxyManagers_.length;
ClientProfile[] profiles = new ClientProfile[ nhub ];
for ( int ih = 0; ih < nhub; ih++ ) {
profiles[ ih ] = proxyManagers_[ ih ].getProfile();
}
return profiles;
}
/**
* Returns the hub connectors representing the bridge client running
* on each linked hub. Note this does not include any proxy clients,
* only the one-per-hub manager clients.
*
* @return array of bridge manager clients, one for each hub
* (in corresponding positions to the profiles)
*/
public HubConnector[] getBridgeClients() {
int nhub = proxyManagers_.length;
HubConnector[] connectors = new HubConnector[ nhub ];
for ( int ih = 0; ih < nhub; ih++ ) {
connectors[ ih ] = proxyManagers_[ ih ].getManagerConnector();
}
return connectors;
}
/**
* Sets up a URL exporter for one of the hubs. This will attempt to
* edit transmitted data contents for use in remote contexts;
* the main job is to adjust loopback host references in URLs
* (127.0.0.1 or localhost) to become fully qualified domain names
* for non-local use. It's not an exact science, but a best effort
* is made.
*
* @param index index of the profile for which to export URLs
* @param host the name substitute for loopback host identifiers
* on the host on which that profile's hub is running
*/
public void exportUrls( int index, String host ) {
proxyManagers_[ index ]
.setExporter( new UrlExporter( host, isLocalHost( host ) ) );
}
/**
* Starts this bridge running.
*
* @return true iff all the participating hubs have been contacted
* successfully
*/
public boolean start() {
HubConnector[] connectors = getBridgeClients();
boolean allConnected = true;
for ( int ih = 0; ih < connectors.length; ih++ ) {
HubConnector connector = connectors[ ih ];
connector.setActive( true );
allConnected = allConnected && connector.isConnected();
}
return allConnected;
}
/**
* Stops this bridge running.
* All associated manager and proxy clients are unregistered.
*/
public void stop() {
HubConnector[] connectors = getBridgeClients();
for ( int ih = 0; ih < connectors.length; ih++ ) {
connectors[ ih ].setActive( false );
}
}
/**
* Returns the number of hubs currently connected by this bridge.
* Only connections which are currently live are counted.
*
* @return number of live hubs
*/
private int getConnectionCount() {
HubConnector[] connectors = getBridgeClients();
int nc = 0;
for ( int ih = 0; ih < connectors.length; ih++ ) {
if ( connectors[ ih ].isConnected() ) {
nc++;
}
}
return nc;
}
/**
* Indicates whether a given hostname corresponds to the local host.
*
* @param host hostname to test
* @return true if This is not an exact science; a best effort is made.
*
* @author Mark Taylor
* @since 29 Jul 2009
*/
class UrlExporter {
private final String host_;
private final boolean exportFiles_;
private static final Logger logger_ =
Logger.getLogger( UrlExporter.class.getName() );
private static final Pattern LOCALHOST_REGEX =
Pattern.compile( "(http://|ftp://)"
+ "(127\\.0\\.0\\.1|localhost)"
+ "([:/].*)" );
private static final Pattern FILE_REGEX =
Pattern.compile( "(file://)"
+ "([^/]*)"
+ "/.*" );
/**
* Constructor.
*
* @param host public name of the host to which loopback addresses
* refer
* @param exportFiles whether to export file-protocol URLs
* by turning them into http ones;
* this only makes sense if the current JVM
* is running on a machine which can see
* The default implementation calls
* {@link Response#createSuccessResponse}(processOutput),
* first transforming a null value to an empty map for convenience.
* However, it may be overridden for more flexibility (for instance
* in order to return non-OK responses).
*
* @param processOutput a Map returned by {@link #processCall processCall}
*/
protected Response createResponse( Map processOutput ) {
Map result = processOutput == null ? SampMap.EMPTY : processOutput;
return Response.createSuccessResponse( result );
}
/**
* Sets the subscriptions map. Usually this is called by the constructor,
* but it may be reset manually.
*
* @param subscriptions {@link org.astrogrid.samp.Subscriptions}-like map
* defining which MTypes this handler can process
*/
public void setSubscriptions( Map subscriptions ) {
Subscriptions subs = Subscriptions.asSubscriptions( subscriptions );
subs.check();
subscriptions_ = subs;
}
public Map getSubscriptions() {
return subscriptions_;
}
/**
* Calls {@link #processCall} and discards the result.
*/
public void receiveNotification( HubConnection connection, String senderId,
Message message ) {
try {
processCall( connection, senderId, message );
}
catch ( Throwable e ) {
logger_.log( Level.INFO,
"Error processing notification " + message.getMType()
+ " - ignored", e );
}
}
/**
* Calls {@link #processCall}, generates a response from the result
* using {@link #createResponse}, and sends the resulting response
* as a reply to the hub. In case of an exception, a suitable error
* response is sent instead.
*/
public void receiveCall( HubConnection connection, String senderId,
String msgId, Message message )
throws SampException {
Response response;
try {
response =
createResponse( processCall( connection, senderId, message ) );
}
catch ( Throwable e ) {
response = Response.createErrorResponse( new ErrInfo( e ) );
}
connection.reply( msgId, response );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/CallableClient.java 0000664 0000000 0000000 00000002617 13564500043 0026166 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.Map;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
/**
* Defines callbacks which the hub can make on a callable client.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
public interface CallableClient {
/**
* Receives a message for which no response is required.
*
* @param senderId public ID of sending client
* @param message message
*/
void receiveNotification( String senderId, Message message )
throws Exception;
/**
* Receives a message for which a response is required.
* The implementation must take care to call the hub's The usual way for a prospective SAMP client to obtain an instance of
* this class is by using {@link DefaultClientProfile#getProfile}.
*
* This interface is so-named partly for historical reasons;
* "HubConnectionFactory" might have been more appropriate.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public interface ClientProfile {
/**
* Attempts to register with a SAMP hub and return a corresponding
* connection object. Some profile-specific hub discovery mechanism
* is used to locate the hub.
* If no hub is running, null will normally be returned.
*
* @return hub connection representing a new registration, or null
* @throws SampException in case of some unexpected error
*/
HubConnection register() throws SampException;
/**
* Indicates whether a hub contactable by this profile appears to be
* running. This is intended to execute reasonably quickly.
* It should not go as far as registering.
*
* @return true iff it looks like a hub is running
*/
boolean isHubRunning();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ClientTracker.java 0000664 0000000 0000000 00000040353 13564500043 0026061 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.Subscriptions;
/**
* Message handler which watches hub event messages to keep track of
* what clients are currently registered and what their attributes are
* on behalf of the hub.
* The results are stored in an externally supplied {@link TrackedClientSet}
* object. This class tries its best to handle complications arising
* from the fact that calls concerning a client may arrive out of order
* (for instance declareMetadata before registration or after unregistration).
*
* @author Mark Taylor
* @author Laurent Bourges
* @since 16 Jul 2008
*/
class ClientTracker extends AbstractMessageHandler {
private final TrackedClientSet clientSet_;
private final Map clientMap_;
private final OperationQueue opQueue_;
private final static Logger logger_ =
Logger.getLogger( ClientTracker.class.getName() );
private static final int QUEUE_TIME = 10000;
private static final String REGISTER_MTYPE;
private static final String UNREGISTER_MTYPE;
private static final String METADATA_MTYPE;
private static final String SUBSCRIPTIONS_MTYPE;
private static final String[] TRACKED_MTYPES = new String[] {
REGISTER_MTYPE = "samp.hub.event.register",
UNREGISTER_MTYPE = "samp.hub.event.unregister",
METADATA_MTYPE = "samp.hub.event.metadata",
SUBSCRIPTIONS_MTYPE = "samp.hub.event.subscriptions",
};
/**
* Constructor.
*
* @param clientSet object used to record registered clients and their
* attributes
*/
public ClientTracker( TrackedClientSet clientSet ) {
super( TRACKED_MTYPES );
clientSet_ = clientSet;
clientMap_ = clientSet.getClientMap();
opQueue_ = new OperationQueue();
}
/**
* Removes all clients from the list.
*/
public void clear() {
try {
initialise( null );
}
catch ( SampException e ) {
assert false;
}
}
/**
* Initialises this tracker from a hub connection.
* It is interrogated to find the current list of registered clients
* and their attributes.
*
* @param connection hub connection; may be null for no connection
*/
public void initialise( HubConnection connection ) throws SampException {
String[] clientIds;
// If connection is null, there are no registered clients.
if ( connection == null ) {
clientIds = new String[ 0 ];
}
// If connection is live, get the list of other registered clients,
// and don't forget to add an entry for self, which
// getRegisteredClients() excludes.
else {
String[] otherIds = connection.getRegisteredClients();
clientIds = new String[ otherIds.length + 1 ];
System.arraycopy( otherIds, 0, clientIds, 0, otherIds.length );
clientIds[ otherIds.length ] = connection.getRegInfo().getSelfId();
}
// Prepare an array of client objects, populating their characteristics
// by interrogating the connection.
int nc = clientIds.length;
TrackedClient[] clients = new TrackedClient[ nc ];
for ( int ic = 0; ic < nc; ic++ ) {
String id = clientIds[ ic ];
TrackedClient client = new TrackedClient( id );
client.setMetadata( connection.getMetadata( id ) );
client.setSubscriptions( connection.getSubscriptions( id ) );
clients[ ic ] = client;
}
// Populate the client set. Discard any queued operations first.
// This doesn't guarantee that we've got the most up to date
// information ... but in absence of guaranteed delivery order for
// messages that's more or less impossible.
synchronized ( opQueue_ ) {
ClientOperation[] pendingOps = opQueue_.getOperations();
opQueue_.clear();
clientSet_.setClients( clients );
}
}
public Map processCall( HubConnection connection, String senderId,
Message message ) {
String mtype = message.getMType();
if ( ! senderId.equals( connection.getRegInfo().getHubId() ) ) {
logger_.warning( "Hub admin message " + mtype + " received from "
+ "non-hub client. Acting on it anyhow" );
}
String id = (String) message.getParams().get( "id" );
if ( id == null ) {
throw new IllegalArgumentException( "id parameter missing in "
+ mtype );
}
String selfId = connection.getRegInfo().getSelfId();
if ( REGISTER_MTYPE.equals( mtype ) ) {
TrackedClient client = new TrackedClient( id );
opQueue_.apply( client );
clientSet_.addClient( client );
}
else if ( UNREGISTER_MTYPE.equals( mtype ) ) {
performClientOperation( new ClientOperation( id, mtype ) {
public void perform( TrackedClient client ) {
opQueue_.discard( client );
clientSet_.removeClient( client );
}
}, connection );
}
else if ( METADATA_MTYPE.equals( mtype ) ) {
final Map meta = (Map) message.getParams().get( "metadata" );
performClientOperation( new ClientOperation( id, mtype ) {
public void perform( TrackedClient client ) {
client.setMetadata( meta );
clientSet_.updateClient( client, true, false );
}
}, connection );
}
else if ( SUBSCRIPTIONS_MTYPE.equals( mtype ) ) {
final Map subs = (Map) message.getParams().get( "subscriptions" );
performClientOperation( new ClientOperation( id, mtype ) {
public void perform( TrackedClient client ) {
client.setSubscriptions( subs );
clientSet_.updateClient( client, false, true );
}
}, connection );
}
else {
throw new IllegalArgumentException( "Shouldn't have received MType"
+ mtype );
}
return null;
}
/**
* Performs an operation on a ClientOperation object.
*
* @param op client operation
* @param connection hub connection
*/
private void performClientOperation( ClientOperation op,
HubConnection connection ) {
String id = op.getId();
// If the client is currently part of this tracker's data model,
// we can peform the operation directly.
TrackedClient client = (TrackedClient) clientMap_.get( id );
if ( client != null ) {
op.perform( client );
}
// If it's not, but it applies to this client itself, it's just
// because we haven't added ourself to the client list yet.
// Queue it.
else if ( id.equals( connection.getRegInfo().getSelfId() ) ) {
opQueue_.add( op );
}
// Otherwise, the client is not yet known. This is most likely
// because, in absence of any guarantee about message delivery order
// within SAMP, a message which was sent between its registration
// and its unregistration might still arrive either before its
// registration event has arrived or after its unregistration event
// has arrived. In the hope that it is the former, we hang on to
// this operation so that it can be peformed at some future date
// when we actually have a client object we can apply it to.
else {
// If it's for this client, this is just because it hasn't added
// itself to the client list yet. Should get resolved very soon.
if ( id.equals( connection.getRegInfo().getSelfId() ) ) {
logger_.info( "Message " + op.getMType() + " arrived for self"
+ " - holding till later" );
}
// Otherwise less certain, but we still hope.
else {
logger_.info( "No known client " + id + " for message "
+ op.getMType() + " - holding till later" );
}
// Either way, queue it.
opQueue_.add( op );
}
}
/**
* Client implementation used to populate internal data structures.
* It just implements the Client interface as well as adding mutators
* for metadata and subscriptions, and providing an equals method based
* on public id.
*/
private static class TrackedClient implements Client {
private final String id_;
private Metadata metadata_;
private Subscriptions subscriptions_;
/**
* Constructor.
*
* @param id client public id
*/
public TrackedClient( String id ) {
id_ = id;
}
public String getId() {
return id_;
}
public Metadata getMetadata() {
return metadata_;
}
public Subscriptions getSubscriptions() {
return subscriptions_;
}
/**
* Sets this client's metadata.
*
* @param metadata new metadata
*/
void setMetadata( Map metadata ) {
metadata_ = Metadata.asMetadata( metadata );
}
/**
* Sets this client's subscriptions.
*
* @param subscriptions new subscriptions
*/
void setSubscriptions( Map subscriptions ) {
subscriptions_ = Subscriptions.asSubscriptions( subscriptions );
}
public boolean equals( Object o ) {
if ( o instanceof TrackedClient ) {
TrackedClient other = (TrackedClient) o;
return other.id_.equals( this.id_ );
}
else {
return false;
}
}
public int hashCode() {
return id_.hashCode();
}
public String toString() {
return SampUtils.toString( this );
}
}
/**
* Describes an operation to be performed on a TrackedClient object
* which is already part of this tracker's model.
*/
private static abstract class ClientOperation {
private final String id_;
private final String mtype_;
private final long birthday_;
/**
* Constructor.
*
* @param id client public ID
* @param mtype MType of the message which triggered this operation
*/
ClientOperation( String id, String mtype ) {
id_ = id;
mtype_ = mtype;
birthday_ = System.currentTimeMillis();
}
/**
* Performs the instance-specific operation on a given client.
*
* @param client client
*/
public abstract void perform( TrackedClient client );
/**
* Returns the client ID for the client this operation applies to.
*
* @return client public ID
*/
public String getId() {
return id_;
}
/**
* Returns the MType of the message which triggered this operation.
*
* @return message MType
*/
public String getMType() {
return mtype_;
}
/**
* Returns the creation time of this object.
*
* @return The profile returned by this class depends on the SAMP_HUB environment
* variable ({@link #HUBLOC_ENV}).
* If it consists of the prefix " If no instance has been set, the SAMP_HUB environment variable
* is examined. If it consists of the prefix " The instance is obtained lazily.
*
* @return client profile instance
*/
public static ClientProfile getProfile() {
if ( profile_ == null ) {
final ClientProfile profile;
String hubloc = Platform.getPlatform().getEnv( HUBLOC_ENV );
if ( hubloc == null || hubloc.trim().length() == 0 ) {
profile = StandardClientProfile.getInstance();
}
else if ( hubloc.startsWith( HUBLOC_CLASS_PREFIX ) ) {
String cname = hubloc.substring( HUBLOC_CLASS_PREFIX.length() );
final Class clazz;
try {
clazz = Class.forName( cname );
}
catch ( ClassNotFoundException e ) {
throw (IllegalArgumentException)
new IllegalArgumentException( "No profile class "
+ cname )
.initCause( e );
}
try {
profile = (ClientProfile) clazz.newInstance();
logger_.info( "Using non-standard hub location: "
+ HUBLOC_ENV + "=" + hubloc );
}
catch ( Throwable e ) {
throw (RuntimeException)
new RuntimeException( "Error instantiating custom "
+ "profile " + clazz.getName() )
.initCause( e );
}
}
else if ( hubloc.startsWith( StandardClientProfile
.STDPROFILE_HUB_PREFIX ) ) {
profile = StandardClientProfile.getInstance();
}
else if ( hubloc.startsWith( WebClientProfile
.WEBPROFILE_HUB_PREFIX ) ) {
profile = WebClientProfile.getInstance();
}
else {
throw new RuntimeException( "Can't make sense of " + HUBLOC_ENV
+ "=" + hubloc );
}
profile_ = profile;
}
return profile_;
}
/**
* Sets the profile object which will be returned by {@link #getProfile}.
*
* @param profile default profile instance
*/
public static void setProfile( ClientProfile profile ) {
profile_ = profile;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/HubConnection.java 0000664 0000000 0000000 00000013413 13564500043 0026062 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
/**
* Represents a registered client's connection to a running hub.
* An application typically obtains an instance of this class
* from a {@link ClientProfile} object.
*
* It is good practice to call {@link #unregister} when the connection
* is finished with; however if it is not called explicitly, the
* connection will unregister itself on object finalisation or JVM termination,
* as long as the JVM shuts down cleanly.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public interface HubConnection {
/**
* Returns the registration information associated with this connection.
*
* @return registration info
*/
RegInfo getRegInfo();
/**
* Tells the hub how it can perform callbacks on the client by providing
* a CallableClient object. This is required before the client
* can declare subscriptions or make asynchronous calls.
*
* @param callable callable client
*/
void setCallable( CallableClient callable ) throws SampException;
/**
* Tests whether the connection is currently open.
*
* @throws SampException if the hub has disappeared or communications
* are disrupted in some other way
*/
void ping() throws SampException;
/**
* Unregisters the client and terminates this connection.
*/
void unregister() throws SampException;
/**
* Declares this registered client's metadata.
*
* @param meta {@link org.astrogrid.samp.Metadata}-like map
*/
void declareMetadata( Map meta ) throws SampException;
/**
* Returns the metadata for another registered client.
*
* @param clientId public id for another registered client
* @return metadata map
*/
Metadata getMetadata( String clientId ) throws SampException;
/**
* Declares this registered client's MType subscriptions.
*
* Only permitted if this client is already callable.
*
* @param subs {@link org.astrogrid.samp.Subscriptions}-like map
*/
void declareSubscriptions( Map subs ) throws SampException;
/**
* Returns the subscriptions for another registered client.
*
* @param clientId public id for another registered client
* @return subscriptions map
*/
Subscriptions getSubscriptions( String clientId ) throws SampException;
/**
* Returns the list of client public IDs for those clients currently
* registered.
*
* @return array of client ids, excluding the one for this client
*/
String[] getRegisteredClients() throws SampException;
/**
* Returns a map of subscriptions for a given MType.
*
* @param mtype MType
* @return map in which the keys are the public IDs of clients subscribed
* to Only permitted if this client is already callable.
*
* @param recipientId public-id of client to receive message
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return message ID
*/
String call( String recipientId, String msgTag, Map msg )
throws SampException;
/**
* Sends a message to all subscribed clients expecting responses.
* The Only permitted if this client is already callable.
*
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return public-id->msg-id map for clients to which an attempt to
* send the call will be made
*/
Map callAll( String msgTag, Map msg ) throws SampException;
/**
* Sends a message synchronously to a client, waiting for the response.
* If more seconds elapse than the value of the This object provides a {@link #getConnection} method which provides
* the currently active {@link HubConnection} object if one exists or can be
* acquired. The It is good practice to call {@link #setActive setActive(false)}
* when this object is finished with; however if it is not called
* explicitly, any open connection will unregister itself on
* object finalisation or JVM termination, as long as the JVM shuts
* down cleanly.
*
* A real example, including use of the GUI hooks, can be found in the
* {@link org.astrogrid.samp.gui.HubMonitor} client source code.
*
* Note that this call must be made, with a subscription list
* which includes the various hub administrative messages, in order
* for this connector to act on those messages (for instance to
* update its client map and so on). For this reason, it is usual
* to call it with the Note however that this class's {@link #callAndWait callAndWait} method
* can provide a synchronous facade for fully asynchronous messaging,
* which in many cases will be more convenient than installing your
* own response handlers to deal with asynchronous replies.
*
* @param handler handler to add
*/
public void addResponseHandler( ResponseHandler handler ) {
responseHandlerList_.add( handler );
}
/**
* Removes a ResponseHandler from this connector.
*
* @param handler handler to remove
*/
public void removeResponseHandler( ResponseHandler handler ) {
responseHandlerList_.remove( handler );
}
/**
* Sets whether this connector is active or not.
* If set false, any existing connection will be terminated (the client
* will unregister) and autoconnection attempts will be suspended.
* If set true, if there is no existing connection an attempt will be
* made to register, and autoconnection attempts will begin if applicable.
*
* @param active whether this connector should be active
* @see #setAutoconnect
*/
public void setActive( boolean active ) {
isActive_ = active;
if ( active ) {
if ( connection_ == null ) {
try {
getConnection();
}
catch ( SampException e ) {
logger_.log( Level.WARNING,
"Hub connection attempt failed", e );
}
}
configureRegisterTimer( autoSec_ );
}
else {
HubConnection connection = connection_;
if ( connection != null ) {
disconnect();
try {
connection.unregister();
}
catch ( SampException e ) {
logger_.log( Level.INFO, "Unregister attempt failed", e );
}
}
configureRegisterTimer( 0 );
}
}
/**
* Indicates whether this connector is active or not.
*
* @return true if this connector is willing to connect
*/
public boolean isActive() {
return isActive_;
}
/**
* Sends a message synchronously to a client, waiting for the response.
* If more seconds elapse than the value of the The semantics of this call are, as far as the caller is concerned,
* identical to that of the similarly named {@link HubConnection} method.
* However, in this case the client communicates with the hub
* asynchronously and internally simulates the synchrony for the caller,
* rather than letting the hub do that.
* This is more robust and almost certainly a better idea.
*
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param timeout timeout in seconds, or <=0 for no timeout
* @return response
*/
public Response callAndWait( String recipientId, Map msg, int timeout )
throws SampException {
long finish = timeout > 0
? System.currentTimeMillis() + timeout * 1000
: Long.MAX_VALUE; // 3e8 years
HubConnection connection = getConnection();
String msgTag = createTag( this );
responseMap_.put( msgTag, null );
connection.call( recipientId, msgTag, msg );
synchronized ( responseMap_ ) {
while ( responseMap_.containsKey( msgTag ) &&
responseMap_.get( msgTag ) == null &&
System.currentTimeMillis() < finish ) {
long millis = finish - System.currentTimeMillis();
if ( millis > 0 ) {
try {
responseMap_.wait( millis );
}
catch ( InterruptedException e ) {
throw new SampException( "Wait interrupted", e );
}
}
}
if ( responseMap_.containsKey( msgTag ) ) {
Response response = (Response) responseMap_.remove( msgTag );
if ( response != null ) {
return response;
}
else {
assert System.currentTimeMillis() >= finish;
throw new SampException( "Synchronous call timeout" );
}
}
else {
if ( connection != connection_ ) {
throw new SampException( "Hub connection lost" );
}
else {
throw new AssertionError();
}
}
}
}
/**
* Sends a message asynchronously to a single client, making a callback
* on a supplied ResultHandler object when the result arrives.
* The {@link org.astrogrid.samp.client.ResultHandler#done} method will
* be called after the result has arrived or the timeout elapses,
* whichever happens first.
*
* This convenience method allows the user to make an asynchronous
* call without having to worry registering message handlers and
* matching message tags.
*
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param resultHandler object called back when response arrives or
* timeout is exceeded
* @param timeout timeout in seconds, or <=0 for no timeout
*/
public void call( String recipientId, Map msg, ResultHandler resultHandler,
int timeout ) throws SampException {
HubConnection connection = getConnection();
if ( connection == null ) {
throw new SampException( "Not connected" );
}
String tag = createTag( this );
callHandler_.registerHandler( tag, resultHandler, timeout );
try {
connection.call( recipientId, tag, msg );
callHandler_.setRecipients( tag, new String[] { recipientId, } );
}
catch ( SampException e ) {
callHandler_.unregisterHandler( tag );
throw e;
}
}
/**
* Sends a message asynchronously to all subscribed clients,
* making callbacks on a supplied ResultHandler object when the
* results arrive.
* The {@link org.astrogrid.samp.client.ResultHandler#done} method will
* be called after all the results have arrived or the timeout elapses,
* whichever happens first.
*
* This convenience method allows the user to make an asynchronous
* call without having to worry registering message handlers and
* matching message tags.
*
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param resultHandler object called back when response arrives or
* timeout is exceeded
* @param timeout timeout in seconds, or <=0 for no timeout
*/
public void callAll( Map msg, ResultHandler resultHandler, int timeout )
throws SampException {
HubConnection connection = getConnection();
if ( connection == null ) {
throw new SampException( "Not connected" );
}
String tag = createTag( this );
callHandler_.registerHandler( tag, resultHandler, timeout );
try {
Map callMap = connection.callAll( tag, msg );
callHandler_.setRecipients( tag,
(String[])
callMap.keySet()
.toArray( new String[ 0 ] ) );
}
catch ( SampException e ) {
callHandler_.unregisterHandler( tag );
throw e;
}
}
/**
* Indicates whether this connector is currently registered with a
* running hub.
* If true, the result of {@link #getConnection} will be non-null.
*
* @return true if currently connected to a hub
*/
public boolean isConnected() {
return connection_ != null;
}
/**
* If necessary attempts to acquire, and returns, a connection to a
* running hub.
* If there is an existing connection representing a registration
* with a hub, it is returned. If not, and this connector is active,
* an attempt is made to connect and register, followed by a call to
* {@link #configureConnection configureConnection}, is made.
*
* Note that if {@link #setActive setActive(false)} has been called,
* null will be returned.
*
* @return hub connection representing configured registration with a hub
* if a hub is running; if not, null
* @throws SampException in the case of some unexpected error
*/
public HubConnection getConnection() throws SampException {
HubConnection connection = connection_;
if ( connection == null && isActive_ ) {
connection = createConnection();
if ( connection != null ) {
connection_ = connection;
configureConnection( connection );
clientTracker_.initialise( connection );
connectionChanged( true );
}
}
return connection;
}
/**
* Configures a connection with a hub in accordance with the state of
* this object.
* The hub is made aware of how to perform callbacks on the registered
* client, and any current metadata and subscriptions are declared.
*
* @param connection connection representing registration with a hub
*/
public void configureConnection( HubConnection connection )
throws SampException {
if ( metadata_ != null ) {
connection.declareMetadata( metadata_ );
}
if ( callable_ != null ) {
connection.setCallable( callable_ );
callable_.setConnection( connection );
if ( subscriptions_ != null ) {
connection.declareSubscriptions( subscriptions_ );
}
}
}
/**
* Returns a map which keeps track of other clients currently registered
* with the hub to which this object is connected, including their
* currently declared metadata and subscriptions.
* Map keys are public IDs and values are
* {@link org.astrogrid.samp.Client}s.
*
* This map is {@link java.util.Collections#synchronizedMap synchronized}
* which means that to iterate over any of its views
* you must synchronize on it.
* When the map or any of its contents changes, it will receive a
* {@link java.lang.Object#notifyAll}.
*
* To keep itself up to date, the client map reads hub status messages.
* These will only be received if
* This map is {@link java.util.Collections#synchronizedMap synchronized}
* which means that to iterate over any of its views
* you must synchronize on it.
* When the map or any of its contents changes, it will receive a
* {@link java.lang.Object#notifyAll}.
*
* @return id -> Client map
*/
public Map getClientMap() {
return clientMapView_;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/package.html 0000664 0000000 0000000 00000000460 13564500043 0024740 0 ustar 00root root 0000000 0000000 Clients will normally use a {@link org.astrogrid.samp.client.HubConnector}
to keep track of connections with a SAMP hub.
However clients requiring a lower-level interface may simply use a
{@link org.astrogrid.samp.client.HubConnection} object.
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ 0000775 0000000 0000000 00000000000 13564500043 0021765 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/AbstractCallActionManager.java 0000664 0000000 0000000 00000035317 13564500043 0027631 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ListModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.LogResultHandler;
import org.astrogrid.samp.client.ResponseHandler;
import org.astrogrid.samp.client.ResultHandler;
/**
* Partial SendActionManager implementation which
* uses the Asynchronous Call/Response delivery pattern.
* It supplies most of the machinery required for tracking what happened
* to responses to messages sent at the same time, but does not
* implement the actual {@link #createBroadcastAction} method.
* Subclasses are provided which do this.
*
* @author Mark Taylor
* @since 11 Nov 2008
*/
public abstract class AbstractCallActionManager extends SendActionManager {
private final Component parent_;
private final GuiHubConnector connector_;
private final CallResponseHandler responder_;
private static final Logger logger_ =
Logger.getLogger( AbstractCallActionManager.class.getName() );
/**
* Constructor.
*
* @param parent parent component
* @param connector hub connector
* @param clientListModel list model containing only those clients
* which are suitable recipients;
* all elements must be {@link Client}s
*/
public AbstractCallActionManager( Component parent,
GuiHubConnector connector,
ListModel clientListModel ) {
super( connector, clientListModel );
parent_ = parent;
connector_ = connector;
responder_ = new CallResponseHandler();
connector_.addResponseHandler( responder_ );
connector_.addConnectionListener( responder_ );
updateState();
}
/**
* Must be implemented by concrete subclasses.
*/
abstract protected Action createBroadcastAction();
/**
* Returns an object which will be informed of the results of a single-
* or multiple-recipient send as they arrive.
* This method will be called from the event dispatch thread.
*
* The default implementation returns an instance of
* {@link org.astrogrid.samp.client.LogResultHandler}.
*
* @param connection connection object
* @param msg the message which was sent
* @param recipients the recipients to whom the message was sent
* @return result handler object
*/
protected ResultHandler createResultHandler( HubConnection connection,
Message msg,
Client[] recipients ) {
return new LogResultHandler( msg );
}
/**
* Releases resources associated with this object.
* Specifically, it removes listeners from the hub connector.
* Following a call to this method, this object should not be used again.
*/
public void dispose() {
connector_.removeResponseHandler( responder_ );
connector_.removeConnectionListener( responder_ );
}
/**
* Returns the Message object which is to be transmitted by this manager
* to a given client. This is called by the action returned by
* {@link #getSendAction}.
*
* @param client target
* @return message
*/
protected abstract Map createMessage( Client client ) throws Exception;
protected Action getSendAction( Client client ) {
return new SendAction( client );
}
/**
* Creates and returns a new tag which will be attached to
* an outgoing message, and updates internal structures so that
* it will be recognised in the future.
* A subsequent call to {@link #registerHandler} should be made for the
* returned tag.
*
* @return new tag
*/
public String createTag() {
return responder_.createTag();
}
/**
* Registers a result handler to handle results corresponding to a
* message tag.
*
* @param tag tag returned by an earlier invocation of
* {@link #createTag}
* @param recipients clients from which responses are expected
* @param handler result handler for responses; may be null
* if no handling is required
*/
public void registerHandler( String tag, Client[] recipients,
ResultHandler handler ) {
responder_.registerHandler( tag, recipients, handler );
}
/**
* Action which performs a send to a particular client.
*/
private class SendAction extends AbstractAction {
private final Client client_;
private final String cName_;
/**
* Constructor.
*
* @param client target client
*/
SendAction( Client client ) {
client_ = client;
cName_ = client.toString();
putValue( NAME, cName_ );
putValue( SHORT_DESCRIPTION,
"Transmit to " + cName_ + " using SAMP protocol" );
}
public void actionPerformed( ActionEvent evt ) {
boolean sent = false;
Message msg = null;
HubConnection connection = null;
String tag = null;
// Attempt to send the messsage.
try {
msg = Message.asMessage( createMessage( client_ ) );
msg.check();
connection = connector_.getConnection();
if ( connection != null ) {
tag = responder_.createTag();
connection.call( client_.getId(), tag, msg );
sent = true;
}
}
catch ( Exception e ) {
ErrorDialog.showError( parent_, "Send Error",
"Send failure " + e.getMessage(), e );
}
// If it was sent, arrange for the result to be processed by
// a suitable result handler.
if ( sent ) {
assert connection != null;
assert msg != null;
assert tag != null;
Client[] recipients = new Client[] { client_ };
ResultHandler handler =
createResultHandler( connection, msg, recipients );
responder_.registerHandler( tag, recipients, handler );
}
}
public boolean equals( Object o ) {
if ( o instanceof SendAction ) {
SendAction other = (SendAction) o;
return this.client_.equals( other.client_ )
&& this.cName_.equals( other.cName_ );
}
else {
return false;
}
}
public int hashCode() {
return client_.hashCode() * 23 + cName_.hashCode();
}
}
/**
* ResponseHandler implementation for use by this class.
* It handles all SAMP responses for calls which have been made by
* this object and passes them on to the appropriate ResultHandlers.
*/
private class CallResponseHandler implements ResponseHandler,
ChangeListener {
private int iCall_;
private final Map tagMap_;
/**
* Constructor.
*/
CallResponseHandler() {
tagMap_ = Collections.synchronizedMap( new HashMap() );
}
/**
* Creates and returns a new tag which will be attached to
* an outgoing message, and updates internal structures so that
* it will be recognised in the future.
* A subsequent call to {@link #registerHandler} should be made for the
* returned tag.
*
* @return new tag
*/
public synchronized String createTag() {
String tag = connector_.createTag( this );
tagMap_.put( tag, null );
return tag;
}
/**
* Registers a result handler to handle results corresponding to a
* message tag.
*
* @param tag tag returned by an earlier invocation of
* {@link #createTag}
* @param recipients clients from which responses are expected
* @param handler result handler for responses; may be null
* if no handling is required
*/
public void registerHandler( String tag, Client[] recipients,
ResultHandler handler ) {
synchronized ( tagMap_ ) {
if ( handler != null ) {
tagMap_.put( tag, new TagInfo( recipients, handler ) );
}
else {
tagMap_.remove( tag );
}
tagMap_.notifyAll();
}
}
public boolean ownsTag( String tag ) {
return tagMap_.containsKey( tag );
}
public void receiveResponse( HubConnection connection,
final String responderId, final String tag,
final Response response ) {
synchronized ( tagMap_ ) {
if ( tagMap_.containsKey( tag ) ) {
// If the result handler is already registered, pass the
// result on to it.
TagInfo info = (TagInfo) tagMap_.get( tag );
if ( info != null ) {
processResponse( tag, info, responderId, response );
}
// If the response was received very quickly, it's possible
// that the handler has not been registered yet.
// In this case, wait until it is.
// Do this in a separate thread so that the
// receiveResponse can return quickly (not essential, but
// good behaviour).
else {
new Thread( "TagWaiter-" + tag ) {
public void run() {
TagInfo tinfo;
try {
synchronized ( tagMap_ ) {
do {
tinfo =
(TagInfo) tagMap_.get( tag );
if ( tinfo == null ) {
tagMap_.wait();
}
} while ( tinfo == null );
}
processResponse( tag, tinfo, responderId,
response );
}
catch ( InterruptedException e ) {
logger_.warning( "Interrupted??" );
}
}
}.start();
}
}
// Shouldn't happen - HubConnector should not have invoked
// in this case.
else {
logger_.warning( "Receive response for unknown tag "
+ tag + "??" );
return;
}
}
}
/**
* Does the work of passing on a received response to a registered
* result handler.
*
* @param tag message tag
* @param info tag handling information object
* @param responderId client ID of responder
* @param response response object
*/
private void processResponse( String tag, TagInfo info,
String responderId, Response response ) {
ResultHandler handler = info.handler_;
Map recipientMap = info.recipientMap_;
synchronized ( info ) {
Client responder = (Client) recipientMap.remove( responderId );
// Pass response on to handler.
if ( responder != null ) {
handler.result( responder, response );
}
// If there are no more to come, notify the handler of this.
if ( recipientMap.isEmpty() ) {
handler.done();
}
}
// Unregister the handler if no more responses are expected for it.
synchronized ( tagMap_ ) {
if ( recipientMap.isEmpty() ) {
tagMap_.remove( tag );
}
}
}
public void stateChanged( ChangeEvent evt ) {
if ( ! connector_.isConnected() ) {
hubDisconnected();
}
}
/**
* Called when the connection to the hub disappears.
*/
private void hubDisconnected() {
synchronized ( tagMap_ ) {
// Notify all result handlers that they will receive no more
// responses, then unregister them all.
for ( Iterator it = tagMap_.entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String tag = (String) entry.getKey();
TagInfo info = (TagInfo) entry.getValue();
if ( info != null ) {
info.handler_.done();
}
it.remove();
}
}
}
}
/**
* Aggregates information required for handling responses which
* correspond to a particular message tag.
*/
private static class TagInfo {
final Map recipientMap_;
final ResultHandler handler_;
/**
* Constructor.
*
* @param recipients recipients of message
* @param handler handler for responses
*/
public TagInfo( Client[] recipients, ResultHandler handler ) {
recipientMap_ = Collections.synchronizedMap( new HashMap() );
for ( int i = 0; i < recipients.length; i++ ) {
Client recipient = recipients[ i ];
recipientMap_.put( recipient.getId(), recipient );
}
handler_ = handler;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/BrowserLauncher.java 0000664 0000000 0000000 00000067524 13564500043 0025753 0 ustar 00root root 0000000 0000000 // package edu.stanford.ejalbert;
//
// This class taken from http://browserlauncher.sourceforge.net/
// and renamed with a couple of tweaks for convenience.
// Original version is BrowserLauncher 1.4b1.
package org.astrogrid.samp.gui;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* BrowserLauncher is a class that provides one static method, openURL, which opens the default
* web browser for the current user of the system to the given URL. It may support other
* protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously
* tested and is not guaranteed to work.
*
* Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms
* that are not part of the standard JDK. What we're trying to do, though, is to take something
* that's frequently desirable but inherently platform-specific -- opening a default browser --
* and allow programmers (you, for example) to do so without worrying about dropping into native
* code or doing anything else similarly evil.
*
* Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without
* modification or a need for additional libraries. All classes that are required on certain
* platforms to allow this to run are dynamically loaded at runtime via reflection and, if not
* found, will not cause this to do anything other than returning an error when opening the
* browser.
*
* There are certain system requirements for this class, as it's running through Runtime.exec(),
* which is Java's way of making a native system call. Currently, this requires that a Macintosh
* have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that
* have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder
* in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and
* 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems
* (Windows 95, 98, and NT 4.0, as well as later versions of all). On other systems, this drops
* back from the inherently platform-sensitive concept of a default browser and simply attempts
* to launch Netscape via a shell command.
*
* This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) and may be
* redistributed or modified in any form without restrictions as long as the portion of this
* comment from this paragraph through the end of the comment is not removed. The author
* requests that he be notified of any application, applet, or other binary that makes use of
* this code, but that's more out of curiosity than anything and is not required. This software
* includes no warranty. The author is not repsonsible for any loss of data or functionality
* or any adverse or unexpected effects of using this software.
*
* Credits:
*
* Note that if this is Concrete subclasses must
* Note: concrete subclasses must call {@link #updateState} before use
* (in the constructor).
*
* @author Mark Taylor
* @since 2 Sep 2008
*/
public abstract class SendActionManager {
private final GuiHubConnector connector_;
final ListModel subscribedClientModel_;
private final List menuList_;
private final ListDataListener subscriptionListener_;
private final ChangeListener connectionListener_;
private boolean enabled_;
private Action broadcastAct_;
private boolean broadcastActCreated_;
private Action[] sendActs_;
private static Icon SEND_ICON;
private static Icon BROADCAST_ICON;
private static final Logger logger_ =
Logger.getLogger( SendActionManager.class.getName() );
/** ComboBox element indicating broadcast to all clients. */
public static final String BROADCAST_TARGET = "All Clients";
/**
* Constructor.
*
* @param connector hub connector
* @param clientListModel list model containing only those
* clients which are suitable recipients;
* all elements must be {@link org.astrogrid.samp.Client}s
*/
protected SendActionManager( GuiHubConnector connector,
ListModel clientListModel ) {
connector_ = connector;
subscribedClientModel_ = clientListModel;
subscriptionListener_ = new ListDataListener() {
public void intervalAdded( ListDataEvent evt ) {
updateState();
}
public void intervalRemoved( ListDataEvent evt ) {
updateState();
}
public void contentsChanged( ListDataEvent evt ) {
updateState();
}
};
subscribedClientModel_.addListDataListener( subscriptionListener_ );
// Ensure that changes to the connection status are reflected.
connectionListener_ = new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
updateEnabledness();
}
};
connector.addConnectionListener( connectionListener_ );
// Initialise other state.
enabled_ = true;
menuList_ = new ArrayList();
}
/**
* Returns a new action for broadcast associated with this object.
* The enabled status of the action will be managed by this object.
*
* @return broadcast action; may be null if broadcast is not required
*/
protected abstract Action createBroadcastAction();
/**
* Returns an action which can perform a single-client send associated
* with this object. If it implements This action is currently not disabled when there are no suitable
* listeners, mainly for debugging purposes (so you can see if a
* message is getting sent and what it looks like even in absence of
* suitable listeners).
*
* @return broadcast action
*/
public Action getBroadcastAction() {
if ( ! broadcastActCreated_ ) {
broadcastAct_ = createBroadcastAction();
broadcastActCreated_ = true;
updateEnabledness();
}
return broadcastAct_;
}
/**
* Returns a new menu which provides options to send a message to
* one of the registered listeners at a time. This menu will be
* disabled when no suitable listeners are registered.
*
* @param name menu title
* @return new message send menu
*/
public JMenu createSendMenu( String name ) {
JMenu menu = new JMenu( name );
for ( int is = 0; is < sendActs_.length; is++ ) {
menu.add( sendActs_[ is ] );
}
menuList_.add( menu );
updateEnabledness();
return menu;
}
/**
* Releases any resources associated with a menu previously created
* using {@link #createSendMenu}. Don't use the menu again.
*
* @param menu previously created send menu
*/
public void disposeSendMenu( JMenu menu ) {
menuList_.remove( menu );
}
/**
* Releases any resources associated with this object.
*/
public void dispose() {
subscribedClientModel_.removeListDataListener( subscriptionListener_ );
if ( subscribedClientModel_ instanceof SubscribedClientListModel ) {
((SubscribedClientListModel) subscribedClientModel_).dispose();
}
connector_.removeConnectionListener( connectionListener_ );
}
/**
* Updates the state of actions managed by this object when the
* list of registered listeners has changed.
*/
public void updateState() {
// Get a list of actions for the currently subscribed clients.
int nsub = subscribedClientModel_.getSize();
Action[] sendActs = new Action[ nsub ];
for ( int ia = 0; ia < nsub; ia++ ) {
sendActs[ ia ] =
getSendAction( (Client)
subscribedClientModel_.getElementAt( ia ) );
}
// Update menus if required.
if ( ! Arrays.equals( sendActs, sendActs_ ) ) {
sendActs_ = sendActs;
for ( Iterator menuIt = menuList_.iterator(); menuIt.hasNext(); ) {
JMenu menu = (JMenu) menuIt.next();
menu.removeAll();
for ( int is = 0; is < sendActs.length; is++ ) {
menu.add( sendActs[ is ] );
}
}
updateEnabledness();
}
}
/**
* Returns the client list to which this manager will offer sends.
*
* @return listmodel whose elements are suitably subscribed {@link Client}s
*/
public ListModel getClientListModel() {
return subscribedClientModel_;
}
/**
* Returns a new ComboBoxModel containing selections for each suitable
* client and an additional selection for broadcast to all clients.
* Elements are {@link org.astrogrid.samp.Client} objects, or
* {@link #BROADCAST_TARGET} to indicate broadcast.
* The result of this is suitable for use with {@link #createTargetAction}.
*
* @return new client combo box model
*/
public ComboBoxModel createTargetSelector() {
return new TargetComboBoxModel( subscribedClientModel_ );
}
/**
* Returns an action suitable for sending the message represented by
* this manager to a target selected by a supplied ComboBoxModel.
* This model is typically the result of calling
* {@link #createTargetSelector}.
*
* @param targetSelector combo box model in which the elements are
* {@link org.astrogrid.samp.Client} objects,
* or {@link #BROADCAST_TARGET} null to indicate broadcast
*/
public Action createTargetAction( final ComboBoxModel targetSelector ) {
return new AbstractAction( "Send to selected target" ) {
public void actionPerformed( ActionEvent evt ) {
Object target = targetSelector.getSelectedItem();
if ( target instanceof Client ) {
getSendAction( (Client) target ).actionPerformed( evt );
}
else if ( BROADCAST_TARGET.equals( target ) ) {
getBroadcastAction().actionPerformed( evt );
}
else {
Toolkit.getDefaultToolkit().beep();
logger_.warning( "Unknown send target: " + target
+ " - no action" );
}
}
};
}
/**
* Returns this manager's hub connector.
*
* @return connector
*/
public GuiHubConnector getConnector() {
return connector_;
}
/**
* Updates the enabled status of controlled actions in accordance with
* this object's current state.
*/
private void updateEnabledness() {
boolean active = enabled_
&& connector_.isConnected()
&& sendActs_.length > 0;
if ( broadcastAct_ != null ) {
broadcastAct_.setEnabled( active );
}
for ( Iterator it = menuList_.iterator(); it.hasNext(); ) {
((JMenu) it.next()).setEnabled( active );
}
}
/**
* Returns an icon suitable for depicting a general targetted send.
*
* @return send icon
*/
public static Icon getSendIcon() {
if ( SEND_ICON == null ) {
SEND_ICON = IconStore.createResourceIcon( "phone2.gif" );
}
return SEND_ICON;
}
/**
* Returns an icon suitable for depicting a general broadcast send.
*
* @return broadcast icon
*/
public static Icon getBroadcastIcon() {
if ( BROADCAST_ICON == null ) {
BROADCAST_ICON = IconStore.createResourceIcon( "tx3.gif" );
}
return BROADCAST_ICON;
}
/**
* ComboBoxModel implementation used for selecting a target client.
* It essentiall mirrors an existing client model but prepends a
* broadcast option.
*/
private static class TargetComboBoxModel extends AbstractListModel
implements ComboBoxModel {
private final ListModel clientListModel_;
private Object selectedItem_ = BROADCAST_TARGET;
/**
* Constructor.
*
* @param clientListModel list model containing suitable
* {@link org.astrogrid.samp.Client}s
*/
TargetComboBoxModel( ListModel clientListModel ) {
clientListModel_ = clientListModel;
/* Watch the underlying client model for changes and
* update this one accordingly. */
clientListModel_.addListDataListener( new ListDataListener() {
public void contentsChanged( ListDataEvent evt ) {
fireContentsChanged( evt.getSource(),
adjustIndex( evt.getIndex0() ),
adjustIndex( evt.getIndex1() ) );
}
public void intervalAdded( ListDataEvent evt ) {
fireIntervalAdded( evt.getSource(),
adjustIndex( evt.getIndex0() ),
adjustIndex( evt.getIndex1() ) );
}
public void intervalRemoved( ListDataEvent evt ) {
fireIntervalRemoved( evt.getSource(),
adjustIndex( evt.getIndex0() ),
adjustIndex( evt.getIndex1() ) );
}
private int adjustIndex( int index ) {
return index >= 0 ? index + 1
: index;
}
} );
}
public int getSize() {
return clientListModel_.getSize() + 1;
}
public Object getElementAt( int index ) {
return index == 0 ? BROADCAST_TARGET
: clientListModel_.getElementAt( index - 1 );
}
public Object getSelectedItem() {
return selectedItem_;
}
public void setSelectedItem( Object item ) {
selectedItem_ = item;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SubscribedClientListModel.java 0000664 0000000 0000000 00000005407 13564500043 0027677 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* Selective client list model which contains only those non-self clients
* which are subscribed to one or more of a given list of MTypes.
*
* @author Mark Taylor
* @since 1 Sep 2008
*/
public class SubscribedClientListModel extends SelectiveClientListModel {
private final GuiHubConnector connector_;
private String[] mtypes_;
/**
* Constructor for multiple MTypes.
*
* @param connector hub connector
* @param mtypes mtypes of interest (may have wildcards)
*/
public SubscribedClientListModel( GuiHubConnector connector,
String[] mtypes ) {
super( connector.getClientListModel() );
connector_ = connector;
mtypes_ = (String[]) mtypes.clone();
init();
}
/**
* Constructor for single MType.
*
* @param connector hub connector
* @param mtype mtype of interest (may have wildcards)
*/
public SubscribedClientListModel( GuiHubConnector connector,
String mtype ) {
this( connector, new String[] { mtype } );
}
/**
* Sets the list of MTypes which defines the elements of this list.
* Any client subscribed to one or more of these MTypes is included.
*
* @param mtypes new MType list
*/
public void setMTypes( String[] mtypes ) {
mtypes_ = (String[]) mtypes.clone();
refresh();
fireContentsChanged( this, -1, -1 );
}
/**
* Returns the list of MTypes which defines the elements of this list.
*
* @return MType list
*/
public String[] getMTypes() {
return mtypes_;
}
/**
* Returns true if For if an instance is initialised as
* This class is completely self-contained, so that it can easily be
* lifted out and used in other packages if required.
*
* @author Mark Taylor
* @since 21 Aug 2008
*/
public class HttpServer {
private final ServerSocket serverSocket_;
private boolean isDaemon_;
private List handlerList_;
private final URL baseUrl_;
private volatile boolean started_;
private volatile boolean stopped_;
/** Header string for MIME content type. */
public static final String HDR_CONTENT_TYPE = "Content-Type";
private static final String HDR_CONTENT_LENGTH = "Content-Length";
/** Status code for OK (200). */
public static final int STATUS_OK = 200;
private static final String URI_REGEX =
"([^\\s\\?]*)\\??([^\\s\\?]*)";
private static final String HTTP_VERSION_REGEX =
"HTTP/[0-9]+\\.[0-9]+";
private static final String HTTP_TOKEN_REGEX =
"[a-zA-Z0-9_\\.\\-]+";
private static final Pattern SIMPLE_REQUEST_PATTERN =
Pattern.compile( "GET" + " " + "(" + "\\S+" + ")" );
private static final Pattern REQUEST_LINE_PATTERN =
Pattern.compile( "(" + HTTP_TOKEN_REGEX + ")" // typically GET, HEAD etc
+ " " + "(" + "\\S+" + ")"
+ " " + HTTP_VERSION_REGEX );
private static final Pattern HEADER_PATTERN =
Pattern.compile( "(" + "[^\\s:]+" +")" + ":\\s*(.*)" );
private static final Logger logger_ =
Logger.getLogger( HttpServer.class.getName() );
/**
* Constructs a server based on a given socket.
*
* @param socket listening socket
*/
public HttpServer( ServerSocket socket ) {
serverSocket_ = socket;
isDaemon_ = true;
handlerList_ = Collections.synchronizedList( new ArrayList() );
boolean isTls = socket instanceof SSLServerSocket;
String scheme = isTls ? "https" : "http";
StringBuffer ubuf = new StringBuffer()
.append( scheme )
.append( "://" )
.append( SampUtils.getLocalhost() );
int port = socket.getLocalPort();
if ( port != ( isTls ? 443 : 80 ) ) {
ubuf.append( ':' )
.append( port );
}
try {
baseUrl_ = new URL( ubuf.toString() );
}
catch ( MalformedURLException e ) {
throw new AssertionError( "Bad scheme " + scheme + ": ??" );
}
}
/**
* Constructs a server based on a default socket, on any free port.
*/
public HttpServer() throws IOException {
this( new ServerSocket( 0 ) );
}
/**
* Adds a handler which can serve some requests going through this server.
*
* @param handler handler to add
*/
public void addHandler( Handler handler ) {
handlerList_.add( handler );
}
/**
* Removes a handler previously added by {@link #addHandler}.
*
* @param handler handler to remove
*/
public void removeHandler( Handler handler ) {
handlerList_.remove( handler );
}
/**
* Returns the socket on which this server listens.
*
* @return server socket
*/
public ServerSocket getSocket() {
return serverSocket_;
}
/**
* Returns the base URL for this server.
*
* @return base URL
*/
public URL getBaseUrl() {
return baseUrl_;
}
/**
* Does the work for providing output corresponding to a given HTTP request.
* This implementation calls each Handler in turn and the first one
* to provide a non-null response is used.
*
* @param request represents an HTTP request that has been received
* @return represents the content of an HTTP response that should be sent
*/
public Response serve( Request request ) {
Handler[] handlers =
(Handler[]) handlerList_.toArray( new Handler[ 0 ] );
for ( int ih = 0; ih < handlers.length; ih++ ) {
Handler handler = handlers[ ih ];
Response response = handler.serveRequest( request );
if ( response != null ) {
return response;
}
}
return createErrorResponse( 404, "No handler for URL" );
}
/**
* Determines whether the server thread will be a daemon thread or not.
* Must be called before {@link #start} to have an effect.
* The default is true.
*
* @param isDaemon whether server thread will be daemon
* @see java.lang.Thread#setDaemon
*/
public void setDaemon( boolean isDaemon ) {
isDaemon_ = isDaemon;
}
/**
* Starts the server if it is not already started.
*/
public synchronized void start() {
if ( ! started_ ) {
Thread server = new Thread( "HTTP Server" ) {
public void run() {
try {
while ( ! stopped_ ) {
try {
final Socket sock = serverSocket_.accept();
new Thread( "HTTP Request" ) {
public void run() {
try {
serveRequest( sock );
}
catch ( Throwable e ) {
logger_.log( Level.WARNING,
"Httpd error", e );
}
}
}.start();
}
catch ( IOException e ) {
if ( ! stopped_ ) {
logger_.log( Level.WARNING,
"Socket error", e );
}
}
}
}
finally {
HttpServer.this.stop();
}
}
};
server.setDaemon( isDaemon_ );
logger_.info( "Server " + getBaseUrl() + " starting" );
server.start();
started_ = true;
logger_.config( "Server " + getBaseUrl() + " started" );
}
}
/**
* Stops the server if it is currently running. Processing of any requests
* which have already been received is completed.
*/
public synchronized void stop() {
if ( ! stopped_ ) {
stopped_ = true;
logger_.info( "Server " + getBaseUrl() + " stopping" );
try {
serverSocket_.close();
}
catch ( IOException e ) {
logger_.log( Level.WARNING,
"Error during server stop: " + e, e );
}
}
}
/**
* Indicates whether this server is currently running.
*
* @return true if running
*/
public boolean isRunning() {
return started_ && ! stopped_;
}
/**
* Called by the server thread for each new connection.
*
* @param sock client connection socket
*/
protected void serveRequest( Socket sock ) throws IOException {
// Try to generate a request object by examining the socket's
// input stream. If that fails, generate a response representing
// the error.
InputStream in = sock.getInputStream();
in = new BufferedInputStream( in );
Response response = null;
Request request = null;
try {
request = parseRequest( in, sock.getRemoteSocketAddress() );
// If there was no input, make no response at all.
if ( request == null ) {
return;
}
}
catch ( HttpException e ) {
response = e.createResponse();
}
catch ( IOException e ) {
response = createErrorResponse( 400, "I/O error", e );
}
catch ( Throwable e ) {
response = createErrorResponse( 500, "Server error", e );
}
// If we have a request (and hence no error response) process it to
// obtain a response object.
if ( response == null ) {
assert request != null;
try {
response = serve( request );
}
catch ( Throwable e ) {
response = createErrorResponse( 500, e.toString(), e );
}
}
final Level level;
switch ( response.getStatusCode() ) {
case 200:
level = Level.CONFIG;
break;
case 404:
level = Level.INFO;
break;
default:
level = Level.WARNING;
}
if ( logger_.isLoggable( level ) ) {
StringBuffer sbuf = new StringBuffer();
if ( request != null ) {
sbuf.append( request.getMethod() )
.append( ' ' )
.append( request.getUrl() );
}
else {
sbuf.append( " The functionality of this class overlaps with that of
* {@link URLMapperHandler}. They may be merged at some point.
*
* @author Mark Taylor
* @since 21 Jul 2009
*/
public class MultiURLMapperHandler implements HttpServer.Handler {
private final HttpServer server_;
private final String basePath_;
private final URL baseUrl_;
private final Map urlMap_;
private int resourceCount_;
/**
* Constructor.
*
* @param server server within which this handler will be used
* @param basePath path of served resources relative to the base
* URL of the server itself
*/
public MultiURLMapperHandler( HttpServer server, String basePath )
throws MalformedURLException {
server_ = server;
if ( ! basePath.startsWith( "/" ) ) {
basePath = "/" + basePath;
}
if ( ! basePath.endsWith( "/" ) ) {
basePath = basePath + "/";
}
basePath_ = basePath;
baseUrl_ = new URL( server.getBaseUrl(), basePath );
urlMap_ = Collections.synchronizedMap( new HashMap() );
}
/**
* Returns the base URL for resources served by this handler.
*
* @return base URL for output
*/
public URL getBaseUrl() {
return baseUrl_;
}
/**
* Adds a local URL to the list of those which can be served by this
* handler, and returns the public URL at which it will be available.
*
* @param localUrl URL readable within this JVM
* @return URL readable in principle by external agents with the same
* content as This class performs two functions. Firstly it provides a static
* {@link #getInstance} method which allows its use in a singleton-like way.
* The constructor is public, so singleton use is not enforced, but if
* you need a server but don't need exclusive control over it, obtaining
* one in this way will ensure that you don't start a new server
* (which requires a new socket and other resources) if a suitable one
* is already available.
*
* Secondly, it provides some utility methods,
* {@link #exportResource} and {@link #exportFile},
* useful for turning files or classpath resources into
* publicly viewable URLs, which is sometimes useful within a SAMP
* context (for instance when providing an Icon URL in metadata).
*
* @author Mark Taylor
* @since 22 Jul 2009
*/
public class UtilServer {
private final HttpServer server_;
private final Set baseSet_;
private MultiURLMapperHandler mapperHandler_;
private ResourceHandler resourceHandler_;
/**
* System Property key giving a preferred port number for the server.
* If unset, or 0, or the chosen port is occupied, a system-chosen
* value will be used.
* The property name is {@value}.
*/
public static final String PORT_PROP = "jsamp.server.port";
/** Buffer size for copy data from input to output stream. */
private static int BUFSIZ = 16 * 1024;
/** Default instance of this class. */
private static UtilServer instance_;
private static final Pattern SLASH_REGEX =
Pattern.compile( "(/*)(.*?)(/*)" );
private static final Pattern NUMBER_REGEX =
Pattern.compile( "(.*?)([0-9]+)" );
private static final Logger logger_ =
Logger.getLogger( UtilServer.class.getName() );
/**
* Constructor.
* Note, it may be more appropriate to use the {@link #getInstance} method.
*
* @param server HTTP server providing base services
*/
public UtilServer( HttpServer server ) throws IOException {
server_ = server;
baseSet_ = new HashSet();
}
/**
* Returns the HttpServer associated with this object.
*
* @return a running server instance
*/
public HttpServer getServer() {
return server_;
}
/**
* Returns a handler for mapping local to external URLs associated with
* this server.
*
* @return url mapping handler
*/
public synchronized MultiURLMapperHandler getMapperHandler() {
if ( mapperHandler_ == null ) {
try {
mapperHandler_ =
new MultiURLMapperHandler( server_,
getBasePath( "/export" ) );
}
catch ( MalformedURLException e ) {
throw (AssertionError) new AssertionError().initCause( e );
}
server_.addHandler( mapperHandler_ );
}
return mapperHandler_;
}
/**
* Returns a handler for general purpose resource serving associated with
* this server.
*
* @return resource serving handler
*/
public synchronized ResourceHandler getResourceHandler() {
if ( resourceHandler_ == null ) {
resourceHandler_ =
new ResourceHandler( server_, getBasePath( "/docs" ) );
server_.addHandler( resourceHandler_ );
}
return resourceHandler_;
}
/**
* Exposes a resource from a given URL as a publicly visible URL.
* This is typically used if the given Some of the static methods allow you to indicate which hub profiles
* should be used, others use a default. The default list can be set
* programmatically by using the {@link #setDefaultProfileClasses} method
* or externally by using the
* {@value #HUBPROFILES_PROP} and {@value #EXTRAHUBPROFILES_PROP}
* system properties.
* So, for instance, running an application with
* The If the hub mode corresponds to one of the GUI options,
* one of two things will happen. An attempt will be made to install
* an icon in the "system tray"; if this is successful, the attached
* popup menu will provide options for displaying the hub window and
* for shutting it down. If no system tray is available, the hub window
* will be posted directly, and the hub will shut down when this window
* is closed. System tray functionality is only available when running
* under Java 1.6 or later, and when using a suitable display manager.
*
* @param hubMode hub mode
* @param profiles SAMP profiles to support on hub startup;
* if null a default set will be used
* @param extraProfiles SAMP profiles to offer for later startup under
* user control; if null a default set will be used
* @return running hub
*/
public static Hub runHub( HubServiceMode hubMode, HubProfile[] profiles,
HubProfile[] extraProfiles )
throws IOException {
// Get values for the list of starting profiles, and the list of
// additional profiles that may be started later.
// If these have not been specified explicitly, use defaults.
if ( profiles == null ) {
profiles = createDefaultProfiles( false );
}
if ( extraProfiles == null ) {
extraProfiles = createDefaultProfiles( true );
}
List profList = new ArrayList();
profList.addAll( Arrays.asList( profiles ) );
for ( int ip = 0; ip < extraProfiles.length; ip++ ) {
HubProfile ep = extraProfiles[ ip ];
boolean gotit = false;
for ( int jp = 0; jp < profiles.length; jp++ ) {
gotit = gotit
|| profiles[ jp ].getClass().equals( ep.getClass() );
}
if ( ! gotit ) {
profList.add( ep );
}
}
HubProfile[] allProfiles =
(HubProfile[]) profList.toArray( new HubProfile[ 0 ] );
// Construct a hub service ready to use the full profile list.
final Hub[] runners = new Hub[ 1 ];
final HubServiceMode.ServiceGui serviceGui =
hubMode.createHubService( KeyGenerator.createRandom(),
allProfiles, runners );
HubService hubService = serviceGui.getHubService();
final Hub hub = new Hub( hubService ) {
public JFrame getWindow() {
return serviceGui.getWindow();
}
};
runners[ 0 ] = hub;
// Start the initial profiles.
int nStarted = 0;
IOException error1 = null;
for ( int ip = 0; ip < profiles.length; ip++ ) {
HubProfile prof = profiles[ ip ];
String pname = prof.getProfileName();
try {
logger_.info( "Starting hub profile " + pname );
hub.startProfile( prof );
nStarted++;
}
catch ( IOException e ) {
if ( error1 == null ) {
error1 = e;
}
logger_.log( Level.WARNING,
"Failed to start SAMP hub profile " + pname, e );
}
}
logger_.info( "Started " + nStarted + "/" + profiles.length
+ " SAMP profiles" );
if ( nStarted == 0 && profiles.length > 0 ) {
assert error1 != null;
throw (IOException)
new IOException( "No SAMP profiles started: " + error1 )
.initCause( error1 );
}
// Start the hub service itself.
logger_.info( "Starting hub service" );
hubService.start();
ShutdownManager.getInstance()
.registerHook( hub, ShutdownManager.HUB_SEQUENCE,
new Runnable() {
public void run() {
hub.shutdown();
}
} );
// Return the running hub.
return hub;
}
/**
* Starts a SAMP hub with a default set of profiles.
* This convenience method invokes The classes specified by the It is the responsibility of the returned connection, not the
* user of that connection, to broadcast the various
* Most of the A main method is supplied for command-line use.
*
* @author Mark Taylor
* @since 21 Aug 2019
*/
public class SampLoad extends JDialog {
private final GuiHubConnector connector_;
private final URL url_;
private final String mtype_;
private final JComboBox targetSelector_;
private final JLabel statusField_;
private final Action sendAct_;
private Map responseMap_;
private static final URL ICON_URL =
SampLoad.class.getResource( "/org/astrogrid/samp/images/sampload.png" );
private static final Logger logger_ =
Logger.getLogger( SampLoad.class.getName() );
/**
* Constructor.
*
* @param connector connector
* @param rtype resource type
* @param URL resource URL
* @param sendType type of item being sent (for user info)
* @param location location of item being sent (for user info)
*/
public SampLoad( GuiHubConnector connector, final ResourceType rtype,
final URL url, String location ) {
super( (JDialog) null, "SAMP Loader", true );
connector_ = connector;
url_ = url;
mtype_ = rtype == null ? null : rtype.getMType();
// Prepare to send the message to selected target client(s).
String tname = rtype == null ? null : rtype.getName();
AbstractCallActionManager callManager =
new UniformCallActionManager( this, connector, mtype_, tname ) {
protected Map createMessage() {
return rtype.createMessage( url );
}
protected ResultHandler createResultHandler( HubConnection conn,
Message msg,
Client[] recipients ) {
final Collection recips =
new HashSet( Arrays.asList( recipients ) );
return new ResultHandler() {
// Record which responses we have got.
public void result( Client responder, Response response ) {
responseMap_.put( responder, response );
if ( response.isOK() ) {
recips.remove( responder );
}
updateStatus();
}
// If all successful, just dismiss the window after
// a short delay. If there were any problems,
// leave it posted so that any error messages are visible.
public void done() {
boolean success = recips.size() == 0;
if ( success ) {
Timer timer =
new Timer( 1000, new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
closeDialog();
}
} );
timer.setRepeats( false );
timer.start();
}
}
};
}
};
// Provide a target client selection widget and associated send button.
final ComboBoxModel targetModel;
if ( rtype == null ) {
targetModel =
new DefaultComboBoxModel( new String[] { SendActionManager
.BROADCAST_TARGET } );
sendAct_ = new AbstractAction( "Send to selected target" ) {
public void actionPerformed( ActionEvent evt ) {
}
};
}
else {
targetModel = callManager.createTargetSelector();
sendAct_ = callManager.createTargetAction( targetModel );
}
targetSelector_ = new JComboBox( targetModel ) {
public Dimension getMaximumSize() {
return super.getPreferredSize();
}
};
// Provide a button that dismisses the window.
Action cancelAct = new AbstractAction( "Cancel" ) {
public void actionPerformed( ActionEvent evt ) {
closeDialog();
}
};
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// Update GUI on relevant SAMP events.
connector_.addConnectionListener( new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
updateStatus();
}
} );
targetModel.addListDataListener( new ListDataListener() {
public void contentsChanged( ListDataEvent evt ) {
updateSelector();
}
public void intervalAdded( ListDataEvent evt ) {
updateSelector();
}
public void intervalRemoved( ListDataEvent evt ) {
updateSelector();
}
} );
// We don't have hub autodetect switched on, since in most cases
// this will not be a long-lived application, but if the user
// interacts with the window, take it as an opportunity
// to see if a hub has started.
getContentPane().addMouseListener( new MouseAdapter() {
public void mouseEntered( MouseEvent evt ) {
checkConnection();
}
public void mouseExited( MouseEvent evt ) {
checkConnection();
}
public void mousePressed( MouseEvent evt ) {
checkConnection();
}
private void checkConnection() {
try {
connector_.getConnection();
}
catch ( SampException e ) {
}
}
} );
// Note when a send has started by resetting the response map to null.
JButton sendButton = new JButton( sendAct_ );
sendButton.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
responseMap_ = new LinkedHashMap();
updateStatus();
}
} );
// Place components.
statusField_ = new JLabel();
JComponent displayPanel = new JPanel( new GridBagLayout() );
GridBagConstraints gbc = new GridBagConstraints();
addLine( displayPanel, gbc, "Location", createField( location ), true );
addLine( displayPanel, gbc, "MType", createField( mtype_ ), true );
addLine( displayPanel, gbc, "Target", targetSelector_, false );
addLine( displayPanel, gbc, "Status", statusField_, true );
JComponent buttLine = Box.createHorizontalBox();
buttLine.add( Box.createHorizontalGlue() );
buttLine.add( sendButton );
buttLine.add( Box.createHorizontalStrut( 10 ) );
buttLine.add( new JButton( cancelAct ) );
buttLine.add( Box.createHorizontalGlue() );
buttLine.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
Box qbox = Box.createVerticalBox();
Icon qicon = ICON_URL != null
? new ImageIcon( ICON_URL )
: UIManager.getIcon( "OptionPane.questionIcon" );
qbox.add( new JLabel( qicon ) );
qbox.add( Box.createVerticalGlue() );
qbox.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
Component hstrut = Box.createHorizontalStrut( 450 );
JComponent contentPane = (JComponent) getContentPane();
contentPane.setLayout( new BorderLayout() );
contentPane.add( displayPanel, BorderLayout.CENTER );
contentPane.add( buttLine, BorderLayout.SOUTH );
contentPane.add( qbox, BorderLayout.WEST );
contentPane.add( hstrut, BorderLayout.NORTH );
contentPane.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
// Initialise state.
updateSelector();
}
/**
* Updates the GUI according to current state.
*/
private void updateStatus() {
final String status;
final boolean canSend;
if ( url_ == null ) {
status = "No such file";
canSend = false;
}
else if ( mtype_ == null ) {
status = "Unknown resource type (not "
+ commaJoin( ResourceType.getKnownResourceTypes() ) + ")";
canSend = false;
}
else if ( ! connector_.isConnected() ) {
status = "No hub";
canSend = false;
}
else if ( targetSelector_.getItemCount() <= 1 ) {
status = "No clients for " + mtype_;
canSend = false;
}
else if ( responseMap_ == null ) {
status = "Ready";
canSend = true;
}
else if ( responseMap_.size() == 0 ) {
status = "sending ...";
canSend = false;
}
else {
StringBuffer sbuf = new StringBuffer();
for ( Iterator it = responseMap_.entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
if ( sbuf.length() > 0 ) {
sbuf.append( "; " );
}
Client client = (Client) entry.getKey();
Response response = (Response) entry.getValue();
sbuf.append( client + ": " );
if ( response.isOK() ) {
sbuf.append( "OK" );
}
else {
ErrInfo err = response.getErrInfo();
if ( err == null ) {
sbuf.append( "ERROR" );
}
else {
sbuf.append( response.getStatus() )
.append( " " )
.append( err.getErrortxt() );
}
}
}
status = sbuf.toString();
canSend = false;
}
statusField_.setText( status );
sendAct_.setEnabled( canSend );
}
/**
* Updates the target client selector according to the current SAMP
* client list.
*/
private void updateSelector() {
// Note, the first item in the list corresponds to Broadcast
// (all clients), and subsequent items are single clients.
if ( targetSelector_.getItemCount() == 2 ) {
targetSelector_.setSelectedIndex( 1 );
}
else if ( targetSelector_.getSelectedIndex() < 0 ) {
targetSelector_.setSelectedIndex( 0 );
}
updateStatus();
}
/**
* Closes and disposes this dialogue.
*/
private void closeDialog() {
setVisible( false );
dispose();
}
/**
* Utility method to append a labelled component to a JComponent
* using GridBagLayout.
*
* @param panel container
* @param gbc constraints object
* @param labelTxt text of label
* @param comp component to add
* @param hfill true to fill horizontal line
*/
private static void addLine( JComponent panel, GridBagConstraints gbc,
String labelTxt, JComponent comp,
boolean hfill ) {
GridBagLayout layer = (GridBagLayout) panel.getLayout();
gbc.gridy++;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.weightx = 0.0;
gbc.insets = new Insets( 2, 5, 5, 2 );
JLabel label = new JLabel( labelTxt + ": " );
layer.setConstraints( label, gbc );
panel.add( label );
GridBagConstraints gbc1 = (GridBagConstraints) gbc.clone();
gbc1.gridx = 1;
gbc1.anchor = GridBagConstraints.WEST;
gbc1.weightx = 1.0;
gbc1.fill = hfill ? GridBagConstraints.HORIZONTAL
: GridBagConstraints.NONE;
gbc1.gridwidth = GridBagConstraints.REMAINDER;
layer.setConstraints( comp, gbc1 );
panel.add( comp );
}
/**
* Utility method to create an uneditable field with given text.
*
* @param txt text content
* @return field component
*/
private static JTextField createField( String txt ) {
JTextField field = new JTextField();
field.setEditable( false );
field.setText( txt );
field.setCaretPosition( 0 );
return field;
}
/**
* Returns a comma-separated string joining the toString values of
* the elements of a supplied array.
*
* @param items array of items
* @return "i1, i2, ..., iN"
*/
private static String commaJoin( Object[] items ) {
StringBuffer sbuf = new StringBuffer();
for ( int i = 0; i < items.length; i++ ) {
if ( sbuf.length() > 0 ) {
sbuf.append( ", " );
}
sbuf.append( String.valueOf( items[ i ] ) );
}
return sbuf.toString();
}
/**
* Tries to turn a string into a URL.
*
* @param location URL or filename
* @return URL, or null
*/
private static final URL getUrl( String location ) {
File f = new File( location );
if ( f.exists() ) {
return SampUtils.fileToUrl( f );
}
try {
return new URL( location );
}
catch ( MalformedURLException e ) {
}
return null;
}
/**
* Main invocation method.
* Use -help for help.
*
* @param args arg vector
*/
public static int runMain( String[] args ) throws IOException {
ResourceType[] resourceTypes = ResourceType.getKnownResourceTypes();
// Assemble usage string.
StringBuffer rtbuf = new StringBuffer();
for ( int ir = 0; ir < resourceTypes.length; ir++ ) {
if ( rtbuf.length() > 0 ) {
rtbuf.append( "|" );
}
rtbuf.append( resourceTypes[ ir ].getName().toLowerCase() );
}
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " + SampLoad.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( "\n " )
.append( " [-rtype " + rtbuf + "]" )
.append( " All methods have no arguments and return a String.
* The methods with names
* that end " The method names define the keys which can be used if a
* property resource file is used to supply the content.
*/
public static interface Content {
/**
* Returns the title for the confirmation window.
*/
String windowTitle();
/**
* Returns lines introducing the registration request.
*/
String appIntroductionLines();
/**
* Returns the word meaning "Name" (initial capitalised).
*/
String nameWord();
/**
* Returns the word meaning "Origin" (initial capitalised).
*/
String originWord();
/**
* Returns the word meaning "undeclared" (not capitalised).
*/
String undeclaredWord();
/**
* Returns lines suitable explaining the privileges that a
* registered client will have.
*/
String privilegeWarningLines();
/**
* Returns lines with advice on whether you should accept or decline.
*/
String adviceLines();
/**
* Returns a line asking whether to authorize (yes/no).
*/
String questionLine();
/**
* Returns the word meaning "Yes" (initial capitalised).
*/
String yesWord();
/**
* Returns the word meaning "No" (initial capitalised).
*/
String noWord();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_de.java 0000664 0000000 0000000 00000003340 13564500043 0027034 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
/**
* AuthResourceBundle with German text.
*
* @author Markus Demleitner
* @author Mark Taylor
* @since 1 Aug 2011
*/
public class AuthResourceBundle_de extends AuthResourceBundle {
/**
* Constructor.
*/
public AuthResourceBundle_de() {
super( new GermanContent() );
}
/**
* Content implementation for English.
*/
private static class GermanContent implements Content {
public String windowTitle() {
return "SAMP Zugriffskontrolle";
}
public String appIntroductionLines() {
return "Folgendes Programm (vermutlich im Browser laufend)\n"
+ "m\u00f6chte sich am SAMP Hub anmelden:";
}
public String nameWord() {
return "Name";
}
public String originWord() {
return "Auf Seite";
}
public String undeclaredWord() {
return "Nicht gegeben";
}
public String privilegeWarningLines() {
return "Wenn Sie dies zulassen, k\u00f6nnte der Dienst unter\n"
+ "Umst\u00e4nden auf Dateien oder andere Resourcen auf\n"
+ "Ihrem Rechner zugreifen k\u00f6nnen.";
}
public String adviceLines() {
return "Lassen Sie die Verbindung nur zu, wenn Sie gerade\n"
+ "auf einer Seite, der Sie vertrauen, eine Handlung\n"
+ "ausgef\u00fchrt haben, die SAMP anspricht.";
}
public String questionLine() {
return "Die Verbindung erlauben?";
}
public String yesWord() {
return "Ja";
}
public String noWord() {
return "Nein";
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_en.java 0000664 0000000 0000000 00000003160 13564500043 0027046 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
/**
* AuthResourceBundle with English text.
*
* @author Mark Taylor
* @since 15 Jul 2011
*/
public class AuthResourceBundle_en extends AuthResourceBundle {
/**
* Constructor.
*/
public AuthResourceBundle_en() {
super( new EnglishContent() );
}
/**
* Content implementation for English.
*/
static class EnglishContent implements Content {
public String windowTitle() {
return "SAMP Hub Security";
}
public String appIntroductionLines() {
return "The following application, probably running in a browser,\n"
+ "is requesting SAMP Hub registration:";
}
public String nameWord() {
return "Name";
}
public String originWord() {
return "Origin";
}
public String undeclaredWord() {
return "undeclared";
}
public String privilegeWarningLines() {
return "If you permit this, it may be able to access local files\n"
+ "and other resources on your computer.";
}
public String adviceLines() {
return "You should only accept if you have just performed\n"
+ "some action in the browser, on a web site you trust,\n"
+ "that you expect to have caused this.";
}
public String questionLine() {
return "Do you authorize connection?";
}
public String yesWord() {
return "Yes";
}
public String noWord() {
return "No";
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_fr.java 0000664 0000000 0000000 00000003515 13564500043 0027057 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
/**
* AuthResourceBundle with French text.
*
* @author Thomas Boch
* @author Mark Taylor
* @since 23 Aug 2011
*/
public class AuthResourceBundle_fr extends AuthResourceBundle {
/**
* Constructor.
*/
public AuthResourceBundle_fr() {
super( new FrenchContent() );
}
/**
* Content implementation for French.
*/
private static class FrenchContent implements Content {
public String windowTitle() {
return "Avertissement de s\u00e9curit\u00e9 du hub SAMP";
}
public String appIntroductionLines() {
return "L'application suivante, qui s'ex\u00e9cute probablement "
+ "depuis un\n"
+ "navigateur, demande \u00e0 s'enregistrer "
+ "aupr\u00e8s du hub SAMP:";
}
public String nameWord() {
return "Nom";
}
public String originWord() {
return "Origine";
}
public String undeclaredWord() {
return "Inconnue";
}
public String privilegeWarningLines() {
return "Si vous l'autorisez, elle pourra acc\u00e9der aux "
+ "fichiers locaux\n"
+ "et autres ressources de votre ordinateur.";
}
public String adviceLines() {
return "Acceptez uniquement si vous venez d'effectuer dans le "
+ "navigateur\n"
+ "une action, sur un site de confiance, susceptible d'avoir "
+ "entra\u00een\u00e9\n"
+ "cette demande.";
}
public String questionLine() {
return "Acceptez-vous?";
}
public String yesWord() {
return "Oui";
}
public String noWord() {
return "Non";
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_it.java 0000664 0000000 0000000 00000003410 13564500043 0027056 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
/**
* AuthResourceBundle with English text.
*
* @author Luigi Paioro
* @author Mark Taylor
* @since 15 Jul 2011
*/
public class AuthResourceBundle_it extends AuthResourceBundle {
/**
* Constructor.
*/
public AuthResourceBundle_it() {
super( new ItalianContent() );
}
/**
* Content implementation for Italian.
*/
private static class ItalianContent implements Content {
public String windowTitle() {
return "Sicurezza del SAMP Hub";
}
public String appIntroductionLines() {
return "Il seguente programma, probabilmente eseguito all'interno\n"
+ "di un browser, chiede di essere registrato al SAMP Hub:";
}
public String nameWord() {
return "Nome";
}
public String originWord() {
return "Origine";
}
public String undeclaredWord() {
return "non dichiarato";
}
public String privilegeWarningLines() {
return "Se ne consentite la registrazione, esso potrebbe accedere\n"
+ "ai files locali e ad altre risorse del vostro computer.";
}
public String adviceLines() {
return "Il vostro consenso dovrebbe essere dato solo se avete\n"
+ "appena eseguito qualche azione con il browser,\n"
+ "su un sito Web conosciuto, che vi aspettate possa aver\n"
+ "causato questa richiesta.";
}
public String questionLine() {
return "Autorizzate la registrazione?";
}
public String yesWord() {
return "S\u00ec";
}
public String noWord() {
return "No";
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/Callback.java 0000664 0000000 0000000 00000005150 13564500043 0024316 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.SampMap;
import org.astrogrid.samp.SampUtils;
/**
* Map representing a client callback from the hub.
* It normally contains a callback method name and a list of parameters.
* An instance of this class can be used to correspond to one of the calls
* in the {@link org.astrogrid.samp.client.CallableClient} interface.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
class Callback extends SampMap {
/** Key for the callback method name (a string). */
public static final String METHODNAME_KEY = "samp.methodName";
/** Key for the callback parameters (a list). */
public static final String PARAMS_KEY = "samp.params";
private static final String[] KNOWN_KEYS = new String[] {
METHODNAME_KEY,
PARAMS_KEY,
};
/**
* Constructs an empty callback.
*/
public Callback() {
super( KNOWN_KEYS );
}
/**
* Constructs a callback based on an existing map.
*
* @param map contents
*/
public Callback( Map map ) {
this();
putAll( map );
}
/**
* Constructs a callback given a method name and parameter list.
*/
public Callback( String methodName, List params ) {
this();
setMethodName( methodName );
setParams( params );
}
/**
* Sets the method name.
*
* @param methodName method name
*/
public void setMethodName( String methodName ) {
put( METHODNAME_KEY, methodName );
}
/**
* Returns the method name.
*
* @return method name
*/
public String getMethodName() {
return getString( METHODNAME_KEY );
}
/**
* Sets the parameter list.
*
* @param params parameter list
*/
public void setParams( List params ) {
SampUtils.checkList( params );
put( PARAMS_KEY, params );
}
/**
* Returns the parameter list.
*
* @return parameter list
*/
public List getParams() {
return getList( PARAMS_KEY );
}
public void check() {
super.check();
checkHasKeys( new String[] { METHODNAME_KEY, PARAMS_KEY, } );
SampUtils.checkString( getMethodName() );
SampUtils.checkList( getParams() );
}
/**
* Returns a given map as a Callback object.
*
* @param map map
* @return callback
*/
public static Callback asCallback( Map map ) {
return ( map instanceof Callback || map == null )
? (Callback) map
: new Callback( map );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientAuthorizer.java 0000664 0000000 0000000 00000002142 13564500043 0026113 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Map;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
/**
* Defines authorization functionality which is used to determine whether
* a client is permitted to register with the hub.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public interface ClientAuthorizer {
/**
* Indicates whether an HTTP request representing an otherwise
* unauthorized connection attempt will be permitted access to
* sensitive system resources. If so, the method exits normally.
* If authorization is denied, a SampException is thrown,
* with a message that indicates the reason for denial.
*
* @param request incoming HTTP request
* @param securityMap credential items supplied explicitly by
* aspiring client to support its registration
* request
* @throws SampException with reason if authorization is denied
*/
void authorize( HttpServer.Request request, Map securityMap )
throws SampException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientAuthorizers.java 0000664 0000000 0000000 00000012474 13564500043 0026307 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
/**
* Utility class containing ClientAuthorizer implementations.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class ClientAuthorizers {
/**
* Authorizer which always denies access,
* with INFO logging either way.
*/
public static final ClientAuthorizer FALSE =
createLoggingClientAuthorizer( createFixedClientAuthorizer( false ),
Level.INFO, Level.INFO );
/**
* Authorizer which always permits access,
* with WARNING logging either way.
*/
public static final ClientAuthorizer TRUE =
createLoggingClientAuthorizer( createFixedClientAuthorizer( true ),
Level.WARNING, Level.WARNING );
/**
* Authorizer which queries the user via a popup dialogue,
* with INFO logging either way.
*/
private static ClientAuthorizer swingAuth_;
private static final Logger logger_ =
Logger.getLogger( ClientAuthorizers.class.getName() );
private ClientAuthorizers() {
}
/**
* Returns a new authorizer instance which always produces the same
* authorization status.
*
* @param policy true for accept, false for deny
* @return new authorizer
*/
public static ClientAuthorizer
createFixedClientAuthorizer( final boolean policy ) {
if ( policy ) {
return new ClientAuthorizer() {
public void authorize( HttpServer.Request request,
Map securityMap ) {
}
};
}
else {
return new ClientAuthorizer() {
public void authorize( HttpServer.Request request,
Map securityMap )
throws SampException {
throw new SampException( "All registration requests "
+ "unconditionally denied" );
}
};
}
}
/**
* Returns a new authorizer instance based on an existing one which
* logs authorization results through the logging system.
*
* @param auth base authorizer
* @param acceptLevel logging level at which auth acceptances are logged
* @param refuseLevel logging level at which auth refusals are logged
* @return new authorizer
*/
public static ClientAuthorizer
createLoggingClientAuthorizer( final ClientAuthorizer auth,
final Level acceptLevel,
final Level refuseLevel ) {
return new ClientAuthorizer() {
public synchronized void authorize( HttpServer.Request request,
Map securityMap )
throws SampException {
boolean isAuth;
try {
auth.authorize( request, securityMap );
log( true, securityMap, null );
}
catch ( SampException e ) {
log( false, securityMap, e );
throw e;
}
}
private void log( boolean accept, Map securityMap,
SampException err ) {
String clTxt = securityMap.containsKey( Metadata.NAME_KEY )
? securityMap.get( Metadata.NAME_KEY ).toString()
: securityMap.toString();
if ( accept ) {
logger_.log( acceptLevel,
"Accepted registration for client " + clTxt );
}
else {
logger_.log( refuseLevel,
"Rejected registration for client " + clTxt
+ "(" + err.getMessage() + ")" );
}
}
};
}
/**
* Returns the mandatory application name entry from the security map
* supplied explicitly by clients wishing to register.
* The relevant key is "samp.name" (Metadata.NAME_KEY).
* If it's not present and correct, a SampException is thrown
* indicating that registration is rejected.
*
* @param securityMap map supplied by client
* @return value of samp.name key, not null
* @throws SampException if name not present
*/
public static String getAppName( Map securityMap ) throws SampException {
String nameKey = Metadata.NAME_KEY;
assert "samp.name".equals( nameKey );
Object appNameObj = securityMap.get( nameKey );
final String appName;
if ( appNameObj instanceof String ) {
return (String) appNameObj;
}
else if ( appNameObj == null ) {
throw new SampException( "No " + nameKey
+ " entry in securityInfo map" );
}
else {
throw new SampException( "Wrong data type (not string) for "
+ nameKey + " entry in securityInfo map" );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientCallbackOperation.java 0000664 0000000 0000000 00000013011 13564500043 0027331 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.client.CallableClient;
/**
* Represents one of the possible callbacks which may be invoked on a
* CallableClient. The {@link #invoke} static method arranges for a Callback
* acquired from the hub to be dispatched to a CallableClient.
*
* @author Mark Taylor
* @since 3 Feb 2011
*/
abstract class ClientCallbackOperation {
private final String fqName_;
private final Class[] sampSig_;
private static final Map OPERATION_MAP = createOperationMap();
/**
* Constructor.
*
* @param unqualified callback method name
* @param signature of callback; an array of SAMP-friendly classes,
* one for each argument
*/
private ClientCallbackOperation( String methodName, Class[] sampSig ) {
fqName_ = WebClientProfile.WEBSAMP_CLIENT_PREFIX + methodName;
sampSig_ = sampSig;
}
/**
* Makes a call to a callable client of the method represented by
* this operation with a given list of parameters.
* No checking is performed on the parameter list.
*
* This method should be private really, but abstract private is
* not permitted.
*
* @param client target callable client
* @param paramList parameters for call, assumed to be valid
*/
abstract void dispatch( CallableClient client, List paramList )
throws Exception;
/**
* Dispatches a callback to a CallableClient.
*
* @param callback callback acquired from the hub
* @param client client which should execute If the supplied identity information looks OK,
* then returning an empty array is a good idea.
* But if there is some kind of inconsistency or cause for alarm,
* a sequence of GUI elements may be returned.
*
* The return value is suitable for use as the Details of the implementation are arguable.
*
* @author Mark Taylor
* @since 22 Jul 2011
*/
public class UrlTracker {
private final Set permittedSet_;
private final Set blockedSet_;
private final String[] localhostNames_;
private final Logger logger_ =
Logger.getLogger( UrlTracker.class.getName() );
/**
* Constructor.
*/
public UrlTracker() {
permittedSet_ = new HashSet();
blockedSet_ = new HashSet();
// Set up a list of aliases for the local host so we can identify
// and prepare to restrict access to localhost URLs.
List localNameList = new ArrayList();
localNameList.add( "localhost" );
localNameList.add( "127.0.0.1" );
try {
InetAddress localAddr = InetAddress.getLocalHost();
localNameList.add( localAddr.getHostName() );
localNameList.add( localAddr.getHostAddress() );
localNameList.add( localAddr.getCanonicalHostName() );
}
catch ( UnknownHostException e ) {
logger_.log( Level.WARNING, "Can't determine local host name", e );
}
localhostNames_ = (String[]) localNameList.toArray( new String[ 0 ] );
}
/**
* Note that a URL has been communicated to a Web Profile client
* from the outside world.
*
* @param url incoming URL
*/
public synchronized void noteIncomingUrl( URL url ) {
if ( isSensitive( url ) ) {
if ( ! blockedSet_.contains( url ) ) {
if ( permittedSet_.add( url ) ) {
logger_.config( "Mark for translate permission URL "
+ url );
}
}
}
}
/**
* Note that a Web Profile client has communicated a URL to the
* outside world.
*
* @param url outgoing URL
*/
public synchronized void noteOutgoingUrl( URL url ) {
if ( isSensitive( url ) ) {
if ( ! permittedSet_.contains( url ) ) {
if ( blockedSet_.add( url ) ) {
logger_.config( "Mark for translate blocking URL " + url );
}
}
}
}
/**
* Indicates whether access to a given URL should be permitted,
* according to the strategy implemented by this class,
* from a Web Profile client.
*
* @param url URL to assess
* @return true iff permission to access is appropriate
*/
public synchronized boolean isUrlPermitted( URL url ) {
if ( isSensitive( url ) ) {
if ( permittedSet_.contains( url ) ) {
logger_.config( "Translation permitted for marked URL " + url );
return true;
}
else {
logger_.warning( "Translation denied for unmarked URL " + url );
return false;
}
}
else {
logger_.config( "Translation permitted for non-sensitive URL "
+ url );
return true;
}
}
/**
* Indicates whether a given URL is potentially sensitive.
* The current implementation always returns true.
* This is probably correct, since it's not in general possible
* to tell whether or not a given URL accords privileges to
* requests from the local host. But if this ends up letting
* too much through, identifying only file URLs and http/https
* ones on the local domain would probably be OK.
*
* @param url URL to assess
* @return true iff access should be restricted
*/
protected boolean isSensitive( URL url ) {
return true;
}
/**
* Determines whether a hostname appears to reference the localhost.
*
* @param host hostname from URL
* @return true iff host appears to be, or may be, local
*/
private boolean isLocalHost( String host ) {
if ( host == null || host.length() == 0 ) {
return true;
}
for ( int i = 0; i < localhostNames_.length; i++ ) {
if ( host.equalsIgnoreCase( localhostNames_[ i ] ) ) {
return true;
}
}
return false;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/UrlTrackerHubConnection.java 0000664 0000000 0000000 00000017516 13564500043 0027370 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* HubConnection wrapper implementation which intercepts all incoming
* and outgoing communications, scans them for URLs in the payload,
* and notifies a supplied UrlTracker object.
*
* @author Mark Taylor
* @since 22 Jul 2011
*/
class UrlTrackerHubConnection implements HubConnection {
private final HubConnection base_;
private final UrlTracker urlTracker_;
/**
* Constructor.
*
* @param base connection on which this one is based
* @param urlTracker tracker for URL usage
*/
public UrlTrackerHubConnection( HubConnection base,
UrlTracker urlTracker ) {
base_ = base;
urlTracker_ = urlTracker;
}
/**
* Recursively scans a SAMP map for items that look like URLs,
* and notifies the tracker that they are incoming.
* As a convenience the input map is returned.
*
* @param map map to scan
* @return the input map, unchanged
*/
private Map scanIncoming( Map map ) {
URL[] urls = scanForUrls( map );
for ( int iu = 0; iu < urls.length; iu++ ) {
urlTracker_.noteIncomingUrl( urls[ iu ] );
}
return map;
}
/**
* Recursively scans a SAMP map for items that look like URLs,
* and notifies the tracker that they are outgoing.
* As a convenience the input map is returned.
*
* @param map map to scan
* @return the input map, unchanged
*/
private Map scanOutgoing( Map map ) {
URL[] urls = scanForUrls( map );
for ( int iu = 0; iu < urls.length; iu++ ) {
urlTracker_.noteOutgoingUrl( urls[ iu ] );
}
return map;
}
/**
* Recursively scans a map for items that look like URLs and
* returns them as an array.
*
* @param map map to scan
* @return array of URLs found in Uses the following securityMap items:
* The sole instance of this singleton class is {@link #INSTANCE}.
*
* @author Mark Taylor
* @since 20 Jun 2016
*/
public class WebCredentialPresenter implements CredentialPresenter {
/** Singleton instance. */
public static final WebCredentialPresenter INSTANCE =
new WebCredentialPresenter();
/** Origin header. */
public static final String ORIGIN_HDR = "Origin";
/** Referer header. */
public static final String REFERER_HDR = "Referer";
private static final Logger logger_ =
Logger.getLogger( WebCredentialPresenter.class.getName() );
/**
* Private sole constructor prevents external instantiation.
*/
private WebCredentialPresenter() {
}
public Presentation
createPresentation( HttpServer.Request request, Map securityMap,
AuthResourceBundle.Content authContent )
throws SampException {
// Application self-declared name. Required, but doesn't prove much.
String appName = ClientAuthorizers.getAppName( securityMap );
// Application origin (see http://www.w3.org/TR/cors/,
// http://tools.ietf.org/html/draft-abarth-origin);
// present if CORS is in use.
Map headerMap = request.getHeaderMap();
String origin = HttpServer.getHeader( headerMap, ORIGIN_HDR );
// Referer header (RFC2616 sec 14.36) - present at whim of browser.
String referer = HttpServer.getHeader( headerMap, REFERER_HDR );
final Map map = new LinkedHashMap();
map.put( authContent.nameWord(), appName );
map.put( authContent.originWord(), origin );
map.put( "URL", referer );
final Object[] lines;
if ( referer != null && origin != null &&
! origin.equals( getOrigin( referer ) ) ) {
logger_.warning( "Origin/Referer header mismatch: "
+ "\"" + origin + "\" != "
+ "\"" + getOrigin( referer ) + "\"" );
lines = new Object[] {
"WARNING: Origin/Referer header mismatch!",
"WARNING: This looks suspicious.",
};
}
else {
lines = new Object[ 0 ];
}
return new Presentation() {
public Map getAuthEntries() {
return map;
}
public Object[] getAuthLines() {
return lines;
}
};
}
/**
* Returns the serialized origin for a given URI string.
* @see The Web Origin Concept
*
* @param uri URI
* @return origin of If the implementation is effectively prescribed, why is this
* abstract method here? It's tricky.
* The reason is so that reflective method invocation from this class
* is done by code within the actor implementation class itself
* rather than by code in the superclass, To prevent memory leaks, once any clients added to the returned
* server have been removed (the client count drops to zero), the
* server will be closed and cannot be re-used.
*
* @param server XML-RPC server
* @return new or re-used CallableClientServer which is installed on
* The {@link #main} method can be used to launch a hub from
* the command line. Use the Use this with care; publishing your lockfile means that other people
* can connect to your hub and potentially do disruptive things.
*
* @return lockfile information URL
*/
public URL publishLockfile() throws IOException {
if ( lockUrl_ == null ) {
ByteArrayOutputStream infoStrm = new ByteArrayOutputStream();
writeLockInfo( lockInfo_, infoStrm );
infoStrm.close();
final byte[] infoBuf = infoStrm.toByteArray();
URL url = UtilServer.getInstance().getResourceHandler()
.addResource( "samplock", new ServerResource() {
public long getContentLength() {
return infoBuf.length;
}
public String getContentType() {
return "text/plain";
}
public void writeBody( OutputStream out ) throws IOException {
out.write( infoBuf );
}
} );
// Attempt to replace whatever host name is used by the FQDN,
// for maximal usefulness to off-host clients.
try {
url = new URL( url.getProtocol(),
InetAddress.getLocalHost()
.getCanonicalHostName(),
url.getPort(), url.getFile() );
}
catch ( IOException e ) {
}
lockUrl_ = url;
}
return lockUrl_;
}
/**
* Used to generate the registration password. May be overridden.
*
* @return pasword
*/
public String createSecret() {
return Long.toHexString( random_.nextLong() );
}
/**
* Attempts to determine whether a given lockfile corresponds to a hub
* which is still alive.
*
* @param xClientFactory XML-RPC client factory implementation
* @param lockfile lockfile location
* @return true if the hub described at If the hub mode corresponds to one of the GUI options,
* one of two things will happen. An attempt will be made to install
* an icon in the "system tray"; if this is successful, the attached
* popup menu will provide options for displaying the hub window and
* for shutting it down. If no system tray is available, the hub window
* will be posted directly, and the hub will shut down when this window
* is closed. System tray functionality is only available when running
* under Java 1.6 or later, and when using a suitable display manager.
*
* @param hubMode hub mode
* @param xmlrpc XML-RPC implementation;
* automatically determined if null
* @return running hub
*/
public static HubRunner runHub( HubMode hubMode, XmlRpcKit xmlrpc )
throws IOException {
return runHub( hubMode, xmlrpc, null,
SampUtils
.urlToFile( StandardClientProfile.getLockUrl() ) );
}
/**
* Static method which may be used to start a SAMP hub programmatically,
* with a supplied samp.secret string.
* The returned hub is running ( If the hub mode corresponds to one of the GUI options,
* one of two things will happen. An attempt will be made to install
* an icon in the "system tray"; if this is successful, the attached
* popup menu will provide options for displaying the hub window and
* for shutting it down. If no system tray is available, the hub window
* will be posted directly, and the hub will shut down when this window
* is closed. System tray functionality is only available when running
* under Java 1.6 or later, and when using a suitable display manager.
*
* @param hubMode hub mode
* @param xmlrpc XML-RPC implementation;
* automatically determined if null
* @param secret samp.secret string to be used for hub connection;
* chosen at random if null
* @param lockfile location of lockfile to write,
* or null for lock to be provided by HTTP
* @return running hub
*/
public static HubRunner runHub( HubMode hubMode, XmlRpcKit xmlrpc,
final String secret, File lockfile )
throws IOException {
if ( xmlrpc == null ) {
xmlrpc = XmlRpcKit.getInstance();
}
HubRunner[] hubRunners = new HubRunner[ 1 ];
HubRunner runner =
new HubRunner( xmlrpc.getClientFactory(), xmlrpc.getServerFactory(),
hubMode.createHubService( random_, hubRunners ),
lockfile ) {
public String createSecret() {
return secret == null ? super.createSecret()
: secret;
}
};
hubRunners[ 0 ] = runner;
runner.start();
return runner;
}
/**
* Static method which will attempt to start a hub running in
* an external JVM. The resulting hub can therefore outlast the
* lifetime of the current application.
* Because of the OS interaction required, it's hard to make this
* bulletproof, and it may fail without an exception, but we do our best.
*
* @param hubMode hub mode
* @see #checkExternalHubAvailability
*/
public static void runExternalHub( HubMode hubMode ) throws IOException {
String classpath = System.getProperty( "java.class.path" );
if ( classpath == null || classpath.trim().length() == 0 ) {
throw new IOException( "No classpath available - JNLP context?" );
}
File javaHome = new File( System.getProperty( "java.home" ) );
File javaExec = new File( new File( javaHome, "bin" ), "java" );
String javacmd = ( javaExec.exists() && ! javaExec.isDirectory() )
? javaExec.toString()
: "java";
String[] propagateProps = new String[] {
XmlRpcKit.IMPL_PROP,
UtilServer.PORT_PROP,
SampUtils.LOCALHOST_PROP,
"java.awt.Window.locationByPlatform",
};
List argList = new ArrayList();
argList.add( javacmd );
for ( int ip = 0; ip < propagateProps.length; ip++ ) {
String propName = propagateProps[ ip ];
String propVal = System.getProperty( propName );
if ( propVal != null ) {
argList.add( "-D" + propName + "=" + propVal );
}
}
argList.add( "-classpath" );
argList.add( classpath );
argList.add( HubRunner.class.getName() );
argList.add( "-mode" );
argList.add( hubMode.toString() );
String[] args = (String[]) argList.toArray( new String[ 0 ] );
StringBuffer cmdbuf = new StringBuffer();
for ( int iarg = 0; iarg < args.length; iarg++ ) {
if ( iarg > 0 ) {
cmdbuf.append( ' ' );
}
cmdbuf.append( args[ iarg ] );
}
logger_.info( "Starting external hub" );
logger_.info( cmdbuf.toString() );
execBackground( args );
}
/**
* Attempts to determine whether an external hub can be started using
* {@link #runExternalHub}. If it can be determined that such an
* attempt would fail, this method will throw an exception with
* an informative message. This method succeeding is not a guarantee
* that an external hub can be started successfullly.
* The behaviour of this method is not expected to change over the
* lifetime of a given JVM.
*/
public static void checkExternalHubAvailability() throws IOException {
String classpath = System.getProperty( "java.class.path" );
if ( classpath == null || classpath.trim().length() == 0 ) {
throw new IOException( "No classpath available - JNLP context?" );
}
if ( System.getProperty( "jnlpx.jvm" ) != null ) {
throw new IOException( "Running under WebStart"
+ " - external hub not likely to work" );
}
}
/**
* Executes a command in a separate process, and discards any stdout
* or stderr output generated by it.
* Simply calling NOTE: it seems to be difficult to implement this method in a
* way which is faster than {@link #callAndWait} but does not cause
* problems elsewhere (incomplete HTTP responses). It is probably
* a good idea to avoid using it if possible.
*
* @param method XML-RPC method name
* @param params parameters for XML-RPC call (SAMP-compatible)
*/
void callAndForget( String method, List params ) throws IOException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcClientFactory.java 0000664 0000000 0000000 00000001041 13564500043 0030062 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
import java.net.URL;
/**
* Defines a factory which can create clients for communication with
* XML-RPC servers.
*
* @author Mark Taylor
* @since 16 Sep 2008
*/
public interface SampXmlRpcClientFactory {
/**
* Returns an XML-RPC client implementation.
*
* @param endpoint XML-RPC server endpoint
* @return client which can communicate with the given endpoint
*/
SampXmlRpcClient createClient( URL endpoint ) throws IOException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcHandler.java 0000664 0000000 0000000 00000002521 13564500043 0026675 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.util.List;
/**
* Interface for an object which can process certain XML-RPC requests.
* Used by {@link SampXmlRpcServer}.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public interface SampXmlRpcHandler {
/**
* Returns true if this handler should be able to process
* given XML-RPC method.
*
* @param method method name
*/
boolean canHandleCall( String method );
/**
* Processes an XML-RPC call. This method should only be called if
* {@link #canHandleCall canHandleCall(method)} returns true.
* The Use this with care; publishing your lockfile means that other people
* can connect to your hub and potentially do disruptive things.
*
* @return lockfile information URL
*/
public URL publishLockfile() throws IOException {
if ( lockUrl_ == null ) {
ByteArrayOutputStream infoStrm = new ByteArrayOutputStream();
writeLockInfo( lockInfo_, infoStrm );
infoStrm.close();
final byte[] infoBuf = infoStrm.toByteArray();
URL url = UtilServer.getInstance().getResourceHandler()
.addResource( "samplock", new ServerResource() {
public long getContentLength() {
return infoBuf.length;
}
public String getContentType() {
return "text/plain";
}
public void writeBody( OutputStream out ) throws IOException {
out.write( infoBuf );
}
} );
// Attempt to replace whatever host name is used by the FQDN,
// for maximal usefulness to off-host clients.
try {
url = new URL( url.getProtocol(),
InetAddress.getLocalHost()
.getCanonicalHostName(),
url.getPort(), url.getFile() );
}
catch ( IOException e ) {
}
lockUrl_ = url;
}
return lockUrl_;
}
/**
* Returns a string suitable for use as a Standard Profile Secret.
*
* @return new secret
*/
public static synchronized String createSecret() {
return Long.toHexString( random_.nextLong() );
}
/**
* Attempts to determine whether a given lockfile corresponds to a hub
* which is still alive.
*
* @param xClientFactory XML-RPC client factory implementation
* @param lockfile lockfile location
* @return true if the hub described at This is an " )
.append( "XML-RPC server.\n" )
.append( " Try POSTing.host
is known to be the local host
*/
private static boolean isLocalHost( String host ) {
if ( host == null ) {
return false;
}
if ( SampUtils.getLocalhost().equals( host ) ) {
return true;
}
try {
InetAddress hostAddr = InetAddress.getByName( host );
return hostAddr != null
&& ( hostAddr.isLoopbackAddress() ||
hostAddr.equals( InetAddress.getLocalHost() ) );
}
catch ( UnknownHostException e ) {
return false;
}
}
/**
* Main method. Runs a bridge.
*/
public static void main( String[] args ) throws IOException {
// Unless specially requested, make sure that the local host
// is referred to by something publicly useful, not the loopback
// address, which would be no good if there will be communications
// to/from an external host.
String hostspec = System.getProperty( SampUtils.LOCALHOST_PROP );
if ( hostspec == null ) {
System.setProperty( SampUtils.LOCALHOST_PROP, "[hostname]" );
}
// Run the application.
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
/**
* Does the work for the main method.
* Use -help flag.
*/
public static int runMain( String[] args ) throws IOException {
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " )
.append( Bridge.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( " [-[no]exporturls]" )
.append( "\n " )
.append( " [-nostandard]" )
.append( " [-sampdir inImage
*/
public abstract RenderedImage adjustImage( BufferedImage inImage );
/**
* Returns a URL at which the dynamically adjusted version of the icon
* at the given URL will be served.
*
* @param iconUrl URL of existing icon (GIF, PNG or JPEG)
*/
public URL exportAdjustedIcon( URL iconUrl ) {
try {
return new URL( baseUrl_ + "?"
+ SampUtils.uriEncode( iconUrl.toString() ) );
}
catch ( MalformedURLException e ) {
throw (AssertionError) new AssertionError().initCause( e );
}
}
/**
* Returns the URL at which the underlying icon for the one represented
* by the given server path. The resourcePath
should be
* the path part of a URL returned from an earlier call to
* {@link #exportAdjustedIcon}.
*
* @param resourcePath path part of a URL requesting an adjusted icon
* @return original icon corresponding to resourcePath, or null
* if it doesn't look like a path this object dispensed
*/
private URL getOriginalUrl( String resourcePath )
throws MalformedURLException {
// If there's no query part, it's not one of ours.
URL resourceUrl = new URL( baseUrl_, resourcePath );
String query = resourceUrl.getQuery();
if ( query == null ) {
return null;
}
else {
// If the base does not match our base URL, it's not one of ours.
String base = resourceUrl.toString();
base = base.substring( 0, base.length() - query.length() );
if ( ! base.equals( baseUrl_.toString() + "?" ) ) {
return null;
}
// Otherwise, try to interpret the query as a URL.
// It should be the URL of the original icon.
// If it's not, an exception will result.
String qurl;
try {
qurl = SampUtils.uriDecode( query );
}
catch ( RuntimeException e ) {
throw (MalformedURLException)
new MalformedURLException().initCause( e );
}
return new URL( qurl );
}
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
URL baseIconUrl;
try {
baseIconUrl = getOriginalUrl( request.getUrl() );
}
catch ( MalformedURLException e ) {
return HttpServer.createErrorResponse( 404, "Not found", e );
}
if ( baseIconUrl == null ) {
return null;
}
// Prepare the headers.
Map hdrMap = new HashMap();
hdrMap.put( "Content-Type", OUTPUT_MIME_TYPE );
// Generate the response object.
String method = request.getMethod();
if ( "HEAD".equals( method ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) {
}
};
}
else if ( "GET".equals( method ) ) {
BufferedImage baseImage;
try {
baseImage = ImageIO.read( baseIconUrl );
if ( baseImage == null ) {
throw new FileNotFoundException( baseIconUrl.toString() );
}
}
catch ( IOException e ) {
return HttpServer
.createErrorResponse( 500, "Server I/O error", e );
}
final RenderedImage adjustedImage = adjustImage( baseImage );
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out )
throws IOException {
ImageIO.write( adjustedImage, OUTPUT_FORMAT_NAME, out );
}
};
}
else {
return HttpServer
.create405Response( new String[] { "GET", "HEAD" } );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/ProxyManager.java 0000664 0000000 0000000 00000076510 13564500043 0025725 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.bridge;
import java.awt.Image;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.client.TrackedClientSet;
import org.astrogrid.samp.httpd.HttpServer;
import org.astrogrid.samp.httpd.UtilServer;
/**
* Takes care of client connections for the SAMP Bridge.
* An instance of this class is associated with a given 'local' hub
* participating in the bridge, and makes the following connections:
*
*
* Callbacks from the hub to the proxy clients can be tunnelled by this
* proxy manager to their true destination on the local hub.
* Note that each proxy manager needs the cooperation of all the other
* proxy managers (the ones associated with the other bridged hubs) to
* make this work, so each instance of this class must be made aware of
* the other ProxyMangers before use (see {@link #init}).
*
* @author Mark Taylor
* @since 15 Jul 2009
*/
class ProxyManager {
private final ClientProfile localProfile_;
private final UtilServer server_;
private final HubConnector pmConnector_;
private final Map connectionMap_; // local client ID -> HubConnection[]
private final Map tagMap_;
private final IconAdjuster iconAdjuster_;
private ProxyManager[] remoteManagers_;
private UrlExporter exporter_;
private boolean useProxyHub_;
private int nRemote_;
private static final Logger logger_ =
Logger.getLogger( ProxyManager.class.getName() );
/**
* Constructor.
*
* @param localProfile profile for connection to this manager's local hub
* @param server server instance
*/
public ProxyManager( ClientProfile localProfile, UtilServer server ) {
localProfile_ = localProfile;
server_ = server;
// Set up the local hub connection to monitor client list changes.
pmConnector_ =
new HubConnector( localProfile, new ProxyManagerClientSet() ) {
protected void connectionChanged( boolean isConnected ) {
super.connectionChanged( isConnected );
managerConnectionChanged( isConnected );
}
};
Metadata meta = new Metadata();
meta.setName( "bridge" );
meta.setDescriptionText( "Bridge between hubs" );
try {
meta.setIconUrl( server_
.exportResource( "/org/astrogrid/samp/images/"
+ "bridge.png" )
.toString() );
}
catch ( IOException e ) {
logger_.warning( "Couldn't set icon" );
}
meta.put( "author.name", "Mark Taylor" );
meta.put( "author.email", "m.b.taylor@bristol.ac.uk" );
pmConnector_.declareMetadata( meta );
Subscriptions subs = pmConnector_.computeSubscriptions();
pmConnector_.declareSubscriptions( subs );
// Set up other required data structures.
connectionMap_ = Collections.synchronizedMap( new HashMap() );
tagMap_ = Collections.synchronizedMap( new HashMap() );
iconAdjuster_ = new ProxyIconAdjuster();
server_.getServer().addHandler( iconAdjuster_ );
}
/**
* Returns the profile for this manager's local hub.
*
* @return profile
*/
public ClientProfile getProfile() {
return localProfile_;
}
/**
* Returns the hub connector used by this manager for client monitoring
* on the local hub.
*
* @return hub connector
*/
public HubConnector getManagerConnector() {
return pmConnector_;
}
/**
* Sets an object which is used to export SAMP data contents for use
* in remote contexts.
*
* @param exporter new exporter; may be null
*/
public void setExporter( UrlExporter exporter ) {
exporter_ = exporter;
}
/**
* Sets whether remote proxy should be generated for the local client
* representing the local hub.
* Default is not, since they are not very interesting to talk to.
*
* @param useProxyHub true iff the client representing the local hub
* should be proxied remotely
*/
public void setUseProxyHub( boolean useProxyHub ) {
useProxyHub_ = useProxyHub;
}
/**
* Prepares this manager for use by informing it about all its sibling
* managers. This must be done before the bridge can start operations.
*
* @param allManagers array of ProxyManagers including this one,
* one for each hub participating in the bridge
*/
public void init( ProxyManager[] allManagers ) {
// Store an array of all the other managers, excluding this one,
// for later use.
List remoteList = new ArrayList();
int selfCount = 0;
for ( int im = 0; im < allManagers.length; im++ ) {
ProxyManager pm = allManagers[ im ];
if ( pm == this ) {
selfCount++;
}
else {
remoteList.add( pm );
}
}
if ( selfCount != 1 ) {
throw new IllegalArgumentException( "Self not in list once" );
}
remoteManagers_ =
(ProxyManager[]) remoteList.toArray( new ProxyManager[ 0 ] );
nRemote_ = remoteManagers_.length;
assert nRemote_ == allManagers.length - 1;
}
public String toString() {
return localProfile_.toString();
}
/**
* Returns the connection on the hub associated with a remote
* proxy manager which is the proxy for a given local client.
*
* @param remoteManager proxy manager for a remote bridged hub
* @param localClientId client ID of a client registered with
* this manager's local hub
* @return proxy connection
*/
private HubConnection getProxyConnection( ProxyManager remoteManager,
String localClientId ) {
HubConnection[] proxyConnections =
(HubConnection[]) connectionMap_.get( localClientId );
return proxyConnections == null
? null
: proxyConnections[ getManagerIndex( remoteManager ) ];
}
/**
* Deletes the record of the connection on the hub associated with
* a remote proxy manager which is the proxy for a given local client.
* This proxy can no longer be used.
*
* @param remoteManager proxy manager for a remote bridged hub
* @param localClientId client ID of a client registered with
* this manager's local hub
*/
private void removeProxyConnection( ProxyManager remoteManager,
String localClientId ) {
HubConnection[] proxyConnections =
(HubConnection[]) connectionMap_.get( localClientId );
if ( proxyConnections != null ) {
proxyConnections[ getManagerIndex( remoteManager ) ] = null;
}
}
/**
* Returns the index by which this manager labels a given remote
* proxy manager.
*
* @param remoteManager manager to locate
* @return index of remoteManager
in the list
*/
private int getManagerIndex( ProxyManager remoteManager ) {
return Arrays.asList( remoteManagers_ ).indexOf( remoteManager );
}
/**
* Returns the metadata to use for the remote proxy of a local client.
* This resembles the metadata of the local client itself, but may
* have some adjustments.
*
* @param localClient local client
* @return metadata to use for client's remote proxy
*/
private Metadata getProxyMetadata( Client localClient ) {
Metadata meta = localClient.getMetadata();
if ( meta == null ) {
return null;
}
else {
meta = new Metadata( meta );
if ( exporter_ != null ) {
exporter_.exportMap( meta );
}
meta.setName( proxyName( meta.getName() ) );
if ( meta.getIconUrl() != null ) {
URL iconUrl = proxyIconUrl( meta.getIconUrl() );
meta.setIconUrl( iconUrl == null ? null : iconUrl.toString() );
}
meta.put( "bridge.proxy.source", ProxyManager.this.toString() );
}
return meta;
}
/**
* Returns the name to be used for a proxy client given its local name.
*
* @param localName local name
* @return proxy name
*/
private String proxyName( String localName ) {
return localName == null ? "(proxy)"
: localName + " (proxy)";
}
/**
* Returns the icon to be used for a proxy client given its local icon.
*
* @param localIconUrl URL for local icon
* @return URL for proxy icon
*/
private URL proxyIconUrl( URL localIconUrl ) {
return localIconUrl != null
? iconAdjuster_.exportAdjustedIcon( localIconUrl )
: localIconUrl;
}
/**
* Returns the subscriptions to use for the remote proxy of a local client.
* This resembles the subscriptions of the local client itself, but may
* have some adjustments.
*
* @param localClient local client
* @return subscriptions to use for client's remote proxy
*/
private Subscriptions getProxySubscriptions( Client client ) {
Subscriptions subs = client.getSubscriptions();
if ( subs == null ) {
return null;
}
else {
// Remove subscriptions to most hub administrative MTypes.
// These should not be delivered from the remote hub to the
// local client, since the local client should only receive
// such messages from its own hub. Note this does not mean
// that the local client will not be informed about changes
// to clients on the remote hubs; this information will be
// relayed by the local hub as a consequence of proxies from
// other ProxyManagers making register/declare/etc calls
// on this manager's local hub.
subs = new Subscriptions( subs );
subs.remove( "samp.hub.event.shutdown" );
subs.remove( "samp.hub.event.register" );
subs.remove( "samp.hub.event.unregister" );
subs.remove( "samp.hub.event.metadata" );
subs.remove( "samp.hub.event.subscriptions" );
if ( exporter_ != null ) {
exporter_.exportMap( subs );
}
return subs;
}
}
/**
* Called when this ProxyManager's connector has been disconnected
* (for whatever reason) from its local hub.
* It makes sure that any proxies from other ProxyManagers to the local
* hub are unregistered, so that no further bridge activity takes
* place on the local hub.
*
* @param isConnected true for a connection; false for a disconnection
*/
protected void managerConnectionChanged( boolean isConnected ) {
if ( ! isConnected ) {
for ( int ir = 0; ir < nRemote_; ir++ ) {
ProxyManager remoteManager = remoteManagers_[ ir ];
int im = remoteManager.getManagerIndex( this );
for ( Iterator it = remoteManager.connectionMap_.values()
.iterator();
it.hasNext(); ) {
HubConnection[] connections = (HubConnection[]) it.next();
if ( connections != null ) {
HubConnection connection = connections[ im ];
if ( connection != null ) {
connections[ im ] = null;
try {
connection.unregister();
}
catch ( SampException e ) {
logger_.info( "Unregister failed" );
}
}
}
}
}
}
else {
// not expected except for initial connection
}
}
/**
* Invoked when a client is added to the local hub.
*
* @param client newly added client
*/
private void localClientAdded( Client client ) {
if ( ! isProxiedClient( client ) ) {
return;
}
// Register a proxy for the new local client on all the remote hubs
// in the bridge.
Metadata meta = getProxyMetadata( client );
Subscriptions subs = getProxySubscriptions( client );
HubConnection[] proxyConnections = new HubConnection[ nRemote_ ];
connectionMap_.put( client.getId(), proxyConnections );
for ( int ir = 0; ir < nRemote_; ir++ ) {
ProxyManager remoteManager = remoteManagers_[ ir ];
try {
// This synchronization is here so that the isProxy method
// can work reliably. isProxy may ask whether a client seen
// on a remote hub is a proxy controlled by this one.
// It can only ask after the registration has been done,
// and the determination is synchronized on connectionMap_.
// By synchronizing here, we can ensure that it can't ask
// after the registration, but before the information has
// been recorded in the connectionMap.
final HubConnection proxyConnection;
synchronized ( connectionMap_ ) {
proxyConnection = remoteManager.getProfile().register();
if ( proxyConnection != null ) {
CallableClient callable =
new ProxyCallableClient( client, proxyConnection,
remoteManager );
proxyConnection.setCallable( callable );
proxyConnections[ ir ] = proxyConnection;
}
}
if ( proxyConnection != null ) {
if ( meta != null ) {
try {
proxyConnection.declareMetadata( meta );
}
catch ( SampException e ) {
logger_.warning( "proxy declareMetadata failed for "
+ client );
}
}
if ( subs != null ) {
try {
proxyConnection.declareSubscriptions( subs );
}
catch ( SampException e ) {
logger_.warning( "proxy declareSubscriptions failed"
+ " for " + client );
}
}
}
}
catch ( SampException e ) {
logger_.warning( "proxy registration failed for " + client );
}
}
}
/**
* Invoked when a client is removed from the local hub.
*
* @param client recently removed client
*/
private void localClientRemoved( Client client ) {
if ( ! isProxiedClient( client ) ) {
return;
}
// Remove all the proxies which were registered on remote hubs
// on behalf of the removed client.
HubConnection[] proxyConnections =
(HubConnection[]) connectionMap_.remove( client.getId() );
if ( proxyConnections != null ) {
for ( int ir = 0; ir < nRemote_; ir++ ) {
HubConnection connection = proxyConnections[ ir ];
if ( connection != null ) {
try {
connection.unregister();
}
catch ( SampException e ) {
logger_.warning( "proxy unregister failed for "
+ client );
}
}
}
}
}
/**
* Invoked when information (metadata or subscriptions) have been
* updated for a client on the local hub.
*
* @param client updated client
* @param metaChanged true if metadata may have changed
* (false if known unchanged)
* @param subsChanged true if subscriptions may have changed
* (false if known unchanged)
*/
private void localClientUpdated( Client client, boolean metaChanged,
boolean subsChanged ) {
if ( ! isProxiedClient( client ) ) {
return;
}
// Cause each of the local client's proxies on remote hubs to
// declare subscription/metadata updates appropriately.
HubConnection[] proxyConnections =
(HubConnection[]) connectionMap_.get( client.getId() );
Metadata meta =
metaChanged ? getProxyMetadata( client ) : null;
Subscriptions subs =
subsChanged ? getProxySubscriptions( client ) : null;
if ( proxyConnections != null ) {
for ( int ir = 0; ir < nRemote_; ir++ ) {
HubConnection connection = proxyConnections[ ir ];
if ( connection != null ) {
if ( meta != null ) {
try {
connection.declareMetadata( meta );
}
catch ( SampException e ) {
logger_.warning( "proxy declareMetadata failed "
+ "for " + client );
}
}
if ( subs != null ) {
try {
connection.declareSubscriptions( subs );
}
catch ( SampException e ) {
logger_.warning( "proxy declareSubscriptions "
+ "failed for " + client );
}
}
}
}
}
}
/**
* Determines whether a local client is a genuine third party client
* which requires a remote proxy. Will return false for clients which
* are operating on behalf of this bridge, including the ProxyManager's
* client tracking connection and any proxies controlled by remote
* ProxyManagers. Unless useProxyHub is true, will also return false
* for the hub client on remote hubs, since these are not very
* interesting to talk to.
*
* @param client local client
* @param true if client
has or should have a proxy;
* false if it's an organ of the bridge administration
*/
private boolean isProxiedClient( Client client ) {
// Is it a client on the local hub that we want to exclude?
try {
if ( pmConnector_.isConnected() ) {
HubConnection connection = pmConnector_.getConnection();
if ( connection != null ) {
String clientId = client.getId();
RegInfo regInfo = connection.getRegInfo();
if ( clientId.equals( regInfo.getSelfId() ) ||
( ! useProxyHub_ &&
clientId.equals( regInfo.getHubId() ) ) ) {
return false;
}
}
}
}
catch ( SampException e ) {
}
// Is it a proxy controlled by one of the remote managers?
for ( int ir = 0; ir < nRemote_; ir++ ) {
if ( remoteManagers_[ ir ].isProxy( client, ProxyManager.this ) ) {
return false;
}
}
// No, then it's a genuine local client requiring a proxy.
return true;
}
/**
* Determines whether a given local client is a proxy controlled by
* a given remote ProxyManager.
*
* @param client local client
* @param remoteManager remote proxy manager
* @return true iff client
is one of
* remoteManager
's proxies
*/
private boolean isProxy( Client client, ProxyManager remoteManager ) {
int ir = getManagerIndex( remoteManager );
synchronized ( connectionMap_ ) {
for ( Iterator it = connectionMap_.values().iterator();
it.hasNext(); ) {
HubConnection[] proxyConnections = (HubConnection[]) it.next();
if ( proxyConnections != null ) {
HubConnection proxyConnection = proxyConnections[ ir ];
if ( proxyConnection != null ) {
RegInfo proxyReg = proxyConnection.getRegInfo();
if ( proxyReg.getSelfId().equals( client.getId() ) ) {
return true;
}
}
}
}
}
return false;
}
/**
* CallableClient implementation used by remote proxy connections on
* behalf of local clients. This is the core of the proxy manager.
* Callbacks received by the remote proxy client are tunnelled back
* to the local hub and forwarded by the local proxy of the remote
* sender client to the appropriate local non-proxy client.
* Since local proxies are managed by other proxy managers
* (this one manages remote proxies of local clients)
* this means getting the other proxy managers to do some of the work.
*/
private class ProxyCallableClient implements CallableClient {
private final String localClientId_;
private final HubConnection remoteProxy_;
private final ProxyManager remoteManager_;
private final ProxyManager localManager_;
/**
* Constructor.
*
* @param localClient local client
* @param remoteProxy hub connection to the remote hub for the proxy
* @param remoteManager remote ProxyManager associated with the
* hub where this proxy is connected
*/
ProxyCallableClient( Client localClient, HubConnection remoteProxy,
ProxyManager remoteManager ) {
localClientId_ = localClient.getId();
remoteProxy_ = remoteProxy;
remoteManager_ = remoteManager;
localManager_ = ProxyManager.this;
}
public void receiveNotification( String remoteSenderId, Message msg )
throws SampException {
// Forward the notification.
if ( remoteManager_.exporter_ != null ) {
msg = new Message( msg );
remoteManager_.exporter_.exportMap( msg );
}
HubConnection localProxy = getLocalProxy( remoteSenderId );
if ( localProxy != null ) {
localProxy.notify( localClientId_, msg );
}
proxyProcessMessage( remoteSenderId, msg );
}
public void receiveCall( String remoteSenderId, String remoteMsgId,
Message msg )
throws SampException {
// Choose a tag; use the message ID as its value.
// These things are different, but we are free to choose any
// form for the tag, and we need something which will allow
// us to recover the message ID from it later.
// Making them identical is the easiest way to do that.
String localMsgTag = remoteMsgId;
// Forward the call.
if ( remoteManager_.exporter_ != null ) {
msg = new Message( msg );
remoteManager_.exporter_.exportMap( msg );
}
HubConnection localProxy = getLocalProxy( remoteSenderId );
if ( localProxy != null ) {
localProxy.call( localClientId_, localMsgTag, msg );
}
else {
ErrInfo errInfo = new ErrInfo();
errInfo.setErrortxt( "Bridge can't forward message" );
Client senderClient =
(Client) remoteManager_.getManagerConnector()
.getClientMap().get( remoteSenderId );
String usertxt = new StringBuffer()
.append( "Bridge can't forward message to recipient;\n" )
.append( "sender client " )
.append( senderClient )
.append( " has no proxy on remote hub" )
.toString();
errInfo.setUsertxt( usertxt );
new ErrInfo( "Client " + remoteSenderId + " not present"
+ " on other side of bridge" );
remoteProxy_.reply( remoteMsgId,
Response.createErrorResponse( errInfo ) );
}
proxyProcessMessage( remoteSenderId, msg );
}
public void receiveResponse( String remoteResponderId,
String remoteMsgTag, Response response )
throws SampException {
// The message ID we need for forwarding is the one we encoded
// (by identity) earlier in the tag.
String localMsgId = remoteMsgTag;
// Forward the reply appropriately.
if ( remoteManager_.exporter_ != null ) {
response = new Response( response );
remoteManager_.exporter_.exportMap( response );
}
HubConnection localProxy = getLocalProxy( remoteResponderId );
if ( localProxy != null ) {
localProxy.reply( localMsgId, response );
}
else {
// Should only happen if the proxy has been disconnected
// between send and receive.
logger_.warning( "Bridge can't forward response: "
+ " missing proxy" );
}
}
/**
* Returns the hub connection for the proxy on the local hub
* which corresponds to a given remote client.
*
* @param remoteClientId client ID of remote client
* @return hub connection for local proxy
*/
private HubConnection getLocalProxy( String remoteClientId ) {
return remoteManager_
.getProxyConnection( localManager_, remoteClientId );
}
/**
* Performs housekeeping tasks for an incoming message if any.
* This is in addition to forwarding the message to the client
* for which we are proxying.
*
* @param remoteSenderId id of sending client on remote hub
* @param msg message
*/
private void proxyProcessMessage( String remoteSenderId, Message msg ) {
String mtype = msg.getMType();
boolean fromHub =
remoteSenderId.equals( remoteProxy_.getRegInfo().getHubId() );
if ( "samp.hub.disconnect".equals( mtype ) ) {
if ( fromHub ) {
removeProxyConnection( remoteManager_, localClientId_ );
}
else {
logger_.warning( mtype + " from non-hub client "
+ remoteSenderId + " - ignored" );
}
}
}
}
/**
* TrackedClientSet implementation used by a Proxy Manager.
* Apart from inheriting default behaviour, this triggers
* calls to ProxyManager methods when there are status changes
* to local clients.
*/
private class ProxyManagerClientSet extends TrackedClientSet {
/**
* Constructor.
*/
private ProxyManagerClientSet() {
super();
}
public void addClient( Client client ) {
super.addClient( client );
localClientAdded( client );
}
public void removeClient( Client client ) {
localClientRemoved( client );
super.removeClient( client );
}
public void updateClient( Client client,
boolean metaChanged, boolean subsChanged ) {
super.updateClient( client, metaChanged, subsChanged );
localClientUpdated( client, metaChanged, subsChanged );
}
public void setClients( Client[] clients ) {
for ( Iterator it = getClientMap().entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Client client = (Client) entry.getValue();
localClientRemoved( client );
}
super.setClients( clients );
for ( int i = 0; i < clients.length; i++ ) {
Client client = clients[ i ];
localClientAdded( client );
}
}
}
/**
* Class which can turn a client's icon into the icon for the proxy of
* the same client. Some visually distinctive adjustment is made to
* make it obvious from the icon that it's a proxy.
*/
private class ProxyIconAdjuster extends IconAdjuster {
/**
* Constructor.
*/
ProxyIconAdjuster() {
super( server_.getServer(),
server_.getBasePath( "proxy" + "-" + localProfile_ ) );
}
public RenderedImage adjustImage( BufferedImage inImage ) {
int w = inImage.getWidth();
int h = inImage.getHeight();
// Copy the image to a new image. It would be possible to write
// directly into the input BufferedImage, but this might not
// have the correct image type, so could end up getting the
// transparency wrong or something.
BufferedImage outImage =
new BufferedImage( w, h, BufferedImage.TYPE_4BYTE_ABGR );
Graphics2D g2 = outImage.createGraphics();
g2.drawImage( inImage, null, 0, 0 );
// Slice off half of the image diagonally.
int[] xs = new int[] { 0, w, w, };
int[] ys = new int[] { h, h, 0, };
Composite compos = g2.getComposite();
g2.setComposite( AlphaComposite.Clear );
g2.fillPolygon( xs, ys, 3 );
g2.setComposite( compos );
// Return the result.
return outImage;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/UrlExporter.java 0000664 0000000 0000000 00000012430 13564500043 0025573 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.bridge;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.astrogrid.samp.httpd.UtilServer;
/**
* Exports SAMP data objects which have been created on a given host
* for use in a remote context. The job that needs doing is to
* convert URLs which reference the host in a way that only makes sense
* locally (as a loopback address 127.0.0.1 or localhost) to a form
* which can be used on remote hosts.
*
* host
's filesystem(s)
*/
public UrlExporter( String host, boolean exportFiles ) {
host_ = host;
exportFiles_ = exportFiles;
}
/**
* Exports a single string for remote usage.
* If it looks like a URL, it's changed. Not foolproof.
*
* @param text string to assess
* @return copy of text if it's not a URL, otherwise a possibly
* edited URL with the same content
*/
public String exportString( String text ) {
String t2 = doExportString( text );
if ( t2 != null && ! t2.equals( text ) ) {
logger_.info( "Exported string \"" + text + "\" -> \"" + t2 + '"' );
}
return t2;
}
/**
* Does the work for {@link #exportString}.
*
* @param text string to assess
* @return copy of text if it's not a URL, otherwise a URL with a
* possibly edited host part
*/
private String doExportString( String text ) {
Matcher localMatcher = LOCALHOST_REGEX.matcher( text );
if ( localMatcher.matches() ) {
return localMatcher.group( 1 ) + host_ + localMatcher.group( 3 );
}
else if ( exportFiles_ && FILE_REGEX.matcher( text ).matches() ) {
try {
URL fileUrl = new URL( text );
String path = fileUrl.getPath();
if ( File.separatorChar != '/' ) {
path = path.replace( '/', File.separatorChar );
}
File file = new File( path );
if ( file.canRead() && ! file.isDirectory() ) {
URL expUrl = UtilServer.getInstance()
.getMapperHandler()
.addLocalUrl( fileUrl );
if ( expUrl != null ) {
return expUrl.toString();
}
}
}
catch ( MalformedURLException e ) {
// not a URL at all - don't attempt to export it
}
catch ( IOException e ) {
// something else went wrong - leave alone
}
return text;
}
else {
return text;
}
}
/**
* Exports a list for remote usage by changing its contents in place.
*
* @param list list to edit
*/
public void exportList( List list ) {
for ( ListIterator it = list.listIterator(); it.hasNext(); ) {
Object value = it.next();
if ( value instanceof String ) {
it.set( exportString( (String) value ) );
}
else if ( value instanceof List ) {
exportList( (List) value );
}
else if ( value instanceof Map ) {
exportMap( (Map) value );
}
}
}
/**
* Exports a map for remote usage by changing its contents in place.
*
* @param map map to edit
*/
public void exportMap( Map map ) {
for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue();
if ( value instanceof String ) {
entry.setValue( exportString( (String) value ) );
}
else if ( value instanceof List ) {
exportList( (List) value );
}
else if ( value instanceof Map ) {
exportMap( (Map) value );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/package.html 0000664 0000000 0000000 00000000306 13564500043 0024715 0 ustar 00root root 0000000 0000000 samp.result
part
* of the call response, that is the MType-specific return value
* name->value map.
* As a special case, returning null is equivalent to returning an empty
* map.
* However, if {@link #createResponse} is overridden, the return value
* semantics may be different.
*
* @param connection hub connection
* @param senderId public ID of sender client
* @param message message with MType this handler is subscribed to
* @return result of handling this message; exact semantics determined
* by {@link #createResponse createResponse} implementation
*/
public abstract Map processCall( HubConnection connection, String senderId,
Message message )
throws Exception;
/**
* Invoked by {@link #receiveCall receiveCall} to create a success
* response from the result of calling {@link #processCall processCall}.
*
* reply
* method at some future point.
*
* @param senderId public ID of sending client
* @param msgId message identifier for later use with reply
* @param message message
*/
void receiveCall( String senderId, String msgId, Message message )
throws Exception;
/**
* Receives a response to a message previously sent by this client.
*
* @param responderId public ID of responding client
* @param msgTag client-defined tag labelling previously-sent message
* @param response returned response object
*/
void receiveResponse( String responderId, String msgTag,
Response response ) throws Exception;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ClientProfile.java 0000664 0000000 0000000 00000003005 13564500043 0026057 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
/**
* Defines an object that can be used to register with a running SAMP hub.
* Registration takes the form of providing a connection object which
* a client can use to perform further hub interactions.
* Client-side implementations will take care of communications,
* mapping between profile-specific transport mechanisms and the
* methods of the generated {@link HubConnection} objects.
*
* System.currentTimeMillis()
at construction
*/
public long getBirthday() {
return birthday_;
}
public String toString() {
return "message " + mtype_ + " for client " + id_;
}
}
/**
* Data structure for holding ClientOperation objects which (may) need
* to be applied in the future.
* Operations are dumped here if they cannot be performed immediately
* because the client in question is not (yet) known by this tracker.
* The hope is that the client will register at some point in the future
* and the pending operations can be applied then. However, this may
* never happen, so the queue maintains its own expiry system to throw
* out old events.
*/
private static class OperationQueue {
private final Collection opList_;
private Timer tidyTimer_;
/**
* Constructor.
*/
OperationQueue() {
opList_ = new ArrayList();
}
/**
* Add a new client operation which may get the opportunity to be
* performed some time in the future.
*
* @param op oeration to add
*/
public synchronized void add( ClientOperation op ) {
if ( tidyTimer_ == null ) {
TimerTask tidy = new TimerTask() {
public void run() {
discardOld( QUEUE_TIME );
}
};
tidyTimer_ = new Timer( true );
tidyTimer_.schedule( tidy, QUEUE_TIME, QUEUE_TIME );
}
opList_.add( op );
}
/**
* Apply any pending operations to given client.
* This client was presumably unavailable at the time such operations
* were queued.
*
* @param client client to apply pending operations to
*/
public synchronized void apply( TrackedClient client ) {
String id = client.getId();
for ( Iterator it = opList_.iterator(); it.hasNext(); ) {
ClientOperation op = (ClientOperation) it.next();
if ( op.getId().equals( id ) ) {
logger_.info( "Performing queued " + op );
op.perform( client );
it.remove();
}
}
}
/**
* Discards any operations corresponding to a given client,
* presumably because the client is about to disappear.
*
* @param client client to forget about
*/
public synchronized void discard( TrackedClient client ) {
String id = client.getId();
for ( Iterator it = opList_.iterator(); it.hasNext(); ) {
ClientOperation op = (ClientOperation) it.next();
if ( op.getId().equals( id ) ) {
logger_.warning( "Discarding queued " + op );
it.remove();
}
}
}
/**
* Throws away any pending operations which are older than a certain
* age, presumably in the expectation that their client will never
* register.
*
* @param maxAge oldest operations (in milliseconds) permitted to
* remain in the queue
*/
public synchronized void discardOld( long maxAge ) {
long now = System.currentTimeMillis();
for ( Iterator it = opList_.iterator(); it.hasNext(); ) {
ClientOperation op = (ClientOperation) it.next();
if ( now - op.getBirthday() > maxAge ) {
logger_.warning( "Discarding queued " + op
+ " - client never showed up" );
it.remove();
}
}
}
/**
* Removes all entries from this queue.
*/
public synchronized void clear() {
opList_.clear();
if ( tidyTimer_ != null ) {
tidyTimer_.cancel();
tidyTimer_ = null;
}
}
/**
* Returns an array containing all the operations currently pending.
*
* @return operation list
*/
public synchronized ClientOperation[] getOperations() {
return (ClientOperation[])
opList_.toArray( new ClientOperation[ 0 ] );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/DefaultClientProfile.java 0000664 0000000 0000000 00000011505 13564500043 0027370 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.logging.Logger;
import org.astrogrid.samp.Platform;
import org.astrogrid.samp.web.WebClientProfile;
import org.astrogrid.samp.xmlrpc.StandardClientProfile;
/**
* Factory which supplies the default ClientProfile for use by SAMP clients.
* By using this class to obtain ClientProfile instances, applications
* can be used with non-standard profiles supplied at runtime without
* requiring any code changes.
*
* jsamp-class:
"
* ({@link #HUBLOC_CLASS_PREFIX}) followed by the classname of a class
* which implements {@link ClientProfile} and has a no-arg constructor,
* then an instance of the named class is used.
* Otherwise, an instance of {@link StandardClientProfile} or
* {@link WebClientProfile} is returned.
*
* @author Mark Taylor
* @since 4 Aug 2009
*/
public class DefaultClientProfile {
private static ClientProfile profile_;
private static final Logger logger_ =
Logger.getLogger( DefaultClientProfile.class.getName() );
/** Environment variable used for hub location ({@value}). */
public static final String HUBLOC_ENV = "SAMP_HUB";
/**
* Prefix for SAMP_HUB env var indicating a supplied ClientProfile
* implementation ({@value}).
*/
public static final String HUBLOC_CLASS_PREFIX = "jsamp-class:";
/**
* No-arg constructor prevents instantiation.
*/
private DefaultClientProfile() {
}
/**
* Returns a ClientProfile instance suitable for general purpose use.
* By default this is currently the Standard Profile
* ({@link org.astrogrid.samp.xmlrpc.StandardClientProfile#getInstance
* StandardClientProfile.getInstance()}),
* but the instance may be modified programatically or by use of
* the SAMP_HUB environment variable.
*
* jsamp-class:
"
* ({@link #HUBLOC_CLASS_PREFIX}) followed by the classname of a class
* which implements {@link ClientProfile} and has a no-arg constructor,
* then an instance of the named class is used.
* Otherwise, an instance of {@link StandardClientProfile} is returned.
*
* mtype
*/
Map getSubscribedClients( String mtype ) throws SampException;
/**
* Sends a message to a given client without wanting a response.
*
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
*/
void notify( String recipientId, Map msg ) throws SampException;
/**
* Sends a message to all subscribed clients without wanting a response.
*
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return list of public-ids for clients to which the notify will be sent
*/
List notifyAll( Map msg ) throws SampException;
/**
* Sends a message to a given client expecting a response.
* The receiveResponse
method of this connection's
* {@link CallableClient} will be called with a
* response at some time in the future.
*
* receiveResponse
method of this connection's
* {@link CallableClient} will be called with responses at some
* time in the future.
*
* timeout
* parameter, an exception will result.
*
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param timeout timeout in seconds, or <0 for no timeout
* @return response
*/
Response callAndWait( String recipientId, Map msg, int timeout )
throws SampException;
/**
* Supplies a response to a previously received message.
*
* @param msgId ID associated with earlier send
* @param response {@link org.astrogrid.samp.Response}-like map
*/
void reply( String msgId, Map response ) throws SampException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/HubConnector.java 0000664 0000000 0000000 00000136516 13564500043 0025727 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Platform;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.Subscriptions;
/**
* Manages a client's connection to SAMP hubs.
* Normally SAMP client applications will use one instance of this class
* for as long as they are running.
* It provides the following services:
*
*
*
* samp.app.ping
.
* HubConnection
can be used for direct calls
* on the running hub, but in some cases similar methods with additional
* functionality exist in this class:
*
*
*
* HubConnection
method, but communicates with the hub
* asynchronously and fakes the synchrony at the client end.
* This is more robust and almost certainly a better idea.
* Examples
* Here is an example of what use of this class might look like:
*
* // Construct a connector
* ClientProfile profile = DefaultClientProfile.getProfile();
* HubConnector conn = new HubConnector(profile)
*
* // Configure it with metadata about this application
* Metadata meta = new Metadata();
* meta.setName("Foo");
* meta.setDescriptionText("Application that does stuff");
* conn.declareMetadata(meta);
*
* // Prepare to receive messages with specific MType(s)
* conn.addMessageHandler(new AbstractMessageHandler("stuff.do") {
* public Map processCall(HubConnection c, String senderId, Message msg) {
* // do stuff
* }
* });
*
* // This step required even if no custom message handlers added.
* conn.declareSubscriptions(conn.computeSubscriptions());
*
* // Keep a look out for hubs if initial one shuts down
* conn.setAutoconnect(10);
*
* // Broadcast a message
* conn.getConnection().notifyAll(new Message("stuff.event.doing"));
*
*
* Backwards Compatibility Note
* This class does less than it did in earlier versions;
* the functionality which is no longer here can now be found in the
* {@link org.astrogrid.samp.gui.GuiHubConnector} class instead.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class HubConnector {
private final ClientProfile profile_;
private final TrackedClientSet clientSet_;
private final List messageHandlerList_;
private final List responseHandlerList_;
private final ConnectorCallableClient callable_;
private final Map responseMap_;
private final ClientTracker clientTracker_;
private final CallHandler callHandler_;
private volatile boolean isActive_;
private volatile HubConnection connection_;
private volatile Metadata metadata_;
private volatile Subscriptions subscriptions_;
private volatile int autoSec_;
private volatile Timer regTimer_;
private volatile int iCall_;
private final Logger logger_ =
Logger.getLogger( HubConnector.class.getName() );
private static final String SHUTDOWN_MTYPE = "samp.hub.event.shutdown";
private static final String DISCONNECT_MTYPE = "samp.hub.disconnect";
private static final String PING_MTYPE = "samp.app.ping";
private static final String GETENV_MTYPE = "client.env.get";
/**
* Constructs a HubConnector based on a given profile instance.
* A default client set implementation is used.
*
* @param profile profile implementation
*/
public HubConnector( ClientProfile profile ) {
this( profile, new TrackedClientSet() );
}
/**
* Constructs a HubConnector based on a given profile instance
* using a custom client set implementation.
*
* @param profile profile implementation
* @param clientSet object to keep track of registered clients
*/
public HubConnector( ClientProfile profile, TrackedClientSet clientSet ) {
profile_ = profile;
clientSet_ = clientSet;
isActive_ = true;
// Set up data structures.
messageHandlerList_ = Collections.synchronizedList( new ArrayList() );
responseHandlerList_ = Collections.synchronizedList( new ArrayList() );
callable_ = new ConnectorCallableClient();
responseMap_ = Collections.synchronizedMap( new HashMap() );
// Listen out for events describing changes to registered clients.
clientTracker_ = new ClientTracker( clientSet_ );
addMessageHandler( clientTracker_ );
// Listen out for hub shutdown events.
addMessageHandler( new AbstractMessageHandler( SHUTDOWN_MTYPE ) {
public Map processCall( HubConnection connection,
String senderId, Message message ) {
String mtype = message.getMType();
assert SHUTDOWN_MTYPE.equals( mtype );
checkHubMessage( connection, senderId, mtype );
disconnect();
return null;
}
} );
// Listen out for forcible disconnection events.
addMessageHandler( new AbstractMessageHandler( DISCONNECT_MTYPE ) {
public Map processCall( HubConnection connection,
String senderId, Message message ) {
String mtype = message.getMType();
assert DISCONNECT_MTYPE.equals( mtype );
if ( senderId.equals( connection.getRegInfo().getHubId() ) ) {
Object reason = message.getParam( "reason" );
logger_.warning( "Forcible disconnect from hub"
+ ( reason == null ? " [no reason given]"
: " (" + reason + ")" ) );
disconnect();
isActive_ = false;
return null;
}
else {
throw new IllegalArgumentException( "Ignoring " + mtype
+ " message from non-hub"
+ " client " + senderId );
}
}
} );
// Implement samp.app.ping MType.
addMessageHandler( new AbstractMessageHandler( PING_MTYPE ) {
public Map processCall( HubConnection connection,
String senderId, Message message )
throws InterruptedException {
String waitMillis = (String) message.getParam( "waitMillis" );
if ( waitMillis != null ) {
Object lock = new Object();
synchronized ( lock ) {
lock.wait( SampUtils.decodeInt( waitMillis ) );
}
}
return null;
}
} );
// Implement client.env.get MType.
addMessageHandler( new AbstractMessageHandler( GETENV_MTYPE ) {
public Map processCall( HubConnection connection,
String senderId, Message message ) {
String name = (String) message.getParam( "name" );
String value = Platform.getPlatform().getEnv( name );
Map result = new HashMap();
result.put( "value", value == null ? "" : value );
return result;
};
} );
// Listen out for responses to calls for which we are providing
// faked synchronous call behaviour.
addResponseHandler( new ResponseHandler() {
public boolean ownsTag( String msgTag ) {
return responseMap_.containsKey( msgTag );
}
public void receiveResponse( HubConnection connection,
String responderId, String msgTag,
Response response ) {
synchronized ( responseMap_ ) {
if ( responseMap_.containsKey( msgTag ) &&
responseMap_.get( msgTag ) == null ) {
responseMap_.put( msgTag, response );
responseMap_.notifyAll();
}
}
}
} );
// Listen out for responses to calls for which we have agreed to
// pass results to user-supplied ResultHandler objects.
callHandler_ = new CallHandler();
addResponseHandler( callHandler_ );
}
/**
* Sets the interval at which this connector attempts to connect to a
* hub if no connection currently exists.
* Otherwise, a connection will be attempted whenever
* {@link #getConnection} is called.
*
* @param autoSec number of seconds between attempts;
* <=0 means no automatic connections are attempted
*/
public synchronized void setAutoconnect( int autoSec ) {
autoSec_ = autoSec;
configureRegisterTimer( autoSec_ );
}
/**
* Configures a timer thread to attempt registration periodically.
*
* @param autoSec number of seconds between attempts;
* <=0 means no automatic connections are attempted
*/
private synchronized void configureRegisterTimer( int autoSec ) {
// Cancel and remove any existing auto-connection timer.
if ( regTimer_ != null ) {
regTimer_.cancel();
regTimer_ = null;
}
// If required, install a new one.
if ( autoSec > 0 ) {
TimerTask regTask = new TimerTask() {
public void run() {
if ( ! isConnected() ) {
try {
HubConnection conn = getConnection();
if ( conn == null ) {
logger_.config( "SAMP autoconnection attempt "
+ "failed" );
}
else {
logger_.info( "SAMP autoconnection attempt "
+ "succeeded" );
}
}
catch ( SampException e ) {
logger_.config( "SAMP Autoconnection attempt "
+ " failed: " + e );
}
}
}
};
regTimer_ = new Timer( true );
regTimer_.schedule( regTask, 0, autoSec_ * 1000 );
}
}
/**
* Declares the metadata for this client.
* This declaration affects the current connection and any future ones.
*
* @param meta {@link org.astrogrid.samp.Metadata}-like map
*/
public void declareMetadata( Map meta ) {
Metadata md = Metadata.asMetadata( meta );
md.check();
metadata_ = md;
if ( isConnected() ) {
try {
connection_.declareMetadata( md );
}
catch ( SampException e ) {
logger_.log( Level.WARNING,
"SAMP metadata declaration failed", e );
}
}
}
/**
* Returns this client's own metadata.
*
* @return metadata
*/
public Metadata getMetadata() {
return metadata_;
}
/**
* Declares the MType subscriptions for this client.
* This declaration affects the current connection and any future ones.
*
* subs
argument given by
* the result of calling {@link #computeSubscriptions}.
*
* @param subscriptions {@link org.astrogrid.samp.Subscriptions}-like map
*/
public void declareSubscriptions( Map subscriptions ) {
Subscriptions subs = Subscriptions.asSubscriptions( subscriptions );
subs.check();
subscriptions_ = subs;
if ( isConnected() ) {
try {
connection_.declareSubscriptions( subs );
}
catch ( SampException e ) {
logger_.log( Level.WARNING, "Subscriptions declaration failed",
e );
}
}
}
/**
* Returns this client's own subscriptions.
*
* @return subscriptions
*/
public Subscriptions getSubscriptions() {
return subscriptions_;
}
/**
* Works out the subscriptions map for this connector.
* This is based on the subscriptions declared by for any
* {@link MessageHandler}s installed in this connector as well as
* any MTypes which this connector implements internally.
* The result of this method is usually a suitable value to pass
* to {@link #declareSubscriptions}. However you might wish to
* remove some entries from the result if there are temporarily
* unsubscribed services.
*
* @return subscription list for MTypes apparently implemented by this
* connector
*/
public Subscriptions computeSubscriptions() {
MessageHandler[] mhandlers =
(MessageHandler[])
messageHandlerList_.toArray( new MessageHandler[ 0 ] );
Map subs = new HashMap();
for ( int ih = mhandlers.length - 1; ih >= 0; ih-- ) {
subs.putAll( mhandlers[ ih ].getSubscriptions() );
}
return Subscriptions.asSubscriptions( subs );
}
/**
* Adds a MessageHandler to this connector, which allows it to respond
* to incoming messages.
* Note that this does not in itself update the list of subscriptions
* for this connector; you may want to follow it with a call to
*
* declareSubscriptions(computeSubscriptions());
*
*
* @param handler handler to add
*/
public void addMessageHandler( MessageHandler handler ) {
messageHandlerList_.add( handler );
}
/**
* Removes a previously-added MessageHandler to this connector.
* Note that this does not in itself update the list of subscriptions
* for this connector; you may want to follow it with a call to
*
* declareSubscriptions(computeSubscriptions());
*
*
* @param handler handler to remove
*/
public void removeMessageHandler( MessageHandler handler ) {
messageHandlerList_.remove( handler );
}
/**
* Adds a ResponseHandler to this connector, which allows it to receive
* replies from messages sent asynchronously.
*
* timeout
* parameter, an exception will result.
*
* declareSubscriptions(computeSubscriptions())
has been
* called.
* Hence, this method should only be called after
* {@link #declareSubscriptions} has been called.
* If this order is not observed, a warning will be emitted through
* the logging system.
*
* @return id->Client map
*/
public Map getClientMap() {
if ( subscriptions_ == null ) {
logger_.warning( "Danger: you should call declareSubscriptions "
+ "before using client map" );
}
return getClientSet().getClientMap();
}
/**
* Returns the tracked client set implementation which is used to keep
* track of the currently registered clients.
*
* @return client set implementation
*/
protected TrackedClientSet getClientSet() {
return clientSet_;
}
/**
* Invoked by this class to create a hub connection.
* The default implementation just calls profile.register()
.
*
* @return new hub connection
*/
protected HubConnection createConnection() throws SampException {
return profile_.register();
}
/**
* Unregisters from the currently connected hub, if any.
* Performs any associated required cleanup.
*/
protected void disconnect() {
boolean wasConnected = connection_ != null;
connection_ = null;
clientTracker_.clear();
callHandler_.stopTimeouter();
synchronized ( responseMap_ ) {
responseMap_.clear();
responseMap_.notifyAll();
}
if ( wasConnected ) {
connectionChanged( false );
}
}
/**
* Method which is called every time this connector changes its connection
* status (from disconnected to connected, or vice versa).
* The default implementation does nothing, but it may be overridden
* by subclasses wishing to be informed of these events.
*
* @param isConnected true if we've just registered;
* false if we've just unregistered
*/
protected void connectionChanged( boolean isConnected ) {
}
/**
* Performs sanity checking on a message which is normally expected to
* be sent only by the hub client itself.
*
* @param connection connection to the hub
* @param senderId public client id of sender
* @param mtype MType of sent message
*/
private void checkHubMessage( HubConnection connection, String senderId,
String mtype ) {
if ( ! senderId.equals( connection.getRegInfo().getHubId() ) ) {
logger_.warning( "Hub admin message " + mtype + " received from "
+ "non-hub client. Acting on it anyhow" );
}
}
/**
* Generates a new msgTag
for use with this connector.
* It is guaranteed to return a different value on each invocation.
* It is advisable to use this method whenever a message tag is required
* to prevent clashes.
*
* @param owner object to identify caller
* (not really necessary - may be null)
* @return unique tag for this connector
*/
public synchronized String createTag( Object owner ) {
return ( owner == null ? "tag"
: ( String.valueOf( owner ) + ":" ) )
+ ++iCall_;
}
/**
* CallableClient implementation used by this class.
*/
private class ConnectorCallableClient implements CallableClient {
private HubConnection conn_;
/**
* Sets the currently active hub connection.
*
* @param connection connection
*/
private void setConnection( HubConnection connection ) {
conn_ = connection;
}
public void receiveNotification( String senderId, Message message ) {
// Offer the notification to each registered MessageHandler in turn.
// It may in principle get processed by more than one.
// This is almost certainly harmless.
MessageHandler[] mhandlers =
(MessageHandler[])
messageHandlerList_.toArray( new MessageHandler[ 0 ] );
for ( int ih = 0; ih < mhandlers.length; ih++ ) {
MessageHandler handler = mhandlers[ ih ];
Subscriptions subs =
Subscriptions.asSubscriptions( handler.getSubscriptions() );
String mtype = message.getMType();
if ( subs.isSubscribed( mtype ) ) {
try {
handler.receiveNotification( conn_, senderId, message );
}
catch ( Throwable e ) {
logger_.log( Level.WARNING,
"Notify handler failed " + mtype, e );
}
}
}
}
public void receiveCall( String senderId, String msgId,
Message message ) {
// Offer the call to each registered MessageHandler in turn.
// Since only one should be allowed to respond to it, only
// the first one which bites is allowed to process it.
String mtype = message.getMType();
ErrInfo errInfo = null;
MessageHandler[] mhandlers =
(MessageHandler[])
messageHandlerList_.toArray( new MessageHandler[ 0 ] );
for ( int ih = 0; ih < mhandlers.length; ih++ ) {
MessageHandler handler = mhandlers[ ih ];
Subscriptions subs =
Subscriptions.asSubscriptions( handler.getSubscriptions() );
if ( subs.isSubscribed( mtype ) ) {
try {
handler.receiveCall( conn_, senderId, msgId, message );
return;
}
catch ( Throwable e ) {
errInfo = new ErrInfo( e );
logger_.log( Level.WARNING,
"Call handler failed " + mtype, e );
}
}
}
if ( errInfo == null ) {
logger_.warning( "No handler for subscribed MType " + mtype );
errInfo = new ErrInfo( "No handler found" );
errInfo.setUsertxt( "No handler was found for the supplied"
+ " MType. "
+ "Looks like a programming error "
+ "at the recipient end. Sorry." );
}
Response response = Response.createErrorResponse( errInfo );
response.check();
try {
conn_.reply( msgId, response );
}
catch ( SampException e ) {
logger_.warning( "Failed to reply to " + msgId );
}
}
public void receiveResponse( String responderId, String msgTag,
Response response ) {
// Offer the response to each registered ResponseHandler in turn.
// It shouldn't be processed by more than one, but if it is,
// warn about it.
int handleCount = 0;
ResponseHandler[] rhandlers =
(ResponseHandler[])
responseHandlerList_.toArray( new ResponseHandler[ 0 ] );
for ( int ih = 0; ih < rhandlers.length; ih++ ) {
ResponseHandler handler = rhandlers[ ih ];
if ( handler.ownsTag( msgTag ) ) {
handleCount++;
try {
handler.receiveResponse( conn_, responderId, msgTag,
response );
}
catch ( Exception e ) {
logger_.log( Level.WARNING, "Response handler failed",
e );
}
}
}
if ( handleCount == 0 ) {
logger_.warning( "No handler for message "
+ msgTag + " response" );
}
else if ( handleCount > 1 ) {
logger_.warning( "Multiple (" + handleCount + ")"
+ " handlers handled message "
+ msgTag + " respose" );
}
}
}
/**
* ResponseHandler which looks after responses made by calls using the
* call() and callAll() convenience methods.
*/
private class CallHandler implements ResponseHandler {
private final SortedMap tagMap_;
private Thread timeouter_;
/**
* Constructor.
*/
CallHandler() {
// Set up a structure to contain tag->CallItem entries for
// responses we are expecting. They are arranged in order of
// which is going to time out soonest.
tagMap_ = new TreeMap();
}
/**
* Ensures that a thread is running to wake up when the next timeout
* has (or at least might have) happened.
*/
private void readyTimeouter() {
synchronized ( tagMap_ ) {
if ( timeouter_ == null ) {
timeouter_ = new Thread( "ResultHandler timeout watcher" ) {
public void run() {
watchTimeouts();
}
};
timeouter_.setDaemon( true );
timeouter_.start();
}
}
}
/**
* Stops any current timeout watcher operating on behalf of this
* handler and tidies up associated resources.
*/
private void stopTimeouter() {
synchronized ( tagMap_ ) {
if ( timeouter_ != null ) {
timeouter_.interrupt();
}
timeouter_ = null;
tagMap_.clear();
}
}
/**
* Runs in a daemon thread to watch out for timeouts that might
* have occurred.
*/
private void watchTimeouts() {
while ( ! Thread.currentThread().isInterrupted() ) {
synchronized ( tagMap_ ) {
// Wait until the next scheduled timeout is expected.
long nextFinish =
tagMap_.isEmpty() ? Long.MAX_VALUE
: ((CallItem)
tagMap_.get( tagMap_.firstKey() ))
.finish_;
final long delay = nextFinish - System.currentTimeMillis();
if ( delay > 0 ) {
try {
tagMap_.wait( delay );
}
catch ( InterruptedException e ) {
Thread.currentThread().interrupt();
}
}
// Then process any timeouts that are pending.
long now = System.currentTimeMillis();
for ( Iterator it = tagMap_.entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
CallItem item = (CallItem) entry.getValue();
if ( now >= item.finish_ ) {
item.handler_.done();
it.remove();
}
}
}
}
}
/**
* Stores a ResultHandler object which will take delivery of the
* responses tagged with a given tag.
*
* @param tag message tag identifying send/response
* @param handler callback object
* @param timeout milliseconds before forcing completion
*/
public void registerHandler( String tag, ResultHandler handler,
int timeout ) {
long finish = timeout > 0
? System.currentTimeMillis() + timeout * 1000
: Long.MAX_VALUE; // 3e8 years
CallItem item = new CallItem( handler, finish );
if ( ! item.isDone() ) {
synchronized ( tagMap_ ) {
readyTimeouter();
tagMap_.put( tag, item );
tagMap_.notifyAll();
}
}
else {
handler.done();
}
}
/**
* Set the recipients from which we are expecting responses.
* Once all are in, the handler can be disposed of.
*
* @param tag message tag identifying send/response
* @param recipients clients expected to reply
*/
public void setRecipients( String tag, String[] recipients ) {
CallItem item;
synchronized ( tagMap_ ) {
item = (CallItem) tagMap_.get( tag );
}
item.setRecipients( recipients );
retireIfDone( tag, item );
}
/**
* Unregister a handler for which no responses are expected.
*
* @param tag message tag identifying send/response
*/
public void unregisterHandler( String tag ) {
synchronized ( tagMap_ ) {
tagMap_.remove( tag );
}
}
public boolean ownsTag( String tag ) {
synchronized ( tagMap_ ) {
return tagMap_.containsKey( tag );
}
}
public void receiveResponse( HubConnection connection,
String responderId, String msgTag,
Response response ) {
final CallItem item;
synchronized ( tagMap_ ) {
item = (CallItem) tagMap_.get( msgTag );
}
if ( item != null ) {
item.addResponse( responderId, response );
retireIfDone( msgTag, item );
}
}
/**
* Called when a tag/handler entry might be ready to finish with.
*/
private void retireIfDone( String tag, CallItem item ) {
if ( item.isDone() ) {
synchronized ( tagMap_ ) {
item.handler_.done();
tagMap_.remove( tag );
}
}
}
}
/**
* Stores state about a particular set of responses expected by the
* CallHandler class.
*/
private class CallItem implements Comparable {
final ResultHandler handler_;
final long finish_;
volatile Map responseMap_; // responderId -> Response
volatile Map recipientMap_; // responderId -> Client
/**
* Constructor.
*
* @param handler callback object
* @param finish epoch at which timeout should be called
*/
CallItem( ResultHandler handler, long finish ) {
handler_ = handler;
finish_ = finish;
}
/**
* Sets the recipient Ids for which responses are expected.
*
* @param recipientIds recipient client ids
*/
public synchronized void setRecipients( String[] recipientIds ) {
recipientMap_ = new HashMap();
// Store client objects for each recipient ID. Note however
// because of various synchrony issues we can't guarantee that
// all these client objects can be determined - some may be null.
// Store the ids as keys in any case.
Map clientMap = getClientMap();
for ( int ir = 0; ir < recipientIds.length; ir++ ) {
String id = recipientIds[ ir ];
Client client = (Client) clientMap.get( id );
recipientMap_.put( id, client );
}
// If we have pending responses (couldn't be processed earlier
// because no recipients), take care of them now.
if ( responseMap_ != null ) {
for ( Iterator it = responseMap_.entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String responderId = (String) entry.getKey();
Response response = (Response) entry.getValue();
processResponse( responderId, response );
}
responseMap_ = null;
}
}
/**
* Take delivery of a response object.
*
* @param responderId client ID of responder
* @param response response object
*/
public synchronized void addResponse( String responderId,
Response response ) {
// If we know the recipients, deal with it now.
if ( recipientMap_ != null ) {
processResponse( responderId, response );
}
// Otherwise, defer until we do know the recipients.
else {
if ( responseMap_ == null ) {
responseMap_ = new HashMap();
}
responseMap_.put( responderId, response );
}
}
/**
* Process a response when we have both the list of recipients
* and the response itself.
*
* @param responderId client ID of responder
* @param response response object
*/
private synchronized void processResponse( final String responderId,
Response response ) {
if ( recipientMap_.containsKey( responderId ) ) {
// Get a client object. We have to try belt and braces.
Client client = (Client) recipientMap_.get( responderId );
if ( client == null ) {
client = (Client) getClientMap().get( responderId );
}
if ( client == null ) {
client = new Client() {
public String getId() {
return responderId;
}
public Metadata getMetadata() {
return null;
}
public Subscriptions getSubscriptions() {
return null;
}
};
}
// Make the callback to the supplied handler.
handler_.result( client, response );
// Note that we've done this one.
recipientMap_.remove( responderId );
}
}
/**
* Indicate whether this call item has received all the responses it's
* going to.
*
* @return iff no further activity is expected
*/
public synchronized boolean isDone() {
return ( recipientMap_ != null && recipientMap_.isEmpty() )
|| System.currentTimeMillis() >= finish_;
}
/**
* Compares on timeout epochs.
* Implementation is consistent with equals,
* which means it's OK to use them in a SortedMap.
*/
public int compareTo( Object o ) {
CallItem other = (CallItem) o;
if ( this.finish_ < other.finish_ ) {
return -1;
}
else if ( this.finish_ > other.finish_ ) {
return +1;
}
else {
return System.identityHashCode( this )
- System.identityHashCode( other );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/LogResultHandler.java 0000664 0000000 0000000 00000002727 13564500043 0026550 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.HubConnection;
/**
* ResultHandler implementation which outputs some information about
* responses received through the logging system.
*
* @author Mark Taylor
* @since 12 Nov 2008
*/
public class LogResultHandler implements ResultHandler {
private final String mtype_;
private static final Logger logger_ =
Logger.getLogger( LogResultHandler.class.getName() );
/**
* Constructor.
*
* @param msg message which was sent
*/
public LogResultHandler( Message msg ) {
mtype_ = msg.getMType();
}
public void result( Client client, Response response ) {
if ( response.isOK() ) {
logger_.info( mtype_ + ": successful send to " + client );
}
else {
logger_.warning( mtype_ + ": error sending to " + client );
ErrInfo errInfo = response.getErrInfo();
if ( errInfo != null ) {
String errortxt = errInfo.getErrortxt();
if ( errortxt != null ) {
logger_.warning( errortxt );
}
logger_.info( SampUtils.formatObject( errInfo, 3 ) );
}
}
}
public void done() {
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/MessageHandler.java 0000664 0000000 0000000 00000003216 13564500043 0026206 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.Map;
import org.astrogrid.samp.Message;
/**
* Interface for a client which wishes to receive messages.
* In most cases it is more convenient to subclass the abstract class
* {@link AbstractMessageHandler} than to implement this interface directly.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
public interface MessageHandler {
/**
* Returns a Subscriptions map corresponding to the messages
* handled by this object.
* Only messages with MTypes which match the keys of this map will
* be passed to this object.
*
* @return {@link org.astrogrid.samp.Subscriptions}-like map
*/
Map getSubscriptions();
/**
* Processes a message which does not require a response.
*
* @param connection hub connection
* @param senderId public ID of client which sent the message
* @param message message
*/
void receiveNotification( HubConnection connection,
String senderId, Message message )
throws Exception;
/**
* Processes a message which does require a response.
* Implementations should make sure that a subsequent call to
* connection.reply()
is made using the
* supplied msgId
.
*
* @param connection hub connection
* @param senderId public ID of client which sent the message
* @param msgId message ID
* @param message message
*/
void receiveCall( HubConnection connection,
String senderId, String msgId, Message message )
throws Exception;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ResponseHandler.java 0000664 0000000 0000000 00000002321 13564500043 0026414 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import org.astrogrid.samp.Response;
/**
* Interface for a client which wishes to receive responses to message it
* has sent asynchrnonously using call
or callAll
.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
public interface ResponseHandler {
/**
* Indicates whether this handler will process the response with a
* given message tag.
*
* @param msgTag tag with which earlier call was labelled
* @return true iff this handler wants to process the response labelled
* with msgTag
*/
boolean ownsTag( String msgTag );
/**
* Processes a response to an earlier message.
* Will only be called for msgTag
values which return
* true
from {@link #ownsTag}.
*
* @param connection hub connection
* @param responderId client id of client sending response
* @param msgTag message tag from previous call
* @param response response object
*/
void receiveResponse( HubConnection connection, String responderId,
String msgTag, Response response )
throws Exception;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ResultHandler.java 0000664 0000000 0000000 00000001356 13564500043 0026103 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Response;
/**
* Interface which consumes call responses.
*
* @author Mark Taylor
* @since 12 Nov 2008
*/
public interface ResultHandler {
/**
* Called when a response is received from a client to which the message
* was sent.
*
* @param responder responder client
* @param response content of response
*/
public void result( Client responder, Response response );
/**
* Called when no more {@link #result} invocations will be made,
* either because all have been received or for some other reason,
* such as a timeout or the hub shutting down.
*/
public void done();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/SampException.java 0000664 0000000 0000000 00000002077 13564500043 0026107 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.io.IOException;
/**
* Exception thrown when some error occurs in SAMP processing.
* Note that this is a subclass of {@link java.io.IOException}.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class SampException extends IOException {
/**
* Constructs an exception with no message.
*/
public SampException() {
super();
}
/**
* Consructs an exception with a given message.
*
* @param msg message
*/
public SampException( String msg ) {
super( msg );
}
/**
* Constructs an exception with a given cause.
*
* @param cause cause of this exception
*/
public SampException( Throwable cause ) {
this();
initCause( cause );
}
/**
* Constructs an exception with a given message and cause.
*
* @param msg message
* @param cause cause of this exception
*/
public SampException( String msg, Throwable cause ) {
this( msg );
initCause( cause );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/TrackedClientSet.java 0000664 0000000 0000000 00000006620 13564500043 0026516 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.client;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.astrogrid.samp.Client;
/**
* Collection of Client objects which can be notified and interrogated
* about the clients which are currently registered.
* Instances of this class are thread-safe.
*
* @author Mark Taylor
* @since 25 Nov 2008
*/
public class TrackedClientSet {
private final Map clientMap_;
private final Map clientMapView_;
/**
* Constructor.
*/
public TrackedClientSet() {
clientMap_ = new HashMap();
clientMapView_ =
Collections.synchronizedMap( Collections
.unmodifiableMap( clientMap_ ) );
}
/**
* Adds a client to this model.
* Listeners are informed. May be called from any thread.
*
* @param client client to add
*/
public void addClient( Client client ) {
synchronized ( clientMapView_ ) {
clientMap_.put( client.getId(), client );
clientMapView_.notifyAll();
}
}
/**
* Removes a client from this model.
* Listeners are informed. May be called from any thread.
*
* @param client client to remove
*/
public synchronized void removeClient( Client client ) {
Client c;
synchronized ( clientMapView_ ) {
c = (Client) clientMap_.remove( client.getId() );
clientMapView_.notifyAll();
}
boolean removed = c != null;
if ( ! removed ) {
throw new IllegalArgumentException( "No such client " + client );
}
assert client.equals( c );
}
/**
* Sets the contents of this model to a given list.
* Listeners are informed. May be called from any thread.
*
* @param clients current client list
*/
public synchronized void setClients( Client[] clients ) {
synchronized ( clientMapView_ ) {
clientMap_.clear();
for ( int ic = 0; ic < clients.length; ic++ ) {
Client client = clients[ ic ];
clientMap_.put( client.getId(), client );
}
clientMapView_.notifyAll();
}
}
/**
* Notifies listeners that a given client's attributes (may) have
* changed. May be called from any thread.
*
* @param client modified client
* @param metaChanged true if metadata may have changed
* (false if known unchanged)
* @param subsChanged true if subscriptions may have changed
* (false if known unchanged)
*/
public void updateClient( Client client,
boolean metaChanged, boolean subsChanged ) {
synchronized ( clientMapView_ ) {
clientMapView_.notifyAll();
}
}
/**
* Returns an unmodifiable Map representing the client list.
* Keys are client IDs and values are {@link org.astrogrid.samp.Client}
* objects.
*
Steven Spencer, JavaWorld magazine (Java Tip 66)
*
Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea Cantatore,
* Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
*
* @author Eric Albert (ejalbert@cs.stanford.edu)
* @version 1.4b1 (Released June 20, 2001)
*/
class BrowserLauncher {
/**
* The Java virtual machine that we are running on. Actually, in most cases we only care
* about the operating system, but some operating systems require us to switch on the VM. */
private static int jvm;
/** The browser for the system */
private static Object browser;
/**
* Caches whether any classes, methods, and fields that are not part of the JDK and need to
* be dynamically loaded at runtime loaded successfully.
* false
, openURL()
will always return an
* IOException.
*/
private static boolean loadedWithoutErrors;
/** The com.apple.mrj.MRJFileUtils class */
private static Class mrjFileUtilsClass;
/** The com.apple.mrj.MRJOSType class */
private static Class mrjOSTypeClass;
/** The com.apple.MacOS.AEDesc class */
private static Class aeDescClass;
/** The true
if all intialization succeeded
* false
if any portion of the initialization failed
*/
private static boolean loadClasses() {
switch (jvm) {
case MRJ_2_0:
try {
Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
Class aeClass = Class.forName("com.apple.MacOS.ae");
aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class });
appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class });
aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class });
putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { });
Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
keyDirectObject = (Integer) keyDirectObjectField.get(null);
Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
} catch (ClassNotFoundException cnfe) {
errorMessage = cnfe.getMessage();
return false;
} catch (NoSuchMethodException nsme) {
errorMessage = nsme.getMessage();
return false;
} catch (NoSuchFieldException nsfe) {
errorMessage = nsfe.getMessage();
return false;
} catch (IllegalAccessException iae) {
errorMessage = iae.getMessage();
return false;
}
break;
case MRJ_2_1:
try {
mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
kSystemFolderType = systemFolderField.get(null);
findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class });
getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
} catch (ClassNotFoundException cnfe) {
errorMessage = cnfe.getMessage();
return false;
} catch (NoSuchFieldException nsfe) {
errorMessage = nsfe.getMessage();
return false;
} catch (NoSuchMethodException nsme) {
errorMessage = nsme.getMessage();
return false;
} catch (SecurityException se) {
errorMessage = se.getMessage();
return false;
} catch (IllegalAccessException iae) {
errorMessage = iae.getMessage();
return false;
}
break;
case MRJ_3_0:
try {
Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
Constructor constructor = linker.getConstructor(new Class[]{ Class.class });
linkage = constructor.newInstance(new Object[] { BrowserLauncher.class });
} catch (ClassNotFoundException cnfe) {
errorMessage = cnfe.getMessage();
return false;
} catch (NoSuchMethodException nsme) {
errorMessage = nsme.getMessage();
return false;
} catch (InvocationTargetException ite) {
errorMessage = ite.getMessage();
return false;
} catch (InstantiationException ie) {
errorMessage = ie.getMessage();
return false;
} catch (IllegalAccessException iae) {
errorMessage = iae.getMessage();
return false;
}
break;
case MRJ_3_1:
try {
mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class });
} catch (ClassNotFoundException cnfe) {
errorMessage = cnfe.getMessage();
return false;
} catch (NoSuchMethodException nsme) {
errorMessage = nsme.getMessage();
return false;
}
break;
default:
break;
}
return true;
}
/**
* Attempts to locate the default web browser on the local system. Caches results so it
* only locates the browser once for each use of this class per JVM instance.
* @return The browser for the system. Note that this may not be what you would consider
* to be a standard web browser; instead, it's the application that gets called to
* open the default web browser. In some cases, this will be a non-String object
* that provides the means of calling the default browser.
*/
private static Object locateBrowser() {
if (browser != null) {
return browser;
}
switch (jvm) {
case MRJ_2_0:
try {
Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID });
// Don't set browser = appleEvent because then the next time we call
// locateBrowser(), we'll get the same AppleEvent, to which we'll already have
// added the relevant parameter. Instead, regenerate the AppleEvent every time.
// There's probably a way to do this better; if any has any ideas, please let
// me know.
return appleEvent;
} catch (IllegalAccessException iae) {
browser = null;
errorMessage = iae.getMessage();
return browser;
} catch (InstantiationException ie) {
browser = null;
errorMessage = ie.getMessage();
return browser;
} catch (InvocationTargetException ite) {
browser = null;
errorMessage = ite.getMessage();
return browser;
}
case MRJ_2_1:
File systemFolder;
try {
systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
} catch (IllegalArgumentException iare) {
browser = null;
errorMessage = iare.getMessage();
return browser;
} catch (IllegalAccessException iae) {
browser = null;
errorMessage = iae.getMessage();
return browser;
} catch (InvocationTargetException ite) {
browser = null;
errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
return browser;
}
String[] systemFolderFiles = systemFolder.list();
// Avoid a FilenameFilter because that can't be stopped mid-list
for(int i = 0; i < systemFolderFiles.length; i++) {
try {
File file = new File(systemFolder, systemFolderFiles[i]);
if (!file.isFile()) {
continue;
}
// We're looking for a file with a creator code of 'MACS' and
// a type of 'FNDR'. Only requiring the type results in non-Finder
// applications being picked up on certain Mac OS 9 systems,
// especially German ones, and sending a GURL event to those
// applications results in a logout under Multiple Users.
Object fileType = getFileType.invoke(null, new Object[] { file });
if (FINDER_TYPE.equals(fileType.toString())) {
Object fileCreator = getFileCreator.invoke(null, new Object[] { file });
if (FINDER_CREATOR.equals(fileCreator.toString())) {
browser = file.toString(); // Actually the Finder, but that's OK
return browser;
}
}
} catch (IllegalArgumentException iare) {
browser = browser;
errorMessage = iare.getMessage();
return null;
} catch (IllegalAccessException iae) {
browser = null;
errorMessage = iae.getMessage();
return browser;
} catch (InvocationTargetException ite) {
browser = null;
errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
return browser;
}
}
browser = null;
break;
case MRJ_3_0:
case MRJ_3_1:
browser = ""; // Return something non-null
break;
case WINDOWS_NT:
browser = "cmd.exe";
break;
case WINDOWS_9x:
browser = "command.com";
break;
case OTHER:
default:
browser = "firefox";
break;
}
return browser;
}
/**
* Attempts to open the default web browser to the given URL.
* @param url The URL to open
* @throws IOException If the web browser could not be located or does not run
*/
public static void openURL(String url) throws IOException {
if (!loadedWithoutErrors) {
throw new IOException("Exception in finding browser: " + errorMessage);
}
Object browser = locateBrowser();
if (browser == null) {
throw new IOException("Unable to locate browser: " + errorMessage);
}
switch (jvm) {
case MRJ_2_0:
Object aeDesc = null;
try {
aeDesc = aeDescConstructor.newInstance(new Object[] { url });
putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
sendNoReply.invoke(browser, new Object[] { });
} catch (InvocationTargetException ite) {
throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage());
} catch (IllegalAccessException iae) {
throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage());
} catch (InstantiationException ie) {
throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
} finally {
aeDesc = null; // Encourage it to get disposed if it was created
browser = null; // Ditto
}
break;
case MRJ_2_1:
Runtime.getRuntime().exec(new String[] { (String) browser, url } );
break;
case MRJ_3_0:
int[] instance = new int[1];
int result = ICStart(instance, 0);
if (result == 0) {
int[] selectionStart = new int[] { 0 };
byte[] urlBytes = url.getBytes();
int[] selectionEnd = new int[] { urlBytes.length };
result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
urlBytes.length, selectionStart,
selectionEnd);
if (result == 0) {
// Ignore the return value; the URL was launched successfully
// regardless of what happens here.
ICStop(instance);
} else {
throw new IOException("Unable to launch URL: " + result);
}
} else {
throw new IOException("Unable to create an Internet Config instance: " + result);
}
break;
case MRJ_3_1:
try {
openURL.invoke(null, new Object[] { url });
} catch (InvocationTargetException ite) {
throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage());
} catch (IllegalAccessException iae) {
throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage());
}
break;
case WINDOWS_NT:
case WINDOWS_9x:
// Add quotes around the URL to allow ampersands and other special
// characters to work.
Process process = Runtime.getRuntime().exec(new String[] { (String) browser,
FIRST_WINDOWS_PARAMETER,
SECOND_WINDOWS_PARAMETER,
THIRD_WINDOWS_PARAMETER,
'"' + url + '"' });
// This avoids a memory leak on some versions of Java on Windows.
// That's hinted at in value
*/
private JComponent createViewer( Object value ) {
if ( value instanceof String ) {
JTextField field = new JTextField();
field.setEditable( false );
field.setText( (String) value );
field.setCaretPosition( 0 );
try {
final URL url = new URL( (String) value );
field.setForeground( Color.BLUE );
field.addMouseListener( new MouseAdapter() {
public void mouseClicked( MouseEvent evt ) {
try {
openURL( url );
}
catch ( IOException e ) {
Toolkit.getDefaultToolkit().beep();
logger_.warning( "Can't open URL " + url + e );
}
}
} );
}
catch ( MalformedURLException e ) {
// not a URL - fine
}
return field;
}
else if ( value instanceof List ) {
return new JList( ((List) value).toArray() );
}
else if ( value instanceof Map ) {
JEditorPane edPane =
new JEditorPane( "text/html", toHtml( value ) );
edPane.setEditable( false );
edPane.setCaretPosition( 0 );
return edPane;
}
else {
return new JLabel( "???" );
}
}
/**
* Returns an HTML representation of a legal SAMP object
* (SAMP map, list or string).
*
* @param data SAMP object
* @return HTML representation of data
*/
private static String toHtml( Object data ) {
StringBuffer sbuf = new StringBuffer();
if ( data instanceof Map ) {
sbuf.append( "\n" );
for ( Iterator it = ((Map) data).entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
sbuf.append( "
\n" );
}
else if ( data instanceof List ) {
sbuf.append( "\n" );
for ( Iterator it = ((List) data).iterator(); it.hasNext(); ) {
sbuf.append( "
\n" );
}
else if ( data instanceof String ) {
sbuf.append( htmlEscape( (String) data ) );
}
else {
sbuf.append( "???" );
}
return sbuf.toString();
}
/**
* Escapes a literal string for use within HTML text.
*
* @param text literal string
* @return escaped version of text
safe for use within HTML
*/
private static String htmlEscape( String text ) {
int leng = text.length();
StringBuffer sbuf = new StringBuffer( leng );
for ( int i = 0; i < leng; i++ ) {
char c = text.charAt( i );
switch ( c ) {
case '<':
sbuf.append( "<" );
break;
case '>':
sbuf.append( ">" );
break;
case '&':
sbuf.append( "&" );
break;
default:
sbuf.append( c );
}
}
return sbuf.toString();
}
/**
* Creates a titled border with a uniform style.
*
* @param title title text
* @return border
*/
static Border createTitledBorder( String title ) {
return BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder( 5, 5, 5, 5 ),
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder( Color.BLACK ), title ) );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ClientTransmissionHolder.java 0000664 0000000 0000000 00000001323 13564500043 0027615 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import javax.swing.ListModel;
import org.astrogrid.samp.Client;
/**
* Provides the means to obtain list models containing pending sent and
* received transmissions.
*
* @author Mark Taylor
* @since 26 Nov 2008
*/
interface ClientTransmissionHolder {
/**
* Returns a list model containing messages sent by a given client.
*
* @return list model containing {@link Transmission} objects
*/
ListModel getTxListModel( Client client );
/**
* Returns a list model containing messages received by a given client.
*
* @return list model containing {@link Transmission} objects
*/
ListModel getRxListModel( Client client );
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ErrorDialog.java 0000664 0000000 0000000 00000011050 13564500043 0025036 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.io.StringWriter;
import java.io.PrintWriter;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
/**
* Dialog window which displays an error message, possibly with some
* verbose details optionally visible.
*
* @author Mark Taylor
* @since 5 Sep 2008
*/
public abstract class ErrorDialog extends JDialog {
/**
* Constructor.
*
* @param owner parent frame
* @param title dialog title
* @param summary short text string describing what's up
*/
protected ErrorDialog( Frame owner, String title, String summary ) {
super( owner, title == null ? "Error" : title, true );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// Define buttons.
final JPanel main = new JPanel( new BorderLayout() );
JButton disposeButton = new JButton();
JButton detailButton = new JButton();
final JComponent dataBox = new JPanel( new BorderLayout() );
dataBox.add( new JLabel( summary ) );
JComponent buttonBox = Box.createHorizontalBox();
// Populate main panel.
Border gapBorder = BorderFactory.createEmptyBorder( 5, 5, 5, 5 );
JComponent iconLabel =
new JLabel( UIManager.getIcon( "OptionPane.errorIcon" ) );
iconLabel.setBorder( gapBorder );
main.add( iconLabel, BorderLayout.WEST );
buttonBox.add( Box.createHorizontalGlue() );
buttonBox.add( disposeButton );
buttonBox.add( Box.createHorizontalStrut( 10 ) );
buttonBox.add( detailButton );
buttonBox.add( Box.createHorizontalGlue() );
dataBox.setBorder( gapBorder );
main.add( buttonBox, BorderLayout.SOUTH );
main.add( dataBox, BorderLayout.CENTER );
main.setBorder( gapBorder );
// Set button action for dismiss button.
disposeButton.setAction( new AbstractAction( "OK" ) {
public void actionPerformed( ActionEvent evt ) {
dispose();
}
} );
// Set button action for display detail button.
detailButton.setAction( new AbstractAction( "Show Details" ) {
public void actionPerformed( ActionEvent evt ) {
JTextArea ta = new JTextArea();
ta.setLineWrap( false );
ta.setEditable( false );
ta.append( getDetailText() );
ta.setCaretPosition( 0 );
JScrollPane scroller = new JScrollPane( ta );
dataBox.removeAll();
dataBox.add( scroller );
Dimension size = dataBox.getPreferredSize();
size.height = Math.min( size.height, 300 );
size.width = Math.min( size.width, 500 );
dataBox.revalidate();
dataBox.setPreferredSize( size );
pack();
setEnabled( false );
}
} );
getContentPane().add( main );
}
/**
* Supplies the text to be displayed in the detail panel.
*
* @return detail text
*/
protected abstract String getDetailText();
/**
* Pops up a window which shows the content of a exception.
*
* @param parent parent component
* @param title window title
* @param summary short text string
* @param error throwable
*/
public static void showError( Component parent, String title,
String summary, final Throwable error ) {
Frame fparent = parent == null
? null
: (Frame) SwingUtilities
.getAncestorOfClass( Frame.class, parent );
JDialog dialog = new ErrorDialog( fparent, title, summary ) {
protected String getDetailText() {
StringWriter traceWriter = new StringWriter();
error.printStackTrace( new PrintWriter( traceWriter ) );
return traceWriter.toString();
}
};
dialog.setLocationRelativeTo( parent );
dialog.pack();
dialog.setVisible( true );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/GuiClientSet.java 0000664 0000000 0000000 00000011716 13564500043 0025175 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.astrogrid.samp.hub.BasicClientSet;
import org.astrogrid.samp.hub.HubClient;
import org.astrogrid.samp.hub.MessageRestriction;
import org.astrogrid.samp.hub.ProfileToken;
/**
* ClientSet implementation used by GuiHubService.
* It also implements {@link javax.swing.ListModel}.
*
* @author Mark Taylor
* @since 20 Nov 2008
*/
class GuiClientSet extends BasicClientSet implements ListModel {
private final List clientList_;
private final List listenerList_;
private final static HubClient MORIBUND_CLIENT =
new HubClient( "hubStartActions
parameter - the elements of this array
* will normally be generated by calling the
* {@link #createHubAction createHubAction} method.
*
* @param parent parent component, used for placing dialogue
* @param hubStartActions actions which start a hub,
* or null for a default list
*/
public Action createRegisterOrHubAction( final Component parent,
Action[] hubStartActions ) {
final Action[] hubActs;
if ( hubStartActions != null ) {
hubActs = hubStartActions;
}
else {
HubServiceMode internalMode = SysTray.getInstance().isSupported()
? HubServiceMode.CLIENT_GUI
: HubServiceMode.NO_GUI;
hubActs = new Action[] {
createHubAction( false, internalMode ),
createHubAction( true, HubServiceMode.MESSAGE_GUI ),
};
}
Action regAct = new RegisterAction() {
protected void registerFailed() {
Object msg = new String[] {
"No SAMP hub is running.",
"You may start a hub if you wish.",
};
List buttList = new ArrayList();
JButton[] options = new JButton[ hubActs.length + 1 ];
for ( int i = 0; i < hubActs.length; i++ ) {
options[ i ] = new JButton( hubActs[ i ] );
}
options[ hubActs.length ] = new JButton( "Cancel" );
final JDialog dialog =
new JOptionPane( msg, JOptionPane.WARNING_MESSAGE,
JOptionPane.DEFAULT_OPTION,
null, options, null )
.createDialog( parent, "No Hub" );
ActionListener closeListener = new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
dialog.dispose();
}
};
for ( int iopt = 0; iopt < options.length; iopt++ ) {
options[ iopt ].addActionListener( closeListener );
}
dialog.setVisible( true );
}
};
registerUpdater( regAct, new ConnectionUpdate() {
public void setConnected( Object item, boolean isConnected ) {
((RegisterAction) item).setSense( ! isConnected );
}
} );
return regAct;
}
/**
* Returns an action which will display a SAMP hub monitor window.
*
* @return monitor window action
*/
public Action createShowMonitorAction() {
return new MonitorAction();
}
/**
* Returns an action which will start up a SAMP hub.
* You can specify whether it runs in the current JVM or a newly
* created one; in the former case, it will shut down when the
* current application does.
*
* @param external false to run in the current JVM,
* true to run in a new one
* @param hubMode hub mode
*/
public Action createHubAction( boolean external, HubServiceMode hubMode ) {
return new HubAction( external, hubMode );
}
/**
* Creates a component which indicates whether this connector is currently
* connected or not, using supplied icons.
*
* @param onIcon icon indicating connection
* @param offIcon icon indicating no connection
* @return connection indicator
*/
public JComponent createConnectionIndicator( final Icon onIcon,
final Icon offIcon ) {
JLabel label = new JLabel( new Icon() {
private Icon effIcon() {
return isConnected() ? onIcon : offIcon;
}
public int getIconWidth() {
return effIcon().getIconWidth();
}
public int getIconHeight() {
return effIcon().getIconHeight();
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
effIcon().paintIcon( c, g, x, y );
}
} );
registerUpdater( label, REPAINT_COMPONENT );
return label;
}
/**
* Creates a component which indicates whether this connector is currently
* connected or not, using default icons.
*
* @return connection indicator
*/
public JComponent createConnectionIndicator() {
return createConnectionIndicator(
new ImageIcon( Client.class
.getResource( "images/connected-24.gif" ) ),
new ImageIcon( Client.class
.getResource( "images/disconnected-24.gif" ) )
);
}
/**
* Creates a component which shows an icon for each registered client.
*
* @param vertical true for vertical box, false for horizontal
* @param iconSize dimension in pixel of each icon (square)
*/
public JComponent createClientBox( final boolean vertical, int iconSize ) {
final IconStore iconStore =
new IconStore( IconStore.createMinimalIcon( iconSize ) );
IconBox box = new IconBox( iconSize );
box.setVertical( vertical );
box.setBorder( createBoxBorder() );
box.setModel( clientListModel_ );
box.setRenderer( new IconBox.CellRenderer() {
public Icon getIcon( IconBox iconBox, Object value, int index ) {
return IconStore.scaleIcon( iconStore.getIcon( (Client) value ),
iconBox.getTransverseSize(),
2.0, ! vertical );
}
public String getToolTipText( IconBox iconBox, Object value,
int index ) {
return ((Client) value).toString();
}
} );
Dimension boxSize = box.getPreferredSize();
boxSize.width = 128;
box.setPreferredSize( boxSize );
registerUpdater( box, ENABLE_COMPONENT );
return box;
}
/**
* Returns a new component which displays status for this connector.
*
* @return new hub connection monitor component
*/
public JComponent createMonitorPanel() {
HubView view = new HubView( false );
view.setClientListModel( getClientListModel() );
view.getClientList().setCellRenderer( createClientListCellRenderer() );
return view;
}
/**
* Called when the connection status (registered/unregistered) may have
* changed. May be called from any thread.
*/
private void scheduleConnectionChange() {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
boolean isConnected = isConnected();
if ( isConnected != wasConnected_ ) {
wasConnected_ = isConnected;
ChangeEvent evt = new ChangeEvent( GuiHubConnector.this );
for ( Iterator it = connectionListenerList_.iterator();
it.hasNext(); ) {
((ChangeListener) it.next()).stateChanged( evt );
}
}
}
} );
}
/**
* Called when the connection status has changed, or may have changed.
*/
private void updateConnectionState() {
boolean isConn = isConnected();
for ( Iterator it = updateMap_.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object item = entry.getKey();
ConnectionUpdate update = (ConnectionUpdate) entry.getValue();
update.setConnected( item, isConn );
}
}
/**
* Returns a border suitable for icon boxes.
*
* @return border
*/
static Border createBoxBorder() {
return BorderFactory.createCompoundBorder(
new JTextField().getBorder(),
BorderFactory.createEmptyBorder( 1, 1, 1, 1 ) );
}
/**
* Adds a given item to the list of objects which will be notified
* when the hub is connected/disconnected. By doing it like this
* rather than with the usual listener mechanism the problem of
* retaining references to otherwise unused listeners is circumvented.
*
* @param item object to be notified
* @param updater object which performs the notification on hub
* connect/disconnect
*/
void registerUpdater( Object item, ConnectionUpdate updater ) {
updater.setConnected( item, isConnected() );
updateMap_.put( item, updater );
}
/**
* Interface defining how an object is to be notified when the hub
* connection status changes.
*/
interface ConnectionUpdate {
/**
* Invoked when hub connection status changes.
*
* @param item which is being notified
* @param isConnected whether the hub is now connected or not
*/
void setConnected( Object item, boolean isConnected );
}
/**
* TrackedClientSet implementation used by this class.
* Implements ListModel as well.
*/
private static class ListModelTrackedClientSet extends TrackedClientSet
implements ListModel {
private final List clientList_;
private final List listenerList_;
/**
* Constructor.
*/
ListModelTrackedClientSet() {
clientList_ = new ArrayList();
listenerList_ = new ArrayList();
}
public int getSize() {
return clientList_.size();
}
public Object getElementAt( int index ) {
return clientList_.get( index );
}
public void addListDataListener( ListDataListener listener ) {
listenerList_.add( listener );
}
public void removeListDataListener( ListDataListener listener ) {
listenerList_.remove( listener );
}
public void addClient( final Client client ) {
super.addClient( client );
SwingUtilities.invokeLater( new Runnable() {
public void run() {
int index = clientList_.size();
clientList_.add( client );
ListDataEvent evt =
new ListDataEvent( ListModelTrackedClientSet.this,
ListDataEvent.INTERVAL_ADDED,
index, index );
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
((ListDataListener) it.next()).intervalAdded( evt );
}
}
} );
}
public void removeClient( final Client client ) {
super.removeClient( client );
SwingUtilities.invokeLater( new Runnable() {
public void run() {
int index = clientList_.indexOf( client );
assert index >= 0;
if ( index >= 0 ) {
clientList_.remove( index );
ListDataEvent evt =
new ListDataEvent( ListModelTrackedClientSet.this,
ListDataEvent.INTERVAL_REMOVED,
index, index );
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
((ListDataListener) it.next())
.intervalRemoved( evt );
}
}
}
} );
}
public void setClients( final Client[] clients ) {
super.setClients( clients );
SwingUtilities.invokeLater( new Runnable() {
public void run() {
int oldSize = clientList_.size();
if ( oldSize > 0 ) {
clientList_.clear();
ListDataEvent removeEvt =
new ListDataEvent( ListModelTrackedClientSet.this,
ListDataEvent.INTERVAL_REMOVED,
0, oldSize - 1);
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
((ListDataListener) it.next())
.intervalRemoved( removeEvt );
}
}
if ( clients.length > 0 ) {
clientList_.addAll( Arrays.asList( clients ) );
int newSize = clientList_.size();
ListDataEvent addEvt =
new ListDataEvent( ListModelTrackedClientSet.this,
ListDataEvent.INTERVAL_ADDED,
0, newSize - 1);
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
((ListDataListener) it.next())
.intervalAdded( addEvt );
}
}
}
} );
}
public void updateClient( final Client client,
boolean metaChanged, boolean subsChanged ) {
super.updateClient( client, metaChanged, subsChanged );
SwingUtilities.invokeLater( new Runnable() {
public void run() {
int index = clientList_.indexOf( client );
if ( index >= 0 ) {
ListDataEvent evt =
new ListDataEvent( ListModelTrackedClientSet.this,
ListDataEvent.CONTENTS_CHANGED,
index, index );
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
((ListDataListener) it.next())
.contentsChanged( evt );
}
}
}
} );
}
}
/**
* Action which registers and unregisters with the hub.
*/
private class RegisterAction extends AbstractAction {
/**
* Constructs in an unarmed state.
*/
public RegisterAction() {
}
/**
* Constructs with a given (initial) sense.
*
* @param active true to register, false to unregister
*/
public RegisterAction( boolean active ) {
this();
setSense( active );
}
/**
* Sets whether this action registers or unregisters.
*
* @param active true to register, false to unregister
*/
public void setSense( boolean active ) {
putValue( ACTION_COMMAND_KEY, active ? "REGISTER"
: "UNREGISTER" );
putValue( NAME, active ? "Register with Hub"
: "Unregister from Hub" );
putValue( SHORT_DESCRIPTION,
active ? "Attempt to connect to SAMP hub"
: "Disconnect from SAMP hub" );
}
public void actionPerformed( ActionEvent evt ) {
String cmd = evt.getActionCommand();
if ( "REGISTER".equals( cmd ) ) {
setActive( true );
if ( ! isConnected() ) {
registerFailed();
}
}
else if ( "UNREGISTER".equals( cmd ) ) {
setActive( false );
}
else {
throw new UnsupportedOperationException( "Unknown action "
+ cmd );
}
}
protected void registerFailed() {
Toolkit.getDefaultToolkit().beep();
}
}
/**
* Action subclass for popping up a monitor window.
*/
private class MonitorAction extends AbstractAction {
private JFrame monitorWindow_;
/**
* Constructor.
*/
MonitorAction() {
super( "Show Hub Status" );
putValue( SHORT_DESCRIPTION,
"Display a window showing client applications"
+ " registered with the SAMP hub" );
}
public void actionPerformed( ActionEvent evt ) {
if ( monitorWindow_ == null ) {
monitorWindow_ = new JFrame( "SAMP Status" );
monitorWindow_.getContentPane()
.add( createMonitorPanel(), BorderLayout.CENTER );
monitorWindow_.pack();
}
monitorWindow_.setVisible( true );
}
}
/**
* Action subclass for running a hub.
*/
private class HubAction extends AbstractAction {
private final boolean external_;
private final HubServiceMode hubMode_;
private final boolean isAvailable_;
/**
* Constructor.
*
* @param external false to run in the current JVM,
* true to run in a new one
* @param hubMode hub mode
*/
HubAction( boolean external, HubServiceMode hubMode ) {
external_ = external;
hubMode_ = hubMode;
putValue( NAME,
"Start " + ( external ? "external" : "internal" )
+ " hub" );
putValue( SHORT_DESCRIPTION,
"Attempts to start up a SAMP hub"
+ ( external ? " running independently of this application"
: " running within this application" ) );
setEnabled( ! isConnected() );
registerUpdater( this, DISABLE_ACTION );
boolean isAvailable = true;
if ( external ) {
try {
Hub.checkExternalHubAvailability();
}
catch ( Exception e ) {
isAvailable = false;
}
}
isAvailable_ = isAvailable;
}
public void actionPerformed( ActionEvent evt ) {
try {
attemptRunHub();
}
catch ( Exception e ) {
ErrorDialog.showError( null, "Hub Start Failed",
e.getMessage(), e );
}
setActive( true );
}
public boolean isEnabled() {
return isAvailable_ && super.isEnabled();
}
/**
* Tries to start a hub, but may throw an exception.
*/
private void attemptRunHub() throws IOException {
if ( external_ ) {
Hub.runExternalHub( hubMode_ );
}
else {
Hub.runHub( hubMode_ );
}
setActive( true );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/GuiHubService.java 0000664 0000000 0000000 00000021470 13564500043 0025340 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.util.Map;
import java.util.Random;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.hub.BasicHubService;
import org.astrogrid.samp.hub.ClientSet;
import org.astrogrid.samp.hub.HubClient;
/**
* BasicHubService subclass which provides a GUI window displaying hub
* status as well as the basic hub services.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
public class GuiHubService extends BasicHubService {
private GuiClientSet clientSet_;
private ListSelectionModel selectionModel_;
/**
* Constructor.
*
* @param random random number generator used for message tags etc
*/
public GuiHubService( Random random ) {
super( random );
}
public void start() {
super.start();
clientSet_ = (GuiClientSet) getClientSet();
}
protected ClientSet createClientSet() {
return new GuiClientSet( getIdComparator() );
}
/**
* Creates a new component containing a display of the current hub
* internal state.
*
* @return new hub viewer panel
*/
public JComponent createHubPanel() {
HubView hubView = new HubView( true );
hubView.setClientListModel( getClientListModel() );
JList jlist = hubView.getClientList();
jlist.setCellRenderer( new ClientListCellRenderer() );
jlist.addMouseListener( new HubClientPopupListener( this ) );
selectionModel_ = jlist.getSelectionModel();
return hubView;
}
/**
* Creates a new window which maintains a display of the current hub
* internal state.
*
* @return new hub viewer window
*/
public JFrame createHubWindow() {
JFrame frame = new JFrame( "SAMP Hub" );
frame.getContentPane().add( createHubPanel() );
frame.setIconImage( new ImageIcon( Client.class
.getResource( "images/hub.png" ) )
.getImage() );
frame.pack();
return frame;
}
protected void declareMetadata( HubClient caller, Map meta )
throws SampException {
super.declareMetadata( caller, meta );
clientSet_.scheduleClientChanged( caller );
}
protected void declareSubscriptions( HubClient caller, Map subscriptions )
throws SampException {
super.declareSubscriptions( caller, subscriptions );
clientSet_.scheduleClientChanged( caller );
}
/**
* Returns a ListModel containing information about clients currently
* registered with this hub.
*
* @return list model in which each element is a
* {@link org.astrogrid.samp.Client}
*/
public ListModel getClientListModel() {
return clientSet_;
}
/**
* Returns the selection model corresponding to this service's client
* list model.
*
* @return list selection model for client selection
*/
public ListSelectionModel getClientSelectionModel() {
return selectionModel_;
}
/**
* Returns the client object currently selected in the GUI, if any.
*
* @return currently selected client, or null
*/
private Client getSelectedClient() {
ListSelectionModel selModel = getClientSelectionModel();
int isel = selModel.getMinSelectionIndex();
Object selected = isel >= 0 ? getClientListModel().getElementAt( isel )
: null;
return selected instanceof Client ? (Client) selected : null;
}
/**
* Returns an array of menus which may be added to a window
* containing this service's window.
*
* @return menu array
*/
public JMenu[] createMenus() {
final HubConnection serviceConnection = getServiceConnection();
final String hubId = serviceConnection.getRegInfo().getSelfId();
/* Broadcast ping action. */
final Message pingMessage = new Message( "samp.app.ping" );
final Action pingAllAction = new AbstractAction( "Ping all" ) {
public void actionPerformed( ActionEvent evt ) {
new SampThread( evt, "Ping Error", "Error broadcasting ping" ) {
protected void sampRun() throws SampException {
serviceConnection.callAll( "ping-tag", pingMessage );
}
}.start();
}
};
pingAllAction.putValue( Action.SHORT_DESCRIPTION,
"Send ping message to all clients" );
/* Single client ping action. */
final String pingSelectedName = "Ping selected client";
final Action pingSelectedAction =
new AbstractAction( pingSelectedName ) {
public void actionPerformed( ActionEvent evt ) {
final Client client = getSelectedClient();
if ( client != null ) {
new SampThread( evt, "Ping Error",
"Error sending ping to " + client ) {
protected void sampRun() throws SampException {
serviceConnection.call( client.getId(), "ping-tag",
pingMessage );
}
}.start();
}
}
};
pingSelectedAction.putValue( Action.SHORT_DESCRIPTION,
"Send ping message to selected client" );
/* Single client disconnect action. */
final String disconnectSelectedName = "Disconnect selected client";
final Action disconnectSelectedAction =
new AbstractAction( disconnectSelectedName ) {
public void actionPerformed( ActionEvent evt ) {
final Client client = getSelectedClient();
if ( client != null ) {
new SampThread( evt, "Disconnect Error",
"Error disconnecting " + client ) {
protected void sampRun() throws SampException {
disconnect( client.getId(),
"GUI hub user requested ejection" );
}
}.start();
}
}
};
disconnectSelectedAction.putValue( Action.SHORT_DESCRIPTION,
"Forcibly disconnect selected client"
+ " from the hub" );
/* Ensure that actions are kept up to date. */
ListSelectionListener selListener = new ListSelectionListener() {
public void valueChanged( ListSelectionEvent evt ) {
Client client = getSelectedClient();
boolean isSel = client != null;
boolean canPing = isSel
&& client.getSubscriptions()
.isSubscribed( pingMessage.getMType() );
boolean canDisco = isSel
&& ! hubId.equals( client.getId() );
pingSelectedAction.setEnabled( canPing );
disconnectSelectedAction.setEnabled( canDisco );
String clientDesignation = client == null
? ""
: ( " (" + client + ")" );
pingSelectedAction.putValue( Action.NAME,
pingSelectedName
+ clientDesignation );
disconnectSelectedAction.putValue( Action.NAME,
disconnectSelectedName
+ clientDesignation );
}
};
getClientSelectionModel().addListSelectionListener( selListener );
selListener.valueChanged( null );
/* Prepare and return menus containing the actions. */
JMenu clientMenu = new JMenu( "Clients" );
clientMenu.setMnemonic( KeyEvent.VK_C );
clientMenu.add( new JMenuItem( pingAllAction ) );
clientMenu.add( new JMenuItem( pingSelectedAction ) );
clientMenu.add( new JMenuItem( disconnectSelectedAction ) );
return new JMenu[] { clientMenu };
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/HubClientPopupListener.java 0000664 0000000 0000000 00000015175 13564500043 0027250 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractAction;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.hub.HubClient;
import org.astrogrid.samp.hub.BasicHubService;
/**
* MouseListener which provides a popup menu with per-client options
* for use with a JList containing HubClient objects.
*
* @author Mark Taylor
* @since 8 Jul 2009
*/
class HubClientPopupListener implements MouseListener {
private final BasicHubService hub_;
/** Message which does a ping. */
private static final Message PING_MSG = new Message( "samp.app.ping" );
/**
* Constructor.
*
* @param hub hub service which knows about the HubClients contained
* in the JList this will be listening to
*/
public HubClientPopupListener( BasicHubService hub ) {
hub_ = hub;
}
public void mouseClicked( MouseEvent evt ) {
}
public void mouseEntered( MouseEvent evt ) {
}
public void mouseExited( MouseEvent evt ) {
}
public void mousePressed( MouseEvent evt ) {
maybeShowPopup( evt );
}
public void mouseReleased( MouseEvent evt ) {
maybeShowPopup( evt );
}
/**
* Invoked for a MouseEvent which may be a popup menu trigger.
*
* @param evt popup trigger event candidate
*/
private void maybeShowPopup( MouseEvent evt ) {
if ( evt.isPopupTrigger() && evt.getSource() instanceof JList ) {
final JList jlist = (JList) evt.getSource();
final int index = jlist.locationToIndex( evt.getPoint() );
if ( index >= 0 ) {
Object item = jlist.getModel().getElementAt( index );
if ( item instanceof HubClient ) {
HubClient client = (HubClient) item;
// Set the selection to the client for which the menu
// will be posted. This is not essential, but it can be
// visually confusing for the user if it doesn't happen.
SwingUtilities.invokeLater( new Runnable() {
public void run() {
jlist.setSelectedIndex( index );
}
} );
Component comp = evt.getComponent();
JPopupMenu popper = createPopup( comp, client );
popper.show( comp, evt.getX(), evt.getY() );
}
}
}
}
/**
* Returns a new popup menu for a given client.
* The actions on this menu are not dynamic (e.g. do not enable/disable
* themselves according to changes in the hub status) because the
* menu is likely to be short-lived.
*
* @param parent parent component
* @param client hub client which the menu will affect
* @return new popup menu
*/
private JPopupMenu createPopup( Component parent, HubClient client ) {
JPopupMenu popper = new JPopupMenu();
popper.add( new CallAction( parent, client, "Ping", PING_MSG, true ) );
popper.add( new DisconnectAction( parent, client ) );
return popper;
}
/**
* Action which will forcibly disconnect a given client.
*/
private class DisconnectAction extends AbstractAction {
private final Component parent_;
private final HubClient client_;
/**
* Constructor.
*
* @param parent parent component
* @param client client to disconnect
*/
public DisconnectAction( Component parent, HubClient client ) {
super( "Disconnect" );
parent_ = parent;
client_ = client;
putValue( SHORT_DESCRIPTION,
"Forcibly disconnect client " + client_ + " from hub" );
setEnabled( ! client.getId()
.equals( hub_.getServiceConnection()
.getRegInfo().getSelfId() ) );
}
public void actionPerformed( ActionEvent evt ) {
new SampThread( parent_, "Disconnect Error",
"Error disconnecting client " + client_ ) {
protected void sampRun() throws SampException {
hub_.disconnect( client_.getId(),
"GUI hub user requested ejection" );
}
}.start();
}
}
/**
* Action which will send a message to a client.
*/
private class CallAction extends AbstractAction {
private final Component parent_;
private final HubClient client_;
private final String name_;
private final Message msg_;
private final boolean isCall_;
/**
* Constructor.
*
* @param parent parent component
* @param client client to receive message
* @param name informal name of message (for menu)
* @param msg message to send
* @param isCall true for call, false for notify
*/
public CallAction( Component parent, HubClient client, String name,
Message msg, boolean isCall ) {
super( name );
parent_ = parent;
client_ = client;
name_ = name;
msg_ = msg;
isCall_ = isCall;
String mtype = msg.getMType();
putValue( SHORT_DESCRIPTION,
"Send " + mtype + ( isCall ? " call" : " notification" )
+ " to client " + client );
setEnabled( client_.isSubscribed( mtype ) );
}
public void actionPerformed( ActionEvent evt ) {
final HubConnection connection = hub_.getServiceConnection();
final String recipientId = client_.getId();
new SampThread( parent_, name_ + " Error",
"Error attempting to send message "
+ msg_.getMType() + " to client " + client_ ) {
protected void sampRun() throws SampException {
if ( isCall_ ) {
connection.call( recipientId, name_ + "-tag", msg_ );
}
else {
connection.notify( recipientId, msg_ );
}
}
}.start();
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/HubMonitor.java 0000664 0000000 0000000 00000023763 13564500043 0024731 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.BorderLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.MessageHandler;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.UtilServer;
/**
* Client application which uses a {@link GuiHubConnector}
* to connect to any running hub and display information about all currently
* registered clients.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
public class HubMonitor extends JPanel {
private final GuiHubConnector connector_;
private static Logger logger_ =
Logger.getLogger( HubMonitor.class.getName() );
/**
* Constructor.
*
* @param profile SAMP profile
* @param trackMessages if true, the GUI will contain a visual
* representation of messages sent and received
* @param autoSec number of seconds between automatic hub connection
* attempts; <=0 means no automatic connections
*/
public HubMonitor( ClientProfile profile, boolean trackMessages,
int autoSec ) {
super( new BorderLayout() );
// Set up a new GuiHubConnector and GUI decorations.
connector_ = trackMessages ? new MessageTrackerHubConnector( profile )
: new GuiHubConnector( profile );
// Declare the default subscriptions. This is required so that
// the hub knows the client is subscribed to those hub.event
// MTypes which inform about client registration, hub shutdown etc.
connector_.declareSubscriptions( connector_.computeSubscriptions() );
// Declare metadata about this application.
Metadata meta = new Metadata();
meta.setName( "HubMonitor" );
meta.setDescriptionText( "GUI hub monitor utility" );
try {
meta.setIconUrl( UtilServer.getInstance()
.exportResource( "/org/astrogrid/samp/images/"
+ "eye.gif" )
.toString() );
}
catch ( IOException e ) {
logger_.warning( "Can't set icon" );
}
meta.put( "author", "Mark Taylor" );
connector_.declareMetadata( meta );
// Create and place a component which maintains a display of
// currently registered clients. A more modest GUI could just use
// connector.getClientListModel() as a model for a JList component.
add( connector_.createMonitorPanel(), BorderLayout.CENTER );
// Prepare a container for other widgets at the bottom of the window.
JPanel infoBox = new JPanel( new BorderLayout() );
add( infoBox, BorderLayout.SOUTH );
// Create and place components which allow the user to
// view and control registration/unregistration explicitly.
JComponent connectBox = new JPanel( new BorderLayout() );
connectBox.add( new JButton( connector_.createToggleRegisterAction() ),
BorderLayout.CENTER );
connectBox.add( connector_.createConnectionIndicator(),
BorderLayout.EAST );
infoBox.add( connectBox, BorderLayout.EAST );
// Create and place components which provide a compact display
// of the connector's status.
JComponent statusBox = Box.createHorizontalBox();
statusBox.add( connector_.createClientBox( false, 24 ) );
if ( connector_ instanceof MessageTrackerHubConnector ) {
statusBox.add( ((MessageTrackerHubConnector) connector_)
.createMessageBox( 24 ) );
}
infoBox.add( statusBox, BorderLayout.CENTER );
// Attempt registration, and arrange that if/when unregistered we look
// for a hub to register with on a regular basis.
connector_.setActive( true );
connector_.setAutoconnect( autoSec );
}
/**
* Returns this monitor's HubConnector.
*
* @return hub connector
*/
public GuiHubConnector getHubConnector() {
return connector_;
}
/**
* Does the work for the main method.
*/
public static int runMain( String[] args ) {
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " )
.append( HubMonitor.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [+/-verbose]" )
.append( "\n " )
.append( " [-auto icon
*/
public static Icon sizeIcon( Icon icon, final int size ) {
if ( icon == null ) {
return new Icon() {
public int getIconWidth() {
return size;
}
public int getIconHeight() {
return size;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
}
};
}
else if ( icon.getIconWidth() == size &&
icon.getIconHeight() == size ) {
return icon;
}
else {
return new SizedIcon( icon, size );
}
}
/**
* Icon implementation which is rescaled to so that one dimension
* (either width or height) has a fixed value.
*
* @param icon input icon
* @param fixDim the fixed dimension in pixels
* @param maxAspect maximum aspect ratio (>= 1)
* @param fixVertical true to fix height, false to fix width
*/
public static Icon scaleIcon( final Icon icon, final int fixDim,
double maxAspect, boolean fixVertical ) {
final int w = icon.getIconWidth();
final int h = icon.getIconHeight();
if ( ( fixVertical ? h : w ) == fixDim &&
( fixVertical ? h / (double) w
: w / (double) h ) <= maxAspect ) {
return icon;
}
double factor = fixDim / (double) ( fixVertical ? h : w );
if ( factor > 1.0 && factor < 2.0 ) {
factor = 1.0;
}
double aspect = factor * ( fixVertical ? h : w ) / fixDim;
if ( aspect > maxAspect ) {
factor *= maxAspect / aspect;
}
final int width = fixVertical ? (int) Math.ceil( factor * w ) : fixDim;
final int height = fixVertical ? fixDim : (int) Math.ceil( factor * h );
final double fact = factor;
return new Icon() {
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return height;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
if ( fact == 1.0 ) {
icon.paintIcon( c, g, x + ( width - w ) / 2,
y + ( height - h ) / 2 );
}
else {
Graphics2D g2 = (Graphics2D) g;
AffineTransform trans = g2.getTransform();
g2.translate( x + ( width - w * fact ) / 2,
y + ( height - h * fact ) / 2 );
g2.scale( fact, fact );
icon.paintIcon( c, g2, 0, 0 );
g2.setTransform( trans );
}
}
};
}
/**
* Reads an icon from a URL, with a maximum wait time.
* If the timeout is exceeded, an exception will be thrown.
*
* @param url icon URL
* @param waitSecs maximum time in seconds to wait
* @return icon from url
* @throws IOException if timeout has been exceeded
*/
private static Icon readIcon( String url, int waitSecs )
throws IOException {
final URL urlLoc = new URL( url );
final Icon[] icons = new Icon[ 1 ];
Thread loader = new Thread( "IconLoader " + url ) {
public void run() {
icons[ 0 ] = new ImageIcon( urlLoc );
}
};
loader.start();
try {
loader.join( waitSecs * 1000 );
Icon icon = icons[ 0 ];
if ( icon != null ) {
return icon;
}
else {
throw new IOException( "Icon load timeout ("
+ waitSecs + "s)" );
}
}
catch ( InterruptedException e ) {
throw (IOException) new IOException( "Load interrupted" )
.initCause( e );
}
}
/**
* Icon implementation which looks like an existing one, but is resized
* down if necessary.
*/
private static class SizedIcon implements Icon {
private final Icon icon_;
private final int size_;
private final double factor_;
/**
* Constructor.
*
* @param icon original icon
* @param size number of horizontal and vertical pixels in this icon
*/
public SizedIcon( Icon icon, int size ) {
icon_ = icon;
size_ = size;
factor_ =
Math.min( 1.0,
Math.min( size / (double) icon.getIconWidth(),
size / (double) icon.getIconHeight() ) );
}
public int getIconWidth() {
return size_;
}
public int getIconHeight() {
return size_;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
int iw = icon_.getIconWidth();
int ih = icon_.getIconHeight();
if ( factor_ == 1.0 ) {
icon_.paintIcon( c, g, x + ( size_ - iw ) / 2,
y + ( size_ - ih ) / 2 );
}
else {
Graphics2D g2 = (Graphics2D) g;
AffineTransform trans = g2.getTransform();
g2.translate( x + ( size_ - iw * factor_ ) / 2,
y + ( size_ - ih * factor_ ) / 2 );
g2.scale( factor_, factor_ );
icon_.paintIcon( c, g2, 0, 0 );
g2.setTransform( trans );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/IndividualCallActionManager.java 0000664 0000000 0000000 00000011672 13564500043 0030154 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ListModel;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.ResultHandler;
/**
* SendActionManager which uses the Asynchronous Call/Response delivery
* pattern, but allows a "broadcast" to send different message objects
* to different recipients.
* Multiple targetted sends rather than an actual SAMP broadcast may be
* used to achieve this.
* Concrete subclasses need only implement the
* {@link #createMessage(org.astrogrid.samp.Client)} method.
* They may also wish to to customise the returned Send and Broadcast Action
* objects (for instance give them useful names and descriptions).
*
* @author Mark Taylor
* @since 3 Dec 2008
*/
public abstract class IndividualCallActionManager
extends AbstractCallActionManager {
private final Component parent_;
/**
* Constructor.
*
* @param parent parent component
* @param connector hub connector
* @param clientListModel list model containing only those clients
* which are suitable recipients;
* all elements must be {@link Client}s
*/
public IndividualCallActionManager( Component parent,
GuiHubConnector connector,
ListModel clientListModel ) {
super( parent, connector, clientListModel );
parent_ = parent;
}
protected abstract Map createMessage( Client client ) throws Exception;
public Action createBroadcastAction() {
return new BroadcastAction();
}
/**
* Action which performs "broadcasts". They may actually be multiple
* targetted sends.
*/
private class BroadcastAction extends AbstractAction {
final HubConnector connector_ = getConnector();
final ListModel clientList_ = getClientListModel();
/**
* Constructor.
*/
BroadcastAction() {
putValue( SMALL_ICON, getBroadcastIcon() );
}
public void actionPerformed( ActionEvent evt ) {
// Identify groups of recipients which can receive the same
// Message object as each other.
int nc = clientList_.getSize();
Map msgMap = new HashMap();
try {
for ( int ic = 0; ic < nc; ic++ ) {
Client client = (Client) clientList_.getElementAt( ic );
Map message = createMessage( client );
if ( message != null ) {
Message msg = Message.asMessage( message );
msg.check();
if ( ! msgMap.containsKey( msg ) ) {
msgMap.put( msg, new HashSet() );
}
Collection clientSet = (Collection) msgMap.get( msg );
clientSet.add( client );
}
}
}
catch ( Exception e ) {
ErrorDialog.showError( parent_, "Send Error",
"Error constructing message "
+ e.getMessage(), e );
return;
}
// Send the message to each group at a time.
try {
HubConnection connection = connector_.getConnection();
if ( connection != null ) {
for ( Iterator it = msgMap.entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Message msg = (Message) entry.getKey();
Client[] recipients =
(Client[]) ((Collection) entry.getValue())
.toArray( new Client[ 0 ] );
String tag = createTag();
ResultHandler handler =
createResultHandler( connection, msg, recipients );
registerHandler( tag, recipients, handler );
for ( int ir = 0; ir < recipients.length; ir++ ) {
connection.call( recipients[ ir ].getId(),
tag, msg );
}
}
}
}
catch ( Exception e ) {
ErrorDialog.showError( parent_, "Send Error",
"Error sending message "
+ e.getMessage(), e );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/MessageTrackerHubConnector.java 0000664 0000000 0000000 00000103216 13564500043 0030045 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.client.TrackedClientSet;
/**
* HubConnector implementation which provides facilities for keeping track
* of incoming and outgoing messages as well as the other GUI features.
*
* @author Mark Taylor
* @since 26 Nov 2008
*/
public class MessageTrackerHubConnector extends GuiHubConnector
implements ClientTransmissionHolder {
private final TransmissionListModel txListModel_;
private final TransmissionListModel rxListModel_;
private final TransmissionTableModel txTableModel_;
private final TransmissionTableModel rxTableModel_;
private final Map callAllMap_;
private final Map txModelMap_;
private final Map rxModelMap_;
private final ListDataListener transListListener_;
private final int listRemoveDelay_;
private static final Logger logger_ =
Logger.getLogger( MessageTrackerHubConnector.class.getName() );
/**
* Constructs a hub connector with default message tracker GUI expiry times.
*
* @param profile profile implementation
*/
public MessageTrackerHubConnector( ClientProfile profile ) {
this( profile, 500, 20000, 100 );
}
/**
* Constructs a hub connector with specified message tracker GUI
* expiry times.
* The delay times are times in milliseconds after message resolution
* before message representations expire and hence remove themselves
* from gui components.
*
* @param profile profile implementation
* @param listRemoveDelay expiry delay for summary icons in client
* list display
* @param tableRemoveDelay expiry delay for rows in message
* table display
* @param tableMaxRows maximum number of rows in message table
* (beyond this limit resolved messages may be
* removed early)
*/
public MessageTrackerHubConnector( ClientProfile profile,
int listRemoveDelay,
int tableRemoveDelay,
int tableMaxRows ) {
super( profile );
listRemoveDelay_ = listRemoveDelay;
transListListener_ = new ClientTransmissionListListener();
txListModel_ = new TransmissionListModel( listRemoveDelay_ );
rxListModel_ = new TransmissionListModel( listRemoveDelay_ );
txListModel_.addListDataListener( transListListener_ );
rxListModel_.addListDataListener( transListListener_ );
txTableModel_ =
new TransmissionTableModel( false, true,
tableRemoveDelay, tableMaxRows );
rxTableModel_ =
new TransmissionTableModel( true, false,
tableRemoveDelay, tableMaxRows );
callAllMap_ = new HashMap(); // access only from EDT
txModelMap_ = new WeakHashMap();
rxModelMap_ = new WeakHashMap();
}
/**
* Returns a ListModel representing the pending messages sent using
* this connector.
* Elements of the model are {@link Transmission} objects.
*
* @return transmission list model
*/
public ListModel getTxListModel() {
return txListModel_;
}
/**
* Returns a ListModel representing the pending messages received using
* this connector.
* Elements of the model are {@link Transmission} objects.
*
* @return transmission list model
*/
public ListModel getRxListModel() {
return rxListModel_;
}
public ListModel getTxListModel( Client client ) {
if ( ! txModelMap_.containsKey( client ) ) {
TransmissionListModel listModel =
new TransmissionListModel( listRemoveDelay_ );
listModel.addListDataListener( transListListener_ );
txModelMap_.put( client, listModel );
}
return (ListModel) txModelMap_.get( client );
}
public ListModel getRxListModel( Client client ) {
if ( ! rxModelMap_.containsKey( client ) ) {
TransmissionListModel listModel =
new TransmissionListModel( listRemoveDelay_ );
listModel.addListDataListener( transListListener_ );
rxModelMap_.put( client, listModel );
}
return (ListModel) rxModelMap_.get( client );
}
/**
* Returns a component which displays messages currently being
* sent/received by this connector.
*
* @return iconSize height of icons in box
*/
public JComponent createMessageBox( int iconSize ) {
JComponent box =
createMessageBox( iconSize, rxListModel_, txListModel_ );
registerUpdater( box, ENABLE_COMPONENT );
return box;
}
/**
* Returns a component which displays messages in receiver and/or sender
* list models.
*
* @param iconSize height of icons
* @param rxListModel list model containing received
* {@link Transmission} objects
* @param txListModel list model containing sent
* {@link Transmission} objects
*/
public static JComponent createMessageBox( int iconSize,
ListModel rxListModel,
ListModel txListModel ) {
final Color dtColor = UIManager.getColor( "Label.disabledText" );
JComponent box = new JPanel() {
final Color enabledFg = getForeground();
final Color enabledBg = Color.WHITE;
final Color disabledFg = null;
final Color disabledBg = getBackground();
public void setEnabled( boolean enabled ) {
super.setEnabled( enabled );
setForeground( enabled ? enabledFg : disabledFg );
setBackground( enabled ? enabledBg : disabledFg );
}
};
box.setLayout( new BoxLayout( box, BoxLayout.X_AXIS ) );
if ( rxListModel != null ) {
IconBox rxBox = new IconBox( iconSize );
rxBox.setOpaque( false );
rxBox.setTrailing( true );
rxBox.setModel( rxListModel );
rxBox.setRenderer( new TransmissionCellRenderer() {
public String getToolTipText( IconBox iconBox, Object value,
int index ) {
if ( value instanceof Transmission ) {
Transmission trans = (Transmission) value;
return new StringBuffer()
.append( trans.getMessage().getMType() )
.append( " <- " )
.append( trans.getSender().toString() )
.toString();
}
else {
return super.getToolTipText( iconBox, value, index );
}
}
} );
Dimension prefSize = rxBox.getPreferredSize();
prefSize.width = iconSize * 3;
rxBox.setPreferredSize( prefSize );
box.add( rxBox );
}
IconBox cBox = new IconBox( iconSize );
cBox.setOpaque( false );
cBox.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 2 ) );
cBox.setModel( new AbstractListModel() {
public int getSize() {
return 1;
}
public Object getElementAt( int index ) {
return "app";
}
} );
cBox.setRenderer( new TransmissionCellRenderer() );
Dimension cSize = cBox.getPreferredSize();
cBox.setMaximumSize( cSize );
cBox.setMinimumSize( cSize );
box.add( cBox );
if ( txListModel != null ) {
IconBox txBox = new IconBox( iconSize );
txBox.setOpaque( false );
txBox.setModel( txListModel );
txBox.setRenderer( new TransmissionCellRenderer() {
public String getToolTipText( IconBox iconBox, Object value,
int index ) {
if ( value instanceof Transmission ) {
Transmission trans = (Transmission) value;
return new StringBuffer()
.append( trans.getMessage().getMType() )
.append( " -> " )
.append( trans.getReceiver().toString() )
.toString();
}
else {
return super.getToolTipText( iconBox, value, index );
}
}
} );
Dimension prefSize = txBox.getPreferredSize();
prefSize.width = iconSize * 3;
txBox.setPreferredSize( prefSize );
box.add( txBox );
}
box.setBackground( Color.WHITE );
box.setBorder( createBoxBorder() );
return box;
}
public ListCellRenderer createClientListCellRenderer() {
MessageTrackerListCellRenderer renderer =
new MessageTrackerListCellRenderer( this );
renderer.setTransmissionCellRenderer( new TransmissionCellRenderer() {
public String getToolTipText( IconBox iconBox, Object value,
int index ) {
return value instanceof Transmission
? ((Transmission) value).getMessage().getMType()
: super.getToolTipText( iconBox, value, index );
}
} );
return renderer;
}
public JComponent createMonitorPanel() {
JTabbedPane tabber = new JTabbedPane();
// Add client view tab.
HubView hubView = new HubView( false );
hubView.setClientListModel( getClientListModel() );
hubView.getClientList()
.setCellRenderer( createClientListCellRenderer() );
tabber.add( "Clients", hubView );
// Add received message tab.
tabber.add( "Received Messages",
new TransmissionView( rxTableModel_ ) );
// Add sent message tab.
tabber.add( "Sent Messages",
new TransmissionView( txTableModel_ ) );
// Position and return.
JComponent panel = new JPanel( new BorderLayout() );
panel.add( tabber, BorderLayout.CENTER );
return panel;
}
protected HubConnection createConnection() throws SampException {
HubConnection connection = super.createConnection();
return connection == null
? null
: new MessageTrackerHubConnection( connection );
}
/**
* Schedules a new transmission to add to the appropriate list models.
* May be called from any thread.
*
* @param trans transmission
* @param tx true for send, false for receive
*/
private void scheduleAddTransmission( final Transmission trans,
final boolean tx ) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
( tx ? txTableModel_
: rxTableModel_ ).addTransmission( trans );
((TransmissionListModel) getTxListModel( trans.getSender() ))
.addTransmission( trans );
((TransmissionListModel) getRxListModel( trans.getReceiver() ))
.addTransmission( trans );
}
} );
}
/**
* Schedules a response to be registered for a previously added
* transmission.
* May be called from any thread.
*
* @param trans transmission
* @param response response to associated with trans
*/
private void scheduleSetResponse( final Transmission trans,
final Response response ) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
trans.setResponse( response );
}
} );
}
/**
* Schedules an error to be registered for a previously added
* transmission.
* May be called from any thread.
*
* @param trans transmission
* @param error exception
*/
private void scheduleSetFailure( final Transmission trans,
final Throwable error ) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
trans.setError( error );
}
} );
}
/**
* HubConnection object which intercepts calls to keep track of
* outgoing and incoming messages.
*/
private class MessageTrackerHubConnection extends WrapperHubConnection {
private Client selfClient_;
/**
* Constructor.
*
* @param base connection on which this one is based
*/
MessageTrackerHubConnection( HubConnection base ) {
super( base );
}
/**
* Returns a Client object for use in Transmission objects
* which represents this connection's owner.
* This has to be the same object as is used in the client set,
* otherwise the various models don't get updated correctly.
* For this reason, it has to be obtained lazily, after the client set
* has been initialised.
*
* @return self client object
*/
Client getSelfClient() {
if ( selfClient_ == null ) {
selfClient_ =
(Client) getClientMap().get( getRegInfo().getSelfId() );
assert selfClient_ != null;
txModelMap_.put( selfClient_, txListModel_ );
rxModelMap_.put( selfClient_, rxListModel_ );
}
return selfClient_;
}
public void notify( final String recipientId, final Map msg )
throws SampException {
// Construct a transmission corresponding to this notify and
// add it to the send list.
Client recipient = (Client) getClientMap().get( recipientId );
Transmission trans = recipient == null
? null
: new Transmission( getSelfClient(), recipient,
Message.asMessage( msg ),
null, null );
if ( trans != null ) {
scheduleAddTransmission( trans, true );
}
// Do the actual send.
try {
super.notify( recipientId, msg );
// Notify won't generate a response, so signal that now.
if ( trans != null ) {
scheduleSetResponse( trans, null );
}
}
// If the send failed, signal it.
catch ( SampException e ) {
if ( trans != null ) {
scheduleSetFailure( trans, e );
}
throw e;
}
}
public List notifyAll( Map msg ) throws SampException {
// Do the send.
List recipientIdList = super.notifyAll( msg );
// Construct a list of transmissions corresponding to this notify
// and add them to the send list.
final List transList = new ArrayList();
Message message = Message.asMessage( msg );
Client sender = getSelfClient();
for ( Iterator it = recipientIdList.iterator(); it.hasNext(); ) {
Client recipient =
(Client) getClientMap().get( (String) it.next() );
if ( recipient != null ) {
Transmission trans =
new Transmission( sender, recipient, message,
null, null );
scheduleAddTransmission( trans, true );
// Notify won't generate a response, so signal that now.
scheduleSetResponse( trans, null );
}
}
return recipientIdList;
}
public String call( String recipientId, String msgTag, Map msg )
throws SampException {
// Construct a transmission corresponding to this call
// and add it to the send list.
Client recipient = (Client) getClientMap().get( recipientId );
Transmission trans = recipient == null
? null
: new Transmission( getSelfClient(), recipient,
Message.asMessage( msg ),
msgTag, null );
if ( trans != null ) {
scheduleAddTransmission( trans, true );
}
// Do the actual call.
try {
return super.call( recipientId, msgTag, msg );
}
// If the send failed, signal that since no reply will be
// forthcoming.
catch ( final SampException e ) {
scheduleSetFailure( trans, e );
throw e;
}
}
public Map callAll( final String msgTag, Map msg )
throws SampException {
// This is a bit more complicated than the other cases.
// We can't construct the list of transmissions before the send,
// since we don't know which are the recipient clients.
// But if we wait until after the delegated callAll() method
// we may miss some early responses to it. So we have to
// put in place a mechanism for dealing with responses before
// we know exactly what they are responses to.
// Prepare and store a CallAllHandler for this.
final CallAllHandler cah = new CallAllHandler( msgTag );
SwingUtilities.invokeLater( new Runnable() {
public void run() {
callAllMap_.put( msgTag, cah );
}
} );
// Do the actual call.
Map callMap = super.callAll( msgTag, msg );
// Prepare a post-facto list of the transmissions which were sent.
List transList = new ArrayList();
Message message = Message.asMessage( msg );
for ( Iterator it = callMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String recipientId = (String) entry.getKey();
Client sender = getSelfClient();
Client recipient = (Client) getClientMap().get( recipientId );
if ( recipient != null ) {
String msgId = (String) entry.getValue();
Transmission trans =
new Transmission( sender, recipient, message,
msgTag, msgId );
scheduleAddTransmission( trans, true );
transList.add( trans );
}
}
final Transmission[] transmissions =
(Transmission[]) transList.toArray( new Transmission[ 0 ] );
// And inform the CallAllHandler what the transmissions were, so
// it knows how to process (possibly already received) responses.
SwingUtilities.invokeLater( new Runnable() {
public void run() {
cah.setTransmissions( transmissions );
}
} );
return callMap;
}
public Response callAndWait( String recipientId, Map msg,
int timeout ) throws SampException {
// Construct a transmission obejct corresponding to this call
// and add it to the send list.
Client recipient = (Client) getClientMap().get( recipientId );
Transmission trans =
recipient == null
? null
: new Transmission( getSelfClient(), recipient,
Message.asMessage( msg ),
"
*
*
* @author Mark Taylor
* @since 1 Sep 2008
*/
public abstract class SelectiveClientListModel extends AbstractListModel {
private final ListModel baseModel_;
private final ListDataListener listDataListener_;
private int[] map_;
/**
* Constructor.
*
* @param clientListModel base ListModel containing
* {@link org.astrogrid.samp.Client} objects
*/
public SelectiveClientListModel( ListModel clientListModel ) {
baseModel_ = clientListModel;
// Somewhat haphazard implementation. The ListDataListener interface
// is not constructed (or documented) so as to make it easy to
// fire the right events. Some efficiency measures are taken here,
// but it would be possible to do more.
listDataListener_ = new ListDataListener() {
public void contentsChanged( ListDataEvent evt ) {
int[] oldMap = map_;
map_ = calculateMap();
if ( Arrays.equals( oldMap, map_ ) &&
evt.getIndex0() == evt.getIndex1() &&
evt.getIndex0() >= 0 ) {
int index = evt.getIndex0();
for ( int i = 0; i < map_.length; i++ ) {
if ( map_[ i ] == index ) {
fireContentsChanged( this, index, index );
}
}
}
else {
fireContentsChanged( this, -1, -1 );
}
}
public void intervalAdded( ListDataEvent evt ) {
int[] oldMap = map_;
map_ = calculateMap();
if ( ! Arrays.equals( oldMap, map_ ) ) {
int leng = Math.min( map_.length, oldMap.length );
int index0 = -1;
for ( int i = 0; i < leng; i++ ) {
if ( oldMap[ i ] != map_[ i ] ) {
index0 = i;
break;
}
}
int index1 = -1;
for ( int i = 0; i < leng; i++ ) {
if ( oldMap[ oldMap.length - 1 - i ] !=
map_[ map_.length - 1 - i ] ) {
index1 = map_.length - 1 - i;
break;
}
}
if ( index0 >= 0 && index1 >= 0 ) {
fireIntervalAdded( this, index0, index1 );
}
else {
fireContentsChanged( this, -1, -1 );
}
}
}
public void intervalRemoved( ListDataEvent evt ) {
int[] oldMap = map_;
map_ = calculateMap();
if ( ! Arrays.equals( oldMap, map_ ) ) {
fireContentsChanged( this, -1, -1 );
}
}
};
}
/**
* Implement this method to determine which clients are included in
* this list.
*
* @param client client for consideration
* @return true iff client is to be included in this list
*/
protected abstract boolean isIncluded( Client client );
/**
* Must be called by subclass prior to use.
*/
protected void init() {
refresh();
baseModel_.addListDataListener( listDataListener_ );
}
/**
* Recalculates the inclusions. This should be called if the return
* value from {@link #isIncluded} might have changed for some of the
* elements.
*/
protected void refresh() {
map_ = calculateMap();
}
public int getSize() {
return map_.length;
}
public Object getElementAt( int index ) {
return baseModel_.getElementAt( map_[ index ] );
}
/**
* Releases any resources associated with this transmitter.
*/
public void dispose() {
baseModel_.removeListDataListener( listDataListener_ );
}
/**
* Recalculates the this list -> base list lookup table.
*
* @return array whose indices represent elements of this list, and
* values represent elements of the base list
*/
private int[] calculateMap() {
int nc = baseModel_.getSize();
int[] map = new int[ nc ];
int ij = 0;
for ( int ic = 0; ic < nc; ic++ ) {
Client client = (Client) baseModel_.getElementAt( ic );
if ( isIncluded( client ) ) {
map[ ij++ ] = ic;
}
}
int[] map1 = new int[ ij ];
System.arraycopy( map, 0, map1, 0, ij );
return map1;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SendActionManager.java 0000664 0000000 0000000 00000032242 13564500043 0026155 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.ComboBoxModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JMenu;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import org.astrogrid.samp.Client;
/**
* Manages actions to send SAMP messages to one or all recipients.
* The main useful trick that this class can do is to maintain one or
* more menus for sending messages to suitable recipients.
* The contents of these menus are updated automatically depending on
* the subscriptions of all the currently registered SAMP clients.
*
* equals
* (and hashCode
) intelligently there will be efficiency
* advantages.
* The enabled status of such actions will be managed by this object.
*
* @param client recipient client
* @return action which sends to the given client
*/
protected abstract Action getSendAction( Client client );
/**
* Sets the enabled status of this object. This acts as a restriction
* (AND) on the enabled status of the menus and actions controlled by
* this object. If there are no suitable recipient applications
* registered they will be disabled anyway.
*
* @param enabled false to ensure that the actions are disabled,
* true means they may be enabled
*/
public void setEnabled( boolean enabled ) {
enabled_ = enabled;
updateEnabledness();
}
/**
* Returns an action which will broadcast a message
* to all suitable registered applications.
*
* client
is subscribed to one of this
* model's MTypes.
*/
protected boolean isIncluded( Client client ) {
String selfId;
try {
HubConnection connection = connector_.getConnection();
if ( connection == null ) {
return false;
}
else {
selfId = connection.getRegInfo().getSelfId();
}
}
catch ( SampException e ) {
return false;
}
if ( client.getId().equals( selfId ) ) {
return false;
}
Subscriptions subs = client.getSubscriptions();
if ( subs != null ) {
for ( int im = 0; im < mtypes_.length; im++ ) {
if ( subs.isSubscribed( mtypes_[ im ] ) ) {
return true;
}
}
}
return false;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SysTray.java 0000664 0000000 0000000 00000017600 13564500043 0024252 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.PopupMenu;
import java.awt.event.ActionListener;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Logger;
/**
* Provides basic access to the windowing system's System Tray.
* This is a facade for a subset of the Java 1.6 java.awt.SystemTray
* functionality. When running in a J2SE1.6 JRE it will use reflection
* to access the underlying classes. In an earlier JRE, it will report
* lack of support.
*
* @author Mark Taylor
* @since 20 Jul 2010
*/
public abstract class SysTray {
/**
* Name of system property ({@value}) to inhibit use of system tray;
* if set "true" the system tray will not be used even if it is
* apparently supported. */
public static final String NOTRAY_PROP = "jsamp.nosystray";
private static SysTray instance_;
private static final Logger logger_ =
Logger.getLogger( SysTray.class.getName() );
/**
* Indicates whether system tray functionality is available.
*
* @return true iff the addIcon/removeIcon methods are expected to work
*/
public abstract boolean isSupported();
/**
* Adds an icon to the system tray.
*
* @param im image for display
* @param tooltip tooltip text, or null
* @param popup popup menu, or null
* @param iconListener listener triggered when icon is activated, or null
* @return tray icon object, may be used for later removal
*/
public abstract Object addIcon( Image im, String tooltip, PopupMenu popup,
ActionListener iconListener )
throws AWTException;
/**
* Removes a previously-added icon from the tray.
*
* @param trayIcon object obtained from a previous invocation of
* addIcon
*/
public abstract void removeIcon( Object trayIcon )
throws AWTException;
/**
* Returns an instance of this class.
*
* @return instance
*/
public static SysTray getInstance() {
if ( instance_ == null ) {
String jvers = System.getProperty( "java.specification.version" );
boolean isJava6 =
jvers != null && jvers.matches( "^[0-9]+\\.[0-9]+$" )
&& Double.parseDouble( jvers ) > 1.5999;
if ( ! isJava6 ) {
logger_.info( "Not expecting system tray support"
+ " (java version < 1.6)" );
}
SysTray instance;
String notrayProp = System.getProperty( NOTRAY_PROP );
if ( "true".equalsIgnoreCase( notrayProp ) ) {
logger_.info( "No system tray support by explicit request"
+ " (" + NOTRAY_PROP + ")" );
instance = new NoSysTray();
}
else {
try {
instance = new Java6SysTray();
}
catch ( Throwable e ) {
if ( isJava6 ) {
logger_.info( "No system tray support: " + e );
}
instance = new NoSysTray();
}
}
instance_ = instance;
}
return instance_;
}
/**
* Implementation which provides no system tray access.
*/
private static class NoSysTray extends SysTray {
public boolean isSupported() {
return false;
}
public Object addIcon( Image im, String tooltip, PopupMenu popup,
ActionListener iconListener ) {
throw new UnsupportedOperationException();
}
public void removeIcon( Object trayIcon ) {
throw new UnsupportedOperationException();
}
}
/**
* Implementation which provides system tray access using J2SE 1.6 classes
* by reflection.
*/
private static class Java6SysTray extends SysTray {
private final Class systemTrayClass_;
private final Method addMethod_;
private final Method removeMethod_;
private final Class trayIconClass_;
private final Constructor trayIconConstructor_;
private final Method setImageAutoSizeMethod_;
private final Method addActionListenerMethod_;
private final Object systemTrayInstance_;
/**
* Constructor.
*/
Java6SysTray() throws ClassNotFoundException, IllegalAccessException,
NoSuchMethodException, InvocationTargetException {
systemTrayClass_ = Class.forName( "java.awt.SystemTray" );
trayIconClass_ = Class.forName( "java.awt.TrayIcon" );
addMethod_ =
systemTrayClass_
.getMethod( "add", new Class[] { trayIconClass_ } );
removeMethod_ =
systemTrayClass_
.getMethod( "remove", new Class[] { trayIconClass_ } );
trayIconConstructor_ =
trayIconClass_
.getConstructor( new Class[] { Image.class, String.class,
PopupMenu.class } );
setImageAutoSizeMethod_ =
trayIconClass_
.getMethod( "setImageAutoSize", new Class[] { boolean.class } );
addActionListenerMethod_ =
trayIconClass_
.getMethod( "addActionListener",
new Class[] { ActionListener.class } );
boolean isSupported =
Boolean.TRUE
.equals( systemTrayClass_
.getMethod( "isSupported", new Class[ 0 ] )
.invoke( null, new Object[ 0 ] ) );
systemTrayInstance_ =
isSupported ? systemTrayClass_
.getMethod( "getSystemTray", new Class[ 0 ] )
.invoke( null, new Object[ 0 ] )
: null;
}
public boolean isSupported() {
return systemTrayInstance_ != null;
}
public Object addIcon( Image im, String tooltip, PopupMenu popup,
ActionListener iconListener )
throws AWTException {
try {
Object trayIcon =
trayIconConstructor_
.newInstance( new Object[] { im, tooltip, popup } );
setImageAutoSizeMethod_
.invoke( trayIcon, new Object[] { Boolean.TRUE } );
if ( iconListener != null ) {
addActionListenerMethod_
.invoke( trayIcon, new Object[] { iconListener } );
}
addMethod_.invoke( systemTrayInstance_,
new Object[] { trayIcon } );
return trayIcon;
}
catch ( InvocationTargetException e ) {
String msg = e.getCause() instanceof AWTException
? e.getCause().getMessage()
: "Add tray icon invocation failed";
throw (AWTException) new AWTException( msg ).initCause( e );
}
catch ( Exception e ) {
throw (AWTException)
new AWTException( "Add tray icon invocation failed" )
.initCause( e );
}
}
public void removeIcon( Object trayIcon ) throws AWTException {
try {
removeMethod_.invoke( systemTrayInstance_,
new Object[] { trayIcon } );
}
catch ( Exception e ) {
throw (AWTException)
new AWTException( "Remove tray icon invocation failed" )
.initCause( e );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/Transmission.java 0000664 0000000 0000000 00000025253 13564500043 0025330 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
/**
* Describes the properties of a message which has been sent from one
* client to another. Methods which might change the state of instances
* of this class should be invoked only from the AWT event dispatch thread.
*
* @author Mark Taylor
* @since 20 Nov 2008
*/
public class Transmission {
private final Client sender_;
private final Client receiver_;
private final Message msg_;
private final String msgId_;
private final String msgTag_;
private final List listenerList_;
private final ChangeEvent evt_;
private Response response_;
private Throwable error_;
private boolean senderUnreg_;
private boolean receiverUnreg_;
private long doneTime_;
private static final Logger logger_ =
Logger.getLogger( Transmission.class.getName() );
/**
* Constructor.
*
* @param sender sender
* @param receiver receiver
* @param msg message
* @param msgTag message tag
* @param msgId message ID
*/
public Transmission( Client sender, Client receiver, Message msg,
String msgTag, String msgId ) {
sender_ = sender;
receiver_ = receiver;
msg_ = msg;
msgTag_ = msgTag;
msgId_ = msgId;
listenerList_ = new ArrayList();
evt_ = new ChangeEvent( this );
doneTime_ = Long.MAX_VALUE;
}
/**
* Returns the client which sent this transmission.
*
* @return sender
*/
public Client getSender() {
return sender_;
}
/**
* Returns the client to which this transmission was sent.
*
* @return receiver
*/
public Client getReceiver() {
return receiver_;
}
/**
* Returns the message which was sent.
*
* @return message
*/
public Message getMessage() {
return msg_;
}
/**
* Returns the message tag corresponding to this transmission.
* Will be null for notify-type sends.
*
* @return msg tag
*/
public String getMessageTag() {
return msgTag_;
}
/**
* Returns the message ID associated with this message.
* This is the identifier passed to the receiver which it uses to
* match messages with responses; it will be null iff the transmission
* used the notify delivery pattern (no response expected).
*
* @return msgId; possibly null
*/
public String getMessageId() {
return msgId_;
}
/**
* Sets the response for this transmission.
*
* @param response response
*/
public void setResponse( Response response ) {
response_ = response;
fireChange();
}
/**
* Returns the response for this transmission.
* Will be null if no response has (yet) arrived.
*
* @return response
*/
public Response getResponse() {
return response_;
}
/**
* Associates an error with this transmission.
* This is probably an indication that the send failed or some other
* non-SAMP event intervened to prevent normal resolution.
*
* @param error throwable causing the failure
*/
public void setError( Throwable error ) {
error_ = error;
logger_.log( Level.WARNING,
"Error in hub operation: " + error.getMessage(), error );
fireChange();
}
/**
* Returns a Throwable which prevented normal resolution of this
* transmission.
*
* @return error
*/
public Throwable getError() {
return error_;
}
/**
* Indicates that the sender of this transmission has unregistered.
*/
public void setSenderUnregistered() {
senderUnreg_ = true;
fireChange();
}
/**
* Indicates that the receiver of this transmission has unregistered.
*/
public void setReceiverUnregistered() {
receiverUnreg_ = true;
fireChange();
}
/**
* Returns the epoch at which this transmission was completed.
* If it is still pending ({@link #isDone()}==false),
* the returned value will be (way) in the future.
*
* @return value of System.currentTimeMillis()
at which
* {@link #isDone} first returned true
*/
public long getDoneTime() {
return doneTime_;
}
/**
* Indicates whether further changes to the state of this object
* are expected, that is if a response/failure is yet to be received.
*
* @return true iff no further changes are expected
*/
public boolean isDone() {
return error_ != null
|| response_ != null
|| ( msgId_ == null && msgTag_ == null )
|| receiverUnreg_;
}
/**
* Returns an object which describes the current status of this
* transmission in terms which can be presented to the GUI.
*/
public Status getStatus() {
if ( error_ != null ) {
return Status.EXCEPTION;
}
else if ( response_ != null ) {
String status = response_.getStatus();
if ( Response.OK_STATUS.equals( status ) ) {
return Status.OK;
}
else if ( Response.WARNING_STATUS.equals( status ) ) {
return Status.WARNING;
}
else if ( Response.ERROR_STATUS.equals( status ) ) {
return Status.ERROR;
}
else if ( status == null ) {
return Status.NONE;
}
else {
return new Status( "Completed (" + status + ")",
Status.WARNING_COLOR, true );
}
}
else if ( msgId_ == null && msgTag_ == null ) {
return Status.NOTIFIED;
}
else if ( receiverUnreg_ ) {
return Status.ORPHANED;
}
else {
assert ! isDone();
return Status.PENDING;
}
}
/**
* Adds a listener which will be notified if the state of this transmission
* changes (if a response or failure is signalled).
* The {@link javax.swing.event.ChangeEvent}s sent to these listeners
* will have a source which is this Transmission.
*
* @param listener listener to add
*/
public void addChangeListener( ChangeListener listener ) {
listenerList_.add( listener );
}
/**
* Removes a listener previously added by {@link #addChangeListener}.
*
* @param listener listener to remove
*/
public void removeChangeListener( ChangeListener listener ) {
listenerList_.remove( listener );
}
/**
* Notifies listeners of a state change.
*/
private void fireChange() {
if ( doneTime_ == Long.MAX_VALUE && isDone() ) {
doneTime_ = System.currentTimeMillis();
}
for ( Iterator it = listenerList_.iterator();
it.hasNext(); ) {
ChangeListener listener = (ChangeListener) it.next();
listener.stateChanged( evt_ );
}
}
/**
* Describes the status of a transmission in terms that can be
* presented in the GUI.
*/
public static class Status {
private final String text_;
private final Color iconColor_;
private final boolean isDone_;
private final static Color PENDING_COLOR = Color.BLACK;
private final static Color OK_COLOR = new Color( 0x00c000 );
private final static Color ERROR_COLOR = new Color( 0xc00000 );
private final static Color WARNING_COLOR = new Color( 0x806030 );
private final static Color NOTIFY_COLOR = new Color( 0x808080 );
private final static Status OK =
new Status( "Success", OK_COLOR, true );
private final static Status WARNING =
new Status( "Warning", WARNING_COLOR, true );
private final static Status ERROR =
new Status( "Error", ERROR_COLOR, true );
private final static Status NONE =
new Status( "Completed (??)", WARNING_COLOR, true );
private final static Status NOTIFIED =
new Status( "Notified", NOTIFY_COLOR, true );
private final static Status EXCEPTION =
new Status( "Exception", ERROR_COLOR, true );
private final static Status ORPHANED =
new Status( "Orphaned", WARNING_COLOR, true );
private final static Status PENDING =
new Status( "...pending...", PENDING_COLOR, false );
/**
* Constructor.
*
* @param text short status summary
* @param iconColor colour to plot icon
* @param isDone whether status represents completed processing
*/
Status( String text, Color iconColor, boolean isDone ) {
text_ = text;
iconColor_ = iconColor;
isDone_ = isDone;
}
/**
* Returns the text for this status.
*
* @return short summmary
*/
public String getText() {
return text_;
}
/**
* Returns a little icon representing status.
*
* @param height required height of icon
* @return icon
*/
public Icon getIcon( final int height ) {
final int width = (int) Math.floor( 0.866 * height );
return new Icon() {
public void paintIcon( Component c, Graphics g, int x, int y ) {
int[] xs = new int[] { x, x + width, x, };
int[] ys = new int[] { y, y + height / 2, y + height, };
Color gcolor = g.getColor();
g.setColor( iconColor_ );
if ( isDone_ ) {
g.drawPolygon( xs, ys, 3 );
}
else {
g.fillPolygon( xs, ys, 3 );
}
g.setColor( gcolor );
}
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return height;
}
};
}
public String toString() {
return text_;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionCellRenderer.java 0000664 0000000 0000000 00000002366 13564500043 0027617 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.Icon;
/**
* CellRenderer for transmission objects.
*
* @author Mark Taylor
* @since 27 Nov 2008
*/
class TransmissionCellRenderer implements IconBox.CellRenderer {
public Icon getIcon( IconBox iconBox, Object value, int index ) {
final int size = iconBox.getTransverseSize();
if ( value instanceof Transmission ) {
return ((Transmission) value).getStatus().getIcon( size );
}
else {
return new Icon() {
public void paintIcon( Component c, Graphics g, int x, int y ) {
int s = size - 3 + ( size % 2 );
g.drawOval( x + 1, y + 1, s, s );
}
public int getIconWidth() {
return size;
}
public int getIconHeight() {
return size;
}
};
}
}
public String getToolTipText( IconBox iconBox, Object value, int index ) {
if ( value instanceof Transmission ) {
return ((Transmission) value).getMessage().getMType();
}
else {
return null;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionListIcon.java 0000664 0000000 0000000 00000021365 13564500043 0026775 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.ListModel;
import javax.swing.ToolTipManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
/**
* Icon which paints a graphical representation of a list of Transmissions.
*
* @author Mark Taylor
* @since 21 Nov 2008
*/
public class TransmissionListIcon implements Icon {
private final ListModel rxModel_;
private final ListModel txModel_;
private final int size_;
private final int transIconWidth_;
private final Icon targetIcon_;
/**
* Constructor.
*
* @param rxModel list of messages received;
* all elements must be {@link Transmission} objects
* @param txModel list of messages sent;
* all elements must be {@link Transmission} objects
* @param size height of icon in pixels; this also scales the width
*/
public TransmissionListIcon( ListModel rxModel, ListModel txModel,
int size ) {
rxModel_ = rxModel;
txModel_ = txModel;
size_ = size;
transIconWidth_ = (int) Math.floor( size_ * 0.866 ); // equilateral
final boolean hasTx = txModel_ != null;
final boolean hasRx = rxModel_ != null;
targetIcon_ = new Icon() {
public int getIconWidth() {
return size_;
}
public int getIconHeight() {
return size_;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
g.drawOval( x + 1, y + 1, size_ - 2, size_ - 2 );
}
};
}
/**
* Returns the transmission (if any) which is painted at a given point.
*
* @param point screen point relative to the origin of this icon
* @return transmission painted at point
or null if there
* isn't one
*/
public Transmission getTransmissionAt( Point point ) {
int x = point.x;
int y = point.y;
if ( x < 0 || x > getIconWidth() || y < 0 || y > getIconHeight() ) {
return null;
}
int x0 = 0;
int rxWidth = rxModel_.getSize() * transIconWidth_;
if ( x - x0 < rxWidth ) {
int ir = ( x - x0 ) / transIconWidth_;
return (Transmission) rxModel_.getElementAt( ir );
}
x0 += rxWidth;
int targetWidth = targetIcon_.getIconWidth();
if ( x - x0 < targetWidth ) {
return null;
}
x0 += targetWidth;
int txWidth = txModel_.getSize() * transIconWidth_;
if ( x - x0 < txWidth ) {
int it = ( x - x0 ) / transIconWidth_;
return (Transmission) txModel_.getElementAt( it );
}
x0 += txWidth;
assert x > x0;
return null;
}
public int getIconWidth() {
return ( rxModel_ != null ? rxModel_.getSize() * transIconWidth_ : 0 )
+ targetIcon_.getIconWidth()
+ ( txModel_ != null ? txModel_.getSize() * transIconWidth_ : 0 );
}
public int getIconHeight() {
return size_;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
if ( rxModel_ != null ) {
for ( int i = 0; i < rxModel_.getSize(); i++ ) {
Transmission trans = (Transmission) rxModel_.getElementAt( i );
Icon transIcon = getTransIcon( trans, false );
transIcon.paintIcon( c, g, x, y );
x += transIcon.getIconWidth();
}
}
targetIcon_.paintIcon( c, g, x, y );
x += targetIcon_.getIconWidth();
if ( txModel_ != null ) {
for ( int i = 0; i < txModel_.getSize(); i++ ) {
Transmission trans = (Transmission) txModel_.getElementAt( i );
Icon transIcon = getTransIcon( trans, true );
transIcon.paintIcon( c, g, x, y );
x += transIcon.getIconWidth();
}
}
}
/**
* Returns an icon which can paint a particular transmission.
*
* @param trans transmission
* @param isTx true if trans
represents a send,
* false if it represents a receive
*/
private Icon getTransIcon( Transmission trans, final boolean isTx ) {
return new Icon() {
public int getIconHeight() {
return size_;
}
public int getIconWidth() {
return transIconWidth_;
}
public void paintIcon( Component c, Graphics g, int x, int y ) {
int xlo = x + 1;
int xhi = x + transIconWidth_ - 1;
int[] xs = isTx ? new int[] { xhi, xlo, xhi, }
: new int[] { xlo, xhi, xlo, };
int[] ys = new int[] { y, y + size_ / 2, y + size_ - 1 };
g.fillPolygon( xs, ys, 3 );
}
};
}
public JComponent createBox( int nTrans ) {
return new TransmissionListBox( this, nTrans );
}
private static class TransmissionListBox extends JComponent {
private final TransmissionListIcon icon_;
private final int nTrans_;
private final boolean hasRx_;
private final boolean hasTx_;
private Dimension minSize_;
TransmissionListBox( TransmissionListIcon icon, int nTrans ) {
icon_ = icon;
nTrans_ = nTrans;
setOpaque( true );
setBackground( Color.WHITE );
setBorder( BorderFactory.createLineBorder( Color.BLACK ) );
ListDataListener listener = new ListDataListener() {
public void contentsChanged( ListDataEvent evt ) {
repaint();
}
public void intervalAdded( ListDataEvent evt ) {
repaint();
}
public void intervalRemoved( ListDataEvent evt ) {
repaint();
}
};
hasRx_ = icon.rxModel_ != null;
hasTx_ = icon.txModel_ != null;
if ( hasRx_ ) {
icon.rxModel_.addListDataListener( listener );
}
if ( hasTx_ ) {
icon.txModel_.addListDataListener( listener );
}
setPreferredSize( getSizeForCount( nTrans ) );
setMinimumSize( getSizeForCount( Math.max( nTrans, 4 ) ) );
ToolTipManager.sharedInstance().registerComponent( this );
}
public Dimension getSizeForCount( int nTrans ) {
int width = icon_.targetIcon_.getIconWidth();
width += ( ( hasRx_ ? 1 : 0 ) + ( hasTx_ ? 1 : 0 ) )
* nTrans_ * icon_.transIconWidth_;
int height = icon_.size_;
Dimension size = new Dimension( width, height );
Insets insets = getInsets();
size.width += insets.left + insets.right;
size.height += insets.top + insets.bottom;
return size;
}
public String getToolTipText( MouseEvent evt ) {
Point p = evt.getPoint();
Point iconPos = getIconPosition();
p.x -= iconPos.x;
p.y -= iconPos.y;
Transmission trans = icon_.getTransmissionAt( p );
if ( trans != null ) {
return new StringBuffer()
.append( trans.getMessage().getMType() )
.append( ": " )
.append( trans.getSender().toString() )
.append( " -> " )
.append( trans.getReceiver().toString() )
.toString();
}
else {
return null;
}
}
private Point getIconPosition() {
Insets insets = getInsets();
return new Point( insets.left, insets.top );
}
protected void paintComponent( Graphics g ) {
super.paintComponent( g );
Color color = g.getColor();
Rectangle bounds = getBounds();
Insets insets = getInsets();
if ( isOpaque() ) {
g.setColor( getBackground() );
g.fillRect( insets.left, insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom );
}
g.setColor( color );
Point p = getIconPosition();
icon_.paintIcon( this, g, p.x, p.y );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionListModel.java 0000664 0000000 0000000 00000007307 13564500043 0027145 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* ListModel implementation for containing {@link Transmission} objects.
* This extends the basic ListModel contract as follows:
* all ListDataEvents sent to ListDataListeners will have their
* source
set to the {@link Transmission} object concerned,
* and will have both index
values equal to each other.
*
* @author Mark Taylor
* @since 24 Nov 2008
*/
class TransmissionListModel extends AbstractListModel {
private final List list_;
private final ChangeListener changeListener_;
private int removeDelay_;
/**
* Constructor.
*
* @param removeDelay delay in milliseconds after message completion
* before transmission is removed from list
*/
public TransmissionListModel( int removeDelay ) {
removeDelay_ = removeDelay;
list_ = new ArrayList();
changeListener_ = new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
Object src = evt.getSource();
assert src instanceof Transmission;
if ( src instanceof Transmission ) {
transmissionChanged( (Transmission) src );
}
}
};
}
/**
* Called whenever a transmission which is in this list has changed
* state.
*
* @param trans transmission
*/
private void transmissionChanged( final Transmission trans ) {
int index = list_.indexOf( trans );
if ( index >= 0 ) {
fireContentsChanged( trans, index, index );
if ( trans.isDone() && removeDelay_ >= 0 ) {
long sinceDone =
System.currentTimeMillis() - trans.getDoneTime();
long delay = removeDelay_ - sinceDone;
if ( delay <= 0 ) {
removeTransmission( trans );
}
else {
ActionListener remover = new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
removeTransmission( trans );
}
};
new Timer( (int) delay + 1, remover ).start();
}
}
}
}
public int getSize() {
return list_.size();
}
public Object getElementAt( int index ) {
return list_.get( index );
}
/**
* Adds a transmission to this list.
*
* @param trans transmission to add
*/
public void addTransmission( Transmission trans ) {
int index = list_.size();
list_.add( trans );
fireIntervalAdded( trans, index, index );
trans.addChangeListener( changeListener_ );
}
/**
* Removes a transmission from this list.
*
* @param trans transmission to remove
*/
public void removeTransmission( final Transmission trans ) {
int index = list_.indexOf( trans );
if ( index >= 0 ) {
list_.remove( index );
fireIntervalRemoved( trans, index, index );
}
// Defer listener removal to avoid concurrency problems
// (trying to remove a listener which generated this event).
SwingUtilities.invokeLater( new Runnable() {
public void run() {
trans.removeChangeListener( changeListener_ );
}
} );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionPanel.java 0000664 0000000 0000000 00000022165 13564500043 0026307 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.StringWriter;
import java.io.PrintWriter;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JTextArea;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampMap;
import org.astrogrid.samp.SampUtils;
/**
* Component which displays the details of a given Transmission object.
*
* @author Mark Taylor
* @since 5 Dec 2008
*/
public class TransmissionPanel extends JPanel {
private final JTextField mtypeField_;
private final JTextField idField_;
private final JTextField senderField_;
private final JTextField receiverField_;
private final JTextField statusField_;
private final JTextArea messageField_;
private final JTextArea responseField_;
private final ChangeListener changeListener_;
private Transmission trans_;
/**
* Constructor.
*/
public TransmissionPanel() {
super( new BorderLayout() );
// Panel displaying one-line metadata items.
Stack metaPanel = new Stack();
metaPanel.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) );
mtypeField_ = metaPanel.addField( "MType" );
idField_ = metaPanel.addField( "Message ID" );
senderField_ = metaPanel.addField( "Sender" );
receiverField_ = metaPanel.addField( "Receiver" );
statusField_ = metaPanel.addField( "Status" );
// Panel displaying Message content.
messageField_ = new JTextArea();
messageField_.setEditable( false );
messageField_.setLineWrap( false );
JComponent messagePanel = new JPanel( new BorderLayout() );
JComponent messageHeadBox = Box.createHorizontalBox();
messageHeadBox.add( new JLabel( "Message" ) );
messageHeadBox.add( Box.createHorizontalGlue() );
messagePanel.add( messageHeadBox, BorderLayout.NORTH );
messagePanel.add( new JScrollPane( messageField_ ),
BorderLayout.CENTER );
// Panel displaying Response content.
responseField_ = new JTextArea();
responseField_.setEditable( false );
responseField_.setLineWrap( false );
JComponent responsePanel = new JPanel( new BorderLayout() );
JComponent responseHeadBox = Box.createHorizontalBox();
responseHeadBox.add( new JLabel( "Response" ) );
responseHeadBox.add( Box.createHorizontalGlue() );
responsePanel.add( responseHeadBox, BorderLayout.NORTH );
responsePanel.add( new JScrollPane( responseField_ ),
BorderLayout.CENTER );
// Place panels.
JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
splitter.setTopComponent( messagePanel );
splitter.setBottomComponent( responsePanel );
splitter.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) );
splitter.setResizeWeight( 0.5 );
add( metaPanel, BorderLayout.NORTH );
add( splitter, BorderLayout.CENTER );
// Prepare a listener to react to changes in the displayed
// transmission object.
changeListener_ = new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
updateState();
}
};
}
/**
* Sets the transmission object being displayed.
*
* @param trans transmission object to display
*/
public void setTransmission( Transmission trans ) {
if ( trans_ != null ) {
trans_.removeChangeListener( changeListener_ );
}
trans_ = trans;
if ( trans_ != null ) {
trans_.addChangeListener( changeListener_ );
}
updateState();
}
/**
* Returns the transmission object currently being displayed.
*
* @return transmission
*/
public Transmission getTransmission() {
return trans_;
}
/**
* Invoked whenever the displayed transmission, or its characteristics,
* change.
*/
private void updateState() {
if ( trans_ == null ) {
mtypeField_.setText( null );
idField_.setText( null );
senderField_.setText( null );
receiverField_.setText( null );
statusField_.setText( null );
messageField_.setText( null );
responseField_.setText( null );
}
else {
Message msg = trans_.getMessage();
Response response = trans_.getResponse();
Throwable error = trans_.getError();
mtypeField_.setText( msg.getMType() );
mtypeField_.setCaretPosition( 0 );
idField_.setText( formatId( trans_ ) );
idField_.setCaretPosition( 0 );
senderField_.setText( formatClient( trans_.getSender() ) );
senderField_.setCaretPosition( 0 );
receiverField_.setText( formatClient( trans_.getReceiver() ) );
receiverField_.setCaretPosition( 0 );
statusField_.setText( trans_.getStatus().getText() );
statusField_.setCaretPosition( 0 );
messageField_.setText( SampUtils.formatObject( msg, 2 ) );
messageField_.setCaretPosition( 0 );
String responseText = response == null
? null
: SampUtils.formatObject( response, 2 );
final String errorText;
if ( error == null ) {
errorText = null;
}
else {
StringWriter traceWriter = new StringWriter();
error.printStackTrace( new PrintWriter( traceWriter ) );
errorText = traceWriter.toString();
}
StringBuffer rbuf = new StringBuffer();
if ( responseText != null ) {
rbuf.append( responseText );
}
if ( errorText != null ) {
if ( rbuf.length() > 0 ) {
rbuf.append( "\n\n" );
}
rbuf.append( errorText );
}
responseField_.setText( rbuf.toString() );
responseField_.setCaretPosition( 0 );
}
}
/**
* Formats the identifier of a transmission as a string.
*
* @param trans transmission
* @return id string
*/
private static String formatId( Transmission trans ) {
String msgTag = trans.getMessageTag();
String msgId = trans.getMessageId();
StringBuffer idBuf = new StringBuffer();
if ( msgTag != null ) {
idBuf.append( "Tag: " )
.append( msgTag );
}
if ( msgId != null ) {
if ( idBuf.length() > 0 ) {
idBuf.append( "; " );
}
idBuf.append( "ID: " )
.append( msgId );
}
return idBuf.toString();
}
/**
* Formats a client as a string.
*
* @param client client
* @return string
*/
private static String formatClient( Client client ) {
StringBuffer sbuf = new StringBuffer();
sbuf.append( client.getId() );
Metadata meta = client.getMetadata();
if ( meta != null ) {
String name = meta.getName();
if ( name != null ) {
sbuf.append( ' ' )
.append( '(' )
.append( name )
.append( ')' );
}
}
return sbuf.toString();
}
/**
* Component for aligning headings and text fields for metadata display.
*/
private class Stack extends JPanel {
private final GridBagLayout layer_;
private int line_;
/**
* Constructor.
*/
Stack() {
layer_ = new GridBagLayout();
setLayout( layer_ );
}
/**
* Adds an item.
*
* @param heading text heading for item
* @return text field associated with heading
*/
JTextField addField( String heading ) {
GridBagConstraints cons = new GridBagConstraints();
cons.gridy = line_++;
cons.weighty = 1;
cons.insets = new Insets( 2, 2, 2, 2 );
cons.gridx = 0;
cons.fill = GridBagConstraints.HORIZONTAL;
cons.anchor = GridBagConstraints.WEST;
cons.weightx = 0;
JLabel label = new JLabel( heading + ":" );
JTextField field = new JTextField();
field.setEditable( false );
layer_.setConstraints( label, cons );
add( label );
cons.gridx++;
cons.weightx = 1;
layer_.setConstraints( field, cons );
add( field );
return field;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionTableModel.java 0000664 0000000 0000000 00000031320 13564500043 0027251 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.astrogrid.samp.Client;
/**
* TableModel implementation which displays Transmission objects.
*
* @author Mark Taylor
* @since 5 Dec 2008
*/
class TransmissionTableModel implements TableModel {
private final List transList_;
private final List tableListenerList_;
private final ChangeListener changeListener_;
private final Column[] columns_;
private int maxRows_;
private int removeDelay_;
/** Cell renderer for Transmission.Status objects. */
public static final TableCellRenderer STATUS_RENDERER =
createStatusCellRenderer();
/** Cell renderer for Client objects. */
private static final TableCellRenderer CLIENT_RENDERER =
createClientCellRenderer();
/**
* Constructor.
*
* @param showSender true if a Sender column is required
* @param showReceiver true if a Receiver column is required
* @param removeDelay time in milliseconds after transmission resolution
* that it will stay in the table - after this it will be
* removed automatically
* @param maxRows maximum row count for table - if not set to a finite
* value, Swing can get overloaded in very high message traffic
*/
public TransmissionTableModel( final boolean showSender,
final boolean showReceiver,
int removeDelay, int maxRows ) {
removeDelay_ = removeDelay;
maxRows_ = maxRows;
transList_ = new LinkedList();
tableListenerList_ = new ArrayList();
// Set up table columns.
List colList = new ArrayList();
int charWidth = 8;
int icol = 0;
colList.add( new Column( "MType", String.class,
new TableColumn( icol++, 30 * charWidth ) ) {
Object getValue( Transmission trans ) {
return trans.getMessage().getMType();
}
} );
if ( showSender ) {
colList.add( new Column( "Sender", Object.class,
new TableColumn( icol++, 20 * charWidth,
CLIENT_RENDERER,
null ) ) {
Object getValue( Transmission trans ) {
return trans.getSender();
}
} );
}
if ( showReceiver ) {
colList.add( new Column( "Receiver", Object.class,
new TableColumn( icol++, 20 * charWidth,
CLIENT_RENDERER,
null ) ) {
Object getValue( Transmission trans ) {
return trans.getReceiver();
}
} );
}
colList.add( new Column( "Status", Object.class,
new TableColumn( icol++, 16 * charWidth,
STATUS_RENDERER, null ) ) {
Object getValue( Transmission trans ) {
return trans.getStatus();
}
} );
columns_ = (Column[]) colList.toArray( new Column[ 0 ] );
// Set up listener to monitor changes of transmissions.
changeListener_ = new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
Object src = evt.getSource();
assert src instanceof Transmission;
if ( src instanceof Transmission ) {
transmissionChanged( (Transmission) src );
}
}
};
}
/**
* Returns the transmission corresponding to a given table row.
*
* @param irow row index
* @param transmission displayed in row irow
*/
public Transmission getTransmission( int irow ) {
return (Transmission) transList_.get( irow );
}
/**
* Adds a transmission (row) to this model. It will appear at the top.
*
* @param trans transmission to add
*/
public void addTransmission( Transmission trans ) {
while ( transList_.size() > maxRows_ ) {
transList_.remove( maxRows_ );
fireTableChanged( new TableModelEvent( this, maxRows_, maxRows_,
TableModelEvent.ALL_COLUMNS,
TableModelEvent.DELETE ) );
}
transList_.add( 0, trans );
trans.addChangeListener( changeListener_ );
fireTableChanged( new TableModelEvent( this, 0, 0,
TableModelEvent.ALL_COLUMNS,
TableModelEvent.INSERT ) );
}
/**
* Removes a transmission from this model.
*
* @param trans transmission to remove
*/
public void removeTransmission( final Transmission trans ) {
int index = transList_.indexOf( trans );
if ( index >= 0 ) {
transList_.remove( index );
fireTableChanged( new TableModelEvent( this, index, index,
TableModelEvent.ALL_COLUMNS,
TableModelEvent.DELETE ) );
}
// Defer listener removal to avoid concurrency problems
// (trying to remove a listener which generated this event).
SwingUtilities.invokeLater( new Runnable() {
public void run() {
trans.removeChangeListener( changeListener_ );
}
} );
}
public int getColumnCount() {
return columns_.length;
}
public int getRowCount() {
return transList_.size();
}
public Object getValueAt( int irow, int icol ) {
return columns_[ icol ].getValue( getTransmission( irow ) );
}
public String getColumnName( int icol ) {
return columns_[ icol ].name_;
}
public Class getColumnClass( int icol ) {
return columns_[ icol ].clazz_;
}
public boolean isCellEditable( int irow, int icol ) {
return false;
}
public void setValueAt( Object value, int irow, int icol ) {
throw new UnsupportedOperationException();
}
public void addTableModelListener( TableModelListener listener ) {
tableListenerList_.add( listener );
}
public void removeTableModelListener( TableModelListener listener ) {
tableListenerList_.remove( listener );
}
/**
* Returns a TableColumn suitable for a given column of this table.
* Can be used for more customised presentation.
*
* @param icol column index
* @return table column
*/
public TableColumn getTableColumn( int icol ) {
return columns_[ icol ].tcol_;
}
/**
* Called whenever a transmission which is in this list has changed
* state.
*
* @param trans transmission
*/
private void transmissionChanged( final Transmission trans ) {
int index = transList_.indexOf( trans );
if ( index >= 0 ) {
fireTableChanged( new TableModelEvent( this, index ) );
if ( trans.isDone() && removeDelay_ >= 0 ) {
long sinceDone =
System.currentTimeMillis() - trans.getDoneTime();
long delay = removeDelay_ - sinceDone;
if ( delay <= 0 ) {
removeTransmission( trans );
}
else {
ActionListener remover = new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
removeTransmission( trans );
}
};
new Timer( (int) delay + 1, remover ).start();
}
}
}
}
/**
* Passes a table event to all registered listeners.
*
* @param evt event to forward
*/
private void fireTableChanged( TableModelEvent evt ) {
for ( Iterator it = tableListenerList_.iterator(); it.hasNext(); ) {
((TableModelListener) it.next()).tableChanged( evt );
}
}
/**
* Describes metadata and data for a table column.
*/
private abstract class Column {
final String name_;
final Class clazz_;
final TableColumn tcol_;
/**
* Constructor.
*
* @param name column name
* @param clazz column content class
*/
Column( String name, Class clazz, TableColumn tcol ) {
name_ = name;
clazz_ = clazz;
tcol_ = tcol;
tcol_.setHeaderValue( name );
}
/**
* Returns the item in this column for a given transmission.
*
* @param trans transmission
* @return cell value
*/
abstract Object getValue( Transmission trans );
}
/**
* Template custom TableCellRenderer for subclassing.
*/
private static abstract class CustomTableCellRenderer
extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent( JTable table,
Object value,
boolean isSel,
boolean hasFocus,
int irow, int icol ) {
int size;
try {
size =
(int) Math.ceil( getFont()
.getMaxCharBounds( ((Graphics2D)
table.getGraphics())
.getFontRenderContext() )
.getHeight() );
}
catch ( NullPointerException e ) {
size = 16;
}
Component comp =
super.getTableCellRendererComponent( table, value, isSel,
hasFocus, irow, icol );
if ( comp instanceof JLabel ) {
configureLabel( (JLabel) comp, value, size - 2 );
}
return comp;
}
/**
* Configures a JLabel given the value to render and the
* component size.
*
* @param label renderer component to configure
* @param value object to render
* @param height component height in pixels
*/
abstract void configureLabel( JLabel label, Object value, int height );
}
/**
* Returns a cell renderer for Transmission.Status objects.
*
* @return table cell renderer
*/
private static TableCellRenderer createStatusCellRenderer() {
return new CustomTableCellRenderer() {
void configureLabel( JLabel label, Object value, int height ) {
if ( value instanceof Transmission.Status ) {
Transmission.Status status = (Transmission.Status) value;
label.setText( status.getText() );
label.setIcon( status.getIcon( height ) );
}
}
};
}
/**
* Returns a cell renderer for Client objects.
*
* @return table cell renderer
*/
private static TableCellRenderer createClientCellRenderer() {
final IconStore iconStore =
new IconStore( IconStore.createEmptyIcon( 16 ) );
return new CustomTableCellRenderer() {
void configureLabel( JLabel label, Object value, int height ) {
if ( value instanceof Client ) {
Client client = (Client) value;
label.setText( client.toString() );
label.setIcon( ClientListCellRenderer
.reshapeIcon( iconStore.getIcon( client ),
height ) );
}
}
};
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionView.java 0000664 0000000 0000000 00000005127 13564500043 0026161 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.border.BevelBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableColumnModel;
/**
* Displays a set of transmissions in a table model,
* along with a detail panel for the selected one.
*
* @author Mark Taylor
* @since 5 Dec 2008
*/
class TransmissionView extends JPanel {
/**
* Constructor.
*
* @param transModel table model containing transmissions
*/
public TransmissionView( final TransmissionTableModel transModel ) {
super( new BorderLayout() );
final TransmissionPanel transPanel = new TransmissionPanel();
transPanel.setBorder( BorderFactory
.createBevelBorder( BevelBorder.LOWERED ) );
final JTable table = new JTable( transModel );
Dimension tableSize = table.getPreferredScrollableViewportSize();
tableSize.height = 80;
table.setPreferredScrollableViewportSize( tableSize );
DefaultTableColumnModel tcolModel = new DefaultTableColumnModel();
for ( int icol = 0; icol < transModel.getColumnCount(); icol++ ) {
tcolModel.addColumn( transModel.getTableColumn( icol ) );
}
table.setColumnModel( tcolModel );
final ListSelectionModel selModel = table.getSelectionModel();
selModel.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
selModel.addListSelectionListener( new ListSelectionListener() {
public void valueChanged( ListSelectionEvent evt ) {
Object src = evt.getSource();
if ( ! selModel.getValueIsAdjusting() &&
! selModel.isSelectionEmpty() ) {
Transmission trans =
transModel.getTransmission( table.getSelectedRow() );
transPanel.setTransmission( trans );
}
}
} );
JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
add( splitter, BorderLayout.CENTER );
splitter.setTopComponent( new JScrollPane( table ) );
splitter.setBottomComponent( transPanel );
Dimension splitSize = splitter.getPreferredSize();
splitSize.height = 180;
splitter.setPreferredSize( splitSize );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/UniformCallActionManager.java 0000664 0000000 0000000 00000012521 13564500043 0027475 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JMenu;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.ResultHandler;
/**
* SendActionManager subclass which works with messages of a single MType,
* using the Aysnchronous Call/Response delivery pattern.
* Concrete subclasses need only implement {@link #createMessage()}.
*
* @author Mark Taylor
* @since 11 Nov 2008
*/
public abstract class UniformCallActionManager
extends AbstractCallActionManager {
private final Component parent_;
private final String mtype_;
private final String sendType_;
/**
* Constructor.
*
* @param parent parent component
* @param connector hub connector
* @param mtype MType for messages transmitted by this object's actions
* @param sendType short string identifying the kind of thing being
* sent (used for action descriptions etc)
*/
public UniformCallActionManager( Component parent,
GuiHubConnector connector,
String mtype, String sendType ) {
super( parent, connector,
new SubscribedClientListModel( connector, mtype ) );
parent_ = parent;
mtype_ = mtype;
sendType_ = sendType;
}
/**
* Generates the message which is sent to one or all clients
* by this object's actions.
*
* @return {@link org.astrogrid.samp.Message}-like Map representing
* message to transmit
*/
protected abstract Map createMessage() throws Exception;
/**
* Implemented simply by calling {@link #createMessage()}.
*/
protected Map createMessage( Client client ) throws Exception {
return createMessage();
}
protected Action createBroadcastAction() {
return new BroadcastAction();
}
/**
* Returns a new targetted send menu with a title suitable for this object.
*
* @return new send menu
*/
public JMenu createSendMenu() {
JMenu menu = super.createSendMenu( "Send " + sendType_ + " to..." );
menu.setIcon( getSendIcon() );
return menu;
}
public Action getSendAction( Client client ) {
Action action = super.getSendAction( client );
action.putValue( Action.SHORT_DESCRIPTION,
"Transmit to " + client + " using SAMP " + mtype_ );
return action;
}
/**
* Action for sending broadcast messages.
*/
private class BroadcastAction extends AbstractAction {
/**
* Constructor.
*/
BroadcastAction() {
putValue( NAME, "Broadcast " + sendType_ );
putValue( SHORT_DESCRIPTION,
"Transmit " + sendType_ + " to all applications"
+ " listening using the SAMP protocol" );
putValue( SMALL_ICON, getBroadcastIcon() );
}
public void actionPerformed( ActionEvent evt ) {
HubConnector connector = getConnector();
Set recipientIdSet = null;
Message msg = null;
HubConnection connection = null;
String tag = null;
// Attempt to send the message.
try {
msg = Message.asMessage( createMessage() );
msg.check();
connection = connector.getConnection();
if ( connection != null ) {
tag = createTag();
recipientIdSet = connection.callAll( tag, msg ).keySet();
}
}
catch ( Exception e ) {
ErrorDialog.showError( parent_, "Send Error",
"Send failure " + e.getMessage(), e );
}
// If it was sent, arrange for the results to be passed to
// a suitable result handler.
if ( recipientIdSet != null ) {
assert connection != null;
assert msg != null;
assert tag != null;
List recipientList = new ArrayList();
Map clientMap = connector.getClientMap();
for ( Iterator it = recipientIdSet.iterator(); it.hasNext(); ) {
String id = (String) it.next();
Client recipient = (Client) clientMap.get( id );
if ( recipient != null ) {
recipientList.add( recipient );
}
}
Client[] recipients =
(Client[]) recipientList.toArray( new Client[ 0 ] );
ResultHandler handler =
createResultHandler( connection, msg, recipients );
if ( recipients.length == 0 ) {
if ( handler != null ) {
handler.done();
}
handler = null;
}
registerHandler( tag, recipients, handler );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/WrapperHubConnection.java 0000664 0000000 0000000 00000005200 13564500043 0026724 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.gui;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* HubConnection implementation which delegates all behaviour to a base
* implementation. Intended for subclassing.
*
* @author Mark Taylor
* @since 24 Nov 2008
*/
class WrapperHubConnection implements HubConnection {
private final HubConnection base_;
public WrapperHubConnection( HubConnection base ) {
base_ = base;
}
public RegInfo getRegInfo() {
return base_.getRegInfo();
}
public void setCallable( CallableClient callable ) throws SampException {
base_.setCallable( callable );
}
public void ping() throws SampException {
base_.ping();
}
public void unregister() throws SampException {
base_.unregister();
}
public void declareMetadata( Map meta ) throws SampException {
base_.declareMetadata( meta );
}
public Metadata getMetadata( String clientId ) throws SampException {
return base_.getMetadata( clientId );
}
public void declareSubscriptions( Map subs ) throws SampException {
base_.declareSubscriptions( subs );
}
public Subscriptions getSubscriptions( String clientId )
throws SampException {
return base_.getSubscriptions( clientId );
}
public String[] getRegisteredClients() throws SampException {
return base_.getRegisteredClients();
}
public Map getSubscribedClients( String mtype ) throws SampException {
return base_.getSubscribedClients( mtype );
}
public void notify( String recipientId, Map msg ) throws SampException {
base_.notify( recipientId, msg );
}
public List notifyAll( Map msg ) throws SampException {
return base_.notifyAll( msg );
}
public String call( String recipientId, String msgTag, Map msg )
throws SampException {
return base_.call( recipientId, msgTag, msg );
}
public Map callAll( String msgTag, Map msg ) throws SampException {
return base_.callAll( msgTag, msg );
}
public Response callAndWait( String recipientId, Map msg, int timeout )
throws SampException {
return base_.callAndWait( recipientId, msg, timeout );
}
public void reply( String msgId, Map response ) throws SampException {
base_.reply( msgId, response );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/package.html 0000664 0000000 0000000 00000000125 13564500043 0024244 0 ustar 00root root 0000000 0000000 new DirectoryMapperHandler("/files", "/data")
* and installed on a server running at http://localhost:8000/
,
* then a server request for http://localhost:8000/data/xxx
* would be fulfilled by returning the content of the resource
* /files/xxx
available to the JVM's classloader
* (for instance within a jar file on the classpath).
*
* @author Mark Taylor
* @since 11 Mar 2016
*/
public class DirectoryMapperHandler implements HttpServer.Handler {
private final String localDocBase_;
private final String serverDocPath_;
/**
* Constructor.
*
* @param localDocBase prefix for resources available within the JVM
* @param serverDocPath prefix for resources made available by this server
*/
public DirectoryMapperHandler( String localDocBase, String serverDocPath ) {
localDocBase_ = localDocBase;
serverDocPath_ = serverDocPath;
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
String path = request.getUrl();
if ( ! path.startsWith( serverDocPath_ ) ) {
return null;
}
String relPath = path.substring( serverDocPath_.length() );
final URL srcUrl = getClass().getResource( localDocBase_ + relPath );
return srcUrl == null
? null
: URLMapperHandler.mapUrlResponse( request.getMethod(), srcUrl );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/HttpServer.java 0000664 0000000 0000000 00000076672 13564500043 0025317 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.httpd;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.net.ssl.SSLServerSocket;
import org.astrogrid.samp.SampUtils;
/**
* Simple modular HTTP server.
* One thread is started per request. Connections are not kept open between
* requests.
* Suitable for very large response bodies, but not for very large
* request bodies.
* Add one or more {@link HttpServer.Handler}s to serve actual requests.
* The protocol version served is HTTP/1.0.
*
* localUrl
*/
public synchronized URL addLocalUrl( URL localUrl ) {
// Get a name for the publicly visible URL, using the same as the
// local URL if possible. This is just for cosmetic purposes.
String path = localUrl.getPath();
int lastSlash = path.lastIndexOf( "/" );
String name = lastSlash >= 0 && lastSlash < path.length() - 1
? path.substring( lastSlash + 1 )
: "f";
// Construct a new unique public URL at which this resource will
// be made available.
String relPath;
URL mappedUrl;
try {
relPath = ++resourceCount_ + "/" + name;
mappedUrl = new URL( baseUrl_, relPath );
}
catch ( MalformedURLException e ) {
try {
relPath = resourceCount_ + "/" + "f";
mappedUrl = new URL( baseUrl_, relPath );
}
catch ( MalformedURLException e2 ) {
throw (AssertionError) new AssertionError().initCause( e2 );
}
}
// Add the fragment part to the mapped URL if there is one.
String frag = localUrl.getRef();
if ( frag != null ) {
try {
mappedUrl = new URL( mappedUrl.toString() + "#" + frag );
}
catch ( MalformedURLException e ) {
// shouldn't happen, but if it does, just use the non-frag one.
assert false;
}
}
// Remember the mapping between the local URL and the public one.
urlMap_.put( relPath, localUrl );
// Return the public URL.
return mappedUrl;
}
/**
* Removes access to a resource which was publicised by a previous call
* to {@link #addLocalUrl}.
*
* @param url result of previous call to addLocalUrl
*/
public synchronized void removeServerUrl( URL url ) {
String surl = url.toString();
String sbase = baseUrl_.toString();
if ( ! surl.startsWith( sbase ) ) {
return;
}
String relPath = surl.substring( sbase.length() );
urlMap_.remove( relPath );
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
// Determine the source URL from which the data will be obtained.
String path = request.getUrl();
if ( ! path.startsWith( basePath_ ) ) {
return null;
}
String relPath = path.substring( basePath_.length() );
if ( ! urlMap_.containsKey( relPath ) ) {
return HttpServer.createErrorResponse( 404, "Not found" );
}
URL srcUrl = (URL) urlMap_.get( relPath );
// Forward header and data from the source URL to the response.
return URLMapperHandler.mapUrlResponse( request.getMethod(), srcUrl );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/ResourceHandler.java 0000664 0000000 0000000 00000011672 13564500043 0026263 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.httpd;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* Handler implementation which implements dynamic resource provision.
* HTTP HEAD and GET methods are implemented.
*
* @author Mark Taylor
* @since 7 Jan 2009
*/
public class ResourceHandler implements HttpServer.Handler {
private final String basePath_;
private final URL serverUrl_;
private final Map resourceMap_;
private int iRes_;
private static Logger logger_ =
Logger.getLogger( ResourceHandler.class.getName() );
/** Dummy resource indicating a withdrawn item. */
private static final ServerResource EXPIRED = new ServerResource() {
public String getContentType() {
throw new AssertionError();
}
public long getContentLength() {
throw new AssertionError();
}
public void writeBody( OutputStream out ) {
throw new AssertionError();
}
};
/**
* Constructor.
*
* @param server HTTP server
* @param basePath path from server root beneath which all resources
* provided by this handler will appear
*/
public ResourceHandler( HttpServer server, String basePath ) {
if ( ! basePath.startsWith( "/" ) ) {
basePath = "/" + basePath;
}
if ( ! basePath.endsWith( "/" ) ) {
basePath = basePath + "/";
}
basePath_ = basePath;
serverUrl_ = server.getBaseUrl();
resourceMap_ = new HashMap();
}
/**
* Adds a resource to this server.
*
* @param name resource name, for cosmetic purposes only
* @param resource resource to make available
* @return URL at which resource can be found
*/
public synchronized URL addResource( String name,
ServerResource resource ) {
String path = basePath_ + Integer.toString( ++iRes_ ) + "/";
if ( name != null ) {
try {
path += URLEncoder.encode( name, "utf-8" );
}
catch ( UnsupportedEncodingException e ) {
logger_.warning( "No utf-8?? No cosmetic path name then" );
}
}
resourceMap_.put( path, resource );
try {
URL url = new URL( serverUrl_, path );
logger_.info( "Resource added: " + url );
return new URL( serverUrl_, path );
}
catch ( MalformedURLException e ) {
throw new AssertionError( "Unknown protocol http??" );
}
}
/**
* Removes a resource from this server.
*
* @param url URL returned by a previous addResource call
*/
public synchronized void removeResource( URL url ) {
String path = url.getPath();
if ( resourceMap_.containsKey( path ) ) {
logger_.info( "Resource expired: " + url );
resourceMap_.put( path, EXPIRED );
}
else {
throw new IllegalArgumentException( "Unknown URL to expire: "
+ url );
}
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
String path = request.getUrl();
if ( ! path.startsWith( basePath_ ) ) {
return null;
}
final ServerResource resource =
(ServerResource) resourceMap_.get( path );
if ( resource == EXPIRED ) {
return HttpServer.createErrorResponse( 410, "Gone" );
}
else if ( resource != null ) {
Map hdrMap = new LinkedHashMap();
hdrMap.put( "Content-Type", resource.getContentType() );
long contentLength = resource.getContentLength();
if ( contentLength >= 0 ) {
hdrMap.put( "Content-Length", Long.toString( contentLength ) );
}
String method = request.getMethod();
if ( method.equals( "HEAD" ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) {
}
};
}
else if ( method.equals( "GET" ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out )
throws IOException {
resource.writeBody( out );
}
};
}
else {
return HttpServer
.create405Response( new String[] { "HEAD", "GET" } );
}
}
else {
return HttpServer.createErrorResponse( 404, "Not found" );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/ServerResource.java 0000664 0000000 0000000 00000001455 13564500043 0026152 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.httpd;
import java.io.IOException;
import java.io.OutputStream;
/**
* Defines a resource suitable for serving by the {@link ResourceHandler}
* HTTP server handler.
*
* @author Mark Taylor
* @since 3 Sep 2008
*/
public interface ServerResource {
/**
* Returns the MIME type of this resource.
*
* @return value of Content-Type HTTP header
*/
String getContentType();
/**
* Returns the number of bytes in this resource, if known.
*
* @return value of Content-Length HTTP header if known;
* otherwise a negative number
*/
long getContentLength();
/**
* Writes resource body.
*
* @param out destination stream
*/
void writeBody( OutputStream out ) throws IOException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/URLMapperHandler.java 0000664 0000000 0000000 00000013174 13564500043 0026302 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.httpd;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Handler implementation which allows the server to serve resources which
* are available to it as URLs. The main use for this is if the URLs
* are jar:-type ones which are available to the JVM in which the server
* is running, but not to it's clients.
* Either a single resource or a whole tree may be served.
*
* @author Mark Taylor
* @since 8 Jan 2009
*/
public class URLMapperHandler implements HttpServer.Handler {
private final String basePath_;
private final URL baseUrl_;
private final URL sourceUrl_;
private final boolean includeRelatives_;
/**
* Constructor.
*
* @param server server within which this handler will be used
* @param basePath path of served resources relative to the base path
* of the server itself
* @param sourceUrl URL of the resource which is to be made available
* at the basePath by this handler
* @param includeRelatives if true, relative URLs based at
* basePath
* may be requested (potentially giving access to
* for instance the entire tree of classpath resources);
* if false, only the exact resource named by
* sourceUrl
is served
*/
public URLMapperHandler( HttpServer server, String basePath, URL sourceUrl,
boolean includeRelatives )
throws MalformedURLException {
if ( ! basePath.startsWith( "/" ) ) {
basePath = "/" + basePath;
}
if ( ! basePath.endsWith( "/" ) && includeRelatives ) {
basePath = basePath + "/";
}
basePath_ = basePath;
baseUrl_ = new URL( server.getBaseUrl(), basePath );
sourceUrl_ = sourceUrl;
includeRelatives_ = includeRelatives;
}
/**
* Returns the base URL for this handler.
* If not including relatives, this will be the only URL served.
*
* @return URL
*/
public URL getBaseUrl() {
return baseUrl_;
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
// Determine the source URL from which the data will be obtained.
String path = request.getUrl();
if ( ! path.startsWith( basePath_ ) ) {
return null;
}
String relPath = path.substring( basePath_.length() );
final URL srcUrl;
if ( includeRelatives_ ) {
try {
srcUrl = new URL( sourceUrl_, relPath );
}
catch ( MalformedURLException e ) {
return HttpServer
.createErrorResponse( 500, "Internal server error", e );
}
}
else {
if ( relPath.length() == 0 ) {
srcUrl = sourceUrl_;
}
else {
return HttpServer.createErrorResponse( 403, "Forbidden" );
}
}
// Forward header and data from the source URL to the response.
return mapUrlResponse( request.getMethod(), srcUrl );
}
/**
* Repackages a resource from a given target URL as an HTTP response.
* The data and relevant headers are copied straight through.
* GET and HEAD methods are served.
*
* @param method HTTP method
* @param targetUrl URL containing the resource to forward
* @return response redirecting to the given target URL
*/
public static HttpServer.Response mapUrlResponse( String method,
URL targetUrl ) {
final URLConnection conn;
try {
conn = targetUrl.openConnection();
conn.connect();
}
catch ( IOException e ) {
return HttpServer.createErrorResponse( 404, "Not found", e );
}
try {
Map hdrMap = new LinkedHashMap();
String contentType = conn.getContentType();
if ( contentType != null ) {
hdrMap.put( "Content-Type", contentType );
}
int contentLength = conn.getContentLength();
if ( contentLength >= 0 ) {
hdrMap.put( "Content-Length",
Integer.toString( contentLength ) );
}
String contentEncoding = conn.getContentEncoding();
if ( contentEncoding != null ) {
hdrMap.put( "Content-Encoding", contentEncoding );
}
if ( "GET".equals( method ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out )
throws IOException {
UtilServer.copy( conn.getInputStream(), out );
}
};
}
else if ( "HEAD".equals( method ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) {
}
};
}
else {
return HttpServer
.create405Response( new String[] { "HEAD", "GET" } );
}
}
catch ( Exception e ) {
return HttpServer
.createErrorResponse( 500, "Internal server error", e );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/UtilServer.java 0000664 0000000 0000000 00000022075 13564500043 0025301 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.httpd;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class for use with HttpServer.
*
* localUrl
is
* only visible locally, for instance on this JVM's classpath.
*
* @param localUrl URL visible at least within this JVM
* @return URL for external reference to the resource
*/
public URL exportResource( URL localUrl ) throws IOException {
return getMapperHandler().addLocalUrl( localUrl );
}
/**
* Exposes a resource from the JVM's classpath as a publicly visible URL.
* The classloader of this class is used, which may not be the right one
* for the resource in question.
* For that reason, this utility method is deprecated
* in favour of the overloaded method that takes a URL.
*
* @param resource fully qualified path to a resource in the current
* classpath; separators are "/" characters
* @return URL for external reference to the resource
* @deprecated since after 1.3.5,
* use instead {@link #exportResource(java.net.URL)}
*/
public URL exportResource( String resource ) throws IOException {
URL localUrl = UtilServer.class.getResource( resource );
if ( localUrl != null ) {
return getMapperHandler().addLocalUrl( localUrl );
}
else {
throw new IOException( "Not found on classpath: " + resource );
}
}
/**
* Exposes a file in the local filesystem as a publicly visible URL.
*
* @param file a file on a filesystem visible from the local host
* @return URL for external reference to the resource
*/
public URL exportFile( File file ) throws IOException {
if ( file.exists() ) {
return getMapperHandler().addLocalUrl( file.toURL() );
}
else {
throw new FileNotFoundException( "No such file: " + file );
}
}
/**
* May be used to return a unique base path for use with this class's
* HttpServer. If all users of this server use this method
* to get base paths for use with different handlers, nameclash
* avoidance is guaranteed.
*
* @param txt basic text for base path
* @return base path; will likely bear some resemblance to
* txt
, but may be adjusted to ensure uniqueness
*/
public synchronized String getBasePath( String txt ) {
Matcher slashMatcher = SLASH_REGEX.matcher( txt );
String pre;
String body;
String post;
if ( slashMatcher.matches() ) {
pre = slashMatcher.group( 1 );
body = slashMatcher.group( 2 );
post = slashMatcher.group( 3 );
}
else {
assert false;
pre = "";
body = txt;
post = "";
}
if ( baseSet_.contains( body ) ) {
String stem = body;
int i = 1;
while ( baseSet_.contains( stem + "-" + i ) ) {
i++;
}
body = stem + "-" + i;
}
baseSet_.add( body );
return pre + body + post;
}
/**
* Returns the default instance of this class.
* The first time this method is called a new daemon UtilServer
* is (lazily) created, and started. Any subsequent calls will
* return the same object, unless {@link #getInstance} is called.
*
* @return default instance of this class
*/
public static synchronized UtilServer getInstance() throws IOException {
if ( instance_ == null ) {
ServerSocket sock = null;
String sPort = System.getProperty( PORT_PROP );
if ( sPort != null && sPort.length() > 0 ) {
int port = Integer.parseInt( sPort );
try {
sock = new ServerSocket( port );
}
catch ( BindException e ) {
logger_.warning( "Can't open socket on port " + port
+ " (" + e + ") - use another one" );
}
}
if ( sock == null ) {
sock = new ServerSocket( 0 );
}
HttpServer server = new HttpServer( sock );
server.setDaemon( true );
server.start();
instance_ = new UtilServer( server );
}
return instance_;
}
/**
* Sets the default instance of this class.
*
* @param server default instance to be returned by {@link #getInstance}
*/
public static synchronized void setInstance( UtilServer server ) {
instance_ = server;
}
/**
* Copies the content of an input stream to an output stream.
* The input stream is always closed on exit; the output stream is not.
*
* @param in input stream
* @param out output stream
*/
public static void copy( InputStream in, OutputStream out )
throws IOException {
byte[] buf = new byte[ BUFSIZ ];
try {
for ( int nb; ( nb = in.read( buf ) ) >= 0; ) {
out.write( buf, 0, nb );
}
out.flush();
}
finally {
in.close();
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/package.html 0000664 0000000 0000000 00000001071 13564500043 0024604 0 ustar 00root root 0000000 0000000 xmlrpc.internal
XML-RPC layer implementation.
It may also come in useful for client applications which wish to
serve resources (such as dynamically-generated data or icons
as referenced by the {@link org.astrogrid.samp.Metadata#ICONURL_KEY} item)
as part of SAMP operations.
Other than its use by the xmlprc.internal
classes however
this package is not required by the JSAMP package, and it has no dependencies
on other parts of it, so it may be used as a separate item if required.
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ 0000775 0000000 0000000 00000000000 13564500043 0021757 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/BasicClientSet.java 0000664 0000000 0000000 00000002402 13564500043 0025454 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* Basic ClientSet implementation.
*
* @author Mark Taylor
* @since 20 Nov 2008
*/
public class BasicClientSet implements ClientSet {
private final Map publicIdMap_;
/**
* Constructor.
*
* @param clientIdComparator comparator for client IDs
*/
public BasicClientSet( Comparator clientIdComparator ) {
publicIdMap_ = Collections
.synchronizedMap( new TreeMap( clientIdComparator ) );
}
public synchronized void add( HubClient client ) {
publicIdMap_.put( client.getId(), client );
}
public synchronized void remove( HubClient client ) {
publicIdMap_.remove( client.getId() );
}
public synchronized HubClient getFromPublicId( String publicId ) {
return (HubClient) publicIdMap_.get( publicId );
}
public synchronized HubClient[] getClients() {
return (HubClient[])
publicIdMap_.values().toArray( new HubClient[ 0 ] );
}
public synchronized boolean containsClient( HubClient client ) {
return publicIdMap_.containsValue( client );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/BasicHubService.java 0000664 0000000 0000000 00000133460 13564500043 0025632 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.AbstractMessageHandler;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.UtilServer;
/**
* HubService implementation.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class BasicHubService implements HubService {
private final KeyGenerator keyGen_;
private final ClientIdGenerator idGen_;
private final Map waiterMap_;
private ClientSet clientSet_;
private HubClient serviceClient_;
private HubConnection serviceClientConnection_;
private volatile boolean started_;
private volatile boolean shutdown_;
private static final char ID_DELIMITER = '_';
private final Logger logger_ =
Logger.getLogger( BasicHubService.class.getName() );
private final static ProfileToken INTERNAL_PROFILE = new ProfileToken() {
public String getProfileName() {
return "Internal";
}
public MessageRestriction getMessageRestriction() {
return null;
}
};
/** The maximum timeout for a synchronous call permitted in seconds.
* Default is 43200 = 12 hours. */
public static int MAX_TIMEOUT = 12 * 60 * 60;
/** The maximum number of concurrently pending synchronous calls.
* Default is 100. */
public static int MAX_WAITERS = 100;
/**
* Constructor.
*
* @param random random number generator used for message tags etc
*/
public BasicHubService( Random random ) {
// Prepare ID generators.
keyGen_ = new KeyGenerator( "m:", 16, random );
idGen_ = new ClientIdGenerator( "c" );
// Prepare the data structure which keeps track of pending synchronous
// calls.
waiterMap_ = Collections.synchronizedMap( new HashMap() );
}
public void start() {
// Prepare the data structure which keeps track of registered clients.
clientSet_ = createClientSet();
// Prepare and store the client object which represents the hub itself
// (the one that apparently sends samp.hub.event.shutdown messages etc).
serviceClient_ = createClient( "hub", INTERNAL_PROFILE );
serviceClientConnection_ = createConnection( serviceClient_ );
Metadata meta = new Metadata();
meta.setName( "Hub" );
try {
meta.setIconUrl( UtilServer.getInstance()
.exportResource( "/org/astrogrid/samp/images/"
+ "hub.png" )
.toString() );
}
catch ( Throwable e ) {
logger_.warning( "Can't set icon" );
}
meta.put( "author.name", "Mark Taylor" );
meta.put( "author.mail", "m.b.taylor@bristol.ac.uk" );
meta.setDescriptionText( getClass().getName() );
serviceClient_.setMetadata( meta );
HubCallableClient hubCallable =
new HubCallableClient( serviceClientConnection_,
createHubMessageHandlers() );
serviceClient_.setCallable( hubCallable );
serviceClient_.setSubscriptions( hubCallable.getSubscriptions() );
clientSet_.add( serviceClient_ );
started_ = true;
}
/**
* Factory method used to create the client set used by this hub service.
*
* @return client set
*/
protected ClientSet createClientSet() {
return new BasicClientSet( getIdComparator() ) {
public void add( HubClient client ) {
assert client.getId().indexOf( ID_DELIMITER ) < 0;
super.add( client );
}
};
}
/**
* Factory method used to create all the client objects which will
* be used by this hub service.
*
* @param publicId client public ID
* @param ptoken connection source
* @return hub client
*/
protected HubClient createClient( String publicId, ProfileToken ptoken ) {
return new HubClient( publicId, ptoken );
}
/**
* Constructs a list of MessageHandlers to use for the client
* provided by the Hub.
*
* @return hub message handler list
*/
protected AbstractMessageHandler[] createHubMessageHandlers() {
return new AbstractMessageHandler[] {
new PingMessageHandler(),
new MetaQueryMessageHandler( getClientSet() ),
};
}
/**
* Returns a comparator which will order client IDs.
* The ordering is in creation sequence.
*
* @return public ID comparator
*/
public Comparator getIdComparator() {
return idGen_.getComparator();
}
/**
* Returns the structure which keeps track of registered clients.
*
* @return client set
*/
public ClientSet getClientSet() {
return clientSet_;
}
public HubConnection register( ProfileToken ptoken ) throws SampException {
if ( ! started_ ) {
throw new SampException( "Not started" );
}
HubClient client = createClient( idGen_.next(), ptoken );
assert client.getId().indexOf( ID_DELIMITER ) < 0;
clientSet_.add( client );
hubEvent( new Message( "samp.hub.event.register" )
.addParam( "id", client.getId() ) );
return createConnection( client );
}
public void disconnectAll( ProfileToken profileToken ) {
HubClient[] clients = clientSet_.getClients();
List profClientIdList = new ArrayList();
for ( int ic = 0; ic < clients.length; ic++ ) {
HubClient client = clients[ ic ];
if ( profileToken.equals( client.getProfileToken() ) ) {
profClientIdList.add( client.getId() );
}
}
disconnect( (String[]) profClientIdList.toArray( new String[ 0 ] ),
new Message( "samp.hub.event.shutdown" ) );
}
/**
* Returns a new HubConnection for use by a given hub client.
* The instance methods of the returned object delegate to similarly
* named protected methods of this BasicHubService object.
* These BasicHubService methods may therefore be overridden to
* modify the behaviour of such returned connections.
*
* @param caller client requiring a connection
* @return connection whose methods may be called by or on behalf of
* caller
*/
protected HubConnection createConnection( final HubClient caller ) {
final BasicHubService service = this;
final RegInfo regInfo = new RegInfo();
regInfo.put( RegInfo.HUBID_KEY, serviceClient_.getId() );
regInfo.put( RegInfo.SELFID_KEY, caller.getId() );
return new HubConnection() {
public RegInfo getRegInfo() {
return regInfo;
}
public void ping() throws SampException {
if ( ! service.isHubRunning() ) {
throw new SampException( "Service is stopped" );
}
}
public void unregister() throws SampException {
checkCaller();
service.unregister( caller );
}
public void setCallable( CallableClient callable )
throws SampException {
checkCaller();
service.setCallable( caller, callable );
}
public void declareMetadata( Map meta ) throws SampException {
checkCaller();
service.declareMetadata( caller, meta );
}
public Metadata getMetadata( String clientId )
throws SampException {
checkCaller();
return service.getMetadata( caller, clientId );
}
public void declareSubscriptions( Map subs )
throws SampException {
checkCaller();
service.declareSubscriptions( caller, subs );
}
public Subscriptions getSubscriptions( String clientId )
throws SampException {
checkCaller();
return service.getSubscriptions( caller, clientId );
}
public String[] getRegisteredClients() throws SampException {
checkCaller();
return service.getRegisteredClients( caller );
}
public Map getSubscribedClients( String mtype )
throws SampException {
checkCaller();
return service.getSubscribedClients( caller, mtype );
}
public void notify( String recipientId, Map message )
throws SampException {
checkCaller();
service.notify( caller, recipientId, message );
}
public String call( String recipientId, String msgTag,
Map message ) throws SampException {
checkCaller();
return service.call( caller, recipientId, msgTag, message );
}
public List notifyAll( Map message ) throws SampException {
checkCaller();
return service.notifyAll( caller, message );
}
public Map callAll( String msgTag, Map message )
throws SampException {
checkCaller();
return service.callAll( caller, msgTag, message );
}
public void reply( String msgId, Map response )
throws SampException {
checkCaller();
service.reply( caller, msgId, response );
}
public Response callAndWait( String recipientId, Map message,
int timeout ) throws SampException {
checkCaller();
return service.callAndWait( caller, recipientId, message,
timeout );
}
/**
* Checks that this connection's client is able to make calls
* on this connection. If it is not, for instance if it has been
* unregistered, an exception will be thrown.
*/
private void checkCaller() throws SampException {
if ( ! clientSet_.containsClient( caller ) ) {
throw new SampException( "Client not registered" );
}
}
};
}
/**
* Does the work for the unregister
method of conections
* registered with this service.
*
* @param caller client to unregister
* @see org.astrogrid.samp.client.HubConnection#unregister
*/
protected void unregister( HubClient caller ) throws SampException {
clientSet_.remove( caller );
hubEvent( new Message( "samp.hub.event.unregister" )
.addParam( "id", caller.getId() ) );
}
/**
* Does the work for the setCallable
method of connections
* registered with this service.
*
* @param caller client
* @param callable callable object
* @see org.astrogrid.samp.client.HubConnection#setCallable
*/
protected void setCallable( HubClient caller, CallableClient callable )
throws SampException {
caller.setCallable( callable );
}
/**
* Does the work for the declareMetadata
method of connections
* registered with this service.
*
* @param caller client
* @param meta new metadata for client
* @see org.astrogrid.samp.client.HubConnection#declareMetadata
*/
protected void declareMetadata( HubClient caller, Map meta )
throws SampException {
Metadata.asMetadata( meta ).check();
caller.setMetadata( meta );
hubEvent( new Message( "samp.hub.event.metadata" )
.addParam( "id", caller.getId() )
.addParam( "metadata", meta ) );
}
/**
* Does the work for the getMetadata
method of connections
* registered with this service.
*
* @param caller calling client
* @param clientId id of client being queried
* @return metadata for client
* @see org.astrogrid.samp.client.HubConnection#getMetadata
*/
protected Metadata getMetadata( HubClient caller, String clientId )
throws SampException {
return getClient( clientId ).getMetadata();
}
/**
* Does the work for the declareSubscriptions
method of
* connections registered with this service.
*
* @param caller client
* @param subscriptions new subscriptions for client
* @see org.astrogrid.samp.client.HubConnection#declareSubscriptions
*/
protected void declareSubscriptions( HubClient caller, Map subscriptions )
throws SampException {
if ( ! caller.isCallable() ) {
throw new SampException( "Client is not callable" );
}
Subscriptions subs = Subscriptions.asSubscriptions( subscriptions );
subs.check();
caller.setSubscriptions( subs );
String callerId = caller.getId();
String serviceId = serviceClient_.getId();
String mtype = "samp.hub.event.subscriptions";
HubClient[] recipients = clientSet_.getClients();
for ( int ic = 0; ic < recipients.length; ic++ ) {
HubClient recipient = recipients[ ic ];
if ( recipient != serviceClient_ &&
canSend( serviceClient_, recipient, mtype ) &&
clientSet_.containsClient( recipient ) ) {
Message msg = new Message( mtype );
msg.addParam( "id", callerId );
msg.addParam( "subscriptions",
getSubscriptionsFor( recipient, subs ) );
try {
recipient.getCallable()
.receiveNotification( serviceId, msg );
}
catch ( Exception e ) {
logger_.log( Level.WARNING,
"Notification " + caller + " -> " + recipient
+ " failed: " + e, e );
}
}
}
}
/**
* Does the work for the getSubscriptions
method of connections
* registered with this service.
*
* @param caller calling client
* @param clientId id of client being queried
* @return subscriptions for client
* @see org.astrogrid.samp.client.HubConnection#getSubscriptions
*/
protected Subscriptions getSubscriptions( HubClient caller,
String clientId )
throws SampException {
return getSubscriptionsFor( caller,
getClient( clientId ).getSubscriptions() );
}
/**
* Does the work for the getRegisteredClients
method of
* connections registered with this service.
*
* @param caller calling client
* @return array of registered client IDs excluding caller
's
* @see org.astrogrid.samp.client.HubConnection#getRegisteredClients
*/
protected String[] getRegisteredClients( HubClient caller )
throws SampException {
HubClient[] clients = clientSet_.getClients();
List idList = new ArrayList( clients.length );
for ( int ic = 0; ic < clients.length; ic++ ) {
if ( ! clients[ ic ].equals( caller ) ) {
idList.add( clients[ ic ].getId() );
}
}
return (String[]) idList.toArray( new String[ 0 ] );
}
/**
* Does the work for the getSubscribedClients
method of
* connections registered with this service.
*
* @param caller calling client
* @param mtype message type
* @return map in which the keys are the public IDs of clients
* subscribed to mtype
* @see org.astrogrid.samp.client.HubConnection#getSubscribedClients
*/
protected Map getSubscribedClients( HubClient caller, String mtype )
throws SampException {
HubClient[] clients = clientSet_.getClients();
Map subMap = new TreeMap();
for ( int ic = 0; ic < clients.length; ic++ ) {
HubClient client = clients[ ic ];
if ( ! client.equals( caller ) ) {
Map sub = client.getSubscriptions().getSubscription( mtype );
if ( sub != null && canSend( caller, client, mtype ) ) {
subMap.put( client.getId(), sub );
}
}
}
return subMap;
}
/**
* Does the work for the notify
method of connections
* registered with this service.
*
* @param caller calling client
* @param recipientId public ID of client to receive message
* @param message message
* @see org.astrogrid.samp.client.HubConnection#notify
*/
protected void notify( HubClient caller, String recipientId, Map message )
throws SampException {
Message msg = Message.asMessage( message );
msg.check();
String mtype = msg.getMType();
HubClient recipient = getClient( recipientId );
checkSend( caller, recipient, mtype );
try {
recipient.getCallable().receiveNotification( caller.getId(), msg );
}
catch ( SampException e ) {
throw e;
}
catch ( Exception e ) {
throw new SampException( e.getMessage(), e );
}
}
/**
* Does the work for the call
method of connections
* registered with this service.
*
* @param caller calling client
* @param recipientId client ID of recipient
* @param msgTag message tag
* @param message message
* @return message ID
* @see org.astrogrid.samp.client.HubConnection#call
*/
protected String call( HubClient caller, String recipientId, String msgTag,
Map message )
throws SampException {
Message msg = Message.asMessage( message );
msg.check();
String mtype = msg.getMType();
HubClient recipient = getClient( recipientId );
String msgId = MessageId.encode( caller, msgTag, false );
checkSend( caller, recipient, mtype );
try {
recipient.getCallable().receiveCall( caller.getId(), msgId, msg );
}
catch ( SampException e ) {
throw e;
}
catch ( Exception e ) {
throw new SampException( e.getMessage(), e );
}
return msgId;
}
/**
* Does the work for the notifyAll
method of connections
* registered with this service.
*
* @param caller calling client
* @param message message
* @return list of public IDs for clients to which the notify will be sent
* @see org.astrogrid.samp.client.HubConnection#notifyAll
*/
protected List notifyAll( HubClient caller, Map message )
throws SampException {
Message msg = Message.asMessage( message );
msg.check();
String mtype = msg.getMType();
HubClient[] recipients = clientSet_.getClients();
List sentList = new ArrayList();
for ( int ic = 0; ic < recipients.length; ic++ ) {
HubClient recipient = recipients[ ic ];
if ( recipient != caller && canSend( caller, recipient, mtype ) &&
clientSet_.containsClient( recipient ) ) {
try {
recipient.getCallable()
.receiveNotification( caller.getId(), msg );
sentList.add( recipient.getId() );
}
catch ( Exception e ) {
logger_.log( Level.WARNING,
"Notification " + caller + " -> " + recipient
+ " failed: " + e, e );
}
}
}
return sentList;
}
/**
* Does the work for the call
method of connections
* registered with this service.
*
* @param caller calling client
* @param msgTag message tag
* @param message message
* @return publicId->msgId map for clients to which an attempt to
* send the call will be made
* @see org.astrogrid.samp.client.HubConnection#callAll
*/
protected Map callAll( HubClient caller, String msgTag, Map message )
throws SampException {
Message msg = Message.asMessage( message );
msg.check();
String mtype = msg.getMType();
String msgId = MessageId.encode( caller, msgTag, false );
HubClient[] recipients = clientSet_.getClients();
Map sentMap = new HashMap();
for ( int ic = 0; ic < recipients.length; ic++ ) {
HubClient recipient = recipients[ ic ];
if ( recipient != caller && canSend( caller, recipient, mtype ) &&
clientSet_.containsClient( recipient ) ) {
try {
recipient.getCallable()
.receiveCall( caller.getId(), msgId, msg );
}
catch ( SampException e ) {
throw e;
}
catch ( Exception e ) {
throw new SampException( e.getMessage(), e );
}
sentMap.put( recipient.getId(), msgId );
}
}
return sentMap;
}
/**
* Does the work for the reply
method of connections
* registered with this service.
*
* @param caller calling client
* @param msgIdStr message ID
* @param resp response to forward
* @see org.astrogrid.samp.client.HubConnection#reply
*/
protected void reply( HubClient caller, String msgIdStr, Map resp )
throws SampException {
Response response = Response.asResponse( resp );
response.check();
MessageId msgId = MessageId.decode( msgIdStr );
HubClient sender = getClient( msgId.getSenderId() );
String senderTag = msgId.getSenderTag();
// If we can see from the message ID that it was originally sent
// synchronously, take steps to place the response in the map of
// waiting messages where it will get picked up and returned to
// the sender as a callAndWait return value.
if ( msgId.isSynch() ) {
synchronized ( waiterMap_ ) {
if ( waiterMap_.containsKey( msgId ) ) {
if ( waiterMap_.get( msgId ) == null ) {
waiterMap_.put( msgId, response );
waiterMap_.notifyAll();
}
else {
throw new SampException(
"Response ignored - you've already sent one" );
}
}
else {
throw new SampException(
"Response ignored - synchronous call timed out" );
}
}
}
// Otherwise, just pass it to the sender using a callback.
else {
try {
sender.getCallable()
.receiveResponse( caller.getId(), senderTag, response );
}
catch ( SampException e ) {
throw e;
}
catch ( Exception e ) {
throw new SampException( e.getMessage(), e );
}
}
}
/**
* Does the work for the callAndWait
method of connections
* registered with this service.
*
* @param caller calling client
* @param recipientId client ID of recipient
* @param message message
* @param timeout timeout in seconds
* @return response response
* @see org.astrogrid.samp.client.HubConnection#callAndWait
*/
protected Response callAndWait( HubClient caller, String recipientId,
Map message, int timeout )
throws SampException {
Message msg = Message.asMessage( message );
msg.check();
String mtype = msg.getMType();
HubClient recipient = getClient( recipientId );
MessageId hubMsgId =
new MessageId( caller.getId(), keyGen_.next(), true );
long start = System.currentTimeMillis();
checkSend( caller, recipient, mtype );
synchronized ( waiterMap_ ) {
// If the number of pending synchronous calls exceeds the
// permitted maximum, remove the oldest calls until there is
// space for the new one.
if ( MAX_WAITERS > 0 && waiterMap_.size() >= MAX_WAITERS ) {
int excess = waiterMap_.size() - MAX_WAITERS + 1;
List keyList = new ArrayList( waiterMap_.keySet() );
Collections.sort( keyList, MessageId.AGE_COMPARATOR );
logger_.warning( "Pending synchronous calls exceeds limit "
+ MAX_WAITERS + " - giving up on " + excess
+ " oldest" );
for ( int ie = 0; ie < excess; ie++ ) {
Object removed = waiterMap_.remove( keyList.get( ie ) );
assert removed != null;
}
waiterMap_.notifyAll();
}
// Place an entry for this synchronous call in the waiterMap.
waiterMap_.put( hubMsgId, null );
}
// Make the call asynchronously to the receiver.
try {
recipient.getCallable()
.receiveCall( caller.getId(), hubMsgId.toString(), msg );
}
catch ( SampException e ) {
throw e;
}
catch ( Exception e ) {
throw new SampException( e.getMessage(), e );
}
// Wait until either the timeout expires, or the response to the
// message turns up in the waiter map (placed there on another
// thread by this the reply() method).
timeout = Math.min( Math.max( 0, timeout ),
Math.max( 0, MAX_TIMEOUT ) );
long finish = timeout > 0
? System.currentTimeMillis() + timeout * 1000
: Long.MAX_VALUE; // 3e8 years
synchronized ( waiterMap_ ) {
while ( waiterMap_.containsKey( hubMsgId ) &&
waiterMap_.get( hubMsgId ) == null &&
System.currentTimeMillis() < finish ) {
long millis = finish - System.currentTimeMillis();
if ( millis > 0 ) {
try {
waiterMap_.wait( millis );
}
catch ( InterruptedException e ) {
throw new SampException( "Wait interrupted", e );
}
}
}
// If the response is there, return it to the caller of this
// method (the sender of the message).
if ( waiterMap_.containsKey( hubMsgId ) ) {
Response response =
(Response) waiterMap_.remove( hubMsgId );
if ( response != null ) {
return response;
}
// Otherwise, it must have timed out. Exit with an error.
else {
assert System.currentTimeMillis() >= finish;
String millis =
Long.toString( System.currentTimeMillis() - start );
String emsg = new StringBuffer()
.append( "Synchronous call timeout after " )
.append( millis.substring( 0,
millis.length() - 3 ) )
.append( '.' )
.append( millis.substring( millis.length() - 3 ) )
.append( '/' )
.append( timeout )
.append( " sec" )
.toString();
throw new SampException( emsg );
}
}
else {
throw new SampException(
"Synchronous call aborted"
+ " - server load exceeded maximum of " + MAX_WAITERS + "?" );
}
}
}
/**
* Returns the HubConnection object used by the hub itself to send
* and receive messages.
* This is the one which apparently sends samp.hub.event.shutdown messages
* etc.
*
* @return hub service's own hub connection
*/
public HubConnection getServiceConnection() {
return serviceClientConnection_;
}
/**
* Forcibly disconnects a given client.
* This call does three things:
*
*
*
* @param clientId public-id of client to eject
* @param reason short text string indicating reason for ejection
*/
public void disconnect( String clientId, String reason ) {
Message discoMsg = new Message( "samp.hub.disconnect" );
if ( reason != null && reason.length() > 0 ) {
discoMsg.addParam( "reason", reason );
}
disconnect( new String[] { clientId }, discoMsg );
}
/**
* Forcibly disconnects a number of clients for the same reason.
*
* @param clientIds public-ids of clients to disconnect
* @param discoMsg message to send to clients which will effect
* disconnection (samp.hub.disconnect or samp.hub.event.shutdown)
*/
private void disconnect( String[] clientIds, Message discoMsg ) {
// Send the message and remove clients from client set.
for ( int ic = 0; ic < clientIds.length; ic++ ) {
String clientId = clientIds[ ic ];
HubClient client = clientSet_.getFromPublicId( clientId );
if ( client != null ) {
if ( client.isSubscribed( discoMsg.getMType() ) ) {
try {
notify( serviceClient_, clientId, discoMsg );
}
catch ( SampException e ) {
logger_.log( Level.INFO,
discoMsg.getMType() + " to " + client
+ " failed", e );
}
}
clientSet_.remove( client );
}
}
// Notify the remaining clients that the others have been removed.
for ( int ic = 0; ic < clientIds.length; ic++ ) {
hubEvent( new Message( "samp.hub.event.unregister" )
.addParam( "id", clientIds[ ic ] ) );
}
}
public boolean isHubRunning() {
return started_ && ! shutdown_;
}
public synchronized void shutdown() {
if ( ! shutdown_ ) {
shutdown_ = true;
if ( started_ ) {
hubEvent( new Message( "samp.hub.event.shutdown" ) );
}
serviceClientConnection_ = null;
}
}
/**
* Broadcast an event message to all subscribed clients.
* The sender of this message is the hub application itself.
*
* @param msg message to broadcast
*/
private void hubEvent( Message msg ) {
try {
notifyAll( serviceClient_, msg );
}
catch ( SampException e ) {
assert false;
}
}
/**
* Returns the client object corresponding to a public client ID.
* If no such client is registered, throw an exception.
*
* @param id client public id
* @return HubClient object
*/
private HubClient getClient( String id ) throws SampException {
HubClient client = clientSet_.getFromPublicId( id );
if ( client != null ) {
return client;
}
else if ( idGen_.hasUsed( id ) ) {
throw new SampException( "Client " + id
+ " is no longer registered" );
}
else {
throw new SampException( "No registered client with ID \""
+ id + "\"" );
}
}
/**
* Checks if a given send is permitted. Throws an exception if not.
*
* @param sender sending client
* @param recipient receiving client
* @param mtype MType
* @throws SampException if the send is not permitted
*/
private void checkSend( HubClient sender, HubClient recipient,
String mtype ) throws SampException {
String errmsg = getSendError( sender, recipient, mtype );
if ( errmsg != null ) {
throw new SampException( errmsg );
}
}
/**
* Indicates whether a given send is permitted.
*
* @param sender sending client
* @param recipient receiving client
* @param mtype MType
* @return true iff send OK
*/
private boolean canSend( HubClient sender, HubClient recipient,
String mtype ) {
return getSendError( sender, recipient, mtype ) == null;
}
/**
* Does the work to determine whether a given sending client is
* permitted to send a message with a given MType to a given recipient.
* Returns null if allowed, a useful message if not.
* Not intended for direct use, see {@link #canSend} and {@link #checkSend}.
*
* @param sender sending client
* @param recipient receiving client
* @param mtype MType
* @return null if send OK, otherwise explanation message
*/
private String getSendError( HubClient sender, HubClient recipient,
String mtype ) {
if ( ! recipient.isCallable() ) {
return "Client " + recipient + " is not callable";
}
Subscriptions subs = recipient.getSubscriptions();
if ( ! subs.isSubscribed( mtype ) ) {
return "Client " + recipient + " is not subscribed to " + mtype;
}
ProfileToken ptoken = sender.getProfileToken();
MessageRestriction mrestrict = ptoken.getMessageRestriction();
if ( mrestrict != null ) {
Map subsInfo = subs.getSubscription( mtype );
if ( ! mrestrict.permitSend( mtype, subsInfo ) ) {
return "MType " + mtype + " blocked from "
+ ptoken + " profile";
}
}
return null;
}
/**
* Returns the view of a given subscriptions map to be presented to
* a sending client. The result may be affected by any message
* restrictions in force for the client.
*
* @param client client to view subscriptions
* @param subs basic subscription map
* @return view of subscription map for samp.hub.disconnect
message to the
* client which is about to be ejected, if the client is
* subscribed to that MTypesamp.hub.unregister
message to all
* remaining clients indicating that the client has disappearedclient
*/
private Subscriptions getSubscriptionsFor( HubClient client,
Subscriptions subs ) {
MessageRestriction mrestrict =
client.getProfileToken().getMessageRestriction();
if ( mrestrict == null ) {
return subs;
}
Subscriptions csubs = new Subscriptions();
for ( Iterator it = subs.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String mtype = (String) entry.getKey();
Map note = (Map) entry.getValue();
if ( mrestrict.permitSend( mtype, note ) ) {
csubs.put( mtype, note );
}
}
return csubs;
}
/**
* Encapsulates information about a MessageId.
* A message ID can be represented as a string, but encodes information
* which can be retrieved later.
*/
private static class MessageId {
private final String senderId_;
private final String senderTag_;
private final boolean isSynch_;
private final long birthday_;
private static final String T_SYNCH_FLAG = "S";
private static final String F_SYNCH_FLAG = "A";
private static final int CHECK_SEED = (int) System.currentTimeMillis();
private static final int CHECK_LENG = 4;
private static final Comparator AGE_COMPARATOR = new Comparator() {
public int compare( Object o1, Object o2 ) {
return (int) (((MessageId) o1).birthday_ -
((MessageId) o2).birthday_);
}
};
/**
* Constructor.
*
* @param senderId client id of the message sender
* @param senderTag msgTag provided by the sender
* @param isSynch whether the message was sent synchronously or not
*/
public MessageId( String senderId, String senderTag, boolean isSynch ) {
senderId_ = senderId;
senderTag_ = senderTag;
isSynch_ = isSynch;
birthday_ = System.currentTimeMillis();
}
/**
* Returns the sender's public client id.
*
* @return sender's id
*/
public String getSenderId() {
return senderId_;
}
/**
* Returns the msgTag attached to the message by the sender.
*
* @return msgTag
*/
public String getSenderTag() {
return senderTag_;
}
/**
* Returns whether the message was sent synchronously.
*
* @return true iff message was sent using callAndWait
*/
public boolean isSynch() {
return isSynch_;
}
public int hashCode() {
return checksum( senderId_, senderTag_, isSynch_ ).hashCode();
}
public boolean equals( Object o ) {
if ( o instanceof MessageId ) {
MessageId other = (MessageId) o;
return this.senderId_.equals( other.senderId_ )
&& this.senderTag_.equals( other.senderTag_ )
&& this.isSynch_ == other.isSynch_;
}
else {
return false;
}
}
/**
* Returns the string representation of this MessageId.
*
* @return message ID string
*/
public String toString() {
Object checksum = checksum( senderId_, senderTag_, isSynch_ );
return new StringBuffer()
.append( senderId_ )
.append( ID_DELIMITER )
.append( isSynch_ ? T_SYNCH_FLAG : F_SYNCH_FLAG )
.append( ID_DELIMITER )
.append( checksum )
.append( ID_DELIMITER )
.append( senderTag_ )
.toString();
}
/**
* Decodes a msgId string to return the corresponding MessageId object.
* This is the opposite of the {@link #toString} method.
*
* @param msgId string representation of message ID
* @return new MessageId object
*/
public static MessageId decode( String msgId ) throws SampException {
int delim1 = msgId.indexOf( ID_DELIMITER );
int delim2 = msgId.indexOf( ID_DELIMITER, delim1 + 1 );
int delim3 = msgId.indexOf( ID_DELIMITER, delim2 + 1 );
if ( delim1 < 0 || delim2 < 0 || delim3 < 0 ) {
throw new SampException( "Badly formed message ID " + msgId );
}
String senderId = msgId.substring( 0, delim1 );
String synchFlag = msgId.substring( delim1 + 1, delim2 );
String checksum = msgId.substring( delim2 + 1, delim3 );
String senderTag = msgId.substring( delim3 + 1 );
boolean isSynch;
if ( T_SYNCH_FLAG.equals( synchFlag ) ) {
isSynch = true;
}
else if ( F_SYNCH_FLAG.equals( synchFlag ) ) {
isSynch = false;
}
else {
throw new SampException( "Badly formed message ID "
+ msgId + " (synch flag)" );
}
if ( ! checksum( senderId, senderTag, isSynch )
.equals( checksum ) ) {
throw new SampException( "Bad message ID checksum" );
}
MessageId idObj = new MessageId( senderId, senderTag, isSynch );
assert idObj.toString().equals( msgId );
return idObj;
}
/**
* Returns a message ID string corresponding to the arguments.
*
* @param sender sender client
* @param senderTag msgTag attached by sender
* @param isSynch whether message was sent synchronously
* @return string representation of message ID
*/
public static String encode( HubClient sender, String senderTag,
boolean isSynch ) {
return new MessageId( sender.getId(), senderTag, isSynch )
.toString();
}
/**
* Returns a checksum string which is a hash of the given arguments.
*
* @param senderId public client id of sender
* @param senderTag msgTag attached by sender
* @param isSynch whether message was sent synchronously
* @return checksum string
*/
private static String checksum( String senderId, String senderTag,
boolean isSynch ) {
int sum = CHECK_SEED;
sum = 23 * sum + senderId.hashCode();
sum = 23 * sum + senderTag.hashCode();
sum = 23 * sum + ( isSynch ? 3 : 5 );
String check = Integer.toHexString( sum );
check =
check.substring( Math.max( 0, check.length() - CHECK_LENG ) );
while ( check.length() < CHECK_LENG ) {
check = "0" + check;
}
assert check.length() == CHECK_LENG;
return check;
}
}
/**
* Generates client public IDs.
* These must be unique, but don't need to be hard to guess.
*/
private static class ClientIdGenerator {
private int iseq_;
private final String prefix_;
private final Comparator comparator_;
/**
* Constructor.
*
* @param prefix prefix for all generated ids
*/
public ClientIdGenerator( String prefix ) {
prefix_ = prefix;
// Prepare a comparator which will order the keys generated here
// in sequence of generation.
comparator_ = new Comparator() {
public int compare( Object o1, Object o2 ) {
String s1 = o1.toString();
String s2 = o2.toString();
Integer i1 = getIndex( s1 );
Integer i2 = getIndex( s2 );
if ( i1 == null && i2 == null ) {
return s1.compareTo( s2 );
}
else if ( i1 == null ) {
return +1;
}
else if ( i2 == null ) {
return -1;
}
else {
return i1.intValue() - i2.intValue();
}
}
};
}
/**
* Returns the next unused id.
*
* @return next id
*/
public synchronized String next() {
return prefix_ + Integer.toString( ++iseq_ );
}
/**
* Indicates whether a given client ID has previously been dispensed
* by this object.
*
* @param id id to test
* @return true iff id has been returned by a previous call of
* next
*/
public boolean hasUsed( String id ) {
Integer ix = getIndex( id );
return ix != null && ix.intValue() <= iseq_;
}
/**
* Returns an Integer giving the sequence index of the given id string.
* If id
does not look like a string generated by this
* object, null is returned.
*
* @param id identifier to test
* @return object containing sequence index of id
,
* or null
*/
private Integer getIndex( String id ) {
if ( id.startsWith( prefix_ ) ) {
try {
int iseq =
Integer.parseInt( id.substring( prefix_.length() ) );
return new Integer( iseq );
}
catch ( NumberFormatException e ) {
return null;
}
}
else {
return null;
}
}
/**
* Returns a comparator which will order the IDs generated by this
* object in generation sequence.
*
* @return id comparator
*/
public Comparator getComparator() {
return comparator_;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ClientSet.java 0000664 0000000 0000000 00000002132 13564500043 0024512 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
/**
* Data structure for keeping track of clients currently registered with a hub.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public interface ClientSet {
/**
* Adds a new client to the set.
*
* @param client client to add
*/
void add( HubClient client );
/**
* Removes a client from the set.
*
* @param client client to remove
*/
void remove( HubClient client );
/**
* Returns the client in the set corresponding to a given public ID.
*
* @param publicId client public ID
* @return client with id publicId
if registered, or null
*/
HubClient getFromPublicId( String publicId );
/**
* Returns an array of all the currently contained clients.
*
* @return client list
*/
HubClient[] getClients();
/**
* Indicates whether a given client is currently a member of this set.
*
* @return true iff client
is currently a member of this set
*/
boolean containsClient( HubClient client );
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ConfigHubProfile.java 0000664 0000000 0000000 00000001245 13564500043 0026011 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import javax.swing.JToggleButton;
/**
* Marks a HubProfile that can also provide GUI-based configuration.
* This is a bit of a hack, in that it's not very general; it is just
* intended at present for the WebHubProfile and is rather specific to
* its needs. This interface may change or disappear at some point
* in the future.
*
* @author Mark Taylor
* @since 22 Jul 2011
*/
public interface ConfigHubProfile extends HubProfile {
/**
* Returns some toggle button models for hub profile configuration.
*
* @return toggle button model array
*/
JToggleButton.ToggleButtonModel[] getConfigModels();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/FacadeHubService.java 0000664 0000000 0000000 00000014501 13564500043 0025746 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* HubService that provides hub functionality by accessing an existing
* hub service. The existing hub service is defined by a supplied
* ClientProfile object.
*
* @author Mark Taylor
* @since 1 Feb 2011
*/
public class FacadeHubService implements HubService {
private final ClientProfile profile_;
private final Map connectionMap_; // FacadeHubConnection -> ProfileToken
private static final Logger logger_ =
Logger.getLogger( FacadeHubService.class.getName() );
/**
* Constructor.
*
* @param profile defines the hub connection factory on which this
* service is based
*/
public FacadeHubService( ClientProfile profile ) {
profile_ = profile;
connectionMap_ = Collections.synchronizedMap( new HashMap() );
}
public boolean isHubRunning() {
return profile_.isHubRunning();
}
public HubConnection register( ProfileToken profileToken )
throws SampException {
// Mostly delegate registration to the underlying client profile,
// but put in place machinery to keep track of which clients
// are registered via this service. This will be required so that
// they can be messaged if the underlying hub shuts down.
HubConnection baseConnection = profile_.register();
if ( baseConnection != null ) {
HubConnection conn = new FacadeHubConnection( baseConnection ) {
final HubConnection hubConn = this;
public void ping() throws SampException {
if ( FacadeHubService.this.isHubRunning() ) {
super.ping();
}
else {
throw new SampException( "Hub underlying facade "
+ "is not running" );
}
}
public void unregister() throws SampException {
connectionMap_.keySet().remove( hubConn );
super.unregister();
}
};
connectionMap_.put( conn, profileToken );
return conn;
}
// Or return null if there is no underlying hub.
else {
return null;
}
}
public void disconnectAll( ProfileToken profileToken ) {
Map.Entry[] entries = (Map.Entry[])
connectionMap_.entrySet()
.toArray( new Map.Entry[ 0 ] );
List ejectList = new ArrayList();
for ( int ie = 0; ie < entries.length; ie++ ) {
if ( profileToken.equals( entries[ ie ].getValue() ) ) {
ejectList.add( entries[ ie ].getKey() );
}
}
FacadeHubConnection[] ejectConns =
(FacadeHubConnection[])
ejectList.toArray( new FacadeHubConnection[ 0 ] );
int nc = ejectConns.length;
Message discoMsg = new Message( "samp.hub.event.shutdown" );
String[] ejectIds = new String[ nc ];
for ( int ic = 0; ic < nc; ic++ ) {
FacadeHubConnection conn = ejectConns[ ic ];
ejectIds[ ic ] = conn.getRegInfo().getSelfId();
conn.hubEvent( discoMsg );
connectionMap_.remove( conn );
}
for ( int ic = 0; ic < nc; ic++ ) {
hubEvent( new Message( "samp.hub.event.unregister" )
.addParam( "id", ejectIds[ ic ] ) );
}
}
/**
* No-op.
*/
public void start() {
}
public void shutdown() {
hubEvent( new Message( "samp.hub.event.shutdown" ) );
connectionMap_.clear();
}
/**
* Sends a given message by notification, as if from the hub,
* to all the clients which have registered through this service.
*
* @param msg message to send
*/
private void hubEvent( Message msg ) {
String mtype = msg.getMType();
FacadeHubConnection[] connections =
(FacadeHubConnection[])
connectionMap_.keySet().toArray( new FacadeHubConnection[ 0 ] );
for ( int ic = 0; ic < connections.length; ic++ ) {
connections[ ic ].hubEvent( msg );
}
}
/**
* Utility HubConnection class which allows hub event notifications
* to be sent to clients.
*/
private static class FacadeHubConnection extends WrapperHubConnection {
private CallableClient callable_;
private Subscriptions subs_;
/**
* Constructor.
*
* @param base base connection
*/
FacadeHubConnection( HubConnection base ) {
super( base );
}
public void setCallable( CallableClient callable )
throws SampException {
super.setCallable( callable );
callable_ = callable;
}
public void declareSubscriptions( Map subs ) throws SampException {
super.declareSubscriptions( subs );
subs_ = subs == null ? null
: Subscriptions.asSubscriptions( subs );
}
/**
* Sends a given message as a notification, as if from the hub,
* to this connection if it is able to receive it.
*
* @param msg message to send
*/
void hubEvent( Message msg ) {
String mtype = msg.getMType();
CallableClient callable = callable_;
if ( callable != null && subs_.isSubscribed( mtype ) ) {
RegInfo regInfo = getRegInfo();
try {
callable.receiveNotification( regInfo.getHubId(), msg );
}
catch ( Throwable e ) {
logger_.info( "Failed " + mtype + " notification to "
+ regInfo.getSelfId() );
}
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/Hub.java 0000664 0000000 0000000 00000111422 13564500043 0023341 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import org.astrogrid.samp.ShutdownManager;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.UtilServer;
import org.astrogrid.samp.web.WebHubProfile;
import org.astrogrid.samp.web.WebHubProfileFactory;
import org.astrogrid.samp.xmlrpc.StandardHubProfile;
import org.astrogrid.samp.xmlrpc.StandardHubProfileFactory;
import org.astrogrid.samp.xmlrpc.XmlRpcKit;
/**
* Class which manages a hub and its associated profiles.
* Static methods are provided for starting a hub in the current or an
* external JVM, and a main()
method is provided for
* use from the command line.
*
* -Djsamp.hub.profiles=web,std
will cause it to run hubs
* using both the Standard and Web profiles if it does not explicitly choose
* profiles.
*
* @author Mark Taylor
* @author Sylvain Lafrasse
* @since 31 Jan 2011
*/
public class Hub {
private final HubService service_;
private final List profileList_;
private static Class[] defaultDefaultProfileClasses_ = {
StandardHubProfile.class,
WebHubProfile.class,
};
private static Class[] defaultDefaultExtraProfileClasses_ = {
};
private static Class[] defaultProfileClasses_ =
createDefaultProfileClasses( false );
private static Class[] defaultExtraProfileClasses_ =
createDefaultProfileClasses( true );
private static final Map hubList_ = new WeakHashMap();
private static final Logger logger_ =
Logger.getLogger( Hub.class.getName() );
/**
* System property name for supplying default profiles ({@value})
* available at hub startup.
* The value of this property, if any, will be fed to
* {@link #parseProfileList}.
*/
public static final String HUBPROFILES_PROP =
"jsamp.hub.profiles";
/**
* System property name for supplying default profiles ({@value})
* additional to those in {@link #HUBPROFILES_PROP} which will be
* supported by the hub but switched off at hub startup time.
* The value of this property, if any, will be fed to
* {@link #parseProfileList}.
*/
public static final String EXTRAHUBPROFILES_PROP =
"jsamp.hub.profiles.extra";
/**
* Constructor.
* Note that this object does not start the service, it must be
* started explicitly, either before or after this constructor is called.
*
* @param service hub service
*/
public Hub( HubService service ) {
service_ = service;
profileList_ = new ArrayList();
synchronized ( hubList_ ) {
hubList_.put( this, null );
}
}
/**
* Stops this hub and its profiles running.
*/
public synchronized void shutdown() {
logger_.info( "Shutting down hub service" );
try {
service_.shutdown();
}
catch ( Throwable e ) {
logger_.log( Level.WARNING, "Service shutdown error: " + e, e );
}
for ( Iterator it = profileList_.iterator(); it.hasNext(); ) {
HubProfile profile = (HubProfile) it.next();
logger_.info( "Shutting down hub profile "
+ profile.getProfileName() );
try {
profile.stop();
}
catch ( IOException e ) {
logger_.log( Level.WARNING,
"Failed to stop profile "
+ profile.getProfileName(), e );
}
it.remove();
}
synchronized ( hubList_ ) {
hubList_.remove( this );
}
ShutdownManager.getInstance().unregisterHook( this );
}
/**
* Starts a profile running on behalf of this hub.
*
* @param profile to start
*/
public synchronized void startProfile( final HubProfile profile )
throws IOException {
if ( profileList_.contains( profile ) ) {
logger_.info( "Profile " + profile.getProfileName()
+ " already started in this hub" );
}
else {
profile.start( new ClientProfile() {
public HubConnection register() throws SampException {
return service_.register( profile );
}
public boolean isHubRunning() {
return service_.isHubRunning();
}
} );
profileList_.add( profile );
}
}
/**
* Stops a profile running on behalf of this hub, and disconnects
* all clients registered with it.
*
* @param profile profile to stop
*/
public synchronized void stopProfile( HubProfile profile ) {
logger_.info( "Shutting down hub profile " + profile.getProfileName()
+ " and disconnecting clients" );
try {
profile.stop();
}
catch ( IOException e ) {
logger_.log( Level.WARNING,
"Failed to stop profile "
+ profile.getProfileName(), e );
}
profileList_.remove( profile );
service_.disconnectAll( profile );
}
/**
* Returns the hub service associated with this hub.
*
* @return hub service
*/
public HubService getHubService() {
return service_;
}
/**
* Returns the hub profiles currently running on behalf of this hub.
*
* @return profiles that have been started and not yet stopped by this hub
*/
public HubProfile[] getRunningProfiles() {
return (HubProfile[]) profileList_.toArray( new HubProfile[ 0 ] );
}
/**
* Returns a window for user monitoring and control of this hub,
* if available.
* The default implementation returns null, but this may be overridden
* depending on how this hub was instantiated.
*
* @return hub monitor/control window, or null
*/
public JFrame getWindow() {
return null;
}
/**
* Returns a standard list of known HubProfileFactories.
* This is used when parsing hub profile lists
* ({@link #parseProfileList} to supply the well-known named profiles.
*
* @return array of known hub profile factories
*/
public static HubProfileFactory[] getKnownHubProfileFactories() {
return new HubProfileFactory[] {
new StandardHubProfileFactory(),
new WebHubProfileFactory(),
};
}
/**
* Returns a copy of the default set of HubProfile classes used
* when a hub is run and the list of profiles is not set explicitly.
* Each element should be an implementation of {@link HubProfile}
* with a no-arg constructor.
*
* @param extra false for starting classes, true for additional ones
* @return array of hub profile classes
*/
public static Class[] getDefaultProfileClasses( boolean extra ) {
return (Class[]) ( extra ? defaultExtraProfileClasses_
: defaultProfileClasses_ ).clone();
}
/**
* Sets the default set of HubProfile classes.
*
* @param clazzes array to be returned by getDefaultProfileClasses
* @param extra false for starting classes, true for additional ones
*/
public static void setDefaultProfileClasses( Class[] clazzes,
boolean extra ) {
for ( int ip = 0; ip < clazzes.length; ip++ ) {
Class clazz = clazzes[ ip ];
if ( ! HubProfile.class.isAssignableFrom( clazz ) ) {
throw new IllegalArgumentException( "Class " + clazz.getName()
+ " not a HubProfile" );
}
}
clazzes = (Class[]) clazzes.clone();
if ( extra ) {
defaultExtraProfileClasses_ = clazzes;
}
else {
defaultProfileClasses_ = clazzes;
}
}
/**
* Invoked at class load time to come up with the list of hub
* profiles to use when no profiles are specified explicitly.
* By default this is just the standard profile, but if the
* {@link #HUBPROFILES_PROP} system property is defined its value
* is used instead.
*
* @param extra false for starting classes, true for additional ones
* @return default array of hub profile classes
*/
private static Class[] createDefaultProfileClasses( boolean extra ) {
String listTxt = System.getProperty( extra ? EXTRAHUBPROFILES_PROP
: HUBPROFILES_PROP );
if ( listTxt != null ) {
HubProfileFactory[] facts = parseProfileList( listTxt );
Class[] clazzes = new Class[ facts.length ];
for ( int i = 0; i < facts.length; i++ ) {
clazzes[ i ] = facts[ i ].getHubProfileClass();
}
return clazzes;
}
else {
return extra ? defaultDefaultExtraProfileClasses_
: defaultDefaultProfileClasses_;
}
}
/**
* Parses a string representing a list of hub profiles.
* The result is an array of HubProfileFactories.
* The list is comma-separated, and each element may be
* either the {@link HubProfileFactory#getName name}
* of a HubProfileFactory
* or the classname of a {@link HubProfile} implementation
* with a suitable no-arg constructor.
*
* @param listTxt comma-separated list
* @return array of hub profile factories
* @throws IllegalArgumentException if unknown
*/
public static HubProfileFactory[] parseProfileList( String listTxt ) {
String[] txtItems = listTxt == null || listTxt.trim().length() == 0
? new String[ 0 ]
: listTxt.split( "," );
List factoryList = new ArrayList();
for ( int i = 0; i < txtItems.length; i++ ) {
factoryList.add( parseProfileClass( txtItems[ i ] ) );
}
return (HubProfileFactory[])
factoryList.toArray( new HubProfileFactory[ 0 ] );
}
/**
* Parses a string representing a hub profile. Each element may be
* either the {@link HubProfileFactory#getName name}
* of a HubProfileFactory
* or the classname of a {@link HubProfile} implementation
* with a suitable no-arg constructor.
*
* @param txt string
* @return hub profile factory
* @throws IllegalArgumentException if unknown
*/
private static HubProfileFactory parseProfileClass( String txt ) {
HubProfileFactory[] profFacts = getKnownHubProfileFactories();
for ( int i = 0; i < profFacts.length; i++ ) {
if ( txt.equals( profFacts[ i ].getName() ) ) {
return profFacts[ i ];
}
}
final Class clazz;
try {
clazz = Class.forName( txt );
}
catch ( ClassNotFoundException e ) {
throw (IllegalArgumentException)
new IllegalArgumentException( "No known hub/class " + txt )
.initCause( e );
}
if ( HubProfile.class.isAssignableFrom( clazz ) ) {
return new HubProfileFactory() {
public Class getHubProfileClass() {
return clazz;
}
public String[] getFlagsUsage() {
return new String[ 0 ];
}
public String getName() {
return clazz.getName();
}
public HubProfile createHubProfile( List flagList )
throws IOException {
try {
return (HubProfile) clazz.newInstance();
}
catch ( IllegalAccessException e ) {
throw (IOException)
new IOException( "Can't create " + clazz.getName()
+ " instance" )
.initCause( e );
}
catch ( InstantiationException e ) {
throw (IOException)
new IOException( "Can't create " + clazz.getName()
+ " instance" )
.initCause( e );
}
catch ( ExceptionInInitializerError e ) {
Throwable cause = e.getCause();
if ( cause instanceof IOException ) {
throw (IOException) cause;
}
else {
throw (IOException)
new IOException( "Can't create "
+ clazz.getName()
+ " instance" )
.initCause( e );
}
}
}
};
}
else {
throw new IllegalArgumentException( clazz + " is not a "
+ HubProfile.class.getName() );
}
}
/**
* Returns an array of default Hub Profiles.
* This is the result of calling the no-arg constructor
* for each element of the result of {@link #getDefaultProfileClasses}.
*
* @param extra false for starting profiles, true for additional ones
* @return array of hub profiles to use by default
*/
public static HubProfile[] createDefaultProfiles( boolean extra ) {
Class[] clazzes = getDefaultProfileClasses( extra );
List hubProfileList = new ArrayList();
for ( int ip = 0; ip < clazzes.length; ip++ ) {
Class clazz = clazzes[ ip ];
try {
hubProfileList.add( (HubProfile) clazz.newInstance() );
}
catch ( ClassCastException e ) {
logger_.warning( "No hub profile " + clazz.getName()
+ " - not a " + HubProfile.class.getName() );
}
catch ( InstantiationException e ) {
logger_.warning( "No hub profile " + clazz.getName()
+ " - failed to instantiate (" + e + ")" );
}
catch ( IllegalAccessException e ) {
logger_.warning( "No hub profile " + clazz.getName()
+ " - inaccessible constructor (" + e + ")" );
}
catch ( ExceptionInInitializerError e ) {
logger_.warning( "No hub profile " + clazz.getName()
+ " - construction error"
+ " (" + e.getCause() + ")" );
}
}
return (HubProfile[]) hubProfileList.toArray( new HubProfile[ 0 ] );
}
/**
* Starts a SAMP hub with given sets of profiles.
* The returned hub is running.
*
* profiles
argument gives the profiles which will
* be started initially, and the extraProfiles
argument
* lists more that can be started under user control later.
* If either or both list is given as null, suitable defaults will be used.
*
* runHub(hubMode,null,null)
.
*
* @param hubMode hub mode
* @return running hub
* @see #runHub(HubServiceMode,HubProfile[],HubProfile[])
*/
public static Hub runHub( HubServiceMode hubMode ) throws IOException {
return runHub( hubMode, null, null );
}
/**
* Attempts to start a hub in a new JVM with a given set
* of profiles. The resulting hub can therefore outlast the
* lifetime of the current application.
* Because of the OS interaction required, it's hard to make this
* bulletproof, and it may fail without an exception, but we do our best.
*
* profileClasses
and
* extraProfileClasses
arguments must implement
* {@link HubProfile} and must have a no-arg constructor.
* If null is given in either case suitable defaults, taken from the
* current JVM, are used.
*
* @param hubMode hub mode
* @param profileClasses hub profile classes to start on hub startup
* @param extraProfileClasses hub profile classes which may be started
* later under user control
* @see #checkExternalHubAvailability
*/
public static void runExternalHub( HubServiceMode hubMode,
Class[] profileClasses,
Class[] extraProfileClasses )
throws IOException {
String classpath = System.getProperty( "java.class.path" );
if ( classpath == null || classpath.trim().length() == 0 ) {
throw new IOException( "No classpath available - JNLP context?" );
}
File javaHome = new File( System.getProperty( "java.home" ) );
File javaExec = new File( new File( javaHome, "bin" ), "java" );
String javacmd = ( javaExec.exists() && ! javaExec.isDirectory() )
? javaExec.toString()
: "java";
String[] propagateProps = new String[] {
XmlRpcKit.IMPL_PROP,
UtilServer.PORT_PROP,
SampUtils.LOCALHOST_PROP,
HUBPROFILES_PROP,
EXTRAHUBPROFILES_PROP,
"java.awt.Window.locationByPlatform",
};
List argList = new ArrayList();
argList.add( javacmd );
for ( int ip = 0; ip < propagateProps.length; ip++ ) {
String propName = propagateProps[ ip ];
String propVal = System.getProperty( propName );
if ( propVal != null ) {
argList.add( "-D" + propName + "=" + propVal );
}
}
argList.add( "-classpath" );
argList.add( classpath );
argList.add( Hub.class.getName() );
argList.add( "-mode" );
argList.add( hubMode.toString() );
if ( profileClasses != null ) {
argList.add( "-profiles" );
StringBuffer profArg = new StringBuffer();
for ( int ip = 0; ip < profileClasses.length; ip++ ) {
if ( ip > 0 ) {
profArg.append( ',' );
}
profArg.append( profileClasses[ ip ].getName() );
}
argList.add( profArg.toString() );
}
if ( extraProfileClasses != null ) {
argList.add( "-extraprofiles" );
StringBuffer eprofArg = new StringBuffer();
for ( int ip = 0; ip < profileClasses.length; ip++ ) {
if ( ip > 0 ) {
eprofArg.append( ',' );
}
eprofArg.append( extraProfileClasses[ ip ].getName() );
}
argList.add( eprofArg.toString() );
}
String[] args = (String[]) argList.toArray( new String[ 0 ] );
StringBuffer cmdbuf = new StringBuffer();
for ( int iarg = 0; iarg < args.length; iarg++ ) {
if ( iarg > 0 ) {
cmdbuf.append( ' ' );
}
cmdbuf.append( args[ iarg ] );
}
logger_.info( "Starting external hub" );
logger_.info( cmdbuf.toString() );
execBackground( args );
}
/**
* Attempts to run a hub in a new JVM with a default set of profiles.
* The default set is taken from that in this JVM.
* This convenience method invokes
* runExternalHub(hubMode,null,null)
.
*
* @param hubMode hub mode
* @see #runExternalHub(HubServiceMode,java.lang.Class[],java.lang.Class[])
*/
public static void runExternalHub( HubServiceMode hubMode )
throws IOException {
runExternalHub( hubMode, null, null );
}
/**
* Returns an array of all the instances of this class which are
* currently running.
*
* @return running hubs
*/
public static Hub[] getRunningHubs() {
List list;
synchronized ( hubList_ ) {
list = new ArrayList( hubList_.keySet() );
}
for ( Iterator it = list.iterator(); it.hasNext(); ) {
Hub hub = (Hub) it.next();
if ( ! hub.getHubService().isHubRunning() ) {
it.remove();
}
}
return (Hub[]) list.toArray( new Hub[ 0 ] );
}
/**
* Attempts to determine whether an external hub can be started using
* {@link #runExternalHub runExternalHub}.
* If it can be determined that such an
* attempt would fail, this method will throw an exception with
* an informative message. This method succeeding is not a guarantee
* that an external hub can be started successfullly.
* The behaviour of this method is not expected to change over the
* lifetime of a given JVM.
*/
public static void checkExternalHubAvailability() throws IOException {
String classpath = System.getProperty( "java.class.path" );
if ( classpath == null || classpath.trim().length() == 0 ) {
throw new IOException( "No classpath available - JNLP context?" );
}
if ( System.getProperty( "jnlpx.jvm" ) != null ) {
throw new IOException( "Running under WebStart"
+ " - external hub not likely to work" );
}
}
/**
* Main method, which allows configuration of which profiles will run
* and configuration of those individual profiles.
* Use the -h
flag for usage.
*/
public static void main( String[] args ) {
try {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
// Explicit exit on error may be necessary to kill Swing.
catch ( Throwable e ) {
e.printStackTrace();
System.exit( 2 );
}
}
/**
* Invoked by main.
* In case of a usage error, it returns a non-zero value, but does not
* call System.exit.
*
* @param args command-line argument array
* @return non-zero for error completion
*/
public static int runMain( String[] args ) throws IOException {
HubProfileFactory[] knownProfileFactories =
getKnownHubProfileFactories();
// Assemble usage message.
StringBuffer pbuf = new StringBuffer();
for ( int ip = 0; ip < knownProfileFactories.length; ip++ ) {
pbuf.append( knownProfileFactories[ ip ].getName() )
.append( '|' );
}
pbuf.append( "Runtime.exec
can block the process
* until its output is consumed.
*
* @param cmdarray array containing the command to call and its args
*/
private static void execBackground( String[] cmdarray ) throws IOException {
Process process = Runtime.getRuntime().exec( cmdarray );
discardBytes( process.getInputStream() );
discardBytes( process.getErrorStream() );
}
/**
* Ensures that any bytes from a given input stream are discarded.
*
* @param in input stream
*/
private static void discardBytes( final InputStream in ) {
Thread eater = new Thread( "StreamEater" ) {
public void run() {
try {
while ( in.read() >= 0 ) {}
in.close();
}
catch ( IOException e ) {
}
}
};
eater.setDaemon( true );
eater.start();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubCallableClient.java 0000664 0000000 0000000 00000005741 13564500043 0026126 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.AbstractMessageHandler;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* CallableClient implementation used by the hub client.
* This isn't exactly essential, but it enables the hub client
* (the client which represents the hub itself) to subscribe to some MTypes.
* Possibly useful for testing purposes etc.
*
* @author Mark Taylor
* @since 28 Jan 2011
*/
class HubCallableClient implements CallableClient {
private final HubConnection connection_;
private final AbstractMessageHandler[] handlers_;
/**
* Constructs a HubCallableClient with a given set of handlers.
*
* @param connection connection to hub service
* @param handlers array of message handlers
*/
public HubCallableClient( HubConnection connection,
AbstractMessageHandler[] handlers ) {
connection_ = connection;
handlers_ = handlers;
}
public void receiveCall( String senderId, String msgId, Message msg )
throws SampException {
msg.check();
getHandler( msg.getMType() )
.receiveCall( connection_, senderId, msgId, msg );
}
public void receiveNotification( String senderId, Message msg )
throws SampException {
msg.check();
getHandler( msg.getMType() )
.receiveNotification( connection_, senderId, msg );
}
public void receiveResponse( String responderId, String msgTag,
Response response ) throws SampException {
}
/**
* Returns the subscriptions corresponding to the messages that this
* receiver can deal with.
*
* @return subscriptions list
*/
public Subscriptions getSubscriptions() {
Subscriptions subs = new Subscriptions();
for ( int i = 0; i < handlers_.length; i++ ) {
subs.putAll( handlers_[ i ].getSubscriptions() );
}
return subs;
}
/**
* Returns a handler owned by this callable client which can handle
* a given MType. If more than one applies, the first one encountered
* is returned.
*
* @param mtype MType to handle
* @return handler for mtype
* @throws SampException if no suitable handler exists
*/
private AbstractMessageHandler getHandler( String mtype )
throws SampException {
for ( int i = 0; i < handlers_.length; i++ ) {
AbstractMessageHandler handler = handlers_[ i ];
if ( Subscriptions.asSubscriptions( handler.getSubscriptions() )
.isSubscribed( mtype ) ) {
return handler;
}
}
throw new SampException( "Not subscribed to " + mtype );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubClient.java 0000664 0000000 0000000 00000010711 13564500043 0024477 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.Map;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.SampException;
/**
* Represents a client registered with a hub.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class HubClient implements Client {
private final String publicId_;
private final ProfileToken profileToken_;
private volatile Subscriptions subscriptions_;
private volatile Metadata metadata_;
private volatile CallableClient callable_;
/**
* Constructor.
*
* @param publicId client public ID
* @param profileToken identifier for the source of the hub connection
*/
public HubClient( String publicId, ProfileToken profileToken ) {
publicId_ = publicId;
profileToken_ = profileToken;
subscriptions_ = new Subscriptions();
metadata_ = new Metadata();
callable_ = new NoCallableClient();
}
public String getId() {
return publicId_;
}
public Metadata getMetadata() {
return metadata_;
}
public Subscriptions getSubscriptions() {
return subscriptions_;
}
/**
* Returns a token identifying the source of this client's connection
* to the hub.
*
* @return profile token
*/
public ProfileToken getProfileToken() {
return profileToken_;
}
/**
* Sets this client's metadata map.
*
* @param meta metadata map
*/
public void setMetadata( Map meta ) {
metadata_ = new Metadata( meta );
}
/**
* Sets this client's subscriptions list.
*
* @param subs subscriptions map
*/
public void setSubscriptions( Map subs ) {
subscriptions_ = Subscriptions.asSubscriptions( subs );
}
/**
* Indicates whether this client is subscribed to a given MType.
*
* @param mtype MType
* @return true iff subscribed to MType
*/
public boolean isSubscribed( String mtype ) {
return isCallable() && subscriptions_.isSubscribed( mtype );
}
/**
* Returns the subscription information for a given MType for this client.
*
* @param mtype MType
* @return subscriptions map value for key mtype
,
* or null if not subscribed
*/
public Map getSubscription( String mtype ) {
return isCallable() ? subscriptions_.getSubscription( mtype )
: null;
}
/**
* Sets the callable object which allows this client to receive
* callbacks. If null is used, a no-op callable object is installed.
*
* @param callable new callable interface, or null
*/
public void setCallable( CallableClient callable ) {
callable_ = callable == null ? new NoCallableClient() : callable;
}
/**
* Returns the callable object which allows this client to receive
* callbacks. It is never null.
*
* @return callable object
*/
public CallableClient getCallable() {
return callable_;
}
/**
* Indicates whether this client is callable.
*
* @return true iff this client has a non-useless callback handler
* installed
*/
public boolean isCallable() {
return ! ( callable_ instanceof NoCallableClient );
}
public String toString() {
return SampUtils.toString( this );
}
/**
* No-op callback handler implementation.
* Any attempt to call its methods results in an exception.
*/
private class NoCallableClient implements CallableClient {
public void receiveNotification( String senderId, Message message )
throws SampException {
refuse();
}
public void receiveCall( String senderId, String msgId,
Message message )
throws SampException {
refuse();
}
public void receiveResponse( String responderId, String msgId,
Response response )
throws SampException {
refuse();
}
private void refuse() throws SampException {
throw new SampException( "Client " + getId() + " is not callable" );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubProfile.java 0000664 0000000 0000000 00000003224 13564500043 0024662 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.io.IOException;
import java.util.List;
import org.astrogrid.samp.client.ClientProfile;
/**
* Defines a hub profile.
* This profile allows registration and deregistration of clients to
* a given provider of hub connections, using some profile-specific
* transport and authentication arrangements.
* Multiple profiles may be attached to a single connection supplier
* at any time, and may be started and stopped independently of each other.
* The connection supplier is typically a hub service running in the same
* JVM, but may also be a client-side connection to a hub.
* A profile should be able to undergo multiple start/stop cycles.
*
* @author Mark Taylor
* @since 31 Jan 2011
*/
public interface HubProfile extends ProfileToken {
/**
* Starts this profile's activity allowing access to a given supplier of
* hub connections.
*
* @param profile object which can provide hub connections
*/
void start( ClientProfile profile ) throws IOException;
/**
* Indicates whether this profile is currently running.
*
* @return true iff profile is running
*/
boolean isRunning();
/**
* Ends this profile's activity on behalf of the hub.
* Any resources associated with the profile should be released.
* This does not include messaging registered clients about profile
* termination; that should be taken care of by the user of this profile.
*/
void stop() throws IOException;
/**
* Returns the name of this profile.
*
* @return profile name, usually one word
*/
public String getProfileName();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubProfileFactory.java 0000664 0000000 0000000 00000003562 13564500043 0026217 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.io.IOException;
import java.util.List;
/**
* Factory to produce hub profiles of a particular type.
* Used with the command-line invocation of the hub.
*
* @author Mark Taylor
* @since 31 Jan 2011
*/
public interface HubProfileFactory {
/**
* Returns the name used to identify this profile.
*
* @return short name
*/
String getName();
/**
* Returns an array of strings, each describing one command-line flag
* which will be consumed by the createProfile
method.
*
* @return array of plain-text strings suitable for use as part of
* a usage message
*/
String[] getFlagsUsage();
/**
* Creates a HubProfile perhaps configured using a supplied list
* of flags. Any flags which match those described by the
* {@link #getFlagsUsage} command are used for configuration of the
* returned hub, and must be removed from the flagList
list.
* Unrecognised flags should be ignored and left in the list.
* Flags which are recognised but badly formed should raise a
* RuntimeException with a helpful message.
*
* @param flagList mutable list of Strings giving command-ilne flags,
* some of which may be intended for configuring a profile
* @return new profile
*/
HubProfile createHubProfile( List flagList ) throws IOException;
/**
* Returns a HubProfile subclass with a no-arg constructor which,
* when invoked, will produce a basic instance of the HubProfile
* represented by this factory. The instance thus produced will
* typically be similar to that produced by invoking
* {@link #createHubProfile} with an empty flag list.
*
* @return HubProfile subclass with a public no-arg constructor
*/
Class getHubProfileClass();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubService.java 0000664 0000000 0000000 00000004233 13564500043 0024663 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* Interface defining the work that the hub has to do.
* This is independent of profile or transport, and just concerns
* keeping track of clients and routing messages between them.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public interface HubService {
/**
* Begin operation. The {@link #register} method should not be
* called until the hub has been started.
*/
void start();
/**
* Creates a new connection to this hub service, thereby initiating
* a new registered client.
*
* samp.hub.event.*
notifications at the appropriate times.
*
* HubConnection
methods are declared to
* throw SampException
, however, implementations may
* throw unchecked exceptions if that is more convenient;
* users of the connection should be prepared to catch these if
* they occur.
*
* @param profileToken identifier for the profile acting as gatekeeper
* for this connection
* @return new hub connection representing registration of a new client
*/
HubConnection register( ProfileToken profileToken ) throws SampException;
/**
* Forcibly terminates any connections created by a previous call of
* {@link #register}
* with a particular profileToken
.
* Any necessary hub events will be sent.
*
* @param profileToken previous argument to register
*/
void disconnectAll( ProfileToken profileToken );
/**
* Indicates whether this hub service is currently open for operations.
*
* @return true iff called between {@link #start} and {@link #shutdown}
*/
boolean isHubRunning();
/**
* Tidies up any resources owned by this object.
* Should be called when no longer required.
*/
void shutdown();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubServiceMode.java 0000664 0000000 0000000 00000067620 13564500043 0025501 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.awt.AWTException;
import java.awt.CheckboxMenuItem;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.gui.ErrorDialog;
import org.astrogrid.samp.gui.GuiHubService;
import org.astrogrid.samp.gui.MessageTrackerHubService;
import org.astrogrid.samp.gui.SysTray;
/**
* Specifies a particular hub implementation for use with {@link Hub}.
*
* @author Mark Taylor
* @since 20 Nov 2008
*/
public abstract class HubServiceMode {
// This class looks like an enumeration-type class to external users.
// It is actually a HubService factory.
private final String name_;
private final boolean isDaemon_;
private static final Logger logger_ =
Logger.getLogger( HubServiceMode.class.getName() );
/** Hub mode with no GUI representation of hub operations. */
public static final HubServiceMode NO_GUI;
/** Hub mode with a GUI representation of connected clients. */
public static final HubServiceMode CLIENT_GUI;
/** Hub mode with a GUI representation of clients and messages. */
public static HubServiceMode MESSAGE_GUI;
/** Hub Mode which piggy-backs on an existing hub using
* the default client profile. */
public static HubServiceMode FACADE;
/** Array of available hub modes. */
private static final HubServiceMode[] KNOWN_MODES = new HubServiceMode[] {
NO_GUI = createBasicHubMode( "no-gui" ),
CLIENT_GUI = createGuiHubMode( "client-gui" ),
MESSAGE_GUI = createMessageTrackerHubMode( "msg-gui" ),
FACADE = createFacadeHubMode( "facade" ),
};
/**
* Constructor.
*
* @param name mode name
* @param isDaemon true if the hub will start only daemon threads
*/
HubServiceMode( String name, boolean isDaemon ) {
name_ = name;
isDaemon_ = isDaemon;
}
/**
* Creates and returns a new hub service object.
*
* @param random random number generator
* @param profiles hub profiles
* @param runners 1-element array of Hubs - this should be
* populated with the runner once it has been constructed
* @return object containing the hub service and possibly a window
*/
abstract ServiceGui createHubService( Random random, HubProfile[] profiles,
Hub[] runners );
/**
* Indicates whether the hub service will start only daemon threads.
* If it returns true, the caller may need to make sure that the
* JVM doesn't stop too early.
*
* @return true iff no non-daemon threads will be started by the service
*/
boolean isDaemon() {
return isDaemon_;
}
/**
* Returns this mode's name.
*
* @return mode name
*/
String getName() {
return name_;
}
public String toString() {
return name_;
}
/**
* Returns one of the known modes which has a name as given.
*
* @param name mode name (case-insensitive)
* @return mode with given name, or null if none known
*/
public static HubServiceMode getModeFromName( String name ) {
HubServiceMode[] modes = KNOWN_MODES;
for ( int im = 0; im < modes.length; im++ ) {
HubServiceMode mode = modes[ im ];
if ( mode.name_.equalsIgnoreCase( name ) ) {
return mode;
}
}
return null;
}
/**
* Returns an array of the hub modes which can actually be used.
*
* @return available mode list
*/
public static HubServiceMode[] getAvailableModes() {
List modeList = new ArrayList();
for ( int i = 0; i < KNOWN_MODES.length; i++ ) {
HubServiceMode mode = KNOWN_MODES[ i ];
if ( ! ( mode instanceof BrokenHubMode ) ) {
modeList.add( mode );
}
}
return (HubServiceMode[]) modeList.toArray( new HubServiceMode[ 0 ] );
}
/**
* Used to perform common configuration of hub display windows
* for GUI-type hub modes.
*
* @param frame hub window
* @param profiles profiles to run for hub
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
* @param hubService object providing hub services
* @return object which should be shutdown when the hub stops running
*/
private static Tidier configureHubWindow( JFrame frame,
HubProfile[] profiles,
Hub[] runners,
GuiHubService hubService ) {
SysTray sysTray = SysTray.getInstance();
if ( sysTray.isSupported() ) {
try {
SysTrayWindowConfig winConfig =
new SysTrayWindowConfig( frame, profiles, runners,
hubService, sysTray );
winConfig.configureWindow();
winConfig.configureSysTray();
logger_.info( "Hub started in system tray" );
return winConfig;
}
catch ( AWTException e ) {
logger_.warning( "Failed to install in system tray: " + e );
BasicWindowConfig winConfig =
new BasicWindowConfig( frame, profiles, runners,
hubService );
winConfig.configureWindow();
return winConfig;
}
}
else {
logger_.info( "System tray not supported: displaying hub window" );
BasicWindowConfig winConfig =
new BasicWindowConfig( frame, profiles, runners, hubService );
winConfig.configureWindow();
return winConfig;
}
}
/**
* Constructs a mode for BasicHubService.
*
* @param name mode name
* @return non-gui mode
*/
private static HubServiceMode createBasicHubMode( String name ) {
try {
return new HubServiceMode( name, true ) {
ServiceGui createHubService( Random random,
HubProfile[] profiles,
Hub[] runners ) {
ServiceGui serviceGui = new ServiceGui();
serviceGui.service_ = new BasicHubService( random );
return serviceGui;
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* Constructs a mode for GuiHubService.
*
* @return mode without message tracking
*/
private static HubServiceMode createGuiHubMode( String name ) {
try {
/* Check GuiHubService class is present; if GUI classes are
* missing in a stripped-down installation find out now
* (mode creation time) rather than service creation time. */
GuiHubService.class.getName();
/* Create and return the service. */
return new HubServiceMode( name, false ) {
ServiceGui createHubService( Random random,
final HubProfile[] profiles,
final Hub[] runners ) {
final ServiceGui serviceGui = new ServiceGui();
serviceGui.service_ = new GuiHubService( random ) {
volatile Runnable tidierCallback;
public void start() {
final GuiHubService service = this;
super.start();
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JFrame window = createHubWindow();
final Tidier tidier =
configureHubWindow( window, profiles,
runners, service );
tidierCallback = new Runnable() {
public void run() {
tidier.tidyGui();
}
};
serviceGui.window_ = window;
}
} );
}
public void shutdown() {
super.shutdown();
/* It is (apparently) necessary under some
* circumstances to call an existing Runnable here
* rather than creating a new one because of
* weird (buggy?) shutdown behaviour
* in the JNLP class loader (fails with
* IllegalStateException: zip file closed).
* Report and fix provided by Laurent Bourges. */
if ( tidierCallback != null ) {
SwingUtilities.invokeLater( tidierCallback );
};
}
};
return serviceGui;
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* Constructs a mode for MessageTrackerHubService.
*
* @return mode with message tracking
*/
private static HubServiceMode createMessageTrackerHubMode( String name ) {
try {
MessageTrackerHubService.class.getName();
return new HubServiceMode( name, false ) {
ServiceGui createHubService( Random random,
final HubProfile[] profiles,
final Hub[] runners ) {
final ServiceGui serviceGui = new ServiceGui();
serviceGui.service_ =
new MessageTrackerHubService( random ) {
Tidier tidier;
public void start() {
super.start();
final MessageTrackerHubService service = this;
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JFrame window = createHubWindow();
tidier = configureHubWindow( window,
profiles,
runners,
service );
serviceGui.window_ = window;
}
} );
}
public void shutdown() {
super.shutdown();
SwingUtilities.invokeLater( new Runnable() {
public void run() {
if ( tidier != null ) {
tidier.tidyGui();
}
}
} );
}
};
return serviceGui;
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* Constructs a mode for FacadeHubService.
*
* @return mode based on the default client profile
*/
private static HubServiceMode createFacadeHubMode( String name ) {
return new HubServiceMode( name, true ) {
ServiceGui createHubService( Random random, HubProfile[] profiles,
final Hub[] runners ) {
ServiceGui serviceGui = new ServiceGui();
serviceGui.service_ =
new FacadeHubService( DefaultClientProfile.getProfile() );
return serviceGui;
}
};
}
/**
* HubServiceMode implementation for modes which cannot be used because they
* rely on classes unavailable at runtime.
*/
private static class BrokenHubMode extends HubServiceMode {
private final Throwable error_;
/**
* Constructor.
*
* @param name mode name
* @param error error explaining why mode is unavailable for use
*/
BrokenHubMode( String name, Throwable error ) {
super( name, false );
error_ = error;
}
ServiceGui createHubService( Random random, HubProfile[] profiles,
Hub[] runners ) {
throw new RuntimeException( "Hub mode " + getName()
+ " unavailable", error_ );
}
}
/**
* Utility abstract class to define an object which can be tidied up
* on hub shutdown.
*/
private interface Tidier {
/**
* Performs any required tidying operations.
* May be assumed to be called on the AWT Event Dispatch Thread.
*/
void tidyGui();
}
/**
* Aggregates a HubService and an associated monitor/control window.
*/
static class ServiceGui {
private volatile HubService service_;
private JFrame window_;
/**
* Returns the hub service.
*
* @return hub service object
*/
public HubService getHubService() {
return service_;
}
/**
* Returns a monitor/control window for this service, if available.
*
* @return window, or null
*/
public JFrame getWindow() {
return window_;
}
}
/**
* Class to configure a window for use as a hub control.
*/
private static class BasicWindowConfig implements Tidier {
final JFrame frame_;
final Hub[] runners_;
final GuiHubService hubService_;
final ProfileToggler[] profileTogglers_;
final ConfigHubProfile[] configProfiles_;
final Action exitAct_;
/**
* Constructor.
*
* @param frame hub window
* @param profiles hub profiles to run
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
* @param hubService object providing hub services
*/
BasicWindowConfig( JFrame frame, HubProfile[] profiles,
final Hub[] runners, GuiHubService hubService ) {
frame_ = frame;
runners_ = runners;
hubService_ = hubService;
profileTogglers_ = new ProfileToggler[ profiles.length ];
List configProfileList = new ArrayList();
for ( int ip = 0; ip < profiles.length; ip++ ) {
HubProfile profile = profiles[ ip ];
profileTogglers_[ ip ] = new ProfileToggler( profile, runners );
if ( profile instanceof ConfigHubProfile ) {
configProfileList.add( (ConfigHubProfile) profile );
}
}
configProfiles_ =
(ConfigHubProfile[])
configProfileList.toArray( new ConfigHubProfile[ 0 ] );
exitAct_ = new AbstractAction( "Stop Hub" ) {
public void actionPerformed( ActionEvent evt ) {
if ( runners[ 0 ] != null ) {
runners[ 0 ].shutdown();
}
tidyGui();
}
};
exitAct_.putValue( Action.SHORT_DESCRIPTION,
"Shut down SAMP hub" );
exitAct_.putValue( Action.MNEMONIC_KEY,
new Integer( KeyEvent.VK_T ) );
}
/**
* Perform configuration of window.
*/
public void configureWindow() {
configureMenus();
frame_.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
frame_.setVisible( true );
frame_.addWindowListener( new WindowAdapter() {
public void windowClosed( WindowEvent evt ) {
Hub runner = runners_[ 0 ];
if ( runner != null ) {
runner.shutdown();
}
}
} );
}
/**
* Configures menus on the window. Invoked by configureWindow.
*/
protected void configureMenus() {
JMenuBar mbar = new JMenuBar();
JMenu fileMenu = new JMenu( "File" );
fileMenu.setMnemonic( KeyEvent.VK_F );
fileMenu.add( new JMenuItem( exitAct_ ) );
mbar.add( fileMenu );
JMenu[] serviceMenus = hubService_.createMenus();
for ( int im = 0; im < serviceMenus.length; im++ ) {
mbar.add( serviceMenus[ im ] );
}
JMenu profileMenu = new JMenu( "Profiles" );
profileMenu.setMnemonic( KeyEvent.VK_P );
for ( int ip = 0; ip < profileTogglers_.length; ip++ ) {
profileMenu.add( profileTogglers_[ ip ].createJMenuItem() );
}
// Add configuration menus - somewhat hacky, only really intended
// for Web Profile at present.
for ( int ic = 0; ic < configProfiles_.length; ic++ ) {
ConfigHubProfile configProfile = configProfiles_[ ic ];
JToggleButton.ToggleButtonModel[] configModels =
configProfile.getConfigModels();
JMenu configMenu =
new JMenu( configProfile.getProfileName()
+ " Profile Configuration" );
for ( int im = 0; im < configModels.length; im++ ) {
JToggleButton.ToggleButtonModel model = configModels[ im ];
JCheckBoxMenuItem menuItem =
new JCheckBoxMenuItem( model.toString() );
menuItem.setModel( model );
configMenu.add( menuItem );
}
profileMenu.add( configMenu );
}
mbar.add( profileMenu );
frame_.setJMenuBar( mbar );
}
public void tidyGui() {
if ( frame_.isDisplayable() ) {
frame_.dispose();
}
}
}
/**
* Takes care of hub display window configuration with system tray
* functionality.
*/
private static class SysTrayWindowConfig extends BasicWindowConfig {
private final SysTray sysTray_;
private final Action showAct_;
private final Action hideAct_;
private final MenuItem showItem_;
private final MenuItem hideItem_;
private final MenuItem exitItem_;
private final ActionListener iconListener_;
private Object trayIcon_;
/**
* Constructor.
*
* @param frame hub window
* @param profiles hub profiles to run
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
* @param hubService object providing hub services
* @param sysTray system tray facade object
*/
SysTrayWindowConfig( JFrame frame, HubProfile[] profiles, Hub[] runners,
GuiHubService hubService, SysTray sysTray ) {
super( frame, profiles, runners, hubService );
sysTray_ = sysTray;
showAct_ = new AbstractAction( "Show Hub Window" ) {
public void actionPerformed( ActionEvent evt ) {
setWindowVisible( true );
}
};
hideAct_ = new AbstractAction( "Hide Hub Window" ) {
public void actionPerformed( ActionEvent evt ) {
setWindowVisible( false );
}
};
showItem_ = toMenuItem( showAct_ );
hideItem_ = toMenuItem( hideAct_ );
exitItem_ = toMenuItem( exitAct_ );
iconListener_ = showAct_;
}
protected void configureMenus() {
super.configureMenus();
frame_.getJMenuBar().getMenu( 0 ).add( new JMenuItem( hideAct_ ) );
}
public void configureWindow() {
configureMenus();
frame_.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE );
// Arrange that a manual window close will set the action states
// correctly.
frame_.addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent evt ) {
showAct_.setEnabled( true );
hideAct_.setEnabled( false );
}
} );
hideAct_.actionPerformed( null );
}
/**
* Performs configuration.
*/
public void configureSysTray() throws AWTException {
Image im = Toolkit.getDefaultToolkit()
.createImage( Client.class
.getResource( "images/hub.png" ) );
String tooltip = "SAMP Hub";
PopupMenu popup = new PopupMenu();
Menu profileMenu = new Menu( "Profiles" );
for ( int ip = 0; ip < profileTogglers_.length; ip++ ) {
profileMenu.add( profileTogglers_[ ip ].createMenuItem() );
}
popup.add( profileMenu );
popup.add( showItem_ );
popup.add( hideItem_ );
popup.add( exitItem_ );
trayIcon_ = sysTray_.addIcon( im, tooltip, popup, iconListener_ );
}
public void tidyGui() {
super.tidyGui();
try {
sysTray_.removeIcon( trayIcon_ );
}
catch ( AWTException e ) {
logger_.warning( "Can't remove hub system tray icon: " + e );
}
}
/**
* Sets visibility for the hub control window, adjusting actions
* as appropriate.
*
* @param isVis true for visible, false for invisible
*/
private void setWindowVisible( boolean isVis ) {
frame_.setVisible( isVis );
showAct_.setEnabled( ! isVis );
hideAct_.setEnabled( isVis );
showItem_.setEnabled( ! isVis );
hideItem_.setEnabled( isVis );
}
/**
* Turns an action into an AWT menu item.
*
* @param act action
* @return MenuItem facade
*/
private MenuItem toMenuItem( Action act ) {
MenuItem item =
new MenuItem( (String) act.getValue( Action.NAME ) );
item.addActionListener( act );
return item;
}
}
/**
* Manages a toggle button for starting/stopping profiles.
* This object can supply both Swing JMenuItems and AWT MenuItems
* with effectively the same model (which is quite hard work).
*/
private static class ProfileToggler {
final HubProfile profile_;
final Hub[] runners_;
final String title_;
final JToggleButton.ToggleButtonModel toggleModel_;
final List menuItemList_;
/**
* Constructor.
*
* @param profile profile to operate on
* @param runners one-element array containing hub
*/
ProfileToggler( HubProfile profile, Hub[] runners ) {
profile_ = profile;
runners_ = runners;
title_ = profile.getProfileName() + " Profile";
menuItemList_ = new ArrayList();
toggleModel_ = new JToggleButton.ToggleButtonModel() {
public boolean isSelected() {
return profile_.isRunning();
}
public void setSelected( boolean on ) {
Hub hub = runners_[ 0 ];
if ( hub != null ) {
if ( on && ! profile_.isRunning() ) {
try {
hub.startProfile( profile_ );
super.setSelected( on );
}
catch ( IOException e ) {
ErrorDialog
.showError( null, title_ + " Start Error",
"Error starting " + title_, e );
return;
}
}
else if ( ! on && profile_.isRunning() ) {
hub.stopProfile( profile_ );
}
}
super.setSelected( on );
}
};
toggleModel_.addChangeListener( new ChangeListener() {
public void stateChanged( ChangeEvent evt ) {
updateMenuItems();
}
} );
}
/**
* Returns a new Swing JMenuItem for start/stop toggle.
*
* @return menu item
*/
public JMenuItem createJMenuItem() {
JCheckBoxMenuItem item = new JCheckBoxMenuItem( title_ );
item.setToolTipText( "Start or stop the " + title_ );
char chr = Character
.toUpperCase( profile_.getProfileName().charAt( 0 ) );
if ( chr >= 'A' && chr <= 'Z' ) {
item.setMnemonic( (int) chr );
}
item.setModel( toggleModel_ );
return item;
}
/**
* Returns a new AWT MenuItem for start/stop toggle.
*
* @return menu item
*/
public MenuItem createMenuItem() {
final CheckboxMenuItem item = new CheckboxMenuItem( title_ );
item.addItemListener( new ItemListener() {
public void itemStateChanged( ItemEvent evt ) {
boolean on = item.getState();
toggleModel_.setSelected( on );
if ( toggleModel_.isSelected() != on ) {
item.setState( toggleModel_.isSelected() );
}
}
} );
item.setState( toggleModel_.isSelected() );
menuItemList_.add( item );
return item;
}
/**
* Updates all dispatched menu items to the current state.
*/
private void updateMenuItems() {
for ( Iterator it = menuItemList_.iterator(); it.hasNext(); ) {
CheckboxMenuItem item = (CheckboxMenuItem) it.next();
boolean on = toggleModel_.isSelected();
if ( item.getState() != on ) {
item.setState( on );
}
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/KeyGenerator.java 0000664 0000000 0000000 00000003505 13564500043 0025224 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.security.SecureRandom;
import java.util.Random;
/**
* Object which can generate a sequence of private keys.
* The values returned by the next() method should in general not be
* easy to guess.
*
* @author Mark Taylor
* @since 26 Oct 2010
*/
public class KeyGenerator {
private final String prefix_;
private final int nchar_;
private final Random random_;
private int iseq_;
private static final char SEQ_DELIM = '_';
/**
* Constructor.
*
* @param prefix prefix prepended to all generated keys
* @param nchar number of characters in generated keys
* @param random random number generator
*/
public KeyGenerator( String prefix, int nchar, Random random ) {
prefix_ = prefix;
nchar_ = nchar;
random_ = random;
}
/**
* Returns the next key in the sequence.
* Guaranteed different from any previous return value from this method.
*
* @return key string
*/
public synchronized String next() {
StringBuffer sbuf = new StringBuffer();
sbuf.append( prefix_ );
sbuf.append( Integer.toString( ++iseq_ ) );
sbuf.append( SEQ_DELIM );
for ( int i = 0; i < nchar_; i++ ) {
char c = (char) ( 'a' + (char) random_.nextInt( 'z' - 'a' ) );
assert c != SEQ_DELIM;
sbuf.append( c );
}
return sbuf.toString();
}
/**
* Returns a new, randomly seeded, Random object.
*
* @return random
*/
public static Random createRandom() {
byte[] seedBytes = new SecureRandom().generateSeed( 8 );
long seed = 0L;
for ( int i = 0; i < 8; i++ ) {
seed = ( seed << 8 ) | ( seedBytes[ i ] & 0xff );
}
return new Random( seed );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/LockWriter.java 0000664 0000000 0000000 00000007717 13564500043 0024723 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.astrogrid.samp.Platform;
/**
* Writes records to a SAMP Standard Profile hub lockfile.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class LockWriter {
private final OutputStream out_;
private static final byte[] linesep_ = getLineSeparator();
private static final String TOKEN_REGEX = "[a-zA-Z0-9\\-_\\.]+";
private static final Pattern TOKEN_PATTERN = Pattern.compile( TOKEN_REGEX );
/**
* Constructs a writer for writing to a given output stream.
*
* @param out output stream
*/
public LockWriter( OutputStream out ) {
out_ = out;
}
/**
* Writes all the assignments in a given map to the lockfile.
*
* @param map assignment set to output
*/
public void writeAssignments( Map map ) throws IOException {
for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
writeAssignment( (String) entry.getKey(),
(String) entry.getValue() );
}
}
/**
* Writes a single assignment to the lockfile.
*
* @param name assignment key
* @param value assignment value
*/
public void writeAssignment( String name, String value )
throws IOException {
if ( TOKEN_PATTERN.matcher( name ).matches() ) {
writeLine( name + "=" + value );
}
else {
throw new IllegalArgumentException( "Bad name sequence: " + name
+ " !~" + TOKEN_REGEX );
}
}
/**
* Writes a comment line to the lockfile.
*
* @param comment comment text
*/
public void writeComment( String comment ) throws IOException {
writeLine( "# " + comment );
}
/**
* Writes a blank line to the lockfile.
*/
public void writeLine() throws IOException {
out_.write( linesep_ );
}
/**
* Writes a line of text to the lockfile, terminated with a line-end.
*
* @param line line to write
*/
protected void writeLine( String line ) throws IOException {
byte[] bbuf = new byte[ line.length() ];
for ( int i = 0; i < line.length(); i++ ) {
int c = line.charAt( i );
if ( c < 0x20 || c > 0x7f ) {
throw new IllegalArgumentException( "Illegal character 0x" +
Integer.toHexString( c ) );
}
bbuf[ i ] = (byte) c;
}
out_.write( bbuf );
writeLine();
}
/**
* Closes the output stream.
* May be required to ensure that all data is written.
*/
public void close() throws IOException {
out_.close();
}
/**
* Sets the permissions on a given file suitably for a SAMP Standard
* Profile lockfile. This means that nobody apart from the file's
* owner can read it.
*
* @param file file to set access permissions on
*/
public static void setLockPermissions( File file ) throws IOException {
Platform.getPlatform().setPrivateRead( file );
}
/**
* Returns the platform-specific line separator sequence as an array of
* bytes.
*
* @return line separator sequence
*/
private static final byte[] getLineSeparator() {
String linesep = System.getProperty( "line.separator" );
if ( linesep.matches( "[\\r\\n]+" ) ) {
byte[] lsbuf = new byte[ linesep.length() ];
for ( int i = 0; i < linesep.length(); i++ ) {
lsbuf[ i ] = (byte) linesep.charAt( i );
}
return lsbuf;
}
else {
return new byte[] { (byte) '\n' };
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/MessageRestriction.java 0000664 0000000 0000000 00000001562 13564500043 0026440 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.Map;
/**
* Specifies restrictions on the message types that may be sent in
* a particular context.
* In general if null is used in place of a MessageRestriction object,
* the understanding is that no restrictions apply.
*
* @author Mark Taylor
* @since 23 Nov 2011
*/
public interface MessageRestriction {
/**
* Indicates whether a message covered by a given MType subscription
* may be sent.
*
* @param mtype the MType string to be sent
* @param subsInfo the annotation map corresponding to the MType
* subscription (the value from the Subscriptions map
* corresponding to the mtype
key)
* @return true if the message may be sent, false if it is blocked
*/
boolean permitSend( String mtype, Map subsInfo );
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/MetaQueryMessageHandler.java 0000664 0000000 0000000 00000003056 13564500043 0027345 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.client.AbstractMessageHandler;
import org.astrogrid.samp.client.HubConnection;
/**
* Implements MType for querying registered clients by metadata item.
*
* @author Mark Taylor
* @since 21 Nov 2011
*/
class MetaQueryMessageHandler extends AbstractMessageHandler {
private final ClientSet clientSet_;
private static final String BASE_MTYPE = "query.by-meta";
/**
* Constructor.
*
* @param clientSet hub client set object
*/
public MetaQueryMessageHandler( ClientSet clientSet ) {
super( new String[] { "samp." + BASE_MTYPE, "x-samp." + BASE_MTYPE } );
clientSet_ = clientSet;
}
public Map processCall( HubConnection conn, String senderId, Message msg ) {
String key = (String) msg.getRequiredParam( "key" );
String value = (String) msg.getRequiredParam( "value" );
HubClient[] clients = clientSet_.getClients();
List foundList = new ArrayList();
for ( int ic = 0; ic < clients.length; ic++ ) {
HubClient client = clients[ ic ];
Metadata meta = client.getMetadata();
if ( meta != null && value.equals( meta.get( key ) ) ) {
foundList.add( client.getId() );
}
}
Map result = new HashMap();
result.put( "ids", foundList );
return result;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/PingMessageHandler.java 0000664 0000000 0000000 00000001140 13564500043 0026316 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.HashMap;
import java.util.Map;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.client.AbstractMessageHandler;
import org.astrogrid.samp.client.HubConnection;
/**
* Implements samp.app.ping MType.
*
* @author Mark Taylor
* @since 21 Nov 2011
*/
class PingMessageHandler extends AbstractMessageHandler {
/**
* Constructor.
*/
public PingMessageHandler() {
super( "samp.app.ping" );
}
public Map processCall( HubConnection conn, String senderId, Message msg ) {
return new HashMap();
}
};
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ProfileToken.java 0000664 0000000 0000000 00000001366 13564500043 0025231 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
/**
* Marker interface that identifies a hub profile.
* Objects implementing this interface can be identified as the provider of
* a connection to the hub.
*
* @author Mark Taylor
* @since 20 Jul 2011
*/
public interface ProfileToken {
/**
* Returns the name by which this token is to be identified.
*
* @return profile identifier, usually one word
*/
String getProfileName();
/**
* Returns a MessageRestriction object which controls what messages
* may be sent by clients registering under ths profile.
* If null is returned, any messages may be sent.
*
* @return message restriction, or null
*/
MessageRestriction getMessageRestriction();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/WrapperHubConnection.java 0000664 0000000 0000000 00000005302 13564500043 0026721 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.hub;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* HubConnection implementation that delegates all calls to a base instance.
*
* @author Mark Taylor
* @since 3 Feb 2011
*/
class WrapperHubConnection implements HubConnection {
private final HubConnection base_;
/**
* Constructor.
*
* @param base hub connection to which all calls are delegated
*/
public WrapperHubConnection( HubConnection base ) {
base_ = base;
}
public RegInfo getRegInfo() {
return base_.getRegInfo();
}
public void setCallable( CallableClient client ) throws SampException {
base_.setCallable( client );
}
public void ping() throws SampException {
base_.ping();
}
public void unregister() throws SampException {
base_.unregister();
}
public void declareMetadata( Map meta ) throws SampException {
base_.declareMetadata( meta );
}
public Metadata getMetadata( String clientId ) throws SampException {
return base_.getMetadata( clientId );
}
public void declareSubscriptions( Map subs ) throws SampException {
base_.declareSubscriptions( subs );
}
public Subscriptions getSubscriptions( String clientId )
throws SampException {
return base_.getSubscriptions( clientId );
}
public String[] getRegisteredClients() throws SampException {
return base_.getRegisteredClients();
}
public Map getSubscribedClients( String mtype ) throws SampException {
return base_.getSubscribedClients( mtype );
}
public void notify( String recipientId, Map msg ) throws SampException {
base_.notify( recipientId, msg );
}
public List notifyAll( Map msg ) throws SampException {
return base_.notifyAll( msg );
}
public String call( String recipientId, String msgTag, Map msg )
throws SampException {
return base_.call( recipientId, msgTag, msg );
}
public Map callAll( String msgTag, Map msg ) throws SampException {
return base_.callAll( msgTag, msg );
}
public Response callAndWait( String recipientId, Map msg, int timeout )
throws SampException {
return base_.callAndWait( recipientId, msg, timeout );
}
public void reply( String msgId, Map response ) throws SampException {
base_.reply( msgId, response );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/package.html 0000664 0000000 0000000 00000000171 13564500043 0024237 0 ustar 00root root 0000000 0000000 conn
itself
*/
private void assertTestClients( HubConnection conn, String[] otherIds )
throws IOException {
// Call getRegisteredClients.
Set knownOtherIds =
new HashSet( Arrays.asList( conn.getRegisteredClients() ) );
// Remove from the list any clients which were already registered
// before this test instance started up.
for ( int ic = 0; ic < ignoreClients_.length; ic++ ) {
String id = ignoreClients_[ ic ].getId();
knownOtherIds.remove( ignoreClients_[ ic ].getId() );
}
// Assert that the (unordered) set retrieved is the same as that
// asked about.
assertEquals( knownOtherIds,
new HashSet( Arrays.asList( otherIds ) ) );
}
/**
* Generates an object with random content for transmission using SAMP.
* This may be a structure containing strings, lists and maps with
* any legal values as defined by the SAMP data encoding rules.
*
* @param level maximum level of nesting (how deeply lists/maps
* may appear within other lists/maps)
* @param ugly if true, any legal SAMP content will be used;
* if false, the returned object should be reasonably
* human-readable if printed (toString)
* @return random SAMP object
*/
public Object createRandomObject( int level, boolean ugly ) {
if ( level == 0 ) {
return createRandomString( ugly );
}
int type = random_.nextInt( 2 );
if ( type == 0 ) {
int nel = random_.nextInt( ugly ? 23 : 3 );
List list = new ArrayList( nel );
for ( int i = 0; i < nel; i++ ) {
list.add( createRandomObject( level - 1, ugly ) );
}
SampUtils.checkList( list );
return list;
}
else if ( type == 1 ) {
int nent = random_.nextInt( ugly ? 23 : 3 );
Map map = new HashMap( nent );
for ( int i = 0; i < nent; i++ ) {
map.put( createRandomString( ugly ),
createRandomObject( level - 1, ugly ) );
}
SampUtils.checkMap( map );
return map;
}
else {
throw new AssertionError();
}
}
/**
* Creates a new random string for transmission using SAMP.
* This may have any legal content according to the SAMP data encoding
* rules.
*
* @param ugly if true, any legal SAMP content will be used;
* if false, the returned object should be reasonably
* human-readable if printed (toString)
*/
public String createRandomString( boolean ugly ) {
int nchar = random_.nextInt( ugly ? 99 : 4 );
StringBuffer sbuf = new StringBuffer( nchar );
char[] chrs = ugly ? GENERAL_CHARS : ALPHA_CHARS;
for ( int i = 0; i < nchar; i++ ) {
sbuf.append( chrs[ random_.nextInt( chrs.length ) ] );
}
String str = sbuf.toString();
SampUtils.checkString( str );
return str;
}
/**
* Waits for a given number of milliseconds.
*
* @param millis number of milliseconds
*/
private static void delay( int millis ) {
Object lock = new Object();
try {
synchronized ( lock ) {
lock.wait( millis );
}
}
catch ( InterruptedException e ) {
throw new RuntimeException( "Interrupted", e );
}
}
/**
* Returns a character array containing each distinct alphanumeric
* character.
*
* @return array of alphanumeric characters
*/
private static char[] createAlphaCharacters() {
StringBuffer sbuf = new StringBuffer();
for ( char c = 'A'; c <= 'Z'; c++ ) {
sbuf.append( c );
}
for ( char c = '0'; c <= '9'; c++ ) {
sbuf.append( c );
}
return sbuf.toString().toCharArray();
}
/**
* Returns a character array containing every character which is legal
* for inclusion in a SAMP string
.
*
* @return array of string characters
*/
private static char[] createGeneralCharacters() {
StringBuffer sbuf = new StringBuffer();
sbuf.append( (char) 0x09 );
sbuf.append( (char) 0x0a );
// Character 0x0d is problematic. Although it is permissible to
// transmit this in an XML document, it can get transformed to
// 0x0a or (if adjacent to an existing 0x0a) elided.
// The correct thing to do probably would be to note in the standard
// that all bets are off when transmitting line end characters -
// but sending a line-end will probably end up as a line-end.
// However I can't be bothered to start up a new thread about this
// on the apps-samp list, so for the purposes of this test just
// avoid sending it.
// sbuf.append( (char) 0x0d );
for ( char c = 0x20; c <= 0x7f; c++ ) {
sbuf.append( c );
}
return sbuf.toString().toCharArray();
}
/**
* Main method. Tests a hub which is currently running.
*/
public static void main( String[] args ) throws IOException {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
/**
* Does the work for the main method.
* Use -help flag.
*/
public static int runMain( String[] args ) throws IOException {
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " )
.append( HubTester.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( "\n " )
.append( " [-gui]" )
.append( "\n" )
.toString();
List argList = new ArrayList( Arrays.asList( args ) );
boolean gui = false;
int verbAdjust = 0;
for ( Iterator it = argList.iterator(); it.hasNext(); ) {
String arg = (String) it.next();
if ( arg.equals( "-gui" ) ) {
it.remove();
gui = true;
}
else if ( arg.equals( "-nogui" ) ) {
it.remove();
gui = false;
}
else if ( arg.startsWith( "-v" ) ) {
it.remove();
verbAdjust--;
}
else if ( arg.startsWith( "+v" ) ) {
it.remove();
verbAdjust++;
}
else if ( arg.startsWith( "-h" ) ) {
it.remove();
System.out.println( usage );
return 0;
}
else {
it.remove();
System.err.println( usage );
return 1;
}
}
assert argList.isEmpty();
// Adjust logging in accordance with verboseness flags.
int logLevel = Level.WARNING.intValue() + 100 * verbAdjust;
Logger.getLogger( "org.astrogrid.samp" )
.setLevel( Level.parse( Integer.toString( logLevel ) ) );
// Get profile.
ClientProfile profile = DefaultClientProfile.getProfile();
// Set up GUI monitor if required.
JFrame frame;
if ( gui ) {
frame = new JFrame( "HubTester Monitor" );
frame.getContentPane().add( new HubMonitor( profile, true, 1 ) );
frame.pack();
frame.setVisible( true );
}
else {
frame = null;
}
new HubTester( profile ).run();
if ( frame != null ) {
frame.dispose();
}
return 0;
}
/**
* CallableClient implementation for testing.
*/
private static class TestCallableClient extends ReplyCollector
implements CallableClient {
private final HubConnection connection_;
private int pingCount_;
public static final Subscriptions SUBS = getSubscriptions();
/**
* Constructor.
*
* @param connection hub connection
*/
TestCallableClient( HubConnection connection ) {
super( connection );
connection_ = connection;
}
public void receiveNotification( String senderId, Message msg ) {
processCall( senderId, msg );
}
public void receiveCall( String senderId, String msgId, Message msg )
throws SampException {
// If the message contains a WAITMILLIS_KEY entry, interpret this
// as a number of milliseconds to wait before the response is
// sent back to the hub.
String swaitMillis = (String) msg.get( WAITMILLIS_KEY );
if ( swaitMillis != null ) {
int waitMillis = SampUtils.decodeInt( swaitMillis );
if ( waitMillis > 0 ) {
delay( waitMillis );
}
}
Response response;
// Process a FAIL_MTYPE message specially.
if ( msg.getMType().equals( FAIL_MTYPE ) ) {
Map errs = (Map) msg.getParam( ERROR_KEY );
if ( errs == null ) {
throw new IllegalArgumentException();
}
response = Response.createErrorResponse( new ErrInfo( errs ) );
}
// For other MTypes, pass them to the processCall method.
else {
try {
response =
Response
.createSuccessResponse( processCall( senderId, msg ) );
}
catch ( Throwable e ) {
response = Response.createErrorResponse( new ErrInfo( e ) );
}
}
// Insert the message ID into the response if requested to do so.
String msgIdQuery = (String) msg.get( MSGIDQUERY_KEY );
if ( msgIdQuery != null && SampUtils.decodeBoolean( msgIdQuery ) ) {
response.put( MSGIDQUERY_KEY, msgId );
}
response.check();
// Return the reply, whatever it is, to the hub.
connection_.reply( msgId, response );
}
/**
* Do the work of responding to a given SAMP message.
*
* @param senderId sender public ID
* @param msg message object
* @return content of the successful reply's samp.result entry
*/
private Map processCall( String senderId, Message msg ) {
String mtype = msg.getMType();
// Returns the samp.params entry as the samp.result entry.
if ( ECHO_MTYPE.equals( mtype ) ) {
return msg.getParams();
}
// Just bumps a counter and returns an empty samp.result
else if ( PING_MTYPE.equals( mtype ) ) {
synchronized ( this ) {
pingCount_++;
}
return new HashMap();
}
// Shouldn't happen.
else {
throw new TestException( "Unsubscribed MType? " + mtype );
}
}
/**
* Returns the subscriptions object for this client.
*
* @return subscriptions
*/
private static Subscriptions getSubscriptions() {
Subscriptions subs = new Subscriptions();
subs.addMType( ECHO_MTYPE );
subs.addMType( PING_MTYPE );
subs.addMType( FAIL_MTYPE );
subs.check();
return subs;
}
}
/**
* CallableClient implementation which watches hub.event messages
* concerning the registration and attributes of other clients.
*/
private static class ClientWatcher implements CallableClient {
private final HubConnection connection_;
private final Map clientMap_;
private Throwable error_;
/**
* Constructor.
*
* @param connection hub connection
*/
ClientWatcher( HubConnection connection ) {
connection_ = connection;
clientMap_ = Collections.synchronizedMap( new HashMap() );
}
/**
* Returns a WatchedClient object corresponding to a given client
* public ID. This will contain information about the hub event
* messages this watcher has received concerning that client up till
* now.
*
* @param id public id of a client which has been registered
* @return watchedClient object if any messages have been received
* about id
, otherwise null
*/
public WatchedClient getClient( String id ) {
return (WatchedClient) clientMap_.get( id );
}
/**
* Returns an error if any error has been thrown during processing
* of hub event messages.
*
* @return deferred throwable, or null in case of no problems
*/
public Throwable getError() {
return error_;
}
/**
* Returns the hub connection used by this client.
*
* @return hub connection
*/
public HubConnection getConnection() {
return connection_;
}
public void receiveCall( String senderId, String msgId, Message msg ) {
receiveNotification( senderId, msg );
Response response =
error_ == null
? Response.createSuccessResponse( new HashMap() )
: Response.createErrorResponse( new ErrInfo( "broken" ) );
try {
connection_.reply( msgId, response );
}
catch ( SampException e ) {
error_ = e;
}
}
public void receiveNotification( String senderId, Message msg ) {
if ( error_ == null ) {
try {
processMessage( senderId, msg );
}
catch ( Throwable e ) {
error_ = e;
}
}
}
private void processMessage( String senderId, Message msg )
throws IOException {
// Check the message actually comes from the hub.
assertEquals( senderId, connection_.getRegInfo().getHubId() );
String mtype = msg.getMType();
Map params = msg.getParams();
// Get (if necessary lazily creating) a WatchedClient object
// which this message concerns.
String id = (String) msg.getParam( "id" );
assertTrue( id != null );
synchronized ( clientMap_ ) {
if ( ! clientMap_.containsKey( id ) ) {
clientMap_.put( id, new WatchedClient() );
}
WatchedClient client = (WatchedClient) clientMap_.get( id );
// Handle the various hub event messages by updating fields of
// the right WatchedClient object.
if ( REGISTER_MTYPE.equals( mtype ) ) {
assertTrue( ! client.reg_ );
client.reg_ = true;
}
else if ( UNREGISTER_MTYPE.equals( mtype ) ) {
assertTrue( ! client.unreg_ );
client.unreg_ = true;
}
else if ( METADATA_MTYPE.equals( mtype ) ) {
assertTrue( params.containsKey( "metadata" ) );
Metadata meta =
Metadata
.asMetadata( (Map) params.get( "metadata" ) );
meta.check();
client.meta_ = meta;
}
else if ( SUBSCRIPTIONS_MTYPE.equals( mtype ) ) {
assertTrue( params.containsKey( "subscriptions" ) );
Subscriptions subs =
Subscriptions
.asSubscriptions( (Map) params.get( "subscriptions" ) );
subs.check();
client.subs_ = subs;
}
else {
fail();
}
clientMap_.notifyAll();
}
}
public void receiveResponse( String responderId, String msgTag,
Response response ) {
throw new UnsupportedOperationException();
}
/**
* Returns a suitable subscriptions object for this client.
*
* @return subscriptions
*/
public static Subscriptions getSubscriptions() {
Subscriptions subs = new Subscriptions();
subs.addMType( REGISTER_MTYPE );
subs.addMType( UNREGISTER_MTYPE );
subs.addMType( METADATA_MTYPE );
subs.addMType( SUBSCRIPTIONS_MTYPE );
subs.check();
return subs;
}
/**
* Returns a suitable metadata object for this client.
*/
public static Metadata getMetadata() {
Metadata meta = new Metadata();
meta.setName( "ClientWatcher" );
meta.setDescriptionText( "Tracks other clients for HubTester" );
meta.check();
return meta;
}
}
/**
* Struct-type utility class which aggregates mutable information about
* a client, to be updated in response to hub event messages.
*/
private static class WatchedClient {
/** Whether this client has ever been registered. */
boolean reg_;
/** Whether this clent has ever been unregistered. */
boolean unreg_;
/** Current metadata object for this client. */
Metadata meta_;
/** Current subscriptions object for this client. */
Subscriptions subs_;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/MessageSender.java 0000664 0000000 0000000 00000055232 13564500043 0025557 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.test;
import java.io.IOException;
import java.io.PrintStream;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* Sends a message to one or more other SAMP clients.
* Intended for use from the command line.
*
* @author Mark Taylor
* @since 23 Jul 2008
*/
public abstract class MessageSender {
private static Logger logger_ =
Logger.getLogger( MessageSender.class.getName() );
/**
* Sends a message to a given list of recipients.
* If recipientIds
is null, then will be sent to all
* subscribed clients.
*
* @param connection hub connection
* @param msg message to send
* @param recipientIds array of recipients to target, or null
* @return responder Client -> Response map
*/
abstract Map getResponses( HubConnection connection, Message msg,
String[] recipientIds )
throws IOException;
/**
* Sends a message to a list of recipients and displays the results
* on an output stream.
*
* @param connection hub connection
* @param msg message to send
* @param recipientIds array of recipients to target, or null
* @param destination print stream
*/
void showResults( HubConnection connection, Message msg,
String[] recipientIds, PrintStream out )
throws IOException {
Map responses = getResponses( connection, msg, recipientIds );
for ( Iterator it = responses.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
String responderId = (String) entry.getKey();
Client responder = new MetaClient( responderId, connection );
Object response = entry.getValue();
out.println();
out.println( responder );
if ( response instanceof Throwable ) {
((Throwable) response).printStackTrace( out );
}
else {
out.println( SampUtils.formatObject( response, 3 ) );
}
}
}
/**
* Translates an array of client names to client IDs.
* If some or all cannot be identified, an exception is thrown.
*
* @param conn hub connection
* @param names array of client names, interpreted case-insensitively
* @return array of client ids corresponding to names
*/
private static String[] namesToIds( HubConnection conn, String[] names )
throws SampException {
int count = names.length;
if ( count == 0 ) {
return new String[ 0 ];
}
String[] ids = new String[ count ];
BitSet flags = new BitSet( count );
String[] allIds = conn.getRegisteredClients();
for ( int ic = 0; ic < allIds.length; ic++ ) {
String id = allIds[ ic ];
Metadata meta = conn.getMetadata( id );
String name = meta.getName();
for ( int in = 0; in < count; in++ ) {
if ( names[ in ].equalsIgnoreCase( name ) ) {
ids[ in ] = id;
flags.set( in );
}
}
if ( flags.cardinality() == count ) {
return ids;
}
}
assert flags.cardinality() < count;
List unknownList = new ArrayList();
for ( int in = 0; in < count; in++ ) {
if ( ids[ in ] == null ) {
unknownList.add( names[ in ] );
}
}
throw new SampException( "Unknown client "
+ ( unknownList.size() == 1
? ( "name " + unknownList.get( 0 ) )
: ( "names " + unknownList ) ) );
}
/**
* Main method.
* Use -help flag for documentation.
*/
public static void main( String[] args ) throws IOException {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
/**
* Does the work for the main method.
*/
public static int runMain( String[] args ) throws IOException {
// Assemble usage string.
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " + MessageSender.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( "\n " )
.append( " -mtype receiveReply
method.
* This takes care of matching up replies with calls and is intended for
* use with test classes. Some assertions are made within this class
* to check that replies match with messages sent. Call-type messages
* must be sent using this object's call
and callAll
* methods, rather than directly on the HubConnection
,
* to ensure that the internal state stays correct.
*
* @author Mark Taylor
* @since 18 Jul 2008
*/
abstract class ReplyCollector implements CallableClient {
private final HubConnection connection_;
private final Set sentSet_;
private final Map replyMap_;
private boolean allowTagReuse_;
/**
* Constructor.
*
* @param connection hub connection
* @param allowTagReuse if true clients may reuse tags;
* if false any such attempt generates an exception
*/
public ReplyCollector( HubConnection connection ) {
connection_ = connection;
sentSet_ = Collections.synchronizedSet( new HashSet() );
replyMap_ = Collections.synchronizedMap( new HashMap() );
}
/**
* Determines whether clients are permitted to reuse tags for different
* messages. If true, any such attempt generates an exception.
*
* @param allow whether to allow tag reuse
*/
public void setAllowTagReuse( boolean allow ) {
allowTagReuse_ = allow;
}
/**
* Performs a call
method on this collector's hub connection.
* Additional internal state is updated.
* Although it is legal as far as SAMP goes, the msgTag
* must not be one which was used earlier for the same recipient.
*
* @param recipientId public-id of client to receive message
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return message ID
*/
public String call( String recipientId, String msgTag, Map msg )
throws SampException {
Object key = createKey( recipientId, msgTag );
if ( ! allowTagReuse_ && sentSet_.contains( key ) ) {
throw new IllegalArgumentException( "Key " + key + " reused" );
}
sentSet_.add( key );
return connection_.call( recipientId, msgTag, msg );
}
/**
* Performs a callAll
method on this collector's
* hub connection.
* Additional internal state is updated.
* Although it is legal as far as SAMP goes, the msgTag
* must not be one which was used for an earlier broadcast.
*
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return message ID
*/
public Map callAll( String msgTag, Map msg ) throws SampException {
Object key = createKey( null, msgTag );
if ( ! allowTagReuse_ && sentSet_.contains( key ) ) {
throw new IllegalArgumentException( "Key " + key + " reused" );
}
sentSet_.add( key );
return connection_.callAll( msgTag, msg );
}
public void receiveResponse( String responderId, String msgTag,
Response response ) {
Object key = createKey( responderId, msgTag );
Object result;
try {
if ( ! allowTagReuse_ && replyMap_.containsKey( key ) ) {
throw new TestException( "Response for " + key
+ " already received" );
}
else if ( ! sentSet_.contains( key ) &&
! sentSet_.contains( createKey( null, msgTag ) ) ) {
throw new TestException( "Message " + key + " never sent" );
}
result = response;
}
catch ( TestException e ) {
result = e;
}
synchronized ( replyMap_ ) {
if ( ! replyMap_.containsKey( key ) ) {
replyMap_.put( key, new ArrayList() );
}
((List) replyMap_.get( key )).add( result );
replyMap_.notifyAll();
}
}
/**
* Returns the total number of unretrieved replies so far collected by
* this object.
*
* @return reply count
*/
public int getReplyCount() {
int count = 0;
synchronized ( replyMap_ ) {
for ( Iterator it = replyMap_.values().iterator(); it.hasNext(); ) {
count += ((List) it.next()).size();
}
}
return count;
}
/**
* Waits for a reply to a message sent earlier
* using call
or callAll
.
* Blocks until such a response is received.
*
* @param responderId client ID of client providing response
* @param msgTag tag which was used to send the message
* @return response
*/
public Response waitForReply( String responderId, String msgTag ) {
Object key = createKey( responderId, msgTag );
try {
synchronized ( replyMap_ ) {
while ( ! replyMap_.containsKey( key ) ||
((List) replyMap_.get( key )).isEmpty() ) {
replyMap_.wait();
}
}
}
catch ( InterruptedException e ) {
throw new Error( "Interrupted", e );
}
return getReply( responderId, msgTag );
}
/**
* Gets the reply to a message sent earlier
* using call
or callAll
.
* Does not block; if no such response has been received so far,
* returns null.
*
* @param responderId client ID of client providing response
* @param msgTag tag which was used to send the message
* @return response
*/
public Response getReply( String responderId, String msgTag ) {
Object key = createKey( responderId, msgTag );
synchronized ( replyMap_ ) {
List list = (List) replyMap_.get( key );
Object result = list == null || list.isEmpty()
? null
: list.remove( 0 );
if ( result == null ) {
return null;
}
else if ( result instanceof Response ) {
return (Response) result;
}
else if ( result instanceof Throwable ) {
throw new TestException( (Throwable) result );
}
else {
throw new AssertionError();
}
}
}
/**
* Returns an opaque object suitable for use as a map key
* based on a recipient ID and message tag.
*
* @param recipientId recipient ID
* @param msgTag message tag
*/
private static Object createKey( String recipientId, String msgTag ) {
return Arrays.asList( new String[] { recipientId, msgTag } );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/Snooper.java 0000664 0000000 0000000 00000025314 13564500043 0024455 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.test;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.HubConnector;
import org.astrogrid.samp.client.MessageHandler;
import org.astrogrid.samp.httpd.UtilServer;
/**
* Subscribes to SAMP messages and logs any received to an output stream.
* The only responses to messages have samp.status=samp.warning.
*
* @author Mark Taylor
* @since 4 Sep 2008
*/
public class Snooper {
private final OutputStream out_;
private final Map clientMap_;
private static final byte[] newline_;
static {
byte[] nl;
try {
nl = System.getProperty( "line.separator", "\n" )
.getBytes( "UTF-8" );
}
catch ( Exception e ) {
nl = new byte[] { (byte) '\n' };
}
newline_ = nl;
}
private static final Logger logger_ =
Logger.getLogger( Snooper.class.getName() );
/**
* Constructor using default metadata.
*
* @param profile profile
* @param subs subscriptions defining which messages are received
* and logged
* @param out destination stream for logging info
* @param autoSec number of seconds between auto connection attempts
*/
public Snooper( ClientProfile profile, Subscriptions subs,
OutputStream out, int autoSec ) {
this( profile, subs, createDefaultMetadata(), out, autoSec );
}
/**
* Constructor using custom metadata.
*
* @param profile profile
* @param subs subscriptions defining which messages are received
* and logged
* @param meta client metadata
* @param out destination stream for logging info
* @param autoSec number of seconds between auto connection attempts
*/
public Snooper( ClientProfile profile, final Subscriptions subs,
Metadata meta, OutputStream out, int autoSec ) {
HubConnector connector = new HubConnector( profile );
connector.declareMetadata( meta );
out_ = out;
// Prepare all-purpose response to logged messages.
final Response response = new Response();
response.setStatus( Response.WARNING_STATUS );
response.setResult( new HashMap() );
response.setErrInfo( new ErrInfo( "Message logged, not acted on" ) );
// Add a handler which will handle the subscribed messages.
connector.addMessageHandler( new MessageHandler() {
public Map getSubscriptions() {
return subs;
}
public void receiveNotification( HubConnection connection,
String senderId,
Message msg )
throws IOException {
log( senderId, msg, null );
}
public void receiveCall( HubConnection connection,
String senderId,
String msgId, Message msg )
throws IOException {
log( senderId, msg, msgId );
connection.reply( msgId, response );
}
} );
connector.declareSubscriptions( connector.computeSubscriptions() );
clientMap_ = connector.getClientMap();
// Connect and ready to log.
connector.setActive( true );
connector.setAutoconnect( autoSec );
}
/**
* Logs a received message.
*
* @param senderId message sender public ID
* @param msg message object
* @param msgId message ID for call/response type messages
* (null for notify type messages)
*/
private void log( String senderId, Message msg, String msgId )
throws IOException {
StringBuffer sbuf = new StringBuffer();
sbuf.append( senderId );
Client client = (Client) clientMap_.get( senderId );
if ( client != null ) {
Metadata meta = client.getMetadata();
if ( meta != null ) {
String name = meta.getName();
if ( name != null ) {
sbuf.append( " (" )
.append( name )
.append( ")" );
}
}
}
sbuf.append( " --- " );
if ( msgId == null ) {
sbuf.append( "notify" );
}
else {
sbuf.append( "call" )
.append( " (" )
.append( msgId )
.append( ")" );
}
out_.write( newline_ );
out_.write( sbuf.toString().getBytes( "UTF-8" ) );
out_.write( newline_ );
out_.write( SampUtils.formatObject( msg, 3 ).getBytes( "UTF-8" ) );
out_.write( newline_ );
}
/**
* Returns the default metadata for the Snooper client.
*
* @return meta
*/
public static Metadata createDefaultMetadata() {
Metadata meta = new Metadata();
meta.setName( "Snooper" );
meta.setDescriptionText( "Listens in to messages"
+ " for logging purposes" );
try {
meta.setIconUrl( UtilServer.getInstance()
.exportResource( "/org/astrogrid/samp/images/"
+ "ears.png" )
.toString() );
}
catch ( IOException e ) {
logger_.warning( "Can't export icon" );
}
meta.put( "Author", "Mark Taylor" );
return meta;
}
/**
* Main method. Runs a snooper.
*/
public static void main( String[] args ) throws IOException {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
/**
* Does the work for the main method.
* Use -help flag.
*/
public static int runMain( String[] args ) throws IOException {
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( "\n " )
.append( Snooper.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( "\n " )
.append( " [-clientname test
is false
*/
public static void assertTrue( boolean test ) throws TestException {
if ( ! test ) {
throw new TestException( "Test failed" );
}
}
/**
* Tests object equality.
*
* @param o1 object 1
* @param o2 object 2
* @throws TestException unless o1
and o2
* are both null
or are equal in the sense of
* {@link java.lang.Object#equals}
*/
public static void assertEquals( Object o1, Object o2 )
throws TestException {
if ( o1 == null && o2 == null ) {
}
else if ( o1 == null || ! o1.equals( o2 ) ) {
throw new TestException(
"Test failed: " + o1 + " != " + o2 );
}
}
/**
* Tests integer equality.
*
* @param i1 integer 1
* @param i2 integer 2
* @throws TestException iff i1
!= i2
*/
public static void assertEquals( int i1, int i2 ) throws TestException {
if ( i1 != i2 ) {
throw new TestException(
"Test failed: " + i1 + " != " + i2 );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/package.html 0000664 0000000 0000000 00000000403 13564500043 0024436 0 ustar 00root root 0000000 0000000
Classes for testing.
As well as unit testing of this SAMP toolkit, it includes the
{@link org.astrogrid.samp.test.HubTester} class which tests a running
third-party hub implementation and some miscellaneous diagnostic
and utility applications.
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/util/ 0000775 0000000 0000000 00000000000 13564500043 0022156 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/util/ResourceType.java 0000664 0000000 0000000 00000027313 13564500043 0025460 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Message;
/**
* Defines one of the types of resource that can be turned into a
* SAMP load-type message.
*
* @author Mark Taylor
* @since 21 Aug 2019
*/
public abstract class ResourceType {
private final String name_;
private final String mtype_;
private final String[] ctypes_;
/** Resource type for table.load.votable. */
public static final ResourceType RTYPE_VOTABLE;
/** Resource type for image.load.fits. */
public static final ResourceType RTYPE_FITS;
/** Resource type for table.load.cdf. */
public static final ResourceType RTYPE_CDF;
private static final ResourceType[] RESOURCE_TYPES = {
RTYPE_VOTABLE = createVOTableResourceType(),
RTYPE_FITS = createFitsImageResourceType(),
RTYPE_CDF = createCdfTableResourceType(),
};
private static final int MAGIC_SIZE = 1024;
private static final Logger logger_ =
Logger.getLogger( ResourceType.class.getName() );
/**
* Constructor.
*
* @param name identifying name
* @param mtype MType of message that will be sent
* @param ctypes MIME-types to which this corresponds,
* supplied in normalised form
* (lower case, no parameters, no whitespace)
*/
public ResourceType( String name, String mtype, String[] ctypes ) {
name_ = name;
mtype_ = mtype;
ctypes_ = ctypes;
}
/**
* Returns the MType of the message to be constructed.
*
* @return MType string
*/
public String getMType() {
return mtype_;
}
/**
* Returns a Message object that will forward a given URL to SAMP
* clients.
*
* @param url URL of resource
* @return message instance
*/
public Message createMessage( URL url ) {
Map params = new LinkedHashMap();
params.put( "url", url.toString() );
return new Message( mtype_, params );
}
/**
* Indicates whether this resource type is suitable for use
* with a given MIME type. Note that the submitted content type
* may contain additional parameters and have embedded whitespace etc
* as permitted by RFC 2045.
*
* @param ctype content-type header value
* @return true iff this resource type is suitable for use with
* the given content type
*/
public boolean isContentType( String ctype ) {
ctype = ctype.replaceAll( " *;.*", "" )
.replaceAll( "\\s+", "" )
.toLowerCase();
for ( int ic = 0; ic < ctypes_.length; ic++ ) {
if ( ctype.startsWith( ctypes_[ ic ] ) ) {
return true;
}
}
return false;
}
/**
* Indicates whether this resource type is suitable for use
* with a resource having a given magic number.
*
* @param magic buffer containing the first few bytes of
* resource content
* @return true iff this resource type is suitable for use
* with the given content
*/
public abstract boolean isMagic( byte[] magic );
/**
* Returns the name of this resource type.
*
* @return name
*/
public String getName() {
return name_;
}
public String toString() {
return name_;
}
/**
* Returns the known resource types.
*
* @return known instances of this class
*/
public static ResourceType[] getKnownResourceTypes() {
return (ResourceType[]) RESOURCE_TYPES.clone();
}
/**
* Attempts to determine the resource type of a given URL by
* making an HTTP HEAD request and looking at the Content-Type.
*
* @param url resource location
* @return good guess at resource type, or null if can't be determined
*/
public static ResourceType readHeadResourceType( URL url ) {
try {
URLConnection uconn = url.openConnection();
if ( uconn instanceof HttpURLConnection ) {
logger_.info( "HEAD " + url );
HttpURLConnection hconn = (HttpURLConnection) uconn;
hconn.setInstanceFollowRedirects( true );
hconn.setRequestMethod( "HEAD" );
hconn.connect();
int code = hconn.getResponseCode();
if ( code == 200 ) {
String ctype = hconn.getContentType();
logger_.info( "HEAD Content-Type: " + ctype );
return getMimeResourceType( ctype );
}
else {
logger_.warning( "HEAD response code " + code );
return null;
}
}
else {
return null;
}
}
catch ( IOException e ) {
logger_.log( Level.WARNING, "HEAD failed", e );
return null;
}
}
/**
* Attempts to determine the resource type of a given URL by
* downloading the first part of its content and examining the
* magic number.
*
* @param url resource location
* @return good guess at resource type, or null if it can't be determined
*/
public static ResourceType readContentResourceType( URL url ) {
// Acquire the magic number.
byte[] buf = new byte[ MAGIC_SIZE ];
InputStream in = null;
logger_.info( "GET " + url );
try {
// Open a GET connection.
URLConnection uconn = url.openConnection();
if ( uconn instanceof HttpURLConnection ) {
HttpURLConnection hconn = (HttpURLConnection) uconn;
hconn.setInstanceFollowRedirects( true );
hconn.connect();
int code = hconn.getResponseCode();
if ( code != 200 ) {
logger_.warning( "GET response code " + code );
return null;
}
// The content-type may be usable here, even if the
// presumed earlier call to HEAD failed
// (for instance HEAD not implemented).
String ctype = hconn.getContentType();
ResourceType rtype = getMimeResourceType( ctype );
if ( rtype != null ) {
logger_.info( "GET Content-Type: " + ctype );
return rtype;
}
}
else {
uconn.connect();
}
// Read the first few bytes into a buffer, then close the stream.
in = uconn.getInputStream();
for ( int off = 0; off < MAGIC_SIZE; ) {
int nr = in.read( buf, off, MAGIC_SIZE - off );
if ( nr > 0 ) {
off += nr;
}
else {
break;
}
}
}
catch ( IOException e ) {
logger_.log( Level.WARNING, "GET failed", e );
}
finally {
if ( in != null ) {
try {
in.close();
}
catch ( IOException e ) {
}
}
}
// Try to determine type from magic number.
return getMagicResourceType( buf );
}
/**
* Try to identify a resource type from its MIME type.
*
* @param contentType content-type header
* @return resource type, or null if not known
*/
private static ResourceType getMimeResourceType( String contentType ) {
if ( contentType != null ) {
for ( int i = 0; i < RESOURCE_TYPES.length; i++ ) {
ResourceType rtype = RESOURCE_TYPES[ i ];
if ( rtype.isContentType( contentType ) ) {
return rtype;
}
}
}
return null;
}
/**
* Try to identify a resource type from its magic number.
*
* @param magic buffer containing first few bytes of resource content
* @return resource type, or null if not known
*/
private static ResourceType getMagicResourceType( byte[] magic ) {
for ( int i = 0; i < RESOURCE_TYPES.length; i++ ) {
ResourceType rtype = RESOURCE_TYPES[ i ];
if ( rtype.isMagic( magic ) ) {
logger_.info( "GET magic number looks like " + rtype );
return rtype;
}
}
return null;
}
/**
* Returns a ResourceType instance suitable for the table.load.votable
* SAMP MType.
*
* @return VOTable resource type
*/
private static ResourceType createVOTableResourceType() {
return new ResourceType( "VOTable", "table.load.votable",
new String[] { "application/x-votable+xml",
"text/xml" } ) {
public boolean isMagic( byte[] buf ) {
// Shocking hack that should work for UTF-8 and UTF-16*.
String txt;
try {
txt = new String( buf, "US-ASCII" );
}
catch ( UnsupportedEncodingException e ) {
return false;
}
return txt.contains( "Lines
" should return text which contains
* line breaks (\n
characters). Each such line will
* be displayed as it stands in the GUI, so it shouldn't be too long.
*
* callback
*/
public static void invoke( Callback callback, CallableClient client )
throws Exception {
callback.check();
String methodName = callback.getMethodName();
List paramList = callback.getParams();
ClientCallbackOperation op =
(ClientCallbackOperation) OPERATION_MAP.get( methodName );
if ( op == null ) {
throw new UnsupportedOperationException(
"Unknown callback operation " + methodName );
}
else {
boolean sigOk = op.sampSig_.length == paramList.size();
for ( int i = 0; sigOk && i < op.sampSig_.length; i++ ) {
sigOk = sigOk
&& op.sampSig_[ i ]
.isAssignableFrom( paramList.get( i ).getClass() );
}
if ( ! sigOk ) {
throw new IllegalArgumentException(
methodName + " callback signature mismatch" );
}
else {
op.dispatch( client, paramList );
}
}
}
/**
* Returns a map, keyed by unqualified operation name,
* of known callback operations.
*
* @param String->ClientCallbackOperation map
*/
private static Map createOperationMap() {
// First assemble an array of known callback operations.
// It would be possible to assemble this array using reflection
// on the CallableClient interface, but more trouble than it's
// worth for three methods.
ClientCallbackOperation[] operations = new ClientCallbackOperation[] {
new ClientCallbackOperation( "receiveNotification",
new Class[] { String.class,
Map.class } ) {
public void dispatch( CallableClient client, List params )
throws Exception {
client.receiveNotification(
(String) params.get( 0 ),
new Message( (Map) params.get( 1 ) ) );
}
},
new ClientCallbackOperation( "receiveCall",
new Class[] { String.class,
String.class,
Map.class } ) {
public void dispatch( CallableClient client, List params )
throws Exception {
client.receiveCall(
(String) params.get( 0 ),
(String) params.get( 1 ),
new Message( (Map) params.get( 2 ) ) );
}
},
new ClientCallbackOperation( "receiveResponse",
new Class[] { String.class,
String.class,
Map.class } ) {
public void dispatch( CallableClient client, List params )
throws Exception {
client.receiveResponse(
(String) params.get( 0 ),
(String) params.get( 1 ),
new Response( (Map) params.get( 2 ) ) );
}
},
};
// Turn it into a map keyed by operation name, and return.
Map opMap = new HashMap();
for ( int i = 0; i < operations.length; i++ ) {
ClientCallbackOperation op = operations[ i ];
opMap.put( op.fqName_, op );
}
return Collections.unmodifiableMap( opMap );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/CorsHttpServer.java 0000664 0000000 0000000 00000031154 13564500043 0025562 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.astrogrid.samp.httpd.HttpServer;
/**
* HttpServer which allows or rejects cross-origin access according to
* the W3C Cross-Origin Resource Sharing standard.
* This standard is used by XMLHttpResource Level 2 and some other
* web-based platforms, implemented by a number of modern browsers,
* and works by the browser inserting and interpreting special headers
* when cross-origin requests are made by sandboxed clients.
* The effect is that sandboxed clients will under some circumstances
* be permitted to access resources served by instances of this server,
* where they wouldn't for an HTTP server which did not take special
* measures.
*
* @author Mark Taylor
* @since 2 Feb 2011
* @see Cross-Origin Resource Sharing W3C Standard
*/
public class CorsHttpServer extends HttpServer {
private final OriginAuthorizer authorizer_;
private static final String ORIGIN_KEY = "Origin";
private static final String ALLOW_ORIGIN_KEY =
"Access-Control-Allow-Origin";
private static final String REQUEST_METHOD_KEY =
"Access-Control-Request-Method";
private static final String ALLOW_METHOD_KEY =
"Access-Control-Allow-Methods";
private static final String ALLOW_HEADERS_KEY =
"Access-Control-Allow-Headers";
// This regex is constructed with reference to RFC6454 and RFC3986.
// It is less rigorous than those, since the host production in RFC3986
// is quite complex, but the required checking is not all that critical.
private static final Pattern ORIGIN_REGEX =
Pattern.compile( "[A-Za-z][A-Za-z0-9+.-]*://.+" );
private static final InetAddress localHostAddress_ = getLocalHostAddress();
private static final Logger logger_ =
Logger.getLogger( CorsHttpServer.class.getName() );
/**
* System property ({@value}) which can be used to supply host addresses
* explicitly permitted to connect via the Web Profile alongside
* the local host.
* Normally any non-local host is blocked from access to the CORS
* web server for security reasons. However, any host specified
* by hostname or IP number as one element of a comma-separated
* list in the value of this system property will also be allowed.
* This might be used to allow access from a "friendly" near-local
* host like a tablet.
*/
public static final String EXTRAHOSTS_PROP = "jsamp.web.extrahosts";
/** Set of permitted InetAddrs along side localhost. */
private static final Set extraAddrSet_ =
new HashSet( Arrays.asList( getExtraHostAddresses() ) );
/**
* Constructor.
*
* @param socket socket hosting the service
* @param authorizer defines which domains requests will be
* permitted from
*/
public CorsHttpServer( ServerSocket socket, OriginAuthorizer authorizer )
throws IOException {
super( socket );
authorizer_ = authorizer;
}
public Response serve( Request request ) {
if ( ! isPermittedHost( request.getRemoteAddress() ) ) {
return createNonLocalErrorResponse( request );
}
Map hdrMap = request.getHeaderMap();
String method = request.getMethod();
String originTxt = getHeader( hdrMap, ORIGIN_KEY );
if ( originTxt != null ) {
String reqMethod = getHeader( hdrMap, REQUEST_METHOD_KEY );
if ( method.equals( "OPTIONS" ) && reqMethod != null ) {
return servePreflightOriginRequest( request, originTxt,
reqMethod );
}
else {
return serveSimpleOriginRequest( request, originTxt );
}
}
else {
return super.serve( request );
}
}
/**
* Does the work for serving simple requests which bear an
* origin header. Simple requests are effectively ones which do not
* require pre-flight requests - see the CORS standard for details.
*
* @param request HTTP request
* @param originTxt content of the Origin header
* @return HTTP response
*/
private Response serveSimpleOriginRequest( Request request,
String originTxt ) {
Response response = super.serve( request );
if ( isAuthorized( originTxt ) ) {
Map headerMap = response.getHeaderMap();
if ( getHeader( headerMap, ALLOW_ORIGIN_KEY ) == null ) {
headerMap.put( ALLOW_ORIGIN_KEY, originTxt );
}
}
return response;
}
/**
* Does the work for serving pre-flight requests.
* See the CORS standard for details.
*
* @param request HTTP request
* @param originTxt content of the Origin header
* @param reqMethod content of the Access-Control-Request-Method header
* @return HTTP response
*/
private Response servePreflightOriginRequest( Request request,
String originTxt,
String reqMethod ) {
Map hdrMap = new LinkedHashMap();
hdrMap.put( "Content-Length", "0" );
if ( isAuthorized( originTxt ) ) {
hdrMap.put( ALLOW_ORIGIN_KEY, originTxt );
hdrMap.put( ALLOW_METHOD_KEY, reqMethod );
hdrMap.put( ALLOW_HEADERS_KEY, "Content-Type" ); // allow all here?
}
return new Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) {
}
};
}
/**
* Returns an HTTP error response complaining about attempted access
* from a disallowed host.
*
* @param request offending request
* @return HTTP 403 response
*/
public static Response createNonLocalErrorResponse( Request request ) {
int status = 403;
String msg = "Forbidden";
String method = request.getMethod();
if ( "HEAD".equals( method ) ) {
return createErrorResponse( status, msg );
}
else {
Map hdrMap = new LinkedHashMap();
hdrMap.put( HDR_CONTENT_TYPE, "text/plain" );
byte[] mbuf;
try {
mbuf = ( "Access to server from non-local hosts "
+ "is not permitted.\r\n" )
.getBytes( "UTF-8" );
}
catch ( UnsupportedEncodingException e ) {
logger_.warning( "Unsupported UTF-8??" );
mbuf = new byte[ 0 ];
}
final byte[] mbuf1 = mbuf;
hdrMap.put( "Content-Length", Integer.toString( mbuf1.length ) );
return new Response( status, msg, hdrMap ) {
public void writeBody( OutputStream out ) throws IOException {
out.write( mbuf1 );
out.flush();
}
};
}
}
/**
* Determines whether a given origin is permitted access.
* This is done by interrogating this server's OriginAuthorizer policy.
* Results are cached.
*
* @param originTxt content of Origin header
*/
private boolean isAuthorized( String originTxt ) {
// CORS sec 5.1 says multiple space-separated origins may be present
// - but why?? Treat the string as a single origin for now.
// Not incorrect, though possibly annoying if the same origin
// crops up multiple times in different sets (unlikely as far
// as I can see).
boolean hasLegalOrigin;
try {
checkOriginList( originTxt );
hasLegalOrigin = true;
}
catch ( RuntimeException e ) {
logger_.warning( "Origin header: " + e.getMessage() );
hasLegalOrigin = false;
}
return hasLegalOrigin && authorizer_.authorize( originTxt );
}
/**
* Indicates whether a network address is known to represent
* a host permitted to access this server.
* That generally means the local host, but "extra" hosts may be
* permitted as well.
*
* @param address socket address
* @return true iff address is known to be permitted
*/
public boolean isPermittedHost( SocketAddress address ) {
return isLocalHost( address ) || isExtraHost( address );
}
/**
* Indicates whether the given socket address is from the local host.
*
* @param address socket to test
* @return true if the socket is known to be local
*/
public static boolean isLocalHost( SocketAddress address ) {
if ( address instanceof InetSocketAddress ) {
InetAddress iAddress = ((InetSocketAddress) address).getAddress();
return iAddress != null
&& ( iAddress.isLoopbackAddress() ||
iAddress.equals( localHostAddress_ ) );
}
else {
return false;
}
}
/**
* Returns the inet address of the local host, or null if not available.
*
* @return local host address or null
*/
private static InetAddress getLocalHostAddress() {
try {
return InetAddress.getLocalHost();
}
catch ( UnknownHostException e ) {
logger_.log( Level.WARNING,
"Can't determine local host address", e );
return null;
}
}
/**
* Acquires and returns a list of permitted non-local hosts from the
* environment.
*
* @return list of addresses for non-local hosts permitted to access
* CORS web servers in this JVM
*/
private static InetAddress[] getExtraHostAddresses() {
String list;
try {
list = System.getProperty( EXTRAHOSTS_PROP );
}
catch ( SecurityException e ) {
list = null;
}
String[] names;
if ( list != null ) {
list = list.trim();
names = list.length() > 0 ? list.split( ", *" ) : new String[ 0 ];
}
else {
names = new String[ 0 ];
}
int naddr = names.length;
List addrList = new ArrayList();
for ( int i = 0; i < naddr; i++ ) {
String name = names[ i ];
try {
addrList.add( InetAddress.getByName( name ) );
logger_.warning( "Adding web hub exception for host "
+ "\"" + name + "\"" );
}
catch ( UnknownHostException e ) {
logger_.warning( "Unknown host \"" + name + "\""
+ " - not adding web hub exception" );
}
}
return (InetAddress[]) addrList.toArray( new InetAddress[ 0 ] );
}
/**
* Indicates whether a given address represents one of the "extra" hosts
* permitted to access this server alongside the localhost.
*
* @param addr address of non-local host to test
* @return true iff host is permitted to access this server
*/
public static boolean isExtraHost( SocketAddress addr ) {
return addr instanceof InetSocketAddress
&& extraAddrSet_.contains( ((InetSocketAddress) addr)
.getAddress() );
}
/**
* Checks that the content of an Origin header is syntactically legal.
*
* @param originTxt content of Origin header
* @throws IllegalArgumentExeption if originTxt does not represent
* a legal origin or (non-empty) list of origins
*/
private static void checkOriginList( String originTxt ) {
String[] origins = originTxt.split( " +" );
if ( origins.length > 0 ) {
for ( int i = 0; i < origins.length; i++ ) {
if ( ! ORIGIN_REGEX.matcher( origins[ i ] ).matches() ) {
throw new IllegalArgumentException(
"Bad origin syntax: \"" + origins[ i ] + "\"" );
}
}
}
else {
throw new IllegalArgumentException( "No origins supplied" );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/CredentialPresenter.java 0000664 0000000 0000000 00000004527 13564500043 0026573 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Map;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
/**
* Extracts credentials for presentation to the user from available
* information, so that the user can decide whether to allow registration.
*
* @author Mark Taylor
* @since 18 Jun 2016
*/
public interface CredentialPresenter {
/**
* Returns an object which contains user-directed credential messages,
* given available information from the registration request.
* If the request or securityMap can be determined to be
* definitely unsuitable for registration, a SampException is thrown.
*
* @param request HTTP request associated with the registration request
* @param securityMap information explicitly supplied by the aspiring
* client in support of its application to register
* @param authContent content of AuthResourceBundle bundle
* @return items for presentation to the user
* @throws SampException if credentials should definitely not be accepted
*/
Presentation createPresentation( HttpServer.Request request,
Map securityMap,
AuthResourceBundle.Content authContent )
throws SampException;
/**
* Aggregates credential information to be presented to the user.
*/
interface Presentation {
/**
* Returns an ordered map of String->String entries
* containing name, value pairs.
*
* @return map with ordered entries
*/
Map getAuthEntries();
/**
* Returns an array of "message" objects providing additional
* information for the user.
*
* msg
* argument of one of JOptionPane
's methods.
*
* @return message array describing the applicant to the user
* @see javax.swing.JOptionPane
*/
Object[] getAuthLines();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ExtremeSwingClientAuthorizer.java 0000664 0000000 0000000 00000007214 13564500043 0030462 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.net.URL;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.border.Border;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.gui.IconStore;
import org.astrogrid.samp.httpd.HttpServer;
/**
* Client authorizer implementation that does its very best to discourage
* users from accepting regitrations.
*
* @author Mark Taylor
* @since 29 Sep 2011
*/
public class ExtremeSwingClientAuthorizer implements ClientAuthorizer {
private final Component parent_;
/**
* Constructor.
*
* @param parent parent component, may be null
*/
public ExtremeSwingClientAuthorizer( Component parent ) {
parent_ = parent;
if ( GraphicsEnvironment.isHeadless() ) {
throw new HeadlessException( "No graphics - lucky escape" );
}
}
public void authorize( HttpServer.Request request, Map securityMap )
throws SampException {
JComponent panel = Box.createVerticalBox();
JComponent linePic = Box.createHorizontalBox();
URL imageUrl = Client.class.getResource( "images/danger.jpeg" );
linePic.add( Box.createHorizontalGlue() );
linePic.add( new JLabel( new ImageIcon( imageUrl ) ) );
linePic.add( Box.createHorizontalGlue() );
panel.add( linePic );
panel.add( Box.createVerticalStrut( 5 ) );
JComponent line1 = Box.createHorizontalBox();
String appName = ClientAuthorizers.getAppName( securityMap );
line1.add( new JLabel( "Client \"" + appName
+ "\" is requesting Web Profile registration." ) );
line1.add( Box.createHorizontalGlue() );
line1.setBorder( createBorder( false ) );
panel.add( line1 );
JLabel deathLabel = new JLabel( "CERTAIN DEATH" );
deathLabel.setForeground( Color.RED );
deathLabel.setFont( deathLabel.getFont()
.deriveFont( deathLabel.getFont().getSize() + 2f ) );
JComponent line2 = Box.createHorizontalBox();
line2.add( new JLabel( "Accepting this request will lead to " ) );
line2.add( deathLabel );
line2.add( new JLabel( "!" ) );
line2.add( Box.createHorizontalGlue() );
line2.setBorder( createBorder( true ) );
panel.add( line2 );
if ( JOptionPane
.showOptionDialog( parent_, panel, "Registration Request",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
IconStore.createEmptyIcon( 0 ),
new String[] { "Accept", "Reject" },
"Reject" ) != 0 ) {
throw new SampException( "User denied authorization as advised" );
}
}
/**
* Returns a new border of fixed dimensions which may or may not include
* an element of highlighting.
*
* @param highlight true to highlight border
* @return new border
*/
private Border createBorder( boolean highlight ) {
Color color = new Color( 0x00ff0000, ! highlight );
return BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder( new Color( 0xff0000, !highlight ) ),
BorderFactory.createEmptyBorder( 2, 2, 2, 2 )
);
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/HubSwingClientAuthorizer.java 0000664 0000000 0000000 00000021066 13564500043 0027570 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
/**
* ClientAuthorizer implementation that queries the user for permission
* via a popup dialogue.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class HubSwingClientAuthorizer implements ClientAuthorizer {
private final Component parent_;
private final CredentialPresenter presenter_;
private static final int MAX_POPUP_WIDTH = 500;
private static final Logger logger_ =
Logger.getLogger( HubSwingClientAuthorizer.class.getName() );
/**
* Constructor.
*
* @param parent parent component
* @param presenter handles credential presentation to the user
*/
public HubSwingClientAuthorizer( Component parent,
CredentialPresenter presenter ) {
parent_ = parent;
presenter_ = presenter;
if ( GraphicsEnvironment.isHeadless() ) {
throw new HeadlessException( "Client authorization dialogues "
+ "impossible - no graphics" );
}
}
public void authorize( HttpServer.Request request, Map securityMap )
throws SampException {
// Prepare an internationalised query dialogue.
AuthResourceBundle.Content authContent =
AuthResourceBundle
.getAuthContent( ResourceBundle
.getBundle( AuthResourceBundle.class.getName() ) );
Object[] qmsg = getMessageLines( request, securityMap, authContent );
String noOpt = authContent.noWord();
String yesOpt = authContent.yesWord();
// Just calling showOptionDialog can end up with the popup being
// obscured by other windows on the desktop, at least for win XP.
JOptionPane jop =
new JOptionPane( qmsg, JOptionPane.WARNING_MESSAGE,
JOptionPane.YES_NO_OPTION, null,
new String[] { noOpt, yesOpt }, noOpt );
JDialog dialog = jop.createDialog( parent_, authContent.windowTitle() );
attemptSetAlwaysOnTop( dialog, true );
dialog.setModal( true );
dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
// It seems to be OK to call Dialog.setVisible on a modal dialogue
// from threads other than the AWT Event Dispatch Thread.
// I admit though that I haven't seen document which assures that
// this is true however.
dialog.setVisible( true );
dialog.dispose();
if ( jop.getValue() != yesOpt ) {
throw new SampException( "User denied authorization" );
}
}
/**
* Returns a "message" object describing the applying client to the user.
* The return value is suitable for use as the msg
argument
* of one of JOptionPane
's methods.
*
* @param request HTTP request bearing the application
* @param securityMap information supplied explicitly by application
* @param authContent content of AuthResourceBundle bundle
* @return message array describing the applicant to the user
* @throws SampExecution if registration is to be rejected out of hand
* @see javax.swing.JOptionPane
*/
private Object[] getMessageLines( HttpServer.Request request,
Map securityMap,
AuthResourceBundle.Content authContent )
throws SampException {
Map headerMap = request.getHeaderMap();
CredentialPresenter.Presentation presentation =
presenter_.createPresentation( request, securityMap, authContent );
List lineList = new ArrayList();
lineList.addAll( toLineList( authContent.appIntroductionLines() ) );
lineList.add( "\n" );
lineList.add( createLabelledFields( presentation.getAuthEntries(),
authContent.undeclaredWord() ) );
lineList.add( "\n" );
Object[] lines = presentation.getAuthLines();
lineList.addAll( Arrays.asList( lines ) );
if ( lines.length > 0 ) {
lineList.add( "\n" );
}
lineList.addAll( toLineList( authContent.privilegeWarningLines() ) );
lineList.add( "\n" );
lineList.addAll( toLineList( authContent.adviceLines() ) );
lineList.add( "\n" );
lineList.add( authContent.questionLine() );
return lineList.toArray();
}
/**
* Returns a component displaying name/value pairs represented by
* a given String->String map.
*
* @param infoMap String->String map of key->value pairs
* @param undeclaredWord text to use to indicate a null value
* @return display component
*/
private JComponent createLabelledFields( Map infoMap,
String undeclaredWord ) {
GridBagLayout layer = new GridBagLayout();
JComponent box = new JPanel( layer ) {
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
return new Dimension( Math.min( size.width, MAX_POPUP_WIDTH ),
size.height );
}
};
GridBagConstraints keyCons = new GridBagConstraints();
GridBagConstraints valCons = new GridBagConstraints();
keyCons.gridy = 0;
valCons.gridy = 0;
keyCons.gridx = 0;
valCons.gridx = 1;
keyCons.anchor = GridBagConstraints.WEST;
valCons.anchor = GridBagConstraints.WEST;
keyCons.fill = GridBagConstraints.NONE;
valCons.fill = GridBagConstraints.HORIZONTAL;
keyCons.weighty = 1;
valCons.weighty = 1;
keyCons.weightx = 0;
valCons.weightx = 1;
valCons.insets = new Insets( 1, 1, 1, 1 );
JComponent stack = Box.createVerticalBox();
for ( Iterator it = infoMap.keySet().iterator(); it.hasNext(); ) {
String key = (String) it.next();
String value = (String) infoMap.get( key );
String valtxt = value == null ? undeclaredWord : value;
JComponent keyComp = new JLabel( key + ": " );
JTextField valueField = new JTextField( valtxt );
valueField.setEditable( false );
layer.setConstraints( keyComp, keyCons );
layer.setConstraints( valueField, valCons );
box.add( keyComp );
box.add( valueField );
keyCons.gridy++;
valCons.gridy++;
}
box.setBorder( BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) );
return box;
}
/**
* Turns a multi-line string into an array of strings.
*
* @param linesTxt string perhaps with embedded \n characters
* @return array of lines
*/
private static String[] toLines( String linesTxt ) {
return linesTxt.split( "\\n" );
}
/**
* Turns a multi-line string into a List of strings.
*
* @param linesTxt string perhaps with embedded \n characters
* @return list of String lines
*/
private static List toLineList( String linesTxt ) {
return Arrays.asList( toLines( linesTxt ) );
}
/**
* Tries to set the always-on-top property of a window.
* This is only possible in JRE1.5 and later, so it's done here by
* reflection. If it fails, a logging message is emitted.
*
* @param win window to set
* @param isOnTop true for on top, false for not
*/
private static void attemptSetAlwaysOnTop( Window win, boolean isOnTop ) {
try {
Window.class.getMethod( "setAlwaysOnTop",
new Class[] { boolean.class } )
.invoke( win, new Object[] { Boolean.valueOf( isOnTop ) } );
}
catch ( Throwable e ) {
logger_.info( "Can't set window on top, not J2SE5" );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ListMessageRestriction.java 0000664 0000000 0000000 00000011577 13564500043 0027302 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Map;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.hub.MessageRestriction;
/**
* General purpose implementation of MessageRestriction.
* It allows to either whitelist or blacklist a given list of MType
* patterns, with the option for client subscriptions to override
* this policy by setting the "x-samp.mostly-harmless" key in the
* annotation map corresponding to a given MType subscription.
*
* @author Mark Taylor
* @since 23 Nov 2011
*/
public class ListMessageRestriction implements MessageRestriction {
private final boolean allow_;
private final boolean useSubsInfo_;
private final Subscriptions subs_;
/**
* Default list of MType patterns returned by {@link #getSafeMTypes}.
*/
public static String[] DEFAULT_SAFE_MTYPES = new String[] {
"samp.app.*", "samp.msg.progress",
"table.*", "image.*", "coord.*", "spectrum.*",
"bibcode.*", "voresource.*",
};
/**
* System property used to specify a default list of known safe MTypes,
* which the {@link #DEFAULT} policy will permit.
* The value is a comma-separated list of MType patterns.
*/
public static final String SAFE_MTYPE_PROP = "jsamp.mtypes.safe";
/**
* Default MessageRestriction implementation.
* The current implementation allows a list of MTypes believed to be safe,
* as given by calling {@link #getSafeMTypes}, and blocks all others.
* However, client subscriptions may override this by annotating their
* subscriptions with an entry having the key
* "x-samp.mostly-harmless
".
* If this has the value "1" the MType thus annotated is allowed,
* and if it has the value "0" it is blocked, regardless of the safe list.
*/
public static final MessageRestriction DEFAULT =
new ListMessageRestriction( true, getSafeMTypes(), true );
/**
* MessageRestriction that permits all MTypes, except as overridden
* by x-samp.mostly-harmless
annotations.
*/
public static final MessageRestriction ALLOW_ALL =
new ListMessageRestriction( false, new String[ 0 ], true ) {
public String toString() {
return "ALLOW_ALL";
}
};
/**
* MessageRestriction that blocks all MTypes, except as overridden
* by x-samp.mostly-harmless
annotations.
*/
public static final MessageRestriction DENY_ALL =
new ListMessageRestriction( true, new String[ 0 ], true ) {
public String toString() {
return "DENY_ALL";
}
};
/**
* Constructor.
*
* @param allow whether the sense of the mtypes list is those
* that should be allowed (true) or blocked (false)
* @param mtypes mtype patterns to be allowed or blocked
* @param useSubsInfo if true, honour x-samp.mostly-harmless
* subscription annotations
*/
public ListMessageRestriction( boolean allow, String[] mtypes,
boolean useSubsInfo ) {
allow_ = allow;
useSubsInfo_ = useSubsInfo;
subs_ = new Subscriptions();
for ( int im = 0; im < mtypes.length; im++ ) {
subs_.addMType( mtypes[ im ] );
}
}
public boolean permitSend( String mtype, Map subsInfo ) {
if ( useSubsInfo_ ) {
Object markedHarmless = subsInfo.get( "samp.mostly-harmless" );
if ( markedHarmless == null ) {
markedHarmless = subsInfo.get( "x-samp.mostly-harmless" );
}
if ( "0".equals( markedHarmless ) ) {
return false;
}
else if ( "1".equals( markedHarmless ) ) {
return true;
}
}
boolean knownSafe = ( ! allow_ ) ^ subs_.isSubscribed( mtype );
return knownSafe;
}
public String toString() {
StringBuffer sbuf = new StringBuffer()
.append( allow_ ? "Allow" : "Deny" )
.append( ' ' )
.append( subs_.keySet() );
if ( useSubsInfo_ ) {
sbuf.append( "; " )
.append( "honour (x-)samp.mostly-harmless" );
}
return sbuf.toString();
}
/**
* Returns a list of MType patterns which are permitted by the DEFAULT
* policy. If the System Property {@value SAFE_MTYPE_PROP} exists,
* its value is taken as a comma-separated list of known permitted MType
* patterns. Otherwise, the {@link #DEFAULT_SAFE_MTYPES} array is returned.
*
* @return list of MTypes treated as harmless by default
*/
public static String[] getSafeMTypes() {
String safeMtypes = System.getProperty( SAFE_MTYPE_PROP );
if ( safeMtypes == null ) {
return DEFAULT_SAFE_MTYPES;
}
else {
return safeMtypes.split( "," );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/LoggingCorsHttpServer.java 0000664 0000000 0000000 00000017761 13564500043 0027101 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.net.ServerSocket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;
/**
* CorsHttpServer subclass which performs logging to a given print stream
* at the HTTP level. Logging is not done through the logging system.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class LoggingCorsHttpServer extends CorsHttpServer {
private final PrintStream out_;
private int iSeq_;
/**
* Constructor.
*
* @param socket socket hosting the service
* @param auth defines which domains requests will be permitted from
* @param out destination stream for logging
*/
public LoggingCorsHttpServer( ServerSocket socket, OriginAuthorizer auth,
PrintStream out ) throws IOException {
super( socket, auth );
out_ = out;
}
public Response serve( Request request ) {
int iseq;
synchronized ( this ) {
iseq = ++iSeq_;
}
logRequest( request, iseq );
return new LoggedResponse( super.serve( request ), iseq,
"POST".equals( request.getMethod() ) );
}
/**
* Logs a given request.
*
* @param request HTTP request
* @param iseq index of the request; unique integer for each request
*/
private void logRequest( Request request, int iseq ) {
StringBuffer sbuf = new StringBuffer();
sbuf.append( '\n' );
appendBanner( sbuf, '>', iseq );
sbuf.append( request.getMethod() )
.append( ' ' )
.append( request.getUrl() )
.append( '\n' );
appendHeaders( sbuf, request.getHeaderMap() );
byte[] body = request.getBody();
if ( body != null && body.length > 0 ) {
sbuf.append( '\n' );
try {
sbuf.append( new String( request.getBody(), "utf-8" ) );
}
catch ( UnsupportedEncodingException e ) {
throw new AssertionError( "No utf-8??" );
}
}
out_.println( sbuf );
}
/**
* Adds a line to the given stringbuffer which indicates information
* relating to a given sequence number follows.
*
* @param sbuf string buffer to add to
* @param c filler character
* @param iseq sequence number
*/
private void appendBanner( StringBuffer sbuf, char c, int iseq ) {
String label = Integer.toString( iseq );
int nc = 75 - label.length();
for ( int i = 0; i < nc; i++ ) {
sbuf.append( c );
}
sbuf.append( ' ' )
.append( label )
.append( '\n' );
}
/**
* Adds HTTP header information to a string buffer.
*
* @param sbuf buffer to add lines to
* @param map header key->value pair map
*/
private void appendHeaders( StringBuffer sbuf, Map map ) {
for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
sbuf.append( entry.getKey() )
.append( ": " )
.append( entry.getValue() )
.append( '\n' );
}
}
/**
* HTTP response which will log its content at an appropriate time.
*/
private class LoggedResponse extends Response {
private final Response base_;
private final boolean logBody_;
private final String headText_;
private String bodyText_;
/**
* Constructor.
*
* @param base response on which this one is based
* @param iseq sequence number of request that this is a response to
* @param logBody true iff the body of the response is to be logged
*/
LoggedResponse( Response base, int iseq, boolean logBody ) {
super( base.getStatusCode(), base.getStatusPhrase(),
base.getHeaderMap() );
base_ = base;
logBody_ = logBody;
StringBuffer sbuf = new StringBuffer();
sbuf.append( '\n' );
appendBanner( sbuf, '<', iseq );
sbuf.append( getStatusCode() )
.append( ' ' )
.append( getStatusPhrase() )
.append( '\n' );
appendHeaders( sbuf, getHeaderMap() );
headText_ = sbuf.toString();
}
public void writeBody( final OutputStream out ) throws IOException {
// This method captures the logging output as well as writing
// the body text to the given stream. The purpose of doing it
// like this rather than writing the logging information
// directly is so that this method can be harmlessly called
// multiple times (doesn't normally happen, but can do sometimes).
// Prepare an object (an OutputStream) to which you can write
// the body content and then use its toString method to get
// the loggable text. This loggable text is either the
// body content itself, or an indication of how many bytes it
// contained, depending on the logBody_ flag.
final OutputStream lout = logBody_
? (OutputStream) new ByteArrayOutputStream() {
public String toString() {
String txt;
try {
txt = new String( buf, 0, count, "utf-8" );
}
catch ( UnsupportedEncodingException e ) {
txt = e.toString();
}
return "\n" + txt + "\n";
}
}
: (OutputStream) new CountOutputStream() {
public String toString() {
return count_ > 0
? "<" + count_ + " bytes of output omitted>\n"
: "";
}
};
// Prepare an output stream which writes both to the normal
// response destination and to the content logging object we've
// just set up.
OutputStream teeOut = new OutputStream() {
public void write( byte[] b ) throws IOException {
lout.write( b );
out.write( b );
}
public void write( byte[] b, int off, int len )
throws IOException {
lout.write( b, off, len );
out.write( b, off, len );
}
public void write( int b ) throws IOException {
lout.write( b );
out.write( b );
}
};
// Write the body content to the response output stream,
// and store the loggable output.
String slog;
try {
base_.writeBody( teeOut );
slog = lout.toString();
}
catch ( IOException e ) {
slog = "log error? " + e + "\n";
}
bodyText_ = slog;
}
public void writeResponse( final OutputStream out ) throws IOException {
super.writeResponse( out );
out_.print( headText_ + bodyText_ );
}
}
/**
* OutputStream subclass which counts the number of bytes it is being
* asked to write, but otherwise does nothing.
*/
private static class CountOutputStream extends OutputStream {
long count_; // number of bytes counted so far
public void write( byte[] b ) {
count_ += b.length;
}
public void write( byte[] b, int off, int len ) {
count_ += len;
}
public void write( int b ) {
count_++;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OpenPolicyResourceHandler.java 0000664 0000000 0000000 00000015451 13564500043 0027716 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import org.astrogrid.samp.httpd.HttpServer;
import org.astrogrid.samp.httpd.ServerResource;
/**
* HTTP resource handler suitable for serving static cross-origin policy files.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class OpenPolicyResourceHandler implements HttpServer.Handler {
private final String policyPath_;
private final ServerResource policyResource_;
private final OriginAuthorizer authorizer_;
private final HttpServer.Response response405_;
/**
* Constructor.
*
* @param policyPath path at which the policy file will reside on
* this handler's server
* @param policyResource content of policy file
* @param authorizer controls who is permitted to view the policy file
*/
public OpenPolicyResourceHandler( String policyPath,
ServerResource policyResource,
OriginAuthorizer authorizer ) {
policyPath_ = policyPath;
policyResource_ = policyResource;
authorizer_ = authorizer;
response405_ =
HttpServer.create405Response( new String[] { "GET", "HEAD", } );
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
if ( request.getUrl().equals( policyPath_ ) ) {
String method = request.getMethod();
if ( ! method.equals( "HEAD" ) &&
! method.equals( "GET" ) ) {
return response405_;
}
else if ( authorizer_.authorizeAll() ) {
Map hdrMap = new LinkedHashMap();
hdrMap.put( "Content-Type", policyResource_.getContentType() );
long contentLength = policyResource_.getContentLength();
if ( contentLength >= 0 ) {
hdrMap.put( "Content-Length",
Long.toString( contentLength ) );
}
if ( method.equals( "HEAD" ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) {
}
};
}
else if ( method.equals( "GET" ) ) {
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out )
throws IOException {
policyResource_.writeBody( out );
}
};
}
else {
assert false;
return response405_;
}
}
else {
return HttpServer.createErrorResponse( 404, "Not found" );
}
}
else {
return null;
}
}
/**
* Creates a handler suitable for serving static cross-origin policy files.
*
* @param path path at which the policy file will reside on the
* handler's HTTP server
* @param contentUrl external URL at which the resource contents
* can be found; this will be retrieved once and
* cached
* @param oAuth controls who is permitted to retrieve the policy file
*/
public static HttpServer.Handler
createPolicyHandler( String path, URL contentUrl,
String contentType,
OriginAuthorizer oAuth )
throws IOException {
ServerResource resource =
createCachedResource( contentUrl, contentType );
return new OpenPolicyResourceHandler( path, resource, oAuth );
}
/**
* Returns a handler which can serve the /crossdomain.xml file
* used by Adobe Flash. The policy file permits access from anywhere.
*
* @param oAuth controls who is permitted to retrieve the policy file
* @return policy file handler
* @see Adobe Flash cross-origin policy
*/
public static HttpServer.Handler
createFlashPolicyHandler( OriginAuthorizer oAuth )
throws IOException {
return createPolicyHandler( "/crossdomain.xml",
OpenPolicyResourceHandler.class
.getResource( "crossdomain.xml" ),
"text/x-cross-domain-policy", oAuth );
}
/**
* Returns a handler which can serve the /clientaccesspolicy.xml file
* used by Microsoft Silverlight. The policy file permits access
* from anywhere.
*
* @param oAuth controls who is permitted to retrieve the policy file
* @return policy file handler
* @see MS Silverlight cross-origin policy
*/
public static HttpServer.Handler
createSilverlightPolicyHandler( OriginAuthorizer oAuth )
throws IOException {
return createPolicyHandler( "/clientaccesspolicy.xml",
OpenPolicyResourceHandler.class
.getResource( "clientaccesspolicy.xml" ),
"text/xml", oAuth );
}
/**
* Returns a ServerResource which caches the contents of a given
* (presumably smallish and unchanging) external resource.
*
* @param dataUrl location of external resource
* @param contentType MIME type for content of dataUrl
* @return new cached resource representing content of
* dataUrl
*/
private static ServerResource
createCachedResource( URL dataUrl, final String contentType )
throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
InputStream uin = dataUrl.openStream();
byte[] buf = new byte[ 1024 ];
for ( int count; ( count = uin.read( buf ) ) >= 0; ) {
bout.write( buf, 0, count );
}
bout.close();
final byte[] data = bout.toByteArray();
return new ServerResource() {
public long getContentLength() {
return data.length;
}
public String getContentType() {
return contentType;
}
public void writeBody( OutputStream out ) throws IOException {
out.write( data );
}
};
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OriginAuthorizer.java 0000664 0000000 0000000 00000001424 13564500043 0026126 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
/**
* Controls which origins are authorized to perform cross-origin access
* to resources.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public interface OriginAuthorizer {
/**
* Indicates whether a client with a given origin is permitted
* to access resources.
*
* @param origin client Origin
* @return true iff access is permitted
* @see Web Origin concept
*/
boolean authorize( String origin );
/**
* Indicates whether clients from arbitrary origins (including none)
* are permitted to access resources.
*
* @return true iff access is permitted
*/
boolean authorizeAll();
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OriginAuthorizers.java 0000664 0000000 0000000 00000011276 13564500043 0026317 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility class containing OriginAuthorizer implementations.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class OriginAuthorizers {
/** OriginAuthorizer which always denies access. */
public static final OriginAuthorizer FALSE =
createFixedOriginAuthorizer( false, false );
/** OriginAuthorizer which always permits access. */
public static final OriginAuthorizer TRUE =
createFixedOriginAuthorizer( true, true );
/** OriginAuthorizer which queries the user via a popup dialogue. */
public static final OriginAuthorizer SWING =
createMemoryOriginAuthorizer(
createLoggingOriginAuthorizer( new SwingOriginAuthorizer( null ),
Level.INFO, Level.WARNING ) );
private static final Logger logger_ =
Logger.getLogger( OriginAuthorizers.class.getName() );
/**
* Private constructor prevents instantiation.
*/
private OriginAuthorizers() {
}
/**
* Returns an OriginAuthorizer with fixed responses, regardless of input.
*
* @param individualPolicy invariable response of
* authorize
method
* @param generalPolicy invariable response of
* authorizeAll
method
*/
public static OriginAuthorizer
createFixedOriginAuthorizer( final boolean individualPolicy,
final boolean generalPolicy ) {
return new OriginAuthorizer() {
public boolean authorize( String origin ) {
return individualPolicy;
}
public boolean authorizeAll() {
return generalPolicy;
}
};
}
/**
* Returns an OriginAuthorizer based on an existing one which logs
* responses.
*
* @param auth base authorizer
* @param acceptLevel level at which acceptances will be logged
* @param refuseLevel level at which refusals will be logged
*/
public static OriginAuthorizer
createLoggingOriginAuthorizer( final OriginAuthorizer auth,
final Level acceptLevel,
final Level refuseLevel ) {
return new OriginAuthorizer() {
public synchronized boolean authorize( String origin ) {
boolean accept = auth.authorize( origin );
log( accept, "\"" + origin + "\"" );
return accept;
}
public synchronized boolean authorizeAll() {
boolean accept = auth.authorizeAll();
log( accept, "all origins" );
return accept;
}
private void log( boolean accept, String domain ) {
if ( accept ) {
logger_.log( acceptLevel,
"Accepted cross-origin requests for "
+ domain );
}
else {
logger_.log( refuseLevel,
"Rejected cross-origin requests for "
+ domain );
}
}
};
}
/**
* Returns an OriginAuthorizer based on an existing one which caches
* responses.
*
* @param auth base authorizer
*/
public static OriginAuthorizer
createMemoryOriginAuthorizer( final OriginAuthorizer auth ) {
return new OriginAuthorizer() {
private final OriginAuthorizer baseAuth_ = auth;
private final Set acceptedSet_ = new HashSet();
private final Set refusedSet_ = new HashSet();
private Boolean authorizeAll_;
public synchronized boolean authorize( String origin ) {
if ( refusedSet_.contains( origin ) ) {
return false;
}
else if ( acceptedSet_.contains( origin ) ) {
return true;
}
else {
boolean accepted = baseAuth_.authorize( origin );
( accepted ? acceptedSet_ : refusedSet_ ).add( origin );
return accepted;
}
}
public synchronized boolean authorizeAll() {
if ( authorizeAll_ == null ) {
authorizeAll_ = Boolean.valueOf( baseAuth_.authorizeAll() );
}
return authorizeAll_.booleanValue();
}
};
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/SwingOriginAuthorizer.java 0000664 0000000 0000000 00000003105 13564500043 0027134 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.awt.Component;
import javax.swing.JOptionPane;
/**
* OriginAuthorizer which uses a popup dialogue to ask the user.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
class SwingOriginAuthorizer implements OriginAuthorizer {
private final Component parent_;
/**
* Constructor.
*
* @param parent parent component
*/
public SwingOriginAuthorizer( Component parent ) {
parent_ = parent;
}
public boolean authorize( String origin ) {
return getResponse( new String[] {
"Is the following origin authorized for cross-domain HTTP access?",
" " + origin,
} );
}
public boolean authorizeAll() {
return getResponse( new String[] {
"Are all origins authorized for cross-domain HTTP access?",
} );
}
/**
* Presents some lines of text to the user and solicits a yes/no
* response from them.
* This method does not need to be called from the AWT event dispatch
* thread.
*
* @param lines lines of formatted plain text
* (not too many; not too long)
* @return true/false for use yes/no response
*/
protected boolean getResponse( String[] lines ) {
return JOptionPane
.showOptionDialog( parent_, lines, "Security",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null,
new String[] { "Yes", "No" }, "No" )
== 0;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/UrlTracker.java 0000664 0000000 0000000 00000012400 13564500043 0024674 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Keeps track of which URLs have been seen in communications inbound to
* and outbound from Web Profile clients.
* On the basis of these observations it is able to advise whether a
* Web Profile client ought to be permitted to dereference a given URL.
* The idea is that a Web Profile client, which may not be entirely
* trustworthy, has no legitimate reason for dereferencing an arbitrary
* URL, and should only be permitted to dereference local URLs if they
* have previously been sent as message arguments to it.
* (so for instance an attempt to read file:///etc/password is likely to
* be blocked).
* Since a SAMP client may be able to provoke some kind of echo, any
* URL which was mentioned by a Web Profile client before any other
* client mentions it is automatically marked as suspicious.
*
* map
*/
private URL[] scanForUrls( Map map ) {
Collection urlSet = new HashSet();
scanForUrls( map, urlSet );
return (URL[]) urlSet.toArray( new URL[ 0 ] );
}
/**
* Recursively scans a SAMP data item for items that look like URLs
* and appends them into a supplied list.
*
* @param item SAMP data item (String, List or Map)
* @param urlSet list of URL objects to which URLs can be added
*/
private void scanForUrls( Object item, Collection urlSet ) {
if ( item instanceof String ) {
if ( isUrl( (String) item ) ) {
try {
urlSet.add( new URL( (String) item ) );
}
catch ( MalformedURLException e ) {
}
}
}
else if ( item instanceof List ) {
for ( Iterator it = ((List) item).iterator(); it.hasNext(); ) {
scanForUrls( it.next(), urlSet );
}
}
else if ( item instanceof Map ) {
for ( Iterator it = ((Map) item).values().iterator();
it.hasNext(); ) {
scanForUrls( it.next(), urlSet );
}
}
}
/**
* Determines whether a given string is apparently a URL.
*
* @param str string to test
* @return true iff str
looks like a URL
*/
private boolean isUrl( String str ) {
if ( str == null || str.indexOf( ":/" ) <= 0 ) {
return false;
}
else {
try {
new URL( str );
return true;
}
catch ( MalformedURLException e ) {
return false;
}
}
}
public void setCallable( CallableClient callable ) throws SampException {
base_.setCallable( new UrlTrackerCallableClient( callable ) );
}
public void notify( String recipientId, Map msg ) throws SampException {
base_.notify( recipientId, scanOutgoing( msg ) );
}
public List notifyAll( Map msg ) throws SampException {
return base_.notifyAll( scanOutgoing( msg ) );
}
public String call( String recipientId, String msgTag, Map msg )
throws SampException {
return base_.call( recipientId, msgTag, scanOutgoing( msg ) );
}
public Map callAll( String msgTag, Map msg ) throws SampException {
return base_.callAll( msgTag, scanOutgoing( msg ) );
}
public Response callAndWait( String recipientId, Map msg, int timeout )
throws SampException {
return (Response)
scanIncoming( base_.callAndWait( recipientId,
scanOutgoing( msg ),
timeout ) );
}
public void reply( String msgId, Map response ) throws SampException {
base_.reply( msgId, scanOutgoing( response ) );
}
public RegInfo getRegInfo() {
return (RegInfo) scanIncoming( base_.getRegInfo() );
}
public void ping() throws SampException {
base_.ping();
}
public void unregister() throws SampException {
base_.unregister();
}
public void declareMetadata( Map meta ) throws SampException {
base_.declareMetadata( scanOutgoing( meta ) );
}
public Metadata getMetadata( String clientId ) throws SampException {
return (Metadata) scanIncoming( base_.getMetadata( clientId ) );
}
public void declareSubscriptions( Map subs ) throws SampException {
base_.declareSubscriptions( scanOutgoing( subs ) );
}
public Subscriptions getSubscriptions( String clientId )
throws SampException {
return (Subscriptions)
scanIncoming( base_.getSubscriptions( clientId ) );
}
public String[] getRegisteredClients() throws SampException {
return base_.getRegisteredClients();
}
public Map getSubscribedClients( String mtype ) throws SampException {
return scanIncoming( base_.getSubscribedClients( mtype ) );
}
/**
* CallableClient wrapper implementation which intercepts
* communications, scans the payloads for URLs, and informs an
* associated UrlTracker.
*/
private class UrlTrackerCallableClient implements CallableClient {
private final CallableClient baseCallable_;
/**
* Constructor.
*
* @param baseCallable object on which this one is based
*/
UrlTrackerCallableClient( CallableClient baseCallable ) {
baseCallable_ = baseCallable;
}
public void receiveCall( String senderId, String msgId, Message msg )
throws Exception {
baseCallable_.receiveCall( senderId, msgId,
(Message) scanIncoming( msg ) );
}
public void receiveNotification( String senderId, Message msg )
throws Exception {
baseCallable_.receiveNotification( senderId,
(Message) scanIncoming( msg ) );
}
public void receiveResponse( String responderId, String msgTag,
Response response )
throws Exception {
baseCallable_.receiveResponse( responderId, msgTag,
(Response)
scanIncoming( response ) );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebCallableClient.java 0000664 0000000 0000000 00000007754 13564500043 0026132 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.SampException;
/**
* CallableClient implementation used internally by the Web Profile hub.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
class WebCallableClient implements CallableClient {
private final List queue_;
private final int capacity_;
private boolean ended_;
/** Default maximum for queued callbacks. */
public final static int DEFAULT_CAPACITY = 4096;
/**
* Constructs a callable client with default maximum capacity.
*/
public WebCallableClient() {
this( DEFAULT_CAPACITY );
}
/**
* Constructs a callable client with a given maximum callback capacity.
*
* @param capacity maximum number of queued callbacks
*/
public WebCallableClient( int capacity ) {
capacity_ = capacity;
queue_ = new ArrayList();
}
/**
* Blocks for up to a given number of seconds or until any callbacks
* are ready, then returns any ready callbacks.
*
* @param timeout timeout in seconds
* @return list of {@link Callback}-like Maps
*/
public List pullCallbacks( int timeout ) throws SampException {
// Calculate the timeout epoch as currentTimeMillis.
long end = timeout >= 0 ? System.currentTimeMillis() + timeout * 1000
: Long.MAX_VALUE;
// Wait until either there are callbacks or timeout is reached.
try {
synchronized ( queue_ ) {
while ( queue_.isEmpty() &&
end - System.currentTimeMillis() > 0 &&
! ended_ ) {
queue_.wait( end - System.currentTimeMillis() );
}
// Remove available callbacks from the queue, if any,
// and return them.
List callbacks = new ArrayList( queue_ );
queue_.clear();
return callbacks;
}
}
catch ( InterruptedException e ) {
throw new SampException( "Interrupted", e );
}
}
public void receiveNotification( String senderId, Message message ) {
enqueue( "receiveNotification",
new Object[] { senderId, message } );
}
public void receiveCall( String senderId, String msgId, Message message ) {
enqueue( "receiveCall",
new Object[] { senderId, msgId, message } );
}
public void receiveResponse( String responderId, String msgTag,
Response response ) {
enqueue( "receiveResponse",
new Object[] { responderId, msgTag, response } );
}
/**
* Informs this client that no further callbacks (receive* methods)
* will be made on it.
*/
public void endCallbacks() {
ended_ = true;
synchronized ( queue_ ) {
queue_.notifyAll();
}
}
/**
* Adds a new callback to the queue which can be passed out via the
* {@link #pullCallbacks} method.
*
* @param methodName callback method name
* @param params callback parameter list
*/
private void enqueue( String methodName, Object[] params ) {
Callback callback =
new Callback( WebClientProfile.WEBSAMP_CLIENT_PREFIX + methodName,
Arrays.asList( params ) );
callback.check();
synchronized ( queue_ ) {
if ( queue_.size() < capacity_ ) {
queue_.add( callback );
queue_.notifyAll();
}
else {
throw new IllegalStateException( "Callback queue is full"
+ " (" + capacity_
+ " objects)" );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebClientProfile.java 0000664 0000000 0000000 00000013767 13564500043 0026034 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Platform;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClient;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory;
import org.astrogrid.samp.xmlrpc.XmlRpcKit;
/**
* ClientProfile implementation for Web Profile.
*
* @author Mark Taylor
* @since 3 Feb 2011
*/
public class WebClientProfile implements ClientProfile {
private final SampXmlRpcClientFactory xClientFactory_;
private final Map securityMap_;
private final URL hubEndpoint_;
/** Web Profile hub port number ({@value}). */
public static final int WEBSAMP_PORT = 21012;
/**
* Path on WEBSAMP_PORT web server at which XML-RPC server lives
* ({@value}).
*/
public static final String WEBSAMP_PATH = "/";
/**
* Prefix to hub interface operation names for XML-RPC method names
* ({@value}).
*/
public static final String WEBSAMP_HUB_PREFIX = "samp.webhub.";
/**
* Prefix to client interface opeation names for XML-RPC method names
* ({@value}).
*/
public static final String WEBSAMP_CLIENT_PREFIX = "";
/**
* RegInfo map key for URL translation service base URL
* ({@value}).
*/
public static final String URLTRANS_KEY = "samp.url-translator";
/**
* Prefix in SAMP_HUB value indicating web profile application name
* ({@value}).
*/
public static final String WEBPROFILE_HUB_PREFIX = "web-appname:";
/**
* Constructor with configuration options.
*
* @param securityMap map containing security information for registration
* @param xClientFactory XML-RPC client factory
* @param hubEndpoint XML-RPC endpoint for hub server
*/
public WebClientProfile( Map securityMap,
SampXmlRpcClientFactory xClientFactory,
URL hubEndpoint ) {
securityMap_ = securityMap;
xClientFactory_ = xClientFactory;
hubEndpoint_ = hubEndpoint;
}
/**
* Constructor with declared client name.
*
* @param appName client's declared application name
* (samp.name entry in security-info map)
*/
public WebClientProfile( String appName ) {
this( createSecurityMap( appName ),
XmlRpcKit.getInstance().getClientFactory(),
getDefaultHubEndpoint() );
}
/**
* Constructor with no arguments. The client's declared application
* name will be as given by {@link #getDefaultAppName}.
*/
public WebClientProfile() {
this( getDefaultAppName() );
}
public boolean isHubRunning() {
try {
SampXmlRpcClient xClient =
xClientFactory_.createClient( hubEndpoint_ );
xClient.callAndWait( WEBSAMP_HUB_PREFIX + "ping", new ArrayList() );
return true;
}
catch ( IOException e ) {
return false;
}
}
public HubConnection register() throws SampException {
try {
return new WebHubConnection( xClientFactory_
.createClient( hubEndpoint_ ),
securityMap_ );
}
catch ( SampException e ) {
for ( Throwable ex = e; ex != null; ex = ex.getCause() ) {
if ( ex instanceof ConnectException ) {
return null;
}
}
throw e;
}
catch ( ConnectException e ) {
return null;
}
catch ( IOException e ) {
throw new SampException( e );
}
}
/**
* Returns the hub XML-RPC endpoint used by this profile.
*
* @return hub endpoint URL
*/
public URL getHubEndpoint() {
return hubEndpoint_;
}
/**
* Returns the hub XML-RPC endpoint defined by the Web Profile.
*
* @return Web Profile hub endpoint URL
*/
public static URL getDefaultHubEndpoint() {
String surl = "http://" + SampUtils.getLocalhost() + ":"
+ WEBSAMP_PORT + WEBSAMP_PATH;
try {
return new URL( surl );
}
catch ( MalformedURLException e ) {
throw new AssertionError( "http scheme not supported?? " + surl );
}
}
/**
* Returns a default instance of this profile.
*
* @return default web client profile instance
*/
public static WebClientProfile getInstance() {
return new WebClientProfile();
}
/**
* Returns the default application name used by this profile if none
* is supplied explicitly.
* If the SAMP_HUB environment variable has the form
* "web-appname:<appname>" it is taken from there;
* otherwise it's something like "Unknown".
*
* @return default declared client name
*/
public static String getDefaultAppName() {
String hubloc = Platform.getPlatform()
.getEnv( DefaultClientProfile.HUBLOC_ENV );
return hubloc != null && hubloc.startsWith( WEBPROFILE_HUB_PREFIX )
? hubloc.substring( WEBPROFILE_HUB_PREFIX.length() )
: "Unknown Application";
}
/**
* Constructs a security-info map suitable for presentation at
* registration time, containing the mandatory samp.name entry.
*
* @param appName samp.name entry
* @return security map
*/
private static Map createSecurityMap( String appName ) {
Map map = new HashMap();
map.put( Metadata.NAME_KEY, appName );
return map;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebCredentialPresenter.java 0000664 0000000 0000000 00000011124 13564500043 0027220 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
/**
* CredentialPresenter for use with the Web Profile.
*
*
*
* and the following HTTP headers:
*
*
*
* uri
,
* null
(note: not "null") if it cannot be determined
*/
private String getOrigin( String uri ) {
if ( uri == null ) {
return null;
}
URL url;
try {
url = new URL( uri );
}
catch ( MalformedURLException e ) {
return null;
}
String scheme = url.getProtocol();
String host = url.getHost();
int portnum = url.getPort();
StringBuffer sbuf = new StringBuffer()
.append( scheme )
.append( "://" )
.append( host );
if ( portnum >= 0 && portnum != url.getDefaultPort() ) {
sbuf.append( ":" )
.append( Integer.toString( portnum ) );
}
return sbuf.toString().toLowerCase();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubActor.java 0000664 0000000 0000000 00000014306 13564500043 0024772 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.client.SampException;
/**
* Defines the XML-RPC methods which must be implemented by a
* Web Profile hub.
* The register method is handled separately, since it has special
* requirements as regards the HTTP request that it arrives on.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
interface WebHubActor {
/**
* Throws an exception if service is not operating.
*/
void ping() throws SampException;
/**
* Throws an exception if service is not operating.
*
* @param privateKey ignored
*/
void ping( String privateKey ) throws SampException;
/**
* Unregisters a registered client.
*
* @param privateKey calling client private key
*/
void unregister( String privateKey ) throws SampException;
/**
* Declares metadata for the calling client.
*
* @param privateKey calling client private key
* @param meta {@link org.astrogrid.samp.Metadata}-like map
*/
void declareMetadata( String privateKey, Map meta ) throws SampException;
/**
* Returns metadata for a given client.
*
* @param privateKey calling client private key
* @param clientId public ID for client whose metadata is required
* @return {@link org.astrogrid.samp.Metadata}-like map
*/
Map getMetadata( String privateKey, String clientId ) throws SampException;
/**
* Declares subscription information for the calling client.
*
* @param privateKey calling client private key
* @param subs {@link org.astrogrid.samp.Subscriptions}-like map
*/
void declareSubscriptions( String privateKey, Map subs )
throws SampException;
/**
* Returns subscriptions for a given client.
*
* @param privateKey calling client private key
* @return {@link org.astrogrid.samp.Subscriptions}-like map
*/
Map getSubscriptions( String privateKey, String clientId )
throws SampException;
/**
* Returns a list of the public-ids of all currently registered clients.
*
* @param privateKey calling client private key
* @return list of Strings
*/
List getRegisteredClients( String privateKey ) throws SampException;
/**
* Returns a map of the clients subscribed to a given MType.
*
* @param privateKey calling client private key
* @param mtype MType of interest
* @return map in which the keys are the public-ids of clients subscribed
* to mtype
*/
Map getSubscribedClients( String privateKey, String mtype )
throws SampException;
/**
* Sends a message to a given client without wanting a response.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
*/
void notify( String privateKey, String recipientId, Map msg )
throws SampException;
/**
* Sends a message to all subscribed clients without wanting a response.
*
* @param privateKey calling client private key
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return list of public-ids for clients to which the notify will be sent
*/
List notifyAll( String privateKey, Map msg )
throws SampException;
/**
* Sends a message to a given client expecting a response.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return message ID
*/
String call( String privateKey, String recipientId, String msgTag, Map msg )
throws SampException;
/**
* Sends a message to all subscribed clients expecting responses.
*
* @param privateKey calling client private key
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return public-id->msg-id map for clients to which an attempt to
* send the call will be made
*/
Map callAll( String privateKey, String msgTag, Map msg )
throws SampException;
/**
* Sends a message synchronously to a client.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param timeout timeout in seconds encoded as a SAMP int
* @return {@link org.astrogrid.samp.Response}-like map
*/
Map callAndWait( String privateKey, String recipientId, Map msg,
String timeout ) throws SampException;
/**
* Responds to a previously sent message.
*
* @param privateKey calling client private key
* @param msgId ID associated with earlier send
* @param response {@link org.astrogrid.samp.Response}-like map
*/
void reply( String privateKey, String msgId, Map response )
throws SampException;
/**
* Indicates that the client will or will not be calling
* {@link #pullCallbacks} to receive callable client-type
* callbacks until further notice.
*
* @param privateKey calling client private key
* @param allow flag indicating that the client will/will not
* be pulling callbacks, encoded as a SAMP boolean ("1"/"0")
*/
void allowReverseCallbacks( String privateKey, String allow )
throws SampException;
/**
* Waits for up to a certain length of time for any callbacks to be
* delivered.
*
* @param privateKey calling client private key
* @param timeout timeout in seconds encoded as a SAMP int
* @return list of {@link Callback}-like maps ready for
* processing by the client; may be empty if none are ready
*/
List pullCallbacks( String privateKey, String timeout )
throws SampException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubConnection.java 0000664 0000000 0000000 00000014161 13564500043 0026020 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClient;
import org.astrogrid.samp.xmlrpc.XmlRpcHubConnection;
/**
* HubConnection implementation for the Web Profile.
*
* @author Mark Taylor
* @since 3 Feb 2011
*/
class WebHubConnection extends XmlRpcHubConnection {
private final String appName_;
private final String clientKey_;
private CallWorker callWorker_;
private static Logger logger_ =
Logger.getLogger( WebHubConnection.class.getName() );
/**
* Constructor.
*
* @param xClient XML-RPC client
* @param securityMap security information map
* @param appName client's declared name
*/
public WebHubConnection( SampXmlRpcClient xClient, Map securityMap )
throws SampException {
super( xClient, WebClientProfile.WEBSAMP_HUB_PREFIX,
Collections.singletonList( securityMap ) );
Object nameObj = securityMap.get( Metadata.NAME_KEY );
appName_ = nameObj instanceof String ? (String) nameObj : "??";
clientKey_ = getRegInfo().getPrivateKey();
}
public Object getClientKey() {
return clientKey_;
}
public void setCallable( CallableClient client ) throws SampException {
CallWorker oldWorker = callWorker_;
callWorker_ = null;
if ( oldWorker != null ) {
oldWorker.stopped_ = true;
}
exec( "allowReverseCallbacks",
new Object[] { SampUtils.encodeBoolean( client != null ) } );
if ( client != null ) {
CallWorker callWorker = new CallWorker( this, client, appName_ );
callWorker.start();
callWorker_ = callWorker;
}
}
/**
* Thread that performs repeated long polls to pull callbacks from the
* hub and passes them on to this connection's CallableClient for
* execution.
*/
private static class CallWorker extends Thread {
private final XmlRpcHubConnection xconn_;
private final CallableClient client_;
private final int timeoutSec_ = 60 * 10;
private final long minWaitMillis_ = 5 * 1000;
private volatile boolean stopped_;
/**
* Constructor.
*
* @param xconn hub connection
* @parma client callable client
* @param appName client's name
*/
CallWorker( XmlRpcHubConnection xconn, CallableClient client,
String appName ) {
super( "Web Profile Callback Puller for " + appName );
xconn_ = xconn;
client_ = client;
setDaemon( true );
}
public void run() {
String stimeout = SampUtils.encodeInt( timeoutSec_ );
while ( true && ! stopped_ ) {
long start = System.currentTimeMillis();
Object result;
try {
result = xconn_.exec( "pullCallbacks",
new Object[] { stimeout } );
}
catch ( Exception e ) {
long wait = System.currentTimeMillis() - start;
if ( wait < minWaitMillis_ ) {
seriousError( e );
}
else {
logger_.config( "pullCallbacks timeout? "
+ ( wait / 1000 ) + "s" );
}
break;
}
catch ( Throwable e ) {
seriousError( e );
break;
}
if ( ! stopped_ ) {
if ( result instanceof List ) {
List resultList = (List) result;
for ( Iterator it = resultList.iterator();
it.hasNext(); ) {
try {
final Callback cb =
new Callback( (Map) it.next() );
new Thread( "Web Profile Callback" ) {
public void run() {
try {
ClientCallbackOperation
.invoke( cb, client_ );
}
catch ( Throwable e ) {
logger_.log( Level.WARNING,
"Callback failure: "
+ e.getMessage(), e );
}
}
}.start();
}
catch ( Throwable e ) {
logger_.log( Level.WARNING, e.getMessage(), e );
}
}
}
else {
logger_.warning( "pullCallbacks result "
+ "is not a List - ignore" );
}
}
}
}
/**
* Invoked if there is a serious (non-timeout) error when polling
* for callbacks. This currently stops the polling for good.
* That may be a drastic response, but at least it prevents
* repeated high-frequency polling attempts to a broken server,
* which might otherwise result.
*
* @parm e error which caused the trouble
*/
private void seriousError( Throwable e ) {
stopped_ = true;
logger_.log( Level.WARNING,
"Fatal pullCallbacks error - stopped listening", e );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubProfile.java 0000664 0000000 0000000 00000037712 13564500043 0025330 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.logging.Logger;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.httpd.HttpServer;
import org.astrogrid.samp.hub.ConfigHubProfile;
import org.astrogrid.samp.hub.HubProfile;
import org.astrogrid.samp.hub.KeyGenerator;
import org.astrogrid.samp.hub.MessageRestriction;
import org.astrogrid.samp.xmlrpc.internal.InternalServer;
import org.astrogrid.samp.xmlrpc.internal.RpcLoggingInternalServer;
import org.astrogrid.samp.xmlrpc.internal.XmlLoggingInternalServer;
/**
* HubProfile implementation for Web Profile.
*
* @author Mark Taylor
* @author Laurent Bourges
* @since 2 Feb 2011
*/
public class WebHubProfile implements HubProfile, ConfigHubProfile {
private final ServerFactory serverFactory_;
private final ClientAuthorizer auth_;
private final KeyGenerator keyGen_;
private final ConfigEnabler configEnabler_;
private final ConfigEnabler configDisabler_;
private MessageRestriction mrestrict_;
private boolean controlUrls_;
private InternalServer xServer_;
private JToggleButton.ToggleButtonModel[] configModels_;
private static final Logger logger_ =
Logger.getLogger( WebHubProfile.class.getName() );
/**
* Constructs a profile with configuration options.
*
* @param serverFactory factory for server providing HTTP
* and XML-RPC implementation
* @param auth client authorizer implementation
* @param mrestrict restriction for permitted outward MTypes
* @param keyGen key generator for private keys
* @param controlUrls true iff access to local URLs is to be restricted
*/
public WebHubProfile( ServerFactory serverFactory, ClientAuthorizer auth,
MessageRestriction mrestrict,
KeyGenerator keyGen, boolean controlUrls ) {
serverFactory_ = serverFactory;
auth_ = auth;
mrestrict_ = mrestrict;
keyGen_ = keyGen;
controlUrls_ = controlUrls;
/* These Runnables are set up here rather than being defined as
* anonymous classes where they are used to work round an obscure
* JNLP bug related to classloading and JVM shutdown. */
configEnabler_ = new ConfigEnabler( true );
configDisabler_ = new ConfigEnabler( false );
}
/**
* Constructs a profile with default configuration.
*/
public WebHubProfile() throws IOException {
this( new ServerFactory(),
new HubSwingClientAuthorizer( null,
WebCredentialPresenter.INSTANCE ),
ListMessageRestriction.DEFAULT, createKeyGenerator(), true );
}
public String getProfileName() {
return "Web";
}
public MessageRestriction getMessageRestriction() {
return mrestrict_;
}
public synchronized void start( ClientProfile profile ) throws IOException {
if ( isRunning() ) {
logger_.info( "Profile already running" );
return;
}
xServer_ = serverFactory_.createSampXmlRpcServer();
HttpServer hServer = xServer_.getHttpServer();
WebHubXmlRpcHandler wxHandler =
new WebHubXmlRpcHandler( profile, auth_, keyGen_,
hServer.getBaseUrl(),
controlUrls_ ? new UrlTracker() : null );
logger_.info( "Web Profile URL controls: "
+ ( controlUrls_ ? "on" : "off" ) );
logger_.info( "Web Profile MType restrictions: "
+ mrestrict_ );
xServer_.addHandler( wxHandler );
hServer.addHandler( wxHandler.getUrlTranslationHandler() );
hServer.start();
if ( configModels_ != null ) {
SwingUtilities.invokeLater( configDisabler_ );
}
}
public synchronized boolean isRunning() {
return xServer_ != null;
}
public synchronized void stop() {
if ( ! isRunning() ) {
logger_.info( "Profile already stopped" );
return;
}
xServer_.getHttpServer().stop();
xServer_ = null;
if ( configModels_ != null ) {
SwingUtilities.invokeLater( configEnabler_ );
}
}
public synchronized JToggleButton.ToggleButtonModel[] getConfigModels() {
if ( configModels_ == null ) {
configModels_ = createConfigModels();
}
return configModels_;
}
/**
* Creates and returns some toggle models for configuration.
* They are only enabled when the profile is not running.
*/
private JToggleButton.ToggleButtonModel[] createConfigModels() {
ConfigModel[] models = new ConfigModel[] {
new ConfigModel( "CORS cross-domain access" ) {
void setOn( boolean on ) {
serverFactory_
.setOriginAuthorizer( on ? OriginAuthorizers.TRUE
: OriginAuthorizers.FALSE );
}
boolean isOn() {
return serverFactory_.getOriginAuthorizer().authorize( "" );
}
},
new ConfigModel( "Flash cross-domain access" ) {
void setOn( boolean on ) {
serverFactory_.setAllowFlash( on );
}
boolean isOn() {
return serverFactory_.isAllowFlash();
}
},
new ConfigModel( "Silverlight cross-domain access" ) {
void setOn( boolean on ) {
serverFactory_.setAllowSilverlight( on );
}
boolean isOn() {
return serverFactory_.isAllowSilverlight();
}
},
new ConfigModel( "URL Controls" ) {
void setOn( boolean on ) {
controlUrls_ = on;
}
boolean isOn() {
return controlUrls_;
}
},
new ConfigModel( "MType Restrictions" ) {
void setOn( boolean on ) {
mrestrict_ = on ? ListMessageRestriction.DEFAULT
: null;
}
boolean isOn() {
return mrestrict_ != null;
}
},
};
boolean enabled = ! isRunning();
for ( int i = 0; i < models.length; i++ ) {
models[ i ].setEnabled( enabled );
}
return models;
}
/**
* Convenience method to return a new key generator
* suitable for use with a WebHubProfile.
*
* @return new key generator for web hub private keys
*/
public static KeyGenerator createKeyGenerator() {
return new KeyGenerator( "wk:", 24, KeyGenerator.createRandom() );
}
/**
* Runnable to be called on the Event Dispatch Thread which sets the
* enabledness of the user controls for configuration of this profile.
*/
private class ConfigEnabler implements Runnable {
private final boolean isEnabled_;
/**
* Constructor.
*
* @param isEnabled status assigned to config controls by calling
* this object's run() method
*/
ConfigEnabler( boolean isEnabled ) {
isEnabled_ = isEnabled;
}
public void run() {
JToggleButton.ToggleButtonModel[] configModels = configModels_;
if ( configModels != null ) {
for ( int i = 0; i < configModels.length; i++ ) {
configModels[ i ].setEnabled( isEnabled_ );
}
}
}
}
/**
* Helper class to generate toggle button models for hub configuration.
*/
private static abstract class ConfigModel
extends JToggleButton.ToggleButtonModel {
private final String name_;
/**
* Constructor.
*
* @param name control name
*/
public ConfigModel( String name ) {
name_ = name;
}
/**
* Indicates whether this toggle is on.
*
* @return true iff selected
*/
abstract boolean isOn();
/**
* Sets whether this toggle is on.
*
* @param on new selected value
*/
abstract void setOn( boolean on );
public boolean isSelected() {
return isOn();
}
public void setSelected( boolean on ) {
setOn( on );
super.setSelected( on );
}
public String toString() {
return name_;
}
}
/**
* Creates and configures the HTTP server on which the Web Profile resides.
*/
public static class ServerFactory {
private String logType_;
private int port_;
private String xmlrpcPath_;
private boolean allowFlash_;
private boolean allowSilverlight_;
private OriginAuthorizer oAuth_;
/**
* Constructs a ServerFactory with default properties.
*/
public ServerFactory() {
logType_ = null;
port_ = WebClientProfile.WEBSAMP_PORT;
xmlrpcPath_ = WebClientProfile.WEBSAMP_PATH;
allowFlash_ = true;
allowSilverlight_ = false;
oAuth_ = OriginAuthorizers.TRUE;
}
/**
* Returns a new internal server.
*
* @return new server for use with WebHubProfile
*/
public InternalServer createSampXmlRpcServer() throws IOException {
String path = getXmlrpcPath();
ServerSocket socket = createServerSocket( getPort() );
String logType = getLogType();
OriginAuthorizer oAuth = getOriginAuthorizer();
PrintStream logOut = System.err;
CorsHttpServer hServer = "http".equals( logType )
? new LoggingCorsHttpServer( socket, oAuth,
logOut )
: new CorsHttpServer( socket, oAuth );
if ( isAllowFlash() ) {
hServer.addHandler( OpenPolicyResourceHandler
.createFlashPolicyHandler( oAuth ) );
logger_.info( "Web Profile HTTP server permits "
+ "Flash-style cross-domain access" );
}
else {
logger_.info( "Web Profile HTTP server does not permit "
+ "Flash-style cross-domain access" );
}
if ( isAllowSilverlight() ) {
hServer.addHandler( OpenPolicyResourceHandler
.createSilverlightPolicyHandler( oAuth ) );
logger_.info( "Web Profile HTTP server permits "
+ "Silverlight-style cross-domain access" );
}
else {
logger_.info( "Web Profile HTTP server does not permit "
+ "Silverlight-style cross-domain access" );
}
hServer.setDaemon( true );
if ( "rpc".equals( logType ) ) {
return new RpcLoggingInternalServer( hServer, path, logOut );
}
else if ( "xml".equals( logType ) ) {
return new XmlLoggingInternalServer( hServer, path, logOut );
}
else if ( "none".equals( logType ) || "http".equals( logType ) ||
logType == null || logType.length() == 0 ) {
return new InternalServer( hServer, path );
}
else {
throw new IllegalArgumentException( "Unknown logType "
+ logType );
}
}
/**
* Sets the type of logging to use.
*
* @param logType logging type;
* may be "http", "rpc", "xml", "none" or null
*/
public void setLogType( String logType ) {
if ( logType == null ||
logType.equals( "http" ) ||
logType.equals( "rpc" ) ||
logType.equals( "xml" ) ||
logType.equals( "none" ) ) {
logType_ = logType;
}
else {
throw new IllegalArgumentException( "Unknown log type "
+ logType );
}
}
/**
* Returns the type of logging to use.
*
* @return logging type; may be "http", "rpc", "xml", "none" or null
*/
public String getLogType() {
return logType_;
}
/**
* Sets the port number the server will run on.
* If port=0, then an unused port will be used at run time.
*
* @param port port number
*/
public void setPort( int port ) {
port_ = port;
}
/**
* Returns the port number the server will run on.
*
* @return port number
*/
public int getPort() {
return port_;
}
/**
* Sets the path on the HTTP server at which the XML-RPC server
* will reside.
*
* @param xmlrpcPath server path for XML-RPC server
*/
public void setXmlrpcPath( String xmlrpcPath ) {
xmlrpcPath_ = xmlrpcPath;
}
/**
* Returns the path on the HTTP server at which the XML-RPC server
* will reside.
*
* @return XML-RPC path on server
*/
public String getXmlrpcPath() {
return xmlrpcPath_;
}
/**
* Sets whether Adobe Flash cross-domain workaround will be supported.
*
* @param allowFlash true iff supported
*/
public void setAllowFlash( boolean allowFlash ) {
allowFlash_ = allowFlash;
}
/**
* Indicates whether Adobe Flash cross-domain workaround
* will be supported.
*
* @return true iff supported
*/
public boolean isAllowFlash() {
return allowFlash_;
}
/**
* Sets whether Microsoft Silverlight cross-domain workaround
* will be supported.
*
* @param allowSilverlight true iff supported
*/
public void setAllowSilverlight( boolean allowSilverlight ) {
allowSilverlight_ = allowSilverlight;
}
/**
* Indicates whether Microsoft Silverlight cross-domain workaround
* will be supported.
*
* @return true iff supported
*/
public boolean isAllowSilverlight() {
return allowSilverlight_;
}
/**
* Sets the authorization policy for external origins.
*
* @param oAuth authorizer
*/
public void setOriginAuthorizer( OriginAuthorizer oAuth ) {
oAuth_ = oAuth;
}
/**
* Returns the authorization policy for external origins.
*
* @return authorizer
*/
public OriginAuthorizer getOriginAuthorizer() {
return oAuth_;
}
/**
* Creates a socket on a given port to be used by the server this
* object produces.
*
* @param port port number
* @return new server socket
*/
protected ServerSocket createServerSocket( int port )
throws IOException {
ServerSocket sock = new ServerSocket();
sock.setReuseAddress( true );
sock.bind( new InetSocketAddress( port ) );
return sock;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubProfileFactory.java 0000664 0000000 0000000 00000014710 13564500043 0026651 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import org.astrogrid.samp.hub.HubProfile;
import org.astrogrid.samp.hub.HubProfileFactory;
import org.astrogrid.samp.hub.KeyGenerator;
import org.astrogrid.samp.hub.MessageRestriction;
import org.astrogrid.samp.xmlrpc.internal.InternalServer;
/**
* HubProfileFactory implementation for Web Profile.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class WebHubProfileFactory implements HubProfileFactory {
private static final String logUsage_ = "[-web:log none|http|xml|rpc]";
private static final String authUsage_ =
"[-web:auth swing|true|false|extreme]";
private static final String corsUsage_ = "[-web:[no]cors]";
private static final String flashUsage_ = "[-web:[no]flash]";
private static final String silverlightUsage_ = "[-web:[no]silverlight]";
private static final String urlcontrolUsage_ = "[-web:[no]urlcontrol]";
private static final String restrictMtypeUsage_ =
"[-web:[no]restrictmtypes]";
/**
* Returns "web".
*/
public String getName() {
return "web";
}
public String[] getFlagsUsage() {
return new String[] {
logUsage_,
authUsage_,
corsUsage_,
flashUsage_,
silverlightUsage_,
urlcontrolUsage_,
restrictMtypeUsage_,
};
}
public HubProfile createHubProfile( List flagList ) throws IOException {
// Process flags.
String logType = "none";
String authType = "swing";
boolean useCors = true;
boolean useFlash = true;
boolean useSilverlight = false;
boolean urlControl = true;
boolean restrictMtypes = true;
for ( Iterator it = flagList.iterator(); it.hasNext(); ) {
String arg = (String) it.next();
if ( arg.equals( "-web:log" ) ) {
it.remove();
if ( it.hasNext() ) {
logType = (String) it.next();
it.remove();
}
else {
throw new IllegalArgumentException( "Usage: " + logUsage_ );
}
}
else if ( arg.equals( "-web:auth" ) ) {
it.remove();
if ( it.hasNext() ) {
authType = (String) it.next();
it.remove();
}
else {
throw new IllegalArgumentException( "Usage: "
+ authUsage_ );
}
}
else if ( arg.equals( "-web:cors" ) ) {
it.remove();
useCors = true;
}
else if ( arg.equals( "-web:nocors" ) ) {
it.remove();
useCors = false;
}
else if ( arg.equals( "-web:flash" ) ) {
it.remove();
useFlash = true;
}
else if ( arg.equals( "-web:noflash" ) ) {
it.remove();
useFlash = false;
}
else if ( arg.equals( "-web:silverlight" ) ) {
it.remove();
useSilverlight = true;
}
else if ( arg.equals( "-web:nosilverlight" ) ) {
it.remove();
useSilverlight = false;
}
else if ( arg.equals( "-web:urlcontrol" ) ) {
it.remove();
urlControl = true;
}
else if ( arg.equals( "-web:nourlcontrol" ) ) {
it.remove();
urlControl = false;
}
else if ( arg.equals( "-web:restrictmtypes" ) ) {
it.remove();
restrictMtypes = true;
}
else if ( arg.equals( "-web:norestrictmtypes" ) ) {
it.remove();
restrictMtypes = false;
}
}
// Prepare HTTP server.
WebHubProfile.ServerFactory sfact = new WebHubProfile.ServerFactory();
try {
sfact.setLogType( logType );
}
catch ( IllegalArgumentException e ) {
throw (IllegalArgumentException)
new IllegalArgumentException( "Unknown log type " + logType
+ "; Usage: " + logUsage_ )
.initCause( e );
}
sfact.setOriginAuthorizer( useCors ? OriginAuthorizers.TRUE
: OriginAuthorizers.FALSE );
sfact.setAllowFlash( useFlash );
sfact.setAllowSilverlight( useSilverlight );
// Prepare client authorizer.
final ClientAuthorizer clientAuth;
if ( "swing".equalsIgnoreCase( authType ) ) {
clientAuth =
ClientAuthorizers
.createLoggingClientAuthorizer(
new HubSwingClientAuthorizer( null,
WebCredentialPresenter
.INSTANCE ),
Level.INFO, Level.INFO );
}
else if ( "extreme".equalsIgnoreCase( authType ) ) {
clientAuth = ClientAuthorizers
.createLoggingClientAuthorizer(
new ExtremeSwingClientAuthorizer( null ),
Level.WARNING, Level.INFO );
}
else if ( "true".equalsIgnoreCase( authType ) ) {
clientAuth = ClientAuthorizers.TRUE;
}
else if ( "false".equalsIgnoreCase( authType ) ) {
clientAuth = ClientAuthorizers.FALSE;
}
else {
throw new IllegalArgumentException( "Unknown authorizer type "
+ authType + "; Usage: "
+ authUsage_ );
}
// Prepare subscriptions mask.
MessageRestriction mrestrict = restrictMtypes
? ListMessageRestriction.DEFAULT
: null;
// Construct and return an appropriately configured hub profile.
return new WebHubProfile( sfact, clientAuth, mrestrict,
WebHubProfile.createKeyGenerator(),
urlControl );
}
public Class getHubProfileClass() {
return WebHubProfile.class;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubXmlRpcHandler.java 0000664 0000000 0000000 00000042307 13564500043 0026427 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.web;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.Subscriptions;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.httpd.HttpServer;
import org.astrogrid.samp.httpd.URLMapperHandler;
import org.astrogrid.samp.hub.KeyGenerator;
import org.astrogrid.samp.xmlrpc.ActorHandler;
/**
* SampXmlRpcHandler implementation which passes Web Profile-type XML-RPC calls
* to a hub connection factory to provide a Web Profile hub server.
*
* @author Mark Taylor
* @since 2 Feb 2011
*/
public class WebHubXmlRpcHandler extends ActorHandler {
private final WebHubActorImpl impl_;
private static final Logger logger_ =
Logger.getLogger( WebHubXmlRpcHandler.class.getName() );
/**
* Constructor.
*
* @param profile hub connection factory
* @param auth client authorizer
* @param keyGen key generator for private keys
* @param baseUrl base URL of HTTP server, used for URL translation
* @param urlTracker tracks URLs in messages to restrict use in URL
* translation service for security reasons; may be null for
* no restrictions
*/
public WebHubXmlRpcHandler( ClientProfile profile, ClientAuthorizer auth,
KeyGenerator keyGen, URL baseUrl,
UrlTracker urlTracker ) {
super( WebClientProfile.WEBSAMP_HUB_PREFIX, WebHubActor.class,
new WebHubActorImpl( profile, auth, keyGen, baseUrl,
urlTracker ) );
impl_ = (WebHubActorImpl) getActor();
}
public Object handleCall( String fqName, List params, Object reqObj )
throws Exception {
String regMethod = WebClientProfile.WEBSAMP_HUB_PREFIX + "register";
if ( regMethod.equals( fqName ) &&
reqObj instanceof HttpServer.Request ) {
HttpServer.Request req = (HttpServer.Request) reqObj;
final Map securityMap;
if ( params.size() == 1 && params.get( 0 ) instanceof Map ) {
securityMap = (Map) params.get( 0 );
}
else if ( params.size() == 1 &&
params.get( 0 ) instanceof String ) {
securityMap = new HashMap();
securityMap.put( Metadata.NAME_KEY, (String) params.get( 0 ) );
logger_.info( "Deprecated register call signature "
+ "(arg is string appName not map security-info)" );
}
else {
throw new IllegalArgumentException( "Bad args for " + regMethod
+ "(map)" );
}
Map result = impl_.register( req, securityMap );
assert result != null;
return result;
}
else {
return super.handleCall( fqName, params, reqObj );
}
}
/**
* Returns a handler suitable for performing URL translations on behalf
* of sandboxed clients as required by the Web Profile.
*
* @return url translation handler
*/
public HttpServer.Handler getUrlTranslationHandler() {
return impl_.getUrlTranslationHandler();
}
protected Object invokeMethod( Method method, Object obj, Object[] args )
throws IllegalAccessException, InvocationTargetException {
return method.invoke( obj, args );
}
/**
* WebHubActor implementation.
*/
private static class WebHubActorImpl implements WebHubActor {
private final ClientProfile profile_;
private final ClientAuthorizer auth_;
private final KeyGenerator keyGen_;
private final Map regMap_;
private final URLTranslationHandler urlTranslator_;
private final URL baseUrl_;
private final UrlTracker urlTracker_;
/**
* Constructor.
*
* @param profile hub connection factory
* @param auth client authorizer
* @param keyGen key generator for private keys
* @param baseUrl HTTP server base URL
* @param urlTracker controls access to translated URLs,
* may be null for no control
*/
public WebHubActorImpl( ClientProfile profile, ClientAuthorizer auth,
KeyGenerator keyGen, URL baseUrl,
UrlTracker urlTracker ) {
profile_ = profile;
auth_ = auth;
keyGen_ = keyGen;
baseUrl_ = baseUrl;
urlTracker_ = urlTracker;
regMap_ = Collections.synchronizedMap( new HashMap() );
urlTranslator_ =
new URLTranslationHandler( "/proxied/", regMap_.keySet(),
urlTracker );
}
/**
* Returns a handler suitable for performing URL translations on behalf
* of sandboxed clients as required by the Web Profile.
*
* @return url translation handler
*/
public HttpServer.Handler getUrlTranslationHandler() {
return urlTranslator_;
}
/**
* Attempt client registration. An exception is thrown if registration
* fails for any reason.
*
* @param request HTTP request from applicant
* @param securityMap map of required security information
* supplied by applicant
* @return registration information if registration is successful
*/
public RegInfo register( HttpServer.Request request, Map securityMap )
throws SampException {
if ( profile_.isHubRunning() ) {
auth_.authorize( request, securityMap );
HubConnection connection = profile_.register();
if ( connection != null ) {
if ( urlTracker_ != null ) {
connection = new UrlTrackerHubConnection( connection,
urlTracker_ );
}
String clientKey = keyGen_.next();
regMap_.put( clientKey, new Registration( connection ) );
String urlTrans = baseUrl_
+ urlTranslator_
.getTranslationBasePath( clientKey );
RegInfo regInfo = new RegInfo( connection.getRegInfo() );
regInfo.put( RegInfo.PRIVATEKEY_KEY, clientKey );
regInfo.put( WebClientProfile.URLTRANS_KEY, urlTrans );
return regInfo;
}
else {
throw new SampException( "Hub is not running" );
}
}
else {
throw new SampException( "Hub not running" );
}
}
public void unregister( String clientKey ) throws SampException {
HubConnection connection = getConnection( clientKey );
regMap_.remove( clientKey );
connection.unregister();
}
public void allowReverseCallbacks( String clientKey, String allow )
throws SampException {
boolean isAllowed = SampUtils.decodeBoolean( allow );
Registration reg = getRegistration( clientKey );
synchronized ( reg ) {
if ( isAllowed == ( reg.callable_ != null ) ) {
return;
}
else if ( isAllowed ) {
WebCallableClient callable = new WebCallableClient();
reg.connection_.setCallable( callable );
reg.callable_ = callable;
}
else {
reg.connection_.setCallable( null );
reg.callable_.endCallbacks();
reg.callable_ = null;
}
assert isAllowed == ( reg.callable_ != null );
}
}
public List pullCallbacks( String clientKey, String timeout )
throws SampException {
WebCallableClient callable = getRegistration( clientKey ).callable_;
if ( callable != null ) {
return callable
.pullCallbacks( SampUtils.decodeInt( timeout ) );
}
else {
throw new SampException( "Client is not callable (first invoke"
+ " allowReverseCallbacks)" );
}
}
public void declareMetadata( String clientKey, Map meta )
throws SampException {
getConnection( clientKey ).declareMetadata( meta );
}
public Map getMetadata( String clientKey, String clientId )
throws SampException {
return getConnection( clientKey ).getMetadata( clientId );
}
public void declareSubscriptions( String clientKey, Map subs )
throws SampException {
getRegistration( clientKey ).subs_ =
new Subscriptions( subs == null ? new HashMap() : subs );
getConnection( clientKey ).declareSubscriptions( subs );
}
public Map getSubscriptions( String clientKey, String clientId )
throws SampException {
return getConnection( clientKey ).getSubscriptions( clientId );
}
public List getRegisteredClients( String clientKey )
throws SampException {
return Arrays.asList( getConnection( clientKey )
.getRegisteredClients() );
}
public Map getSubscribedClients( String clientKey, String mtype )
throws SampException {
return getConnection( clientKey ).getSubscribedClients( mtype );
}
public void notify( String clientKey, String recipientId, Map msg )
throws SampException {
getConnection( clientKey ).notify( recipientId, msg );
}
public List notifyAll( String clientKey, Map msg )
throws SampException {
return getConnection( clientKey ).notifyAll( msg );
}
public String call( String clientKey, String recipientId, String msgTag,
Map msg )
throws SampException {
return getConnection( clientKey ).call( recipientId, msgTag, msg );
}
public Map callAll( String clientKey, String msgTag, Map msg )
throws SampException {
return getConnection( clientKey ).callAll( msgTag, msg );
}
public Map callAndWait( String clientKey, String recipientId, Map msg,
String timeout )
throws SampException {
return getConnection( clientKey )
.callAndWait( recipientId, msg,
SampUtils.decodeInt( timeout ) );
}
public void reply( String clientKey, String msgId, Map response )
throws SampException {
getConnection( clientKey ).reply( msgId, response );
}
public void ping() {
if ( ! profile_.isHubRunning() ) {
throw new RuntimeException( "No hub running" );
}
}
public void ping( String clientKey ) {
ping();
}
/**
* Returns the registration object associated with a given private key.
*
* @param privateKey private key string known by client and hub
* to identify the connection
* @return registration object for client with key
* privateKey
* @throws SampException if no client is known with that private key
*/
private Registration getRegistration( String privateKey )
throws SampException {
Registration reg = (Registration) regMap_.get( privateKey );
if ( reg == null ) {
throw new SampException( "Unknown client key" );
}
else {
return reg;
}
}
/**
* Returns the connection object associated with a given private key.
*
* @param privateKey private key string known by client and hub
* to identify the connection
* @return connection object for client with key
* privateKey
* @throws SampException if no client is known with that private key
*/
private HubConnection getConnection( String privateKey )
throws SampException {
return getRegistration( privateKey ).connection_;
}
}
/**
* HTTP handler which provides URL translation services for sandboxed
* clients.
*/
private static class URLTranslationHandler implements HttpServer.Handler {
private final String basePath_;
private final Set keySet_;
private final UrlTracker urlTracker_;
/**
* Constructor.
*
* @param basePath base path for HTTP server
* @param keySet set of strings which contains keys for all
* currently registered clients
* @param urlTracker controls access to translated URLs,
* may be null for no control
*/
public URLTranslationHandler( String basePath, Set keySet,
UrlTracker urlTracker ) {
if ( ! basePath.startsWith( "/" ) ) {
basePath = "/" + basePath;
}
if ( ! basePath.endsWith( "/" ) ) {
basePath = basePath + "/";
}
basePath_ = basePath;
keySet_ = keySet;
urlTracker_ = urlTracker;
}
/**
* Returns the translation base path that can be used by a client
* with a given private key.
*
* @param privateKey client private key
* @return URL translation base path that can be used by a
* registered client with the given private key
*/
public String getTranslationBasePath( String privateKey ) {
return basePath_ + privateKey + "?";
}
public HttpServer.Response serveRequest( HttpServer.Request request ) {
// Ignore requests outside this handler's domain.
String path = request.getUrl();
if ( ! path.startsWith( basePath_ ) ) {
return null;
}
// Ensure the URL has a query part.
String relPath = path.substring( basePath_.length() );
int qIndex = relPath.indexOf( '?' );
if ( qIndex < 0 ) {
return HttpServer.createErrorResponse( 404, "Not Found" );
}
// Ensure a valid key for authorization is present; this makes
// sure that only registered clients can use this service.
String authKey = relPath.substring( 0, qIndex );
if ( ! keySet_.contains( authKey ) ) {
return HttpServer.createErrorResponse( 403, "Forbidden" );
}
// Extract the URL whose translation is being requested.
assert path.substring( 0, path.indexOf( '?' ) + 1 )
.equals( getTranslationBasePath( authKey ) );
String targetString;
try {
targetString =
SampUtils.uriDecode( relPath.substring( qIndex + 1 ) );
}
catch ( RuntimeException e ) {
return HttpServer.createErrorResponse( 400, "Bad Request" );
}
URL targetUrl;
try {
targetUrl = new URL( targetString );
}
catch ( MalformedURLException e ) {
return HttpServer.createErrorResponse( 400, "Bad Request" );
}
// Check permissions.
if ( urlTracker_ != null &&
! urlTracker_.isUrlPermitted( targetUrl ) ) {
return HttpServer.createErrorResponse( 403, "Forbidden" );
}
// Perform the translation and return the result.
return URLMapperHandler.mapUrlResponse( request.getMethod(),
targetUrl );
}
}
/**
* Utility class to aggregate information about a registered client.
*/
private static class Registration {
final HubConnection connection_;
WebCallableClient callable_;
Subscriptions subs_;
/**
* Constructor.
*
* @param connection hub connection
*/
Registration( HubConnection connection ) {
connection_ = connection;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/package.html 0000664 0000000 0000000 00000000071 13564500043 0024235 0 ustar 00root root 0000000 0000000 execute
requests. This insulates the implementation object
* from having to worry about any XML-RPC specifics.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public abstract class ActorHandler implements SampXmlRpcHandler {
private final String prefix_;
private final Object actor_;
private final Map methodMap_;
private final Logger logger_ =
Logger.getLogger( ActorHandler.class.getName() );
/**
* Constructor.
*
* @param prefix string prepended to every method name in the
* actorType
interface to form the XML-RPC
* methodName
element
* @param actorType interface defining the XML-RPC methods
* @param actor object implementing actorType
*/
public ActorHandler( String prefix, Class actorType, Object actor ) {
prefix_ = prefix;
actor_ = actor;
methodMap_ = new HashMap();
// Construct a map keyed by method signature of the known methods.
Method[] methods = actorType.getDeclaredMethods();
for ( int im = 0; im < methods.length; im++ ) {
Method method = methods[ im ];
if ( Modifier.isPublic( method.getModifiers() ) ) {
String name = method.getName();
Class[] clazzes = method.getParameterTypes();
SampType[] types = new SampType[ clazzes.length ];
for ( int ic = 0; ic < clazzes.length; ic++ ) {
types[ ic ] = SampType.getClassType( clazzes[ ic ] );
}
Signature sig = new Signature( prefix_ + name, types );
methodMap_.put( sig, method );
}
}
}
public boolean canHandleCall( String fqName ) {
return fqName.startsWith( prefix_ );
}
public Object handleCall( String fqName, List params, Object reqInfo )
throws Exception {
if ( ! canHandleCall( fqName ) ) {
throw new IllegalArgumentException( "No I can't" );
}
// Work out the signature for this method and see if it is recognised.
String name = fqName.substring( prefix_.length() );
List typeList = new ArrayList();
for ( Iterator it = params.iterator(); it.hasNext(); ) {
typeList.add( SampType.getParamType( it.next() ) );
}
SampType[] types = (SampType[]) typeList.toArray( new SampType[ 0 ] );
Signature sig = new Signature( fqName, types );
Method method = (Method) methodMap_.get( sig );
// If the signature is recognised, invoke the relevant method
// on the implementation object.
if ( method != null ) {
Object result;
Throwable error;
try {
result = invokeMethod( method, actor_, params.toArray() );
}
catch ( InvocationTargetException e ) {
Throwable e2 = e.getCause();
if ( e2 instanceof Error ) {
throw (Error) e2;
}
else {
throw (Exception) e2;
}
}
catch ( Exception e ) {
throw e;
}
catch ( Error e ) {
throw e;
}
return result == null ? "" : result;
}
// If the signature is not recognised, but the method name is,
// try to make a helpful comment.
else {
for ( Iterator it = methodMap_.keySet().iterator();
it.hasNext(); ) {
Signature foundSig = (Signature) it.next();
if ( foundSig.name_.equals( fqName ) ) {
throw new IllegalArgumentException(
"Bad arguments: " + foundSig + " got "
+ sig.typeList_ );
}
}
throw new UnsupportedOperationException( "Unknown method "
+ fqName );
}
}
/**
* Returns the implementation object for this handler.
*
* @return implementation object
*/
public Object getActor() {
return actor_;
}
/**
* Invokes a method reflectively on an object.
* This method should be implemented in the obvious way, that is
* return method.invoke(obj,params)
.
*
* ActorHandler
.
* That in turn means that the actorType
class specified
* in the constructor does not need to be visible from
* ActorHandler
's package, only from the package where
* the implementation class lives.
*
* @param method method to invoke
* @param obj object to invoke the method on
* @param args arguments for the method call
* @see java.lang.reflect.Method#invoke
*/
protected abstract Object invokeMethod( Method method, Object obj,
Object[] args )
throws IllegalAccessException, InvocationTargetException;
/**
* Enumeration of permitted types within a SAMP data structure.
*/
private static class SampType {
/** String type. */
public static final SampType STRING =
new SampType( String.class, "string" );
/** List type. */
public static final SampType LIST =
new SampType( List.class, "list" );
/** Map type. */
public static final SampType MAP =
new SampType( Map.class, "map" );
private final Class clazz_;
private final String name_;
/**
* Constructor.
*
* @param clazz java class
* @param name name of SAMP type
*/
private SampType( Class clazz, String name ) {
clazz_ = clazz;
name_ = name;
}
/**
* Returns the java class corresponding to this type.
*
* @return class
*/
public Class getTypeClass() {
return clazz_;
}
/**
* Returns the SAMP name for this type.
*
* @return name
*/
public String toString() {
return name_;
}
/**
* Returns the SampType corresponding to a given java class.
*
* @param clazz class
* @return SAMP type
*/
public static SampType getClassType( Class clazz ) {
if ( String.class.equals( clazz ) ) {
return STRING;
}
else if ( List.class.equals( clazz ) ) {
return LIST;
}
else if ( Map.class.equals( clazz ) ) {
return MAP;
}
else {
throw new IllegalArgumentException( "Illegal type "
+ clazz.getName() );
}
}
/**
* Returns the SampType corresponding to a given object.
*
* @param param object
* return SAMP type
*/
public static SampType getParamType( Object param ) {
if ( param instanceof String ) {
return STRING;
}
else if ( param instanceof List ) {
return LIST;
}
else if ( param instanceof Map ) {
return MAP;
}
else {
throw new DataException( "Param is not a SAMP type" );
}
}
}
/**
* Characterises a method signature.
* The equals
and hashCode
methods are
* implemented sensibly.
*/
private static class Signature {
private final String name_;
private final List typeList_;
/**
* Constructor.
*
* @param name method name
* @param types types of method arguments
*/
Signature( String name, SampType[] types ) {
name_ = name;
typeList_ = new ArrayList( Arrays.asList( types ) );
}
public boolean equals( Object o ) {
if ( o instanceof Signature ) {
Signature other = (Signature) o;
return this.name_.equals( other.name_ )
&& this.typeList_.equals( other.typeList_ );
}
else {
return false;
}
}
public int hashCode() {
int code = 999;
code = 23 * code + name_.hashCode();
code = 23 * code + typeList_.hashCode();
return code;
}
public String toString() {
return name_ + typeList_;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/CallableClientServer.java 0000664 0000000 0000000 00000010404 13564500043 0027375 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.HashMap;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
/**
* XML-RPC server which can host {@link CallableClient} instances.
* There should usually be only one instance of this class for each
* SampXmlRpcServer - see {@link #getInstance}.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
class CallableClientServer {
private final URL url_;
private SampXmlRpcServer server_;
private ClientXmlRpcHandler clientHandler_;
private static final Map serverMap_ = new HashMap();
/**
* Constructor. Note that a {@link #getInstance} method exists as well.
*
* @param server XML-RPC server hosting this client server
*/
public CallableClientServer( SampXmlRpcServer server ) throws IOException {
server_ = server;
clientHandler_ = new ClientXmlRpcHandler();
server_.addHandler( clientHandler_ );
url_ = server_.getEndpoint();
}
/**
* Returns the XML-RPC endpoint for this server.
*
* @return endpoint url
*/
public URL getUrl() {
return url_;
}
/**
* Adds a CallableClient object to this server.
*
* @param connection hub connection for the registered client on behalf
* of which the client will operate
* @param callable callable client object
*/
public void addClient( HubConnection connection, CallableClient callable ) {
if ( clientHandler_ == null ) {
throw new IllegalStateException( "Closed" );
}
clientHandler_.addClient( connection, callable );
}
/**
* Removes a CallableClient object from this server.
*
* @param privateKey hub connection for which this client was added
*/
public void removeClient( HubConnection connection ) {
if ( clientHandler_ != null ) {
clientHandler_.removeClient( connection );
}
}
/**
* Tidies up resources. Following a call to this method, no further
* clients can be added.
*/
public void close() {
if ( server_ != null ) {
server_.removeHandler( clientHandler_ );
}
server_ = null;
clientHandler_ = null;
}
/**
* Indicates whether this server currently has any clients.
*
* @return true iff there are clients
*/
boolean hasClients() {
return clientHandler_ != null && clientHandler_.getClientCount() > 0;
}
/**
* Returns an instance of CallableClientServer for use with a given
* XML-RPC server. Because of the implementation, only one
* CallableClientServer is permitted per XML-RPC server, so if one
* has already been installed for the given server
,
* that one will be returned. Otherwise a new one will be constructed,
* installed and returned.
*
* server
*/
public static synchronized CallableClientServer
getInstance( SampXmlRpcServerFactory serverFact )
throws IOException {
final SampXmlRpcServer server = serverFact.getServer();
if ( ! serverMap_.containsKey( server ) ) {
CallableClientServer clientServer =
new CallableClientServer( server ) {
public void removeClient( HubConnection connection ) {
super.removeClient( connection );
if ( ! hasClients() ) {
close();
synchronized ( CallableClientServer.class ) {
serverMap_.remove( server );
}
}
}
};
serverMap_.put( server, clientServer );
}
return (CallableClientServer) serverMap_.get( server );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/ClientActor.java 0000664 0000000 0000000 00000003077 13564500043 0025567 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.util.Map;
/**
* Defines the methods which an XML-RPC callable client must implement.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
interface ClientActor {
/**
* Receives a message for which no response is required.
*
* @param privateKey private key for hub-client communication
* @param senderId public ID of sending client
* @param msg message
*/
void receiveNotification( String privateKey, String senderId, Map msg )
throws Exception;
/**
* Receives a message for which a response is required.
* The implementation must take care to call the hub's reply
* method at some future point.
*
* @param privateKey private key for hub-client communication
* @param senderId public ID of sending client
* @param msgId message identifier for later use with reply
* @param msg message
*/
void receiveCall( String privateKey, String senderId, String msgId,
Map msg ) throws Exception;
/**
* Receives a response to a message previously sent by this client.
*
* @param privateKey private key for hub-client communication
* @param responderId public ID of responding client
* @param msgTag client-defined tag labelling previously-sent message
* @param response returned response object
*/
void receiveResponse( String privateKey, String responderId, String msgTag,
Map response ) throws Exception;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/ClientXmlRpcHandler.java 0000664 0000000 0000000 00000015225 13564500043 0027220 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.ErrInfo;
import org.astrogrid.samp.Message;
import org.astrogrid.samp.Response;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.HubConnection;
/**
* SampXmlRpcHandler implementation which passes Standard Profile-like XML-RPC
* calls to one or more {@link CallableClient}s to provide client callbacks
* from the hub.
*
* @author Mark Taylor
* @since 16 Jul 2008
*/
class ClientXmlRpcHandler extends ActorHandler {
private final ClientActorImpl clientActor_;
private static final Logger logger_ =
Logger.getLogger( ClientXmlRpcHandler.class.getName() );
/**
* Constructor.
*/
public ClientXmlRpcHandler() {
super( "samp.client.", ClientActor.class, new ClientActorImpl() );
clientActor_ = (ClientActorImpl) getActor();
}
/**
* Adds a CallableClient object to this server.
*
* @param connection hub connection for the registered client on behalf
* of which the client will operate
* @param callable callable client object
*/
public void addClient( HubConnection connection, CallableClient callable ) {
clientActor_.entryMap_.put( connection.getRegInfo().getPrivateKey(),
new Entry( connection, callable ) );
}
/**
* Removes a CallableClient object from this server.
*
* @param privateKey hub connection for which this client was added
*/
public void removeClient( HubConnection connection ) {
clientActor_.entryMap_
.remove( connection.getRegInfo().getPrivateKey() );
}
/**
* Returns the number of clients currently owned by this handler.
*
* @return client count
*/
public int getClientCount() {
return clientActor_.entryMap_.size();
}
protected Object invokeMethod( Method method, Object obj, Object[] args )
throws IllegalAccessException, InvocationTargetException {
return method.invoke( obj, args );
}
/**
* Implementation of the {@link ClientActor} interface which does the
* work for this class.
* The correct CallableClient is determined from the private key,
* and the work is then delegated to it.
*/
private static class ClientActorImpl implements ClientActor {
private Map entryMap_ = new HashMap(); // String -> Entry
public void receiveNotification( String privateKey,
final String senderId, Map msg ) {
Entry entry = getEntry( privateKey );
final CallableClient callable = entry.callable_;
final Message message = Message.asMessage( msg );
final String label = "Notify " + senderId + " "
+ message.getMType();
new Thread( label ) {
public void run() {
try {
callable.receiveNotification( senderId, message );
}
catch ( Throwable e ) {
logger_.log( Level.INFO, label + " error", e );
}
}
}.start();
}
public void receiveCall( String privateKey, final String senderId,
final String msgId, Map msg )
throws Exception {
Entry entry = getEntry( privateKey );
final CallableClient callable = entry.callable_;
final HubConnection connection = entry.connection_;
final Message message = Message.asMessage( msg );
final String label = "Call " + senderId + " " + message.getMType();
new Thread( label ) {
public void run() {
try {
callable.receiveCall( senderId, msgId, message );
}
catch ( Throwable e ) {
try {
Response response =
Response
.createErrorResponse( new ErrInfo( e ) );
connection.reply( msgId, response );
}
catch ( Throwable e2 ) {
logger_.log( Level.INFO,
label + " error replying", e2 );
}
}
}
}.start();
}
public void receiveResponse( String privateKey,
final String responderId,
final String msgTag, Map resp )
throws Exception {
Entry entry = getEntry( privateKey );
final CallableClient callable = entry.callable_;
final Response response = Response.asResponse( resp );
final String label = "Reply " + responderId;
new Thread( label ) {
public void run() {
try {
callable.receiveResponse( responderId, msgTag,
response );
}
catch ( Throwable e ) {
logger_.log( Level.INFO, label + " error replying", e );
}
}
}.start();
}
/**
* Returns the CallableClient corresponding to a given private key.
*
* @param privateKey private key for client
* @return entry identified by privateKey
* @throws IllegalStateException if privateKey
is unknown
*/
private Entry getEntry( String privateKey ) {
Object ent = entryMap_.get( privateKey );
if ( ent instanceof Entry ) {
return (Entry) ent;
}
else {
throw new IllegalStateException( "Client is not listening" );
}
}
}
/**
* Utility class to aggregate information about a client.
*/
private static class Entry {
final HubConnection connection_;
final CallableClient callable_;
/**
* Constructor.
*
* @param connection hub connection
* @param callable callable client
*/
Entry( HubConnection connection, CallableClient callable ) {
connection_ = connection;
callable_ = callable;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubActor.java 0000664 0000000 0000000 00000013345 13564500043 0025066 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.client.SampException;
/**
* Defines the XML-RPC methods which must be implemented by a
* Standard Profile hub.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
interface HubActor {
/**
* Throws an exception if service is not operating.
*/
void ping() throws SampException;
/**
* Throws an exception if service is not operating.
*
* @param privateKey ignored
*/
void ping( String privateKey ) throws SampException;
/**
* Registers a new client and returns a map with registration information.
*
* @param secret registration password
* @return {@link org.astrogrid.samp.RegInfo}-like map.
*/
Map register( String secret ) throws SampException;
/**
* Unregisters a registered client.
*
* @param privateKey calling client private key
*/
void unregister( String privateKey ) throws SampException;
/**
* Sets the XML-RPC URL to use for callbacks for a callable client.
*
* @param privateKey calling client private key
* @param url XML-RPC endpoint for client API callbacks
*/
void setXmlrpcCallback( String privateKey, String url )
throws SampException;
/**
* Declares metadata for the calling client.
*
* @param privateKey calling client private key
* @param meta {@link org.astrogrid.samp.Metadata}-like map
*/
void declareMetadata( String privateKey, Map meta )
throws SampException;
/**
* Returns metadata for a given client.
*
* @param privateKey calling client private key
* @param clientId public ID for client whose metadata is required
* @return {@link org.astrogrid.samp.Metadata}-like map
*/
Map getMetadata( String privateKey, String clientId )
throws SampException;
/**
* Declares subscription information for the calling client.
*
* @param privateKey calling client private key
* @param subs {@link org.astrogrid.samp.Subscriptions}-like map
*/
void declareSubscriptions( String privateKey, Map subs )
throws SampException;
/**
* Returns subscriptions for a given client.
*
* @param privateKey calling client private key
* @return {@link org.astrogrid.samp.Subscriptions}-like map
*/
Map getSubscriptions( String privateKey, String clientId )
throws SampException;
/**
* Returns a list of the public-ids of all currently registered clients.
*
* @param privateKey calling client private key
* @return list of Strings
*/
List getRegisteredClients( String privateKey ) throws SampException;
/**
* Returns a map of the clients subscribed to a given MType.
*
* @param privateKey calling client private key
* @param mtype MType of interest
* @return map in which the keys are the public-ids of clients subscribed
* to mtype
*/
Map getSubscribedClients( String privateKey, String mtype )
throws SampException;
/**
* Sends a message to a given client without wanting a response.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
*/
void notify( String privateKey, String recipientId, Map msg )
throws SampException;
/**
* Sends a message to all subscribed clients without wanting a response.
*
* @param privateKey calling client private key
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return list of public-ids for clients to which the notify will be sent
*/
List notifyAll( String privateKey, Map msg ) throws SampException;
/**
* Sends a message to a given client expecting a response.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return message ID
*/
String call( String privateKey, String recipientId, String msgTag,
Map msg ) throws SampException;
/**
* Sends a message to all subscribed clients expecting responses.
*
* @param privateKey calling client private key
* @param msgTag arbitrary string tagging this message for caller's
* benefit
* @param msg {@link org.astrogrid.samp.Message}-like map
* @return public-id->msg-id map for clients to which an attempt to
* send the call will be made
*/
Map callAll( String privateKey, String msgTag, Map msg )
throws SampException;
/**
* Sends a message synchronously to a client.
*
* @param privateKey calling client private key
* @param recipientId public-id of client to receive message
* @param msg {@link org.astrogrid.samp.Message}-like map
* @param timeout timeout in seconds encoded as a SAMP int
* @return {@link org.astrogrid.samp.Response}-like map
*/
Map callAndWait( String privateKey, String recipientId, Map msg,
String timeout ) throws SampException;
/**
* Responds to a previously sent message.
*
* @param privateKey calling client private key
* @param msgId ID associated with earlier send
* @param response {@link org.astrogrid.samp.Response}-like map
*/
void reply( String privateKey, String msgId, Map response )
throws SampException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubMode.java 0000664 0000000 0000000 00000030105 13564500043 0024673 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
import javax.swing.JFrame;
import org.astrogrid.samp.Client;
import org.astrogrid.samp.gui.GuiHubService;
import org.astrogrid.samp.gui.MessageTrackerHubService;
import org.astrogrid.samp.gui.SysTray;
import org.astrogrid.samp.hub.HubService;
import org.astrogrid.samp.hub.BasicHubService;
/**
* Specifies a particular hub implementation for use with {@link HubRunner}.
*
* @author Mark Taylor
* @since 20 Nov 2008
* @deprecated use {@link org.astrogrid.samp.hub.HubServiceMode} with
* {@link org.astrogrid.samp.hub.Hub} instead
*/
public abstract class HubMode {
// This class looks like an enumeration-type class to external users.
// It is actually a HubService factory.
private final String name_;
private final boolean isDaemon_;
private static final Logger logger_ =
Logger.getLogger( HubMode.class.getName() );
/** Hub mode with no GUI representation of hub operations. */
public static final HubMode NO_GUI;
/** Hub mode with a GUI representation of connected clients. */
public static final HubMode CLIENT_GUI;
/** Hub mode with a GUI representation of clients and messages. */
public static HubMode MESSAGE_GUI;
/** Array of available hub modes. */
private static final HubMode[] KNOWN_MODES = new HubMode[] {
NO_GUI = createBasicHubMode( "no-gui" ),
CLIENT_GUI = createGuiHubMode( "client-gui" ),
MESSAGE_GUI = createMessageTrackerHubMode( "msg-gui" ),
};
/**
* Constructor.
*
* @param name mode name
* @param isDaemon true if the hub will start only daemon threads
*/
HubMode( String name, boolean isDaemon ) {
name_ = name;
isDaemon_ = isDaemon;
}
/**
* Returns a new HubService object.
*
* @param random random number generator
* @param runners 1-element array of HubRunners - this should be
* populated with the runner once it has been constructed
*/
abstract HubService createHubService( Random random, HubRunner[] runners );
/**
* Indicates whether the hub service will start only daemon threads.
* If it returns true, the caller may need to make sure that the
* JVM doesn't stop too early.
*
* @return true iff no non-daemon threads will be started by the service
*/
boolean isDaemon() {
return isDaemon_;
}
/**
* Returns this mode's name.
*
* @return mode name
*/
String getName() {
return name_;
}
public String toString() {
return name_;
}
/**
* Returns one of the known modes which has a name as given.
*
* @param name mode name (case-insensitive)
* @return mode with given name, or null if none known
*/
public static HubMode getModeFromName( String name ) {
HubMode[] modes = KNOWN_MODES;
for ( int im = 0; im < modes.length; im++ ) {
HubMode mode = modes[ im ];
if ( mode.name_.equalsIgnoreCase( name ) ) {
return mode;
}
}
return null;
}
/**
* Returns an array of the hub modes which can actually be used.
*
* @return available mode list
*/
public static HubMode[] getAvailableModes() {
List modeList = new ArrayList();
for ( int i = 0; i < KNOWN_MODES.length; i++ ) {
HubMode mode = KNOWN_MODES[ i ];
if ( ! ( mode instanceof BrokenHubMode ) ) {
modeList.add( mode );
}
}
return (HubMode[]) modeList.toArray( new HubMode[ 0 ] );
}
/**
* Used to perform common configuration of hub display windows
* for GUI-type hub modes.
*
* @param frame hub window
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
*/
static void configureHubWindow( JFrame frame, HubRunner[] runners ) {
SysTray sysTray = SysTray.getInstance();
if ( sysTray.isSupported() ) {
try {
configureWindowForSysTray( frame, runners, sysTray );
}
catch ( AWTException e ) {
logger_.warning( "Failed to install in system tray: " + e );
configureWindowBasic( frame, runners );
}
logger_.info( "Hub started in system tray" );
}
else {
logger_.info( "System tray not supported: displaying hub window" );
configureWindowBasic( frame, runners );
}
}
/**
* Performs common configuration of hub display window without
* system tray functionality.
* @param frame hub window
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
*/
private static void configureWindowBasic( JFrame frame,
final HubRunner[] runners ) {
frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
frame.addWindowListener( new WindowAdapter() {
public void windowClosed( WindowEvent evt ) {
HubRunner runner = runners[ 0 ];
if ( runner != null ) {
runner.shutdown();
}
}
} );
frame.setVisible( true );
}
/**
* Performs common configuration of hub display window with
* system tray functionality.
*
* @param frame hub window
* @param runners 1-element array which will contain an associated
* hub runner object if one exists
* @param sysTray system tray facade object
*/
private static void configureWindowForSysTray( final JFrame frame,
final HubRunner[] runners,
final SysTray sysTray )
throws AWTException {
/* Prepare the items for display in the tray icon popup menu. */
final MenuItem showItem;
final MenuItem hideItem;
final MenuItem stopItem;
MenuItem[] items = new MenuItem[] {
showItem = new MenuItem( "Show Hub Window" ),
hideItem = new MenuItem( "Hide Hub Window" ),
stopItem = new MenuItem( "Stop Hub" ),
};
ActionListener iconListener = new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
frame.setVisible( true );
showItem.setEnabled( false );
hideItem.setEnabled( true );
}
};
/* Construct and install the tray icon. */
Image im = Toolkit.getDefaultToolkit()
.createImage( Client.class.getResource( "images/hub.png" ) );
String tooltip = "SAMP Hub";
PopupMenu popup = new PopupMenu();
final Object trayIcon =
sysTray.addIcon( im, tooltip, popup, iconListener );
/* Arrange for the menu items to do something appropriate when
* triggered. */
ActionListener popListener = new ActionListener() {
public void actionPerformed( ActionEvent evt ) {
String cmd = evt.getActionCommand();
if ( cmd.equals( showItem.getActionCommand() ) ||
cmd.equals( hideItem.getActionCommand() ) ) {
boolean visible = cmd.equals( showItem.getActionCommand() );
frame.setVisible( visible );
showItem.setEnabled( ! visible );
hideItem.setEnabled( visible );
}
else if ( cmd.equals( stopItem.getActionCommand() ) ) {
HubRunner runner = runners[ 0 ];
if ( runner != null ) {
runner.shutdown();
}
try {
sysTray.removeIcon( trayIcon );
}
catch ( AWTException e ) {
logger_.warning( e.toString() );
}
frame.dispose();
}
}
};
for ( int i = 0; i < items.length; i++ ) {
items[ i ].addActionListener( popListener );
popup.add( items[ i ] );
}
/* Arrange that a manual window close will set the action states
* correctly. */
frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
frame.addWindowListener( new WindowAdapter() {
public void windowClosed( WindowEvent evt ) {
showItem.setEnabled( true );
hideItem.setEnabled( false );
}
} );
/* Set to initial state. */
popListener.actionPerformed(
new ActionEvent( frame, 0, hideItem.getActionCommand() ) );
}
/**
* Constructs a mode for BasicHubService.
*
* @return non-gui mode
*/
private static HubMode createBasicHubMode( String name ) {
try {
return new HubMode( name, true ) {
HubService createHubService( Random random,
HubRunner[] runners ) {
return new BasicHubService( random );
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* Constructs a mode for GuiHubService.
*
* @return mode without message tracking
*/
private static HubMode createGuiHubMode( String name ) {
try {
GuiHubService.class.getName();
return new HubMode( name, false ) {
HubService createHubService( Random random,
final HubRunner[] runners ) {
return new GuiHubService( random ) {
public void start() {
super.start();
configureHubWindow( createHubWindow(), runners );
}
};
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* Constructs a mode for MessageTrackerHubService.
*
* @return mode with message tracking
*/
private static HubMode createMessageTrackerHubMode( String name ) {
try {
MessageTrackerHubService.class.getName();
return new HubMode( name, false ) {
HubService createHubService( Random random,
final HubRunner[] runners ) {
return new MessageTrackerHubService( random ) {
public void start() {
super.start();
configureHubWindow( createHubWindow(), runners );
}
};
}
};
}
catch ( Throwable e ) {
return new BrokenHubMode( name, e );
}
}
/**
* HubMode implemenetation for modes which cannot be used because they
* rely on classes unavailable at runtime.
*/
private static class BrokenHubMode extends HubMode {
private final Throwable error_;
/**
* Constructor.
*
* @param name mode name
* @param error error explaining why mode is unavailable for use
*/
BrokenHubMode( String name, Throwable error ) {
super( name, false );
error_ = error;
}
HubService createHubService( Random random, HubRunner[] runners ) {
throw new RuntimeException( "Hub mode " + getName()
+ " unavailable", error_ );
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubRunner.java 0000664 0000000 0000000 00000064321 13564500043 0025267 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.hub.Hub;
import org.astrogrid.samp.hub.HubService;
import org.astrogrid.samp.hub.KeyGenerator;
import org.astrogrid.samp.hub.LockWriter;
import org.astrogrid.samp.hub.MessageRestriction;
import org.astrogrid.samp.hub.ProfileToken;
import org.astrogrid.samp.httpd.ServerResource;
import org.astrogrid.samp.httpd.UtilServer;
/**
* Runs a SAMP hub using the SAMP Standard Profile.
* The {@link #start} method must be called to start it up.
*
* -help
flag for more information.
*
* @author Mark Taylor
* @since 15 Jul 2008
* @deprecated use {@link org.astrogrid.samp.hub.Hub} instead
*/
public class HubRunner {
private final SampXmlRpcClientFactory xClientFactory_;
private final SampXmlRpcServerFactory xServerFactory_;
private final HubService hub_;
private final File lockfile_;
private URL lockUrl_;
private LockInfo lockInfo_;
private SampXmlRpcServer server_;
private HubXmlRpcHandler hubHandler_;
private boolean shutdown_;
private final static ProfileToken STANDARD_PROFILE = new ProfileToken() {
public String getProfileName() {
return "Standard";
}
public MessageRestriction getMessageRestriction() {
return null;
}
};
private final static Logger logger_ =
Logger.getLogger( HubRunner.class.getName() );
private final static Random random_ = KeyGenerator.createRandom();
/**
* Constructor.
* If the supplied lockfile
is null, no lockfile will
* be written at hub startup.
*
* @param xClientFactory XML-RPC client factory implementation
* @param xServerFactory XML-RPC server implementation
* @param hub object providing hub services
* @param lockfile location to use for hub lockfile, or null
*/
public HubRunner( SampXmlRpcClientFactory xClientFactory,
SampXmlRpcServerFactory xServerFactory,
HubService hub, File lockfile ) {
xClientFactory_ = xClientFactory;
xServerFactory_ = xServerFactory;
hub_ = hub;
lockfile_ = lockfile;
logger_.warning( "Class " + HubRunner.class.getName()
+ " is deprecated; use "
+ Hub.class.getName() + " instead." );
}
/**
* Starts the hub and writes the lockfile.
*
* @throws IOException if a hub is already running or an error occurs
*/
public void start() throws IOException {
// Check for running or moribund hub.
if ( lockfile_ != null && lockfile_.exists() ) {
if ( isHubAlive( xClientFactory_, lockfile_ ) ) {
throw new IOException( "A hub is already running" );
}
else {
logger_.warning( "Overwriting " + lockfile_ + " lockfile "
+ "for apparently dead hub" );
lockfile_.delete();
}
}
// Start up server.
try {
server_ = xServerFactory_.getServer();
}
catch ( IOException e ) {
throw e;
}
catch ( Exception e ) {
throw (IOException) new IOException( "Can't start XML-RPC server" )
.initCause( e );
}
// Start the hub service.
hub_.start();
String secret = createSecret();
ClientProfile connectionFactory = new ClientProfile() {
public HubConnection register() throws SampException {
return hub_.register( STANDARD_PROFILE );
}
public boolean isHubRunning() {
return hub_.isHubRunning();
}
};
hubHandler_ =
new HubXmlRpcHandler( xClientFactory_, connectionFactory, secret,
new KeyGenerator( "k:", 16, random_ ) );
server_.addHandler( hubHandler_ );
// Ensure tidy up in case of JVM shutdown.
Runtime.getRuntime().addShutdownHook(
new Thread( "HubRunner shutdown" ) {
public void run() {
shutdown();
}
} );
// Prepare lockfile information.
lockInfo_ = new LockInfo( secret, server_.getEndpoint().toString() );
lockInfo_.put( "hub.impl", hub_.getClass().getName() );
lockInfo_.put( "hub.start.date", new Date().toString() );
// Write lockfile information to file if required.
if ( lockfile_ != null ) {
logger_.info( "Writing new lockfile " + lockfile_ );
FileOutputStream out = new FileOutputStream( lockfile_ );
try {
writeLockInfo( lockInfo_, out );
try {
LockWriter.setLockPermissions( lockfile_ );
logger_.info( "Lockfile permissions set to "
+ "user access only" );
}
catch ( IOException e ) {
logger_.log( Level.WARNING,
"Failed attempt to change " + lockfile_
+ " permissions to user access only"
+ " - possible security implications", e );
}
}
finally {
try {
out.close();
}
catch ( IOException e ) {
logger_.log( Level.WARNING, "Error closing lockfile?", e );
}
}
}
}
/**
* Shuts down the hub and tidies up.
* May harmlessly be called multiple times.
*/
public synchronized void shutdown() {
// Return if we have already done this.
if ( shutdown_ ) {
return;
}
shutdown_ = true;
// Delete the lockfile if it exists and if it is the one originally
// written by this runner.
if ( lockfile_ != null ) {
if ( lockfile_.exists() ) {
try {
LockInfo lockInfo = readLockFile( lockfile_ );
if ( lockInfo.getSecret()
.equals( lockInfo_.getSecret() ) ) {
assert lockInfo.equals( lockInfo_ );
boolean deleted = lockfile_.delete();
logger_.info( "Lockfile " + lockfile_ + " "
+ ( deleted ? "deleted"
: "deletion attempt failed" ) );
}
else {
logger_.warning( "Lockfile " + lockfile_ + " has been "
+ " overwritten - not deleting" );
}
}
catch ( Throwable e ) {
logger_.log( Level.WARNING,
"Failed to delete lockfile " + lockfile_,
e );
}
}
else {
logger_.warning( "Lockfile " + lockfile_ + " has disappeared" );
}
}
// Withdraw service of the lockfile, if one has been published.
if ( lockUrl_ != null ) {
try {
UtilServer.getInstance().getResourceHandler()
.removeResource( lockUrl_ );
}
catch ( IOException e ) {
logger_.warning( "Failed to withdraw lockfile URL" );
}
lockUrl_ = null;
}
// Shut down the hub service if exists. This sends out shutdown
// messages to registered clients.
if ( hub_ != null ) {
try {
hub_.shutdown();
}
catch ( Throwable e ) {
logger_.log( Level.WARNING, "Hub service shutdown failed", e );
}
}
// Remove the hub XML-RPC handler from the server.
if ( hubHandler_ != null && server_ != null ) {
server_.removeHandler( hubHandler_ );
server_ = null;
}
lockInfo_ = null;
}
/**
* Returns the HubService object used by this runner.
*
* @return hub service
*/
public HubService getHub() {
return hub_;
}
/**
* Returns the lockfile information associated with this object.
* Only present after {@link #start} has been called.
*
* @return lock info
*/
public LockInfo getLockInfo() {
return lockInfo_;
}
/**
* Returns an HTTP URL at which the lockfile for this hub can be found.
* The first call to this method causes the lockfile to be published
* in this way; subsequent calls return the same value.
*
* lockfile
appears
* to be alive and well
*/
private static boolean isHubAlive( SampXmlRpcClientFactory xClientFactory,
File lockfile ) {
LockInfo info;
try {
info = readLockFile( lockfile );
}
catch ( Exception e ) {
logger_.log( Level.WARNING, "Failed to read lockfile", e );
return false;
}
if ( info == null ) {
return false;
}
URL xurl = info.getXmlrpcUrl();
if ( xurl != null ) {
try {
xClientFactory.createClient( xurl )
.callAndWait( "samp.hub.ping", new ArrayList() );
return true;
}
catch ( Exception e ) {
logger_.log( Level.WARNING, "Hub ping method failed", e );
return false;
}
}
else {
logger_.warning( "No XMLRPC URL in lockfile" );
return false;
}
}
/**
* Reads lockinfo from a file.
*
* @param lockFile file
* @return info from file
*/
private static LockInfo readLockFile( File lockFile ) throws IOException {
return LockInfo.readLockFile( new FileInputStream( lockFile ) );
}
/**
* Writes lockfile information to a given output stream.
* The stream is not closed.
*
* @param info lock info to write
* @param out destination stream
*/
private static void writeLockInfo( LockInfo info, OutputStream out )
throws IOException {
LockWriter writer = new LockWriter( out );
writer.writeComment( "SAMP Standard Profile lockfile written "
+ new Date() );
writer.writeComment( "Note contact URL hostname may be "
+ "configured using "
+ SampUtils.LOCALHOST_PROP + " property" );
writer.writeAssignments( info );
out.flush();
}
/**
* Main method. Starts a hub.
* Use "-help" flag for more information.
*
* @param args command-line arguments
*/
public static void main( String[] args ) throws IOException {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
/**
* Does the work for running the {@link #main} method.
* System.exit() is not called from this method.
* Use "-help" flag for more information.
*
* @param args command-line arguments
* @return 0 means success, non-zero means error status
*/
public static int runMain( String[] args ) throws IOException {
StringBuffer ubuf = new StringBuffer();
ubuf.append( "\n Usage:" )
.append( "\n " )
.append( HubRunner.class.getName() )
.append( "\n " )
.append( " [-help]" )
.append( " [-/+verbose]" )
.append( "\n " )
.append( " [-mode " );
HubMode[] modes = HubMode.getAvailableModes();
for ( int im = 0; im < modes.length; im++ ) {
if ( im > 0 ) {
ubuf.append( '|' );
}
ubuf.append( modes[ im ].getName() );
}
ubuf.append( ']' )
.append( " [-secret start
has been called).
*
* start
has been called).
*
* Runtime.exec
can block the process
* until its output is consumed.
*
* @param cmdarray array containing the command to call and its args
*/
private static void execBackground( String[] cmdarray ) throws IOException {
Process process = Runtime.getRuntime().exec( cmdarray );
discardBytes( process.getInputStream() );
discardBytes( process.getErrorStream() );
}
/**
* Ensures that any bytes from a given input stream are discarded.
*
* @param in input stream
*/
private static void discardBytes( final InputStream in ) {
Thread eater = new Thread( "StreamEater" ) {
public void run() {
try {
while ( in.read() >= 0 ) {}
in.close();
}
catch ( IOException e ) {
}
}
};
eater.setDaemon( true );
eater.start();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubXmlRpcHandler.java 0000664 0000000 0000000 00000020520 13564500043 0026512 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.astrogrid.samp.RegInfo;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
import org.astrogrid.samp.hub.KeyGenerator;
/**
* SampXmlRpcHandler implementation which passes Standard Profile-type XML-RPC
* calls to a hub connection factory to provide a Standard Profile hub server.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
class HubXmlRpcHandler extends ActorHandler {
/**
* Constructor.
*
* @param xClientFactory XML-RPC client factory implementation
* @param profile hub connection factory
* @param secret password required for client registration
*/
public HubXmlRpcHandler( SampXmlRpcClientFactory xClientFactory,
ClientProfile profile, String secret,
KeyGenerator keyGen ) {
super( "samp.hub.", HubActor.class,
new HubActorImpl( xClientFactory, profile, secret, keyGen ) );
}
protected Object invokeMethod( Method method, Object obj, Object[] args )
throws IllegalAccessException, InvocationTargetException {
return method.invoke( obj, args );
}
/**
* Implementation of the {@link HubActor} interface which does
* the work for this class.
* Apart from a few methods which have Standard-Profile-specific
* aspects, the work is simply delegated to the hub connection factory.
*/
private static class HubActorImpl implements HubActor {
private final SampXmlRpcClientFactory xClientFactory_;
private final ClientProfile profile_;
private final String secret_;
private final KeyGenerator keyGen_;
private final Map clientMap_;
/**
* Constructor.
*
* @param xClientFactory XML-RPC client factory implementation
* @param profile hub connection factory
* @param secret password required for client registration
* @param keyGen generator for private keys
*/
HubActorImpl( SampXmlRpcClientFactory xClientFactory,
ClientProfile profile, String secret,
KeyGenerator keyGen ) {
xClientFactory_ = xClientFactory;
profile_ = profile;
secret_ = secret;
keyGen_ = keyGen;
clientMap_ = Collections.synchronizedMap( new HashMap() );
}
public Map register( String secret ) throws SampException {
if ( ! profile_.isHubRunning() ) {
throw new SampException( "Hub not running" );
}
else if ( secret_.equals( secret ) ) {
HubConnection connection = profile_.register();
if ( connection == null ) {
throw new SampException( "Hub is not running" );
}
String privateKey = keyGen_.next();
RegInfo regInfo = connection.getRegInfo();
regInfo.put( RegInfo.PRIVATEKEY_KEY, privateKey );
clientMap_.put( privateKey, connection );
return regInfo;
}
else {
throw new SampException( "Bad password" );
}
}
public void unregister( String privateKey ) throws SampException {
HubConnection connection =
(HubConnection) clientMap_.remove( privateKey );
if ( connection == null ) {
throw new SampException( "Unknown private key" );
}
else {
connection.unregister();
}
}
public void ping( String privateKey ) throws SampException {
getConnection( privateKey ).ping();
}
public void setXmlrpcCallback( String privateKey, String surl )
throws SampException {
SampXmlRpcClient xClient;
try {
xClient = xClientFactory_.createClient( new URL( surl ) );
}
catch ( MalformedURLException e ) {
throw new SampException( "Bad URL: " + surl, e );
}
catch ( IOException e ) {
throw new SampException( "No connection: "
+ e.getMessage(), e );
}
getConnection( privateKey )
.setCallable( new XmlRpcCallableClient( xClient, privateKey ) );
}
public void declareMetadata( String privateKey, Map metadata )
throws SampException {
getConnection( privateKey ).declareMetadata( metadata );
}
public Map getMetadata( String privateKey, String clientId )
throws SampException {
return getConnection( privateKey ).getMetadata( clientId );
}
public void declareSubscriptions( String privateKey, Map subs )
throws SampException {
getConnection( privateKey ).declareSubscriptions( subs );
}
public Map getSubscriptions( String privateKey, String clientId )
throws SampException {
return getConnection( privateKey ).getSubscriptions( clientId );
}
public List getRegisteredClients( String privateKey )
throws SampException {
return Arrays.asList( getConnection( privateKey )
.getRegisteredClients() );
}
public Map getSubscribedClients( String privateKey, String mtype )
throws SampException {
return getConnection( privateKey ).getSubscribedClients( mtype );
}
public void notify( String privateKey, String recipientId, Map msg )
throws SampException {
getConnection( privateKey ).notify( recipientId, msg );
}
public List notifyAll( String privateKey, Map msg )
throws SampException {
return getConnection( privateKey ).notifyAll( msg );
}
public String call( String privateKey, String recipientId,
String msgTag, Map msg )
throws SampException {
return getConnection( privateKey ).call( recipientId, msgTag, msg );
}
public Map callAll( String privateKey, String msgTag, Map msg )
throws SampException {
return getConnection( privateKey ).callAll( msgTag, msg );
}
public Map callAndWait( String privateKey, String recipientId, Map msg,
String timeoutStr )
throws SampException {
int timeout;
try {
timeout = SampUtils.decodeInt( timeoutStr );
}
catch ( Exception e ) {
throw new SampException( "Bad timeout format"
+ " (should be SAMP int)", e );
}
return getConnection( privateKey )
.callAndWait( recipientId, msg, timeout );
}
public void reply( String privateKey, String msgId, Map response )
throws SampException {
getConnection( privateKey ).reply( msgId, response );
}
public void ping() throws SampException {
if ( ! profile_.isHubRunning() ) {
throw new SampException( "Hub is stopped" );
}
}
/**
* Returns the HubConnection associated with a private key used
* by this hub actor.
*
* @param privateKey private key
* @return connection for privateKey
*/
private HubConnection getConnection( String privateKey )
throws SampException {
HubConnection connection =
(HubConnection) clientMap_.get( privateKey );
if ( connection == null ) {
throw new SampException( "Unknown private key" );
}
else {
return connection;
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/LockInfo.java 0000664 0000000 0000000 00000017035 13564500043 0025063 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.logging.Logger;
import org.astrogrid.samp.DataException;
import org.astrogrid.samp.SampMap;
import org.astrogrid.samp.SampUtils;
/**
* Represents the information read from a SAMP Standard Profile Lockfile.
* This contains a key-value entry for each assignment read from the file.
* Any non-assignment lines are not represented by this object.
*
* @author Mark Taylor
* @since 14 Jul 2008
*/
public class LockInfo extends SampMap {
private static final Logger logger_ =
Logger.getLogger( LockInfo.class.getName() );
/** Key for opaque text string required by the hub for registration. */
public static final String SECRET_KEY = "samp.secret";
/** Key for XML-RPC endpoint for communication with the hub. */
public static final String XMLRPCURL_KEY = "samp.hub.xmlrpc.url";
/** Key for the SAMP Standard Profile version implemented by the hub. */
public static final String VERSION_KEY = "samp.profile.version";
private static final String[] KNOWN_KEYS = new String[] {
SECRET_KEY,
XMLRPCURL_KEY,
VERSION_KEY,
};
/** SAMP Standard Profile version for this toolkit implementation. */
public static final String DEFAULT_VERSION_VALUE = "1.3";
private static final Pattern TOKEN_REGEX =
Pattern.compile( "[a-zA-Z0-9\\-_\\.]+" );
private static final Pattern ASSIGNMENT_REGEX =
Pattern.compile( "(" + TOKEN_REGEX.pattern() + ")=(.*)" );
private static final Pattern COMMENT_REGEX =
Pattern.compile( "#[\u0020-\u007f]*" );
/**
* Constructs an empty LockInfo.
*/
public LockInfo() {
super( KNOWN_KEYS );
}
/**
* Constructs a LockInfo based on an existing map.
*
* @param map map containing initial data for this object
*/
public LockInfo( Map map ) {
this();
putAll( map );
}
/**
* Constructs a LockInfo from a given SAMP secret and XML-RPC URL.
* The version string is set to the default for this toolkit.
*
* @param secret value for {@link #SECRET_KEY} key
* @param xmlrpcurl value for {@link #XMLRPCURL_KEY} key
*/
public LockInfo( String secret, String xmlrpcurl ) {
this();
put( SECRET_KEY, secret );
put( XMLRPCURL_KEY, xmlrpcurl );
put( VERSION_KEY, DEFAULT_VERSION_VALUE );
}
/**
* Returns the value of the {@link #XMLRPCURL_KEY} key.
*
* @return hub XML-RPC connection URL
*/
public URL getXmlrpcUrl() {
return getUrl( XMLRPCURL_KEY );
}
/**
* Returns the value of the {@link #VERSION_KEY} key.
*
* @return version of the SAMP standard profile implemented
*/
public String getVersion() {
return getString( VERSION_KEY );
}
/**
* Returns the value of the {@link #SECRET_KEY} key.
*
* @return password for hub connection
*/
public String getSecret() {
return getString( SECRET_KEY );
}
public void check() {
super.check();
checkHasKeys( new String[] { SECRET_KEY, XMLRPCURL_KEY, } );
for ( Iterator it = entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
if ( key instanceof String ) {
if ( ! TOKEN_REGEX.matcher( key.toString() ).matches() ) {
throw new DataException( "Bad key syntax: " + key +
" does not match " +
TOKEN_REGEX.pattern() );
}
}
else {
throw new DataException( "Map key " + entry.getKey()
+ " is not a string" );
}
Object value = entry.getValue();
if ( value instanceof String ) {
String sval = (String) value;
for ( int i = 0; i < sval.length(); i++ ) {
int c = sval.charAt( i );
if ( c < 0x20 || c > 0x7f ) {
throw new DataException( "Value contains illegal "
+ "character 0x"
+ Integer.toHexString( c ) );
}
}
}
else {
throw new DataException( "Map value " + value +
" is not a string" );
}
}
}
/**
* Returns a LockInfo as read from a lockfile at a given location.
* If the lockfile does not exist, null is returned.
* An exception may be thrown if it exists but is cannot be read.
*
* @param url lockfile location
* @return lockfile contents, or null if it is absent
*/
public static LockInfo readLockFile( URL url ) throws IOException {
final InputStream in;
File file = SampUtils.urlToFile( url );
if ( file != null ) {
if ( file.exists() ) {
in = new FileInputStream( file );
}
else {
return null;
}
}
else {
try {
in = url.openStream();
}
catch ( IOException e ) {
return null;
}
}
return readLockFile( in );
}
/**
* Returns the LockInfo read from a given stream.
* The stream is closed if the read is successful.
*
* @param in input stream to read
* @return lockfile information
*/
public static LockInfo readLockFile( InputStream in ) throws IOException {
LockInfo info = new LockInfo();
in = new BufferedInputStream( in );
for ( String line; ( line = readLine( in ) ) != null; ) {
Matcher assigner = ASSIGNMENT_REGEX.matcher( line );
if ( assigner.matches() ) {
info.put( assigner.group( 1 ), assigner.group( 2 ) );
}
else if ( COMMENT_REGEX.matcher( line ).matches() ) {
}
else if ( line.length() == 0 ) {
}
else {
logger_.warning( "Ignoring lockfile line with bad syntax" );
logger_.info( "Bad line: " + line );
}
}
in.close();
return info;
}
/**
* Returns a given map as a LockInfo object.
*
* @param map map
* @return lock info
*/
public static LockInfo asLockInfo( Map map ) {
return map instanceof LockInfo ? (LockInfo) map
: new LockInfo( map );
}
/**
* Returns a line from a lockfile-type input stream.
*
* @param in input stream
* @return next line
*/
private static String readLine( InputStream in ) throws IOException {
StringBuffer sbuf = new StringBuffer();
while ( true ) {
int c = in.read();
switch ( c ) {
case '\r':
case '\n':
return sbuf.toString();
case -1:
return sbuf.length() > 0 ? sbuf.toString() : null;
default:
sbuf.append( (char) c );
}
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcClient.java 0000664 0000000 0000000 00000002606 13564500043 0026542 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
import java.net.URL;
import java.util.List;
/**
* Interface for a client which can make XML-RPC calls for SAMP.
* The method parameters and return values must be of SAMP-compatible types,
* that is only Strings, Lists, and String-keyed Maps are allowed in
* the data structures.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public interface SampXmlRpcClient {
/**
* Makes a synchronous call, waiting for the response and returning
* the result.
*
* @param method XML-RPC method name
* @param params parameters for XML-RPC call (SAMP-compatible)
* @return XML-RPC call return value (SAMP-compatible)
*/
Object callAndWait( String method, List params ) throws IOException;
/**
* Sends a call, but does not wait around for the response.
* If possible, this method should complete quickly.
*
* params
list and the return value must be
* SAMP-compatible, that is only Strings, Lists and String-keyed Maps
* are allowed in the data structures.
* The reqInfo
parameter may be used to provide additional
* information about the XML-RPC request, for instance the originating
* host; this is implementation specific, and may be null.
*
* @param method XML-RPC method name
* @param params XML-RPC parameter list (SAMP-compatible)
* @param reqInfo optional additional request information; may be null
* @return return value (SAMP-compatible)
*/
Object handleCall( String method, List params, Object reqInfo )
throws Exception;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcServer.java 0000664 0000000 0000000 00000001560 13564500043 0026570 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.net.URL;
/**
* Interface for a server which can respond to XML-RPC calls for SAMP.
* The method parameters and return values must be of SAMP-compatible types,
* that is only Strings, Lists, and String-keyed Maps are allowed in
* the data structures.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public interface SampXmlRpcServer {
/**
* Returns the server's endpoint.
*
* @return URL to which XML-RPC requests are POSTed
*/
URL getEndpoint();
/**
* Adds a handler which can service certain XML-RPC methods.
*
* @param handler handler to add
*/
void addHandler( SampXmlRpcHandler handler );
/**
* Removes a previously-added handler.
*
* @param handler handler to remove
*/
void removeHandler( SampXmlRpcHandler handler );
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcServerFactory.java 0000664 0000000 0000000 00000002101 13564500043 0030110 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
/**
* Defines a factory for SampXmlRpcServer instances.
* In most cases it will make sense to implement this class so that
* a single server instance is constructed lazily, and the same instance
* is always returned from the {@link #getServer} method.
* This means that the same server can be used for everything that requires
* an XML-RPC server, thus keeping resource usage down.
* Users of this class must keep this implementation model in mind,
* so must not assume that a new instance is returned each time.
* But if an implementation wants to return a new instance each time for
* some reason, that is permissible.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public interface SampXmlRpcServerFactory {
/**
* Returns an XML-RPC server implementation.
* Implementations are permitted, but not required, to return the same
* object from different calls of this method.
*
* @return new or re-used server
*/
SampXmlRpcServer getServer() throws IOException;
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardClientProfile.java 0000664 0000000 0000000 00000016662 13564500043 0027604 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Logger;
import org.astrogrid.samp.DataException;
import org.astrogrid.samp.Platform;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.client.HubConnection;
import org.astrogrid.samp.client.SampException;
/**
* Standard Profile implementation of ClientProfile.
* It is normally appropriate to use one of the static methods
* to obtain an instance based on a particular XML-RPC implementation.
*
* @author Mark Taylor
* @since 15 Jul 2008
*/
public class StandardClientProfile implements ClientProfile {
private final SampXmlRpcClientFactory xClientFactory_;
private final SampXmlRpcServerFactory xServerFactory_;
private static StandardClientProfile defaultInstance_;
private static URL dfltLockUrl_;
private static URL lockUrl_;
private static final Logger logger_ =
Logger.getLogger( StandardClientProfile.class.getName() );
/** Filename used for lockfile in home directory by default ({@value}). */
public static final String LOCKFILE_NAME = ".samp";
/** Prefix in SAMP_HUB value indicating lockfile URL ({@value}). */
public static final String STDPROFILE_HUB_PREFIX = "std-lockurl:";
/**
* Constructs a profile given client and server factory implementations.
*
* @param xClientFactory XML-RPC client factory implementation
* @param xServerFactory XML-RPC server factory implementation
*/
public StandardClientProfile( SampXmlRpcClientFactory xClientFactory,
SampXmlRpcServerFactory xServerFactory ) {
xClientFactory_ = xClientFactory;
xServerFactory_ = xServerFactory;
}
/**
* Constructs a profile given an XmlRpcKit object.
*
* @param xmlrpc XML-RPC implementation
*/
public StandardClientProfile( XmlRpcKit xmlrpc ) {
this( xmlrpc.getClientFactory(), xmlrpc.getServerFactory() );
}
public boolean isHubRunning() {
try {
LockInfo lockInfo = getLockInfo();
if ( lockInfo == null ) {
return false;
}
URL xurl = lockInfo.getXmlrpcUrl();
if ( xurl == null ) {
return false;
}
SampXmlRpcClient xClient = xClientFactory_.createClient( xurl );
xClient.callAndWait( "samp.hub.ping", new ArrayList() );
return true;
}
catch ( IOException e ) {
return false;
}
}
public HubConnection register() throws SampException {
LockInfo lockInfo;
try {
lockInfo = getLockInfo();
}
catch ( SampException e ) {
throw (SampException) e;
}
catch ( IOException e ) {
throw new SampException( "Error reading lockfile", e );
}
if ( lockInfo == null ) {
return null;
}
else {
try {
lockInfo.check();
}
catch ( DataException e ) {
String msg = "Incomplete/broken lock file";
try {
File lockFile = SampUtils.urlToFile( getLockUrl() );
if ( lockFile != null ) {
msg += " - try deleting " + lockFile;
}
}
catch ( IOException e2 ) {
// never mind
}
throw new SampException( msg, e );
}
SampXmlRpcClient xClient;
URL xurl = lockInfo.getXmlrpcUrl();
try {
xClient = xClientFactory_.createClient( xurl );
}
catch ( IOException e ) {
throw new SampException( "Can't connect to " + xurl, e );
}
return new StandardHubConnection( xClient, xServerFactory_,
lockInfo.getSecret() );
}
}
/**
* Returns the LockInfo which indicates how to locate the hub.
* If no lockfile exists (probably becuause no appropriate hub
* is running), null is returned.
* The default implementation returns
* LockInfo.readLockFile(getLockUrl())
;
* it may be overridden to provide a non-standard client profiles.
*
* @return hub location information
* @throws IOException if the lockfile exists but cannot be read for
* some reason
*/
public LockInfo getLockInfo() throws IOException {
return LockInfo.readLockFile( getLockUrl() );
}
/**
* Returns the location of the Standard Profile lockfile.
* By default this is the file .samp
in the user's "home"
* directory, unless overridden by a value of the SAMP_HUB environment
* variable starting with "std-lockurl".
*
* @return lockfile URL
*/
public static URL getLockUrl() throws IOException {
if ( lockUrl_ == null ) {
String hublocEnv = DefaultClientProfile.HUBLOC_ENV;
String hubloc = Platform.getPlatform().getEnv( hublocEnv );
final URL lockUrl;
if ( hubloc != null &&
hubloc.startsWith( STDPROFILE_HUB_PREFIX ) ) {
lockUrl = new URL( hubloc.substring( STDPROFILE_HUB_PREFIX
.length() ) );
logger_.info( "Lockfile as set by env var: "
+ hublocEnv + "=" + hubloc );
}
else if ( hubloc != null && hubloc.trim().length() > 0 ) {
logger_.warning( "Ignoring non-Standard " + hublocEnv + "="
+ hubloc );
lockUrl = getDefaultLockUrl();
}
else {
lockUrl = getDefaultLockUrl();
logger_.info( "Using default Standard Profile lockfile: "
+ SampUtils.urlToFile( lockUrl ) );
}
lockUrl_ = lockUrl;
}
return lockUrl_;
}
/**
* Returns the lockfile URL which will be used in absence of any
* SAMP_HUB environment variable.
*
* @return URL for file .samp in user's home directory
*/
public static URL getDefaultLockUrl() throws IOException {
if ( dfltLockUrl_ == null ) {
dfltLockUrl_ =
SampUtils.fileToUrl( new File( Platform.getPlatform()
.getHomeDirectory(),
LOCKFILE_NAME ) );
}
return dfltLockUrl_;
}
/**
* Returns an instance based on the default XML-RPC implementation.
* This can be configured using system properties.
*
* @see XmlRpcKit#getInstance
* @see org.astrogrid.samp.client.DefaultClientProfile#getProfile
* @return a client profile instance
*/
public static StandardClientProfile getInstance() {
if ( defaultInstance_ == null ) {
XmlRpcKit xmlrpc = XmlRpcKit.getInstance();
defaultInstance_ =
new StandardClientProfile( xmlrpc.getClientFactory(),
xmlrpc.getServerFactory() );
}
return defaultInstance_;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubConnection.java 0000664 0000000 0000000 00000003701 13564500043 0027571 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.IOException;
import java.util.Collections;
import org.astrogrid.samp.client.CallableClient;
import org.astrogrid.samp.client.SampException;
/**
* HubConnection implementation for the Standard Profile.
*
* @author Mark Taylor
* @since 27 Oct 2010
*/
class StandardHubConnection extends XmlRpcHubConnection {
private final SampXmlRpcServerFactory serverFactory_;
private final String clientKey_;
private CallableClientServer callableServer_;
/**
* Constructor.
*
* @param xClient XML-RPC client
* @param serverFactory XML-RPC server factory implementation
* @param secret samp.secret registration password
*/
public StandardHubConnection( SampXmlRpcClient xClient,
SampXmlRpcServerFactory serverFactory,
String secret )
throws SampException {
super( xClient, "samp.hub.", Collections.singletonList( secret ) );
clientKey_ = getRegInfo().getPrivateKey();
serverFactory_ = serverFactory;
}
public Object getClientKey() {
return clientKey_;
}
public void setCallable( CallableClient callable ) throws SampException {
if ( callableServer_ == null ) {
try {
callableServer_ =
CallableClientServer.getInstance( serverFactory_ );
}
catch ( IOException e ) {
throw new SampException( "Can't start client XML-RPC server",
e );
}
}
callableServer_.addClient( this, callable );
exec( "setXmlrpcCallback",
new Object[] { callableServer_.getUrl().toString() } );
}
public void unregister() throws SampException {
if ( callableServer_ != null ) {
callableServer_.removeClient( this );
}
super.unregister();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubProfile.java 0000664 0000000 0000000 00000033305 13564500043 0027075 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.client.ClientProfile;
import org.astrogrid.samp.client.DefaultClientProfile;
import org.astrogrid.samp.httpd.ServerResource;
import org.astrogrid.samp.httpd.UtilServer;
import org.astrogrid.samp.hub.HubProfile;
import org.astrogrid.samp.hub.KeyGenerator;
import org.astrogrid.samp.hub.LockWriter;
import org.astrogrid.samp.hub.MessageRestriction;
/**
* HubProfile implementation for the SAMP Standard Profile.
*
* @author Mark Taylor
* @since 31 Jan 2011
*/
public class StandardHubProfile implements HubProfile {
private final SampXmlRpcClientFactory xClientFactory_;
private final SampXmlRpcServerFactory xServerFactory_;
private final File lockfile_;
private final String secret_;
private URL lockUrl_;
private SampXmlRpcServer server_;
private volatile HubXmlRpcHandler hubHandler_;
private LockInfo lockInfo_;
private static final Logger logger_ =
Logger.getLogger( StandardHubProfile.class.getName() );
private static final Random random_ = KeyGenerator.createRandom();
/**
* Constructs a hub profile with given configuration information.
* If the supplied lockfile
is null, no lockfile will
* be written at hub startup.
*
* @param xClientFactory XML-RPC client factory implementation
* @param xServerFactory XML-RPC server implementation
* @param lockfile location to use for hub lockfile, or null
* @param secret value for samp.secret lockfile key
*/
public StandardHubProfile( SampXmlRpcClientFactory xClientFactory,
SampXmlRpcServerFactory xServerFactory,
File lockfile, String secret ) {
xClientFactory_ = xClientFactory;
xServerFactory_ = xServerFactory;
lockfile_ = lockfile;
secret_ = secret;
}
/**
* Constructs a hub profile with default configuration.
*/
public StandardHubProfile() throws IOException {
this( XmlRpcKit.getInstance().getClientFactory(),
XmlRpcKit.getInstance().getServerFactory(),
SampUtils.urlToFile( StandardClientProfile.getLockUrl() ),
createSecret() );
}
public String getProfileName() {
return "Standard";
}
public MessageRestriction getMessageRestriction() {
return null;
}
public synchronized void start( ClientProfile profile ) throws IOException {
// Check state.
if ( isRunning() ) {
logger_.info( "Profile already started" );
return;
}
// Check for running hub. If there is a running hub, bail out.
// If there is a lockfile but apparently no running hub,
// continue preparations to start; the hub reference by the lockfile
// may either be moribund or in the process of starting up.
// To deal with the latter case, make all our preparations to give
// it more time to get going before preempting it.
if ( lockfile_ != null && lockfile_.exists() ) {
if ( isHubAlive( xClientFactory_, lockfile_ ) ) {
throw new IOException( "A hub is already running" );
}
}
// Start up server.
try {
server_ = xServerFactory_.getServer();
}
catch ( IOException e ) {
throw e;
}
catch ( Exception e ) {
throw (IOException) new IOException( "Can't start XML-RPC server" )
.initCause( e );
}
hubHandler_ =
new HubXmlRpcHandler( xClientFactory_, profile, secret_,
new KeyGenerator( "k:", 16, random_ ) );
server_.addHandler( hubHandler_ );
// Prepare lockfile information.
lockInfo_ = new LockInfo( secret_, server_.getEndpoint().toString() );
lockInfo_.put( "hub.impl", profile.getClass().getName() );
lockInfo_.put( "profile.impl", this.getClass().getName() );
lockInfo_.put( "profile.start.date", new Date().toString() );
// Write lockfile information to file if required.
if ( lockfile_ != null ) {
// If the lockfile already exists, wait a little while in case
// a hub is in the process of waking up. If it's still not
// present, overwrite the lockfile with a warning.
if ( ! lockfile_.createNewFile() ) {
try {
Thread.sleep( 500 );
}
catch ( InterruptedException e ) {
}
if ( isHubAlive( xClientFactory_, lockfile_ ) ) {
server_.removeHandler( hubHandler_ );
hubHandler_ = null;
throw new IOException( "A hub is already running" );
}
else {
logger_.warning( "Overwriting " + lockfile_ + " lockfile "
+ "for apparently dead hub" );
}
}
FileOutputStream out = new FileOutputStream( lockfile_ );
try {
writeLockInfo( lockInfo_, out );
logger_.info( "Wrote new lockfile " + lockfile_ );
try {
LockWriter.setLockPermissions( lockfile_ );
logger_.info( "Lockfile permissions set to "
+ "user access only" );
}
catch ( IOException e ) {
logger_.log( Level.WARNING,
"Failed attempt to change " + lockfile_
+ " permissions to user access only"
+ " - possible security implications", e );
}
}
finally {
try {
out.close();
}
catch ( IOException e ) {
logger_.log( Level.WARNING, "Error closing lockfile?", e );
}
}
}
// If the lockfile is not the default one, write a message through
// the logging system.
URL lockfileUrl = lockfile_ == null
? publishLockfile()
: SampUtils.fileToUrl( lockfile_ );
boolean isDflt = StandardClientProfile.getDefaultLockUrl().toString()
.equals( lockfileUrl.toString() );
String hubassign = DefaultClientProfile.HUBLOC_ENV + "="
+ StandardClientProfile.STDPROFILE_HUB_PREFIX
+ lockfileUrl;
logger_.log( isDflt ? Level.INFO : Level.WARNING, hubassign );
}
public synchronized boolean isRunning() {
return hubHandler_ != null;
}
public synchronized void stop() {
if ( ! isRunning() ) {
logger_.info( "Profile already stopped" );
return;
}
// Delete the lockfile if it exists and if it is the one originally
// written by this runner.
if ( lockInfo_ != null && lockfile_ != null ) {
if ( lockfile_.exists() ) {
try {
LockInfo lockInfo = readLockFile( lockfile_ );
if ( lockInfo_.getSecret()
.equals( lockInfo.getSecret() ) ) {
assert lockInfo.equals( lockInfo_ );
boolean deleted = lockfile_.delete();
logger_.info( "Lockfile " + lockfile_ + " "
+ ( deleted ? "deleted"
: "deletion attempt failed" ) );
}
else {
logger_.warning( "Lockfile " + lockfile_ + " has been "
+ " overwritten - not deleting" );
}
}
catch ( Throwable e ) {
logger_.log( Level.WARNING,
"Failed to delete lockfile " + lockfile_,
e );
}
}
else {
logger_.warning( "Lockfile " + lockfile_ + " has disappeared" );
}
}
// Withdraw service of the lockfile, if one has been published.
if ( lockUrl_ != null ) {
try {
UtilServer.getInstance().getResourceHandler()
.removeResource( lockUrl_ );
}
catch ( IOException e ) {
logger_.warning( "Failed to withdraw lockfile URL" );
}
lockUrl_ = null;
}
// Remove the hub XML-RPC handler from the server.
if ( hubHandler_ != null && server_ != null ) {
server_.removeHandler( hubHandler_ );
}
server_ = null;
hubHandler_ = null;
lockInfo_ = null;
}
/**
* Returns the lockfile information associated with this object.
* Only present when running.
*
* @return lock info
*/
public LockInfo getLockInfo() {
return lockInfo_;
}
/**
* Returns an HTTP URL at which the lockfile for this hub can be found.
* The first call to this method causes the lockfile to be published
* in this way; subsequent calls return the same value.
*
* lockfile
appears
* to be alive and well
*/
private static boolean isHubAlive( SampXmlRpcClientFactory xClientFactory,
File lockfile ) {
LockInfo info;
try {
info = readLockFile( lockfile );
}
catch ( Exception e ) {
logger_.log( Level.WARNING, "Failed to read lockfile", e );
return false;
}
if ( info == null ) {
return false;
}
URL xurl = info.getXmlrpcUrl();
if ( xurl != null ) {
try {
xClientFactory.createClient( xurl )
.callAndWait( "samp.hub.ping", new ArrayList() );
return true;
}
catch ( Exception e ) {
logger_.log( Level.WARNING, "Hub ping method failed", e );
return false;
}
}
else {
logger_.warning( "No XMLRPC URL in lockfile" );
return false;
}
}
/**
* Reads lockinfo from a file.
*
* @param lockFile file
* @return info from file
*/
private static LockInfo readLockFile( File lockFile ) throws IOException {
return LockInfo.readLockFile( new FileInputStream( lockFile ) );
}
/**
* Writes lockfile information to a given output stream.
* The stream is not closed.
*
* @param info lock info to write
* @param out destination stream
*/
private static void writeLockInfo( LockInfo info, OutputStream out )
throws IOException {
LockWriter writer = new LockWriter( out );
writer.writeComment( "SAMP Standard Profile lockfile written "
+ new Date() );
writer.writeComment( "Note contact URL hostname may be "
+ "configured using "
+ SampUtils.LOCALHOST_PROP + " property" );
writer.writeAssignments( info );
out.flush();
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubProfileFactory.java 0000664 0000000 0000000 00000004441 13564500043 0030424 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.hub.HubProfile;
import org.astrogrid.samp.hub.HubProfileFactory;
/**
* HubProfileFactory implementation for Standard Profile.
*
* @author Mark Taylor
* @since 31 Jan 2011
*/
public class StandardHubProfileFactory implements HubProfileFactory {
private static final String secretUsage_ = "[-std:secret
*
* Alternatively, it may be the classname of a class which implements
* {@link org.astrogrid.samp.xmlrpc.XmlRpcKit}
* and has a no-arg constructor.
* The property name is {@value}.
*/
public static final String IMPL_PROP = "jsamp.xmlrpc.impl";
private static XmlRpcKit defaultInstance_;
private static Logger logger_ =
Logger.getLogger( XmlRpcKit.class.getName() );
/**
* Returns an XML-RPC client factory.
*
* @return client factory
*/
public abstract SampXmlRpcClientFactory getClientFactory();
/**
* Returns an XML-RPC server factory.
*
* @return server factory
*/
public abstract SampXmlRpcServerFactory getServerFactory();
/**
* Indicates whether this object is ready for use.
* If it returns false (perhaps because some classes are unavailable
* at runtime) then {@link #getClientFactory} and {@link #getServerFactory}
* may throw exceptions rather than behaving as documented.
*
* @return true if this object works
*/
public abstract boolean isAvailable();
/**
* Returns the name of this kit.
*
* @return implementation name
*/
public abstract String getName();
/**
* Returns the default instance of this class.
* What implementation this is normally depends on what classes
* are present at runtime.
* However, if the system property {@link #IMPL_PROP} is set this
* will determine the implementation used. It may be one of:
*
*
*
* @return default instance of this class
*/
public static XmlRpcKit getInstance() {
if ( defaultInstance_ == null ) {
defaultInstance_ = createDefaultInstance();
logger_.info( "Default XmlRpcInstance is " + defaultInstance_ );
}
return defaultInstance_;
}
/**
* Returns an XmlRpcKit instance given its name.
*
* @param name name of one of the known implementations, or classname
* of an XmlRpcKit implementatation with a no-arg
* constructor
* @return named implementation object
* @throws IllegalArgumentException if none by that name can be found
*/
public static XmlRpcKit getInstanceByName( String name ) {
// Implementation specified by system property -
// try to find one with a matching name in the known list.
XmlRpcKit[] impls = KNOWN_IMPLS;
for ( int i = 0; i < impls.length; i++ ) {
if ( name.equalsIgnoreCase( impls[ i ].getName() ) ) {
return impls[ i ];
}
}
// Still not got one -
// try to interpret system property as class name.
Class clazz;
try {
clazz = Class.forName( name );
}
catch ( ClassNotFoundException e ) {
throw new IllegalArgumentException( "No such XML-RPC "
+ "implementation \""
+ name + "\"" );
}
try {
return (XmlRpcKit) clazz.newInstance();
}
catch ( Throwable e ) {
throw (RuntimeException)
new IllegalArgumentException( "Error instantiating custom "
+ "XmlRpcKit "
+ clazz.getName() )
.initCause( e );
}
}
/**
* Constructs the default instance of this class based on system property
* and class availability.
*
* @return XmlRpcKit object
* @see #getInstance
*/
private static XmlRpcKit createDefaultInstance() {
XmlRpcKit[] impls = KNOWN_IMPLS;
String implName = System.getProperty( IMPL_PROP );
logger_.info( "Creating default XmlRpcInstance: " + IMPL_PROP + "=" +
implName );
// No implementation specified by system property -
// use the first one in the list that works.
if ( implName == null ) {
for ( int i = 0; i < impls.length; i++ ) {
if ( impls[ i ].isAvailable() ) {
return impls[ i ];
}
}
return impls[ 0 ];
}
// Implementation specified by system property -
// try to find one with a matching name in the known list.
else {
return getInstanceByName( implName );
}
}
/**
* Returns a new XmlRpcKit given classnames for the client and server
* factory classes. If the classes are not available, a kit which
* returns {@link #isAvailable}()=false will be returned.
*
* @param name kit name
* @param clientFactoryClassName name of class implementing
* SampXmlRpcClientFactory which has a no-arg constructor
* @param serverFactoryClassName name of class implementing
* SampXmlRpcServerFactory which has a no-arg constructor
* @return new XmlRpcKit constructed using reflection
*/
public static XmlRpcKit createReflectionKit( String name,
String clientFactoryClassName,
String serverFactoryClassName
) {
SampXmlRpcClientFactory clientFactory = null;
SampXmlRpcServerFactory serverFactory = null;
Throwable error = null;
try {
clientFactory = (SampXmlRpcClientFactory)
Class.forName( clientFactoryClassName )
.newInstance();
serverFactory = (SampXmlRpcServerFactory)
Class.forName( serverFactoryClassName )
.newInstance();
}
catch ( ClassNotFoundException e ) {
error = e;
}
catch ( LinkageError e ) {
error = e;
}
catch ( InstantiationException e ) {
error = e;
}
catch ( IllegalAccessException e ) {
error = e;
}
if ( clientFactory != null && serverFactory != null ) {
assert error == null;
return new AvailableKit( name, clientFactory, serverFactory );
}
else {
assert error != null;
return new UnavailableKit( name, error );
}
}
/**
* XmlRpcKit implementation which is available.
*/
private static class AvailableKit extends XmlRpcKit {
private final String name_;
private final SampXmlRpcClientFactory clientFactory_;
private final SampXmlRpcServerFactory serverFactory_;
/**
* Constructor.
*
* @param name implementation name
* @param clientFactory SampXmlRpcClientFactory instance
* @param serverFactory SampXmlRpcServerFactory instance
*/
AvailableKit( String name,
SampXmlRpcClientFactory clientFactory,
SampXmlRpcServerFactory serverFactory ) {
name_ = name;
clientFactory_ = clientFactory;
serverFactory_ = serverFactory;
}
public SampXmlRpcClientFactory getClientFactory() {
return clientFactory_;
}
public SampXmlRpcServerFactory getServerFactory() {
return serverFactory_;
}
public String getName() {
return name_;
}
public boolean isAvailable() {
return true;
}
public String toString() {
return name_;
}
}
/**
* XmlRpcKit implementation which always returns false from isAvailable
* and throws exceptions from getServer/Client factory methods.
*/
private static class UnavailableKit extends XmlRpcKit {
private final String name_;
private final Throwable error_;
/**
* Constructor.
*
* @param kit name
* @param error the reason the kit is unavailable
*/
UnavailableKit( String name, Throwable error ) {
name_ = name;
error_ = error;
}
public SampXmlRpcClientFactory getClientFactory() {
throw (RuntimeException)
new UnsupportedOperationException( name_
+ " implementation not"
+ " available" )
.initCause( error_ );
}
public SampXmlRpcServerFactory getServerFactory() {
throw (RuntimeException)
new UnsupportedOperationException( name_
+ " implementation not"
+ " available" )
.initCause( error_ );
}
public String getName() {
return name_;
}
public boolean isAvailable() {
return false;
}
public String toString() {
return name_;
}
}
/**
* Returns an available or unavailable XmlRpcKit based on Apache XML-RPC
* version 1.2.
*
* @param name kit name
* @return new kit
*/
private static XmlRpcKit createApacheKit( String name ) {
XmlRpcKit kit = createReflectionKit(
name,
"org.astrogrid.samp.xmlrpc.apache.ApacheClientFactory",
"org.astrogrid.samp.xmlrpc.apache.ApacheServerFactory" );
if ( kit.isAvailable() ) {
try {
Class xClazz = Class.forName( "org.apache.xmlrpc.XmlRpc" );
Field vField = xClazz.getField( "version" );
Object version = Modifier.isStatic( vField.getModifiers() )
? vField.get( null )
: null;
if ( version instanceof String
&& ((String) version)
.startsWith( "Apache XML-RPC 1.2" ) ) {
return kit;
}
else {
String msg = "Wrong Apache XML-RPC version: " + version
+ " not 1.2";
return
new UnavailableKit( name,
new ClassNotFoundException( msg ) );
}
}
catch ( Throwable e ) {
return new UnavailableKit( name, e );
}
}
else {
return kit;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ 0000775 0000000 0000000 00000000000 13564500043 0023727 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheClient.java 0000664 0000000 0000000 00000003066 13564500043 0027117 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.apache;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import org.apache.xmlrpc.XmlRpcClient;
import org.apache.xmlrpc.XmlRpcException;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClient;
/**
* SampXmlRpcClient implementation based on Apache XMLRPC classes.
*
* @author Mark Taylor
* @since 16 Sep 2008
*/
public class ApacheClient implements SampXmlRpcClient {
private final XmlRpcClient xmlrpcClient_;
/**
* Constructor.
*
* @param xmlrpcClient Apache XML-RPC client instance
*/
public ApacheClient( XmlRpcClient xmlrpcClient ) {
xmlrpcClient_ = xmlrpcClient;
}
public Object callAndWait( String method, List params )
throws IOException {
try {
return xmlrpcClient_
.execute( method, (Vector) ApacheUtils.toApache( params ) );
}
catch ( XmlRpcException e ) {
throw (IOException) new IOException( e.getMessage() )
.initCause( e );
}
}
public void callAndForget( String method, List params )
throws IOException {
// I'm not sure that the Apache implementation is *sufficiently*
// asynchronous. It does leave a thread hanging around waiting
// for a response, though the result of this response is
// discarded. May cause problems under heavy load.
xmlrpcClient_
.executeAsync( method, (Vector) ApacheUtils.toApache( params ),
null );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheClientFactory.java 0000664 0000000 0000000 00000001135 13564500043 0030442 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.apache;
import java.io.IOException;
import java.net.URL;
import org.apache.xmlrpc.XmlRpcClientLite;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClient;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory;
/**
* SampXmlRpcClientFactory implementation based on Apache XMLRPC classes.
*
* @author Mark Taylor
* @since 16 Sep 2008
*/
public class ApacheClientFactory implements SampXmlRpcClientFactory {
public SampXmlRpcClient createClient( URL endpoint ) throws IOException {
return new ApacheClient( new XmlRpcClientLite( endpoint ) );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheServer.java 0000664 0000000 0000000 00000012576 13564500043 0027155 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.apache;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.xmlrpc.WebServer;
import org.apache.xmlrpc.XmlRpcHandler;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.xmlrpc.SampXmlRpcServer;
import org.astrogrid.samp.xmlrpc.SampXmlRpcHandler;
/**
* SampXmlRpcServer implementation based on Apache XML-RPC library.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public class ApacheServer implements SampXmlRpcServer {
private final WebServer webServer_;
private final URL endpoint_;
private final List handlerList_;
/**
* Private constructor used by all other constructors.
* Uses the private LabelledServer class to aggregate the required
* information.
*
* @param server server with metadata
*/
private ApacheServer( LabelledServer server ) {
webServer_ = server.webServer_;
endpoint_ = server.endpoint_;
handlerList_ = Collections.synchronizedList( new ArrayList() );
webServer_.addHandler( "$default", new XmlRpcHandler() {
public Object execute( String method, Vector params )
throws Exception {
return doExecute( method, params );
}
} );
}
/**
* Constructs a new server based on a given WebServer object.
* Responsibility for apache
: implementation based on the
* Apache XML-RPC libraryinternal
: implementation which requires no libraries
* beyond JSAMP itselfstart
ing the WebServer and performing
* any other required configuration lies with the caller.
*
* @param webServer apache xmlrpc webserver object
* @param port port number on which the server is running
*/
public ApacheServer( WebServer webServer, int port ) {
this( new LabelledServer( webServer, getServerEndpoint( port ) ) );
}
/**
* Constructs a new server starting up a new WebServer object.
* The server runs in a daemon thread.
*/
public ApacheServer() throws IOException {
this( createLabelledServer( true ) );
webServer_.start();
}
public URL getEndpoint() {
return endpoint_;
}
public void addHandler( SampXmlRpcHandler handler ) {
handlerList_.add( handler );
}
public void removeHandler( SampXmlRpcHandler handler ) {
handlerList_.remove( handler );
}
/**
* Does the work for executing an XML-RPC request.
*
* @param fqMethod fully qualified XML-RPC method name
* @param paramVec Apache-style list of method parameters
*/
private Object doExecute( String fqMethod, Vector paramVec )
throws Exception {
SampXmlRpcHandler[] handlers =
(SampXmlRpcHandler[])
handlerList_.toArray( new SampXmlRpcHandler[ 0 ] );
for ( int ih = 0; ih < handlers.length; ih++ ) {
SampXmlRpcHandler handler = handlers[ ih ];
if ( handler.canHandleCall( fqMethod ) ) {
List paramList = (List) ApacheUtils.fromApache( paramVec );
Object result = handler.handleCall( fqMethod, paramList, null );
return ApacheUtils.toApache( result );
}
}
throw new UnsupportedOperationException( "No handler for method "
+ fqMethod );
}
/**
* Constructs a new LabelledServer object suitable for use with this
* server.
*
* @param isDaemon whether the WebServer's main thread should run
* in daemon mode
*/
private static LabelledServer createLabelledServer( final boolean isDaemon )
throws IOException {
int port = SampUtils.getUnusedPort( 2300 );
WebServer server = new WebServer( port ) {
// Same as superclass implementation except that the listener
// thread is marked as a daemon.
public void start() {
if ( this.listener == null ) {
this.listener =
new Thread( this, "XML-RPC Weblistener" );
this.listener.setDaemon( isDaemon );
this.listener.start();
}
}
};
return new LabelledServer( server, getServerEndpoint( port ) );
}
/**
* Returns the endpoint URL to use for an Apache server running on a
* given port.
*
* @param port port number
* @return URL
*/
private static URL getServerEndpoint( int port ) {
String endpoint =
"http://" + SampUtils.getLocalhost() + ":" + port + "/";
try {
return new URL( endpoint );
}
catch ( MalformedURLException e ) {
throw (Error)
new AssertionError( "Bad protocol http?? " + endpoint )
.initCause( e );
}
}
/**
* Convenience class which aggregates a WebServer and an endpoint.
*/
private static class LabelledServer {
private final WebServer webServer_;
private final URL endpoint_;
/**
* Constructor.
*
* @param webServer web server
* @param endpoint URL
*/
LabelledServer( WebServer webServer, URL endpoint ) {
webServer_ = webServer;
endpoint_ = endpoint;
}
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheServerFactory.java 0000664 0000000 0000000 00000001255 13564500043 0030475 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.apache;
import java.io.IOException;
import org.astrogrid.samp.xmlrpc.SampXmlRpcServer;
import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory;
/**
* SampXmlRpcServerFactory implementation which uses Apache classes.
* Server construction is lazy and the same server is returned each time.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
public class ApacheServerFactory implements SampXmlRpcServerFactory {
private SampXmlRpcServer server_;
public synchronized SampXmlRpcServer getServer() throws IOException {
if ( server_ == null ) {
server_ = new ApacheServer();
}
return server_;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheUtils.java 0000664 0000000 0000000 00000004445 13564500043 0027003 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.apache;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* Provides utility methods to perform translations between the
* data structurs used by the org.apache.xmlrpc classes and those used
* by JSAMP.
*
* @author Mark Taylor
* @since 22 Aug 2008
*/
class ApacheUtils {
/**
* Private constructor prevents instantiation.
*/
private ApacheUtils() {
}
/**
* Converts an object from JSAMP XML-RPC form to Apache XML-RPC form.
* Basically, this means converting
* {@link java.util.Map}s to {@link java.util.Hashtable}s and
* {@link java.util.List}s to {@link java.util.Vector}s.
*
* @param obj XML-RPC data structure suitable for use within JSAMP
* @return XML-RPC data structure suitable for use within Apache
*/
public static Object toApache( Object obj ) {
if ( obj instanceof List ) {
Vector vec = new Vector();
for ( Iterator it = ((List) obj).iterator(); it.hasNext(); ) {
vec.add( toApache( it.next() ) );
}
return vec;
}
else if ( obj instanceof Map ) {
Hashtable hash = new Hashtable();
for ( Iterator it = ((Map) obj).entrySet().iterator();
it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
hash.put( entry.getKey(), toApache( entry.getValue() ) );
}
return hash;
}
else if ( obj instanceof String ) {
return obj;
}
else {
throw new IllegalArgumentException(
"Non-SAMP object type " + ( obj == null
? null
: obj.getClass().getName() ) );
}
}
/**
* Converts an object from Apache XML-RPC form to JSAMP XML-RPC form.
* Since Hashtable implements Map and Vector implements List, this is
* a no-op.
*
* @param data XML-RPC data structure suitable for use within Apache
* @return XML-RPC data structure suitable for use within JSAMP
*/
public static Object fromApache( Object data ) {
return data;
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/package.html 0000664 0000000 0000000 00000000315 13564500043 0026207 0 ustar 00root root 0000000 0000000
Implementation of pluggable XML-RPC layer using Apache XML-RPC.
These classes were developed against the apache-1.2-b1 version;
they may or may not work with other versions of that library.
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/ 0000775 0000000 0000000 00000000000 13564500043 0024322 5 ustar 00root root 0000000 0000000 jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/InternalClient.java 0000664 0000000 0000000 00000017273 13564500043 0030112 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.internal;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.astrogrid.samp.SampUtils;
import org.astrogrid.samp.xmlrpc.SampXmlRpcClient;
/**
* XML-RPC client implementation suitable for use with SAMP.
* This implementation is completely freestanding and requires no other
* libraries.
*
* @author Mark Taylor
* @since 26 Aug 2008
*/
public class InternalClient implements SampXmlRpcClient {
private final URL endpoint_;
private final String userAgent_;
private static final Logger logger_ =
Logger.getLogger( InternalClient.class.getName() );
/**
* Constructor.
*
* @param endpoint endpoint
*/
public InternalClient( URL endpoint ) {
endpoint_ = endpoint;
userAgent_ = "JSAMP/" + SampUtils.getSoftwareVersion();
}
public Object callAndWait( String method, List params )
throws IOException {
HttpURLConnection connection =
(HttpURLConnection) endpoint_.openConnection();
byte[] callBuf = serializeCall( method, params );
connection.setDoOutput( true );
connection.setDoInput( true );
connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "text/xml" );
connection.setRequestProperty( "Content-Length",
Integer.toString( callBuf.length ) );
connection.setRequestProperty( "User-Agent", userAgent_ );
connection.connect();
OutputStream out = connection.getOutputStream();
out.write( callBuf );
out.flush();
out.close();
int responseCode = connection.getResponseCode();
if ( responseCode != HttpURLConnection.HTTP_OK ) {
throw new IOException( responseCode + " "
+ connection.getResponseMessage() );
}
InputStream in = new BufferedInputStream( connection.getInputStream() );
Object result = deserializeResponse( in );
connection.disconnect();
return result;
}
// NOTE: if this method is invoked from a shutdownHook thread,
// the call may not complete because it is completed from a new thread.
public void callAndForget( String method, List params )
throws IOException {
final HttpURLConnection connection =
(HttpURLConnection) endpoint_.openConnection();
byte[] callBuf = serializeCall( method, params );
connection.setDoOutput( true );
connection.setDoInput( true );
connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "text/xml" );
connection.setRequestProperty( "Content-Length",
Integer.toString( callBuf.length ) );
connection.setRequestProperty( "User-Agent", userAgent_ );
connection.connect();
OutputStream out = connection.getOutputStream();
out.write( callBuf );
out.flush();
out.close();
// It would be nice to just not read the input stream at all.
// However, connection.setDoInput(false) and doing no reads causes
// trouble - probably the call doesn't complete at the other end or
// something. So read it to the end asynchronously.
new Thread() {
public void run() {
try {
InputStream in =
new BufferedInputStream( connection.getInputStream() );
while ( in.read() >= 0 ) {}
int responseCode = connection.getResponseCode();
if ( responseCode != HttpURLConnection.HTTP_OK ) {
logger_.warning( responseCode + " " +
connection.getResponseMessage() );
}
}
catch ( IOException e ) {
}
finally {
connection.disconnect();
}
}
}.start();
}
/**
* Generates the XML methodCall
document corresponding
* to an XML-RPC method call.
*
* @param method methodName string
* @param paramList list of XML-RPC parameters
* @return XML document as byte array
*/
protected byte[] serializeCall( String method, List paramList )
throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
XmlWriter xout = new XmlWriter( bos, 2 );
xout.start( "methodCall" );
xout.inline( "methodName", method );
if ( ! paramList.isEmpty() ) {
xout.start( "params" );
for ( Iterator it = paramList.iterator(); it.hasNext(); ) {
xout.start( "param" );
xout.sampValue( it.next() );
xout.end( "param" );
}
xout.end( "params" );
}
xout.end( "methodCall" );
xout.close();
return bos.toByteArray();
}
/**
* Deserializes an XML-RPC methodResponse
document to a
* Java object.
*
* @param in input stream containing response document
*/
protected Object deserializeResponse( InputStream in )
throws IOException {
try {
Document doc = XmlUtils.createDocumentBuilder().parse( in );
Element top =
XmlUtils.getChild( XmlUtils.getChild( doc, "methodResponse" ) );
String topName = top.getTagName();
if ( "fault".equals( topName ) ) {
Element value = XmlUtils.getChild( top, "value" );
XmlUtils.getChild( value, "struct" );
Map faultMap = (Map) XmlUtils.parseSampValue( value );
Object fcode = faultMap.get( "faultCode" );
Object fmsg = faultMap.get( "faultString" );
int code = fcode instanceof Integer
? ((Integer) fcode).intValue()
: -9999;
final String msg = String.valueOf( fmsg );
throw new XmlRpcFault( code, msg );
}
else if ( "params".equals( topName ) ) {
Element value =
XmlUtils.getChild( XmlUtils.getChild( top, "param" ),
"value" );
return XmlUtils.parseSampValue( value );
}
else {
throw new XmlRpcFormatException( "Not reqInfo
argument passed to the
* {@link SampXmlRpcHandler#handleCall handleCall} method of registered
* SampXmlRpcHandler
s is the associated
* {@link org.astrogrid.samp.httpd.HttpServer.Request}.
*
* @author Mark Taylor
* @since 27 Aug 2008
*/
public class InternalServer implements SampXmlRpcServer {
private final HttpServer server_;
private final URL endpoint_;
private final List handlerList_;
private final HttpServer.Handler serverHandler_;
private static final HttpServer.Response GET_RESPONSE =
createInfoResponse( true );
private static final HttpServer.Response HEAD_RESPONSE =
createInfoResponse( false );
private static final Logger logger_ =
Logger.getLogger( InternalServer.class.getName() );
/**
* Constructor based on a given HTTP server.
* It is the caller's responsibility to configure and start the HttpServer.
*
* @param httpServer server for processing HTTP requests
* @param path path part of server endpoint (starts with "/");
*/
public InternalServer( HttpServer httpServer, final String path )
throws IOException {
server_ = httpServer;
endpoint_ = new URL( server_.getBaseUrl(), path );
handlerList_ = Collections.synchronizedList( new ArrayList() );
serverHandler_ = new HttpServer.Handler() {
public HttpServer.Response serveRequest( HttpServer.Request req ) {
if ( req.getUrl().equals( path ) ) {
String method = req.getMethod();
if ( "POST".equals( method ) ) {
return getXmlRpcResponse( req );
}
else if ( "GET".equals( method ) ) {
return GET_RESPONSE;
}
else if ( "HEAD".equals( method ) ) {
return HEAD_RESPONSE;
}
else {
return HttpServer
.create405Response( new String[] { "POST", "GET",
"HEAD", } );
}
}
else {
return null;
}
}
};
}
/**
* Constructs a server running with default characteristics.
* Currently, the default server
* {@link org.astrogrid.samp.httpd.UtilServer#getInstance} is used.
*/
public InternalServer() throws IOException {
this( UtilServer.getInstance().getServer(),
UtilServer.getInstance().getBasePath( "/xmlrpc" ) );
}
public URL getEndpoint() {
return endpoint_;
}
/**
* Returns the HTTP server hosting this XML-RPC server.
*
* @return http server
*/
public HttpServer getHttpServer() {
return server_;
}
public void addHandler( SampXmlRpcHandler handler ) {
synchronized ( handlerList_ ) {
if ( handlerList_.isEmpty() ) {
server_.addHandler( serverHandler_ );
}
handlerList_.add( handler );
}
}
public void removeHandler( SampXmlRpcHandler handler ) {
synchronized ( handlerList_ ) {
handlerList_.remove( handler );
if ( handlerList_.isEmpty() ) {
server_.removeHandler( serverHandler_ );
}
}
}
/**
* Returns the HTTP response object given an incoming XML-RPC POST request.
* Any error should be handled by returning a fault-type methodResponse
* element rather than by throwing an exception.
*
* @param request POSTed HTTP request
* @return XML-RPC response (possibly fault)
*/
protected HttpServer.Response
getXmlRpcResponse( HttpServer.Request request ) {
byte[] rbuf;
try {
rbuf = getResultBytes( getXmlRpcResult( request ) );
}
catch ( Throwable e ) {
boolean isSerious = e instanceof Error;
logger_.log( isSerious ? Level.WARNING : Level.INFO,
"XML-RPC fault return", e );
try {
rbuf = getFaultBytes( e );
}
catch ( IOException e2 ) {
return HttpServer.createErrorResponse( 500, "Server error",
e2 );
}
}
final byte[] replyBuf = rbuf;
Map hdrMap = new LinkedHashMap();
hdrMap.put( "Content-Length", Integer.toString( replyBuf.length ) );
hdrMap.put( "Content-Type", "text/xml" );
return new HttpServer.Response( 200, "OK", hdrMap ) {
public void writeBody( OutputStream out ) throws IOException {
out.write( replyBuf );
}
};
}
/**
* Returns the SAMP-friendly (string, list and map only) object representing
* the reply to an XML-RPC request given by a request.
*
* @param request POSTed HTTP request
* @return SAMP-friendly object
* @throws Exception in case of error (will become XML-RPC fault)
*/
private Object getXmlRpcResult( HttpServer.Request request )
throws Exception {
byte[] body = request.getBody();
// Parse body as XML document.
if ( body == null || body.length == 0 ) {
throw new XmlRpcFormatException( "No body in POSTed request" );
}
Document doc = XmlUtils.createDocumentBuilder()
.parse( new ByteArrayInputStream( body ) );
// Extract XML-RPC information from DOM.
XmlRpcCall call = XmlRpcCall.createCall( doc );
String methodName = call.getMethodName();
List paramList = call.getParams();
// Find one of the registered handlers to handle this request.
SampXmlRpcHandler handler = null;
SampXmlRpcHandler[] handlers =
(SampXmlRpcHandler[])
handlerList_.toArray( new SampXmlRpcHandler[ 0 ] );
for ( int ih = 0; ih < handlers.length && handler == null; ih++ ) {
SampXmlRpcHandler h = handlers[ ih ];
if ( h.canHandleCall( methodName ) ) {
handler = h;
}
}
if ( handler == null ) {
throw new XmlRpcFormatException( "Unknown XML-RPC method "
+ methodName );
}
// Pass the call to the handler and return the result.
return handleCall( handler, methodName, paramList, request );
}
/**
* Actually passes the XML-RPC method name and parameter list to one
* of the registered servers for processing.
*
* @param handler handler which has declared it can handle the
* named method
* @param methodName XML-RPC method name
* @param paramList list of parameters to XML-RPC call
* @param request HTTP request from which this call originated
*/
protected Object handleCall( SampXmlRpcHandler handler, String methodName,
List paramList, HttpServer.Request request )
throws Exception {
return handler.handleCall( methodName, paramList, request );
}
/**
* Turns a SAMP-friendly (string, list, map only) object into an array
* of bytes giving an XML-RPC methodResponse document.
*
* @param result SAMP-friendly object
* @return XML methodResponse document as byte array
*/
public static byte[] getResultBytes( Object result ) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedOutputStream bout = new BufferedOutputStream( out );
XmlWriter xout = new XmlWriter( bout, 2 );
xout.start( "methodResponse" );
xout.start( "params" );
xout.start( "param" );
xout.sampValue( result );
xout.end( "param" );
xout.end( "params" );
xout.end( "methodResponse" );
xout.close();
return out.toByteArray();
}
/**
* Turns an exception into an array of bytes giving an XML-RPC
* methodResponse (fault) document.
*
* @param error throwable
* @return XML methodResponse document as byte array
*/
public static byte[] getFaultBytes( Throwable error ) throws IOException {
int faultCode = 1;
String faultString = error.toString();
// Write the method response element. We can't use the XmlWriter
// sampValue method to do the grunt-work here since the faultCode
// contains an XML-RPC Server
\n" )
.append( "methodName
element
* @param params SAMP-friendly list of parameters as contained
* in the params
element
*/
public XmlRpcCall( String methodName, List params ) {
methodName_ = methodName;
params_ = Collections.unmodifiableList( params );
}
/**
* Returns the method name.
*
* @return content of methodName
element
*/
public String getMethodName() {
return methodName_;
}
/**
* Returns the parameter list.
*
* @return SAMP-friendly list of parameter values from
* params
element
*/
public List getParams() {
return params_;
}
/**
* Constructs an XmlRpcCall instance from a document with a
* methodCall element at top level.
*
* @param callDoc node whose child is an XML-RPC
* methodCall
element
* @return call instance
* @throws XmlRpcFormatException if the document does not have the
* expected form
*/
public static XmlRpcCall createCall( Document callDoc )
throws XmlRpcFormatException {
// Get expected top-level element.
Element callEl = XmlUtils.getChild( callDoc, "methodCall" );
// Get methodName and params elements.
String methodName = null;
Element paramsEl = null;
Element[] methodChildren = XmlUtils.getChildren( callEl );
for ( int i = 0; i < methodChildren.length; i++ ) {
Element el = methodChildren[ i ];
String tagName = el.getTagName();
if ( tagName.equals( "methodName" ) ) {
methodName = XmlUtils.getTextContent( el );
}
else if ( tagName.equals( "params" ) ) {
paramsEl = el;
}
}
if ( methodName == null ) {
throw new XmlRpcFormatException( "No methodName element" );
}
// Extract parameter values as list.
Element[] paramEls = paramsEl == null
? new Element[ 0 ]
: XmlUtils.getChildren( paramsEl );
int np = paramEls.length;
List paramList = new ArrayList( np );
for ( int i = 0; i < np; i++ ) {
Element paramEl = paramEls[ i ];
if ( ! "param".equals( paramEl.getTagName() ) ) {
throw new XmlRpcFormatException( "Non-param child of params" );
}
else {
Element valueEl = XmlUtils.getChild( paramEl, "value" );
paramList.add( XmlUtils.parseSampValue( valueEl ) );
}
}
// Construct and return call.
return new XmlRpcCall( methodName, paramList );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlRpcFormatException.java 0000664 0000000 0000000 00000001164 13564500043 0031424 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.internal;
import java.io.IOException;
/**
* Exception thrown when an XML document which is intended for XML-RPC
* processing has the wrong format, for instance violates the XML-RPC spec.
*
* @author Mark Taylor
* @since 26 Aug 2008
*/
class XmlRpcFormatException extends IOException {
/**
* No-arg constructor.
*/
public XmlRpcFormatException() {
this( "Badly-formed XML-RPC request/response" );
}
/**
* Constructor.
*
* @param msg message
*/
public XmlRpcFormatException( String msg ) {
super( msg );
}
}
jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlUtils.java 0000664 0000000 0000000 00000021116 13564500043 0026747 0 ustar 00root root 0000000 0000000 package org.astrogrid.samp.xmlrpc.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* Utilities for XML manipulations required by SAMP/XML-RPC.
*
* @author Mark Taylor
* @since 26 Aug 2008
*/
public class XmlUtils {
private static Logger logger_ =
Logger.getLogger( XmlUtils.class.getName() );
private static DocumentBuilderFactory dbFact_;
/**
* Private constructor prevents instantiation.
*/
private XmlUtils() {
}
/**
* Returns an array of all the Element children of a DOM node.
*
* @param parent parent node
* @return children array
*/
public static Element[] getChildren( Node parent ) {
NodeList nodeList = parent.getChildNodes();
int nnode = nodeList.getLength();
List elList = new ArrayList( nnode );
for ( int i = 0; i < nnode; i++ ) {
Node node = nodeList.item( i );
if ( node instanceof Element ) {
elList.add( (Element) node );
}
}
return (Element[]) elList.toArray( new Element[ 0 ] );
}
/**
* Returns the single element child of a DOM node.
*
* @param parent parent node
* @return sole child element
* @throws XmlRpcFormatException if there is not exactly one child
* per element
*/
public static Element getChild( Node parent ) throws XmlRpcFormatException {
Element[] els = getChildren( parent );
if ( els.length == 1 ) {
return els[ 0 ];
}
else if ( els.length == 0 ) {
throw new XmlRpcFormatException( "No child element of "
+ ((Element) parent).getTagName() );
}
else {
throw new XmlRpcFormatException( "Multiple children of "
+ ((Element) parent).getTagName() );
}
}
/**
* Returns the single child element of a DOM node, which has a given
* known name.
*
* @param parent parent node
* @param tagName child node name
* @return sole child element with name tagName
* @throws XmlRpcFormatException if there is not exactly one child
* element or if it does not have name tagName
*/
public static Element getChild( Node parent, String tagName )
throws XmlRpcFormatException {
Element child = getChild( parent );
if ( ! tagName.equals( child.getTagName() ) ) {
throw new XmlRpcFormatException( "Unexpected child of "
+ ((Element) parent).getTagName()
+ ": " + child.getTagName()
+ " is not " + tagName );
}
return child;
}
/**
* Returns the text content of an element as a string.
*
* @param el parent node
* @return text content
* @throws XmlRpcFormatException if content is not just text
*/
public static String getTextContent( Element el ) throws
XmlRpcFormatException {
StringBuffer sbuf = new StringBuffer();
for ( Node node = el.getFirstChild(); node != null;
node = node.getNextSibling() ) {
if ( node instanceof Text ) {
sbuf.append( ((Text) node).getData() );
}
else if ( node instanceof Element ) {
throw new XmlRpcFormatException( "Unexpected node " + node
+ " in " + el.getTagName()
+ " content" );
}
}
return sbuf.toString();
}
/**
* Returns the content of a DOM element representing a value
* element of an XML-RPC document.
* Note that some content which would be legal in XML-RPC, but is not
* legal in SAMP, may result in an exception.
*
* @param valueEl value element
* @return SAMP-friendly object (string, list or map)
*/
public static Object parseSampValue( Element valueEl )
throws XmlRpcFormatException {
if ( getChildren( valueEl ).length == 0 ) {
return getTextContent( valueEl );
}
Element el = getChild( valueEl );
String name = el.getTagName();
if ( "array".equals( name ) ) {
Element[] valueEls = getChildren( getChild( el, "data" ) );
int nel = valueEls.length;
List list = new ArrayList( nel );
for ( int i = 0; i < nel; i++ ) {
list.add( parseSampValue( valueEls[ i ] ) );
}
return list;
}
else if ( "struct".equals( name ) ) {
Element[] memberEls = getChildren( el );
Map map = new HashMap();
for ( int i = 0; i < memberEls.length; i++ ) {
Element member = memberEls[ i ];
if ( ! "member".equals( member.getTagName() ) ) {
throw new XmlRpcFormatException(
"Non-