tag:blogger.com,1999:blog-36198816953442539372024-03-19T03:22:58.956-07:00Tech EurekaRandom thoughts about Java, Patterns, EAI, C++Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.comBlogger37125tag:blogger.com,1999:blog-3619881695344253937.post-58430297914561924892015-05-22T07:38:00.002-07:002015-05-22T07:38:14.176-07:00WebSphere MQ Client throws 2035 (MQRC_NOT_AUTHORIZED) after upgrading MQ QueueManager to version 8<br />
<span style="color: #333333; font-family: Arial, sans-serif;"><span style="background-color: white; line-height: 15.3600006103516px;">It took me a while to figure out, so here is a solution to this issue:</span></span><br />
<span style="color: #333333; font-family: Arial, sans-serif;"><span style="background-color: white; line-height: 15.3600006103516px;"><br /></span></span>
<span style="color: #333333; font-family: Arial, sans-serif;"><span style="background-color: white; line-height: 15.3600006103516px;"><b><u>Problem</u></b>: a</span></span><span style="background-color: white; color: #333333; font-family: Arial, sans-serif; line-height: 15.3600006103516px;">fter an upgrade (or rather uninstall/install) of an MQSeries Queue Manager to Version 8.0.0, my C application suddenly complains about being not authorized:</span><span style="background-color: white; color: #333333; font-family: Arial, sans-serif; font-size: 12.8000001907349px; line-height: 15.3600006103516px;"> </span><br />
<br />
MQCONNX(qmgr=,MQCNO_STANDARD_BINDING) - reason: 2035 (MQRC_NOT_AUTHORIZED)<br />
<div>
<br /></div>
<div>
Now, the issue, it seems is that with MQ Verson 8, by default, the administrator must have user id and password set if he uses the MQ Client connection. I'm not here to judge, but you should read this </div>
<div>
first: <a href="http://www-01.ibm.com/support/docview.wss?uid=swg21577137">http://www-01.ibm.com/support/docview.wss?uid=swg21577137</a></div>
<div>
<br /></div>
<div>
To be sure your issue is the same as mine, check your error log in C:\ProgramData\IBM\MQ\qmgrs\QM1\errors\AMQERR01.LOG:</div>
<div>
<br /></div>
<div>
----- amqzfuca.c : 4107 -------------------------------------------------------</div>
<div>
5/22/2015 10:12:55 - Process(27776.20) User(apodehl) Program(amqzlaa0.exe)</div>
<div>
Host(BERLIN) Installation(Installation1)</div>
<div>
VRMF(8.0.0.0) QMgr(QM1) </div>
<div>
AMQ5541: The failed authentication check was caused by the queue manager</div>
<div>
CONNAUTH CHCKCLNT(REQDADM) configuration.</div>
<div>
EXPLANATION:</div>
<div>
The user ID 'apodehl' and its password were checked because the user ID is</div>
<div>
privileged and the queue manager connection authority (CONNAUTH) configuration</div>
<div>
refers to an authentication information (AUTHINFO) object named</div>
<div>
'SYSTEM.DEFAULT.AUTHINFO.IDPWOS' with CHCKCLNT(REQDADM). </div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b style="text-decoration: underline;">Solution:</b> be aware of the security implications, but you can change this authentication in the WebSphere MQ Explorer -> QM1 -> Authentication Information, choose SYSTEM...IDPWOS and set "<b>Check client connections</b>" to be "<b>None</b>" instead of 'Required for Adminstrators' </div>
<div>
<br /></div>
<div>
<br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQeaY04X0e_vuATU0wVLUj7Jui90ZcraXVPUKO2wTd29kUSqeNNbbphhhD7wKz4tOCOQbG2D1wq_KS4D5lLv5s897ZLPe1kFTkam9F6LoJl_wv62s3yc6_Lk4S2_cIdPkI_SLeERHpuMw/s1600/mq.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQeaY04X0e_vuATU0wVLUj7Jui90ZcraXVPUKO2wTd29kUSqeNNbbphhhD7wKz4tOCOQbG2D1wq_KS4D5lLv5s897ZLPe1kFTkam9F6LoJl_wv62s3yc6_Lk4S2_cIdPkI_SLeERHpuMw/s1600/mq.jpg" /></a></div>
</div>
Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-80516951638956538732014-09-16T07:06:00.000-07:002014-09-16T07:22:34.565-07:00How to secure a password file on Windows 7 (JMX interface of ActiveMQ, to be specific)<div style="font-family: Arial, Helvetica, FreeSans, Luxi-sans, 'Nimbus Sans L', sans-serif; font-size: 14px; margin: 3px 0px 0px; padding: 0px;">
This took me a while to figure out, so here's a description of how to make use of a <b>password-protected JMX interface</b> with ActiveMQ (5.8 in my case).</div>
<br />
1. Make sure your activemq.xml specifies that you actually want to allow JMX monitoring:<br />
<pre><code> <managementContext>
<managementContext createConnector="true" connectorPort="1098"/>
</managementContext>
</code></pre>
<pre><code>
</code></pre>
2. Change activemq.bat startup script to specify <b>an explicit password files</b>:
<br />
<div>
<br /></div>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">set SUNJMX=-Dcom.sun.management.jmxremote.port=1098</span><br />
<span style="font-size: x-small;"><span style="font-family: Courier New, Courier, monospace;">-Dcom.sun.management.jmxremote.ssl=false</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">-Dcom.sun.management.jmxremote.password.file=%ACTIVEMQ_BASE%/conf/<b>jmx.password</b></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">-Dcom.sun.management.jmxremote.access.file=%ACTIVEMQ_BASE%/conf/jmx.access</span><br />
<br />
when you start ActiveMQ, you will probably get this error now:<br />
<br />
> activemq.bat<br />
Error: <b>Password file read access must be restricted</b>: .../conf/jmx.password<br />
<br />
ActiveMQ requires the password file to have specific user-only permissions, see <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/management/security-windows.html">here</a> for more information. Unfortunately this link is for Windows XP, so here's what to do on <b><u>Windows 7</u></b><br />
<b><u><br /></u></b>
I've actually found two solutions, one graphical, the other one from the command line:<br />
<br />
<b><u><span style="font-size: large;">Solution (using Windows Explorer):</span></u></b><br />
<br />
1) change the owner to be 'you' (required step!!)<br />
Select jmx.password, Right-Mouse-Cick -> Properties -> Security -> Advanced -> Owner -> Edit<br />
and select the single owner of this to be your username.<br />
<u><br /></u>
<u>Note:</u> you need to click OK and exit out of Properties for this to be effective<br />
<br />
2) Select jmx.password, Right-Mouse-Cick -> Properties -> Security -> Advanced -> <b>Change Permissions </b><br />
<b><br /></b>
<b>- uncheck </b>"Include inheritable permissions" and click <b>Remove </b>to remove all inherited permissions<br />
<b>- </b>then click <b>Add... </b>to add read/write permissions for only your user: Enter your username as object name, and select for example '<b>Full Control</b>'. Click Ok and exit out of properties.<br />
<br />
<br />
<b><u><span style="font-size: large;">Solution (using Windows command line):</span></u></b><br />
<b><u><br /></u></b>
1) open a windows command prompt in your ActiveMQ 'conf' folder.<br />
<br />
<br />
2) use <b>icacls </b>(run 'icacls' without options for help) to change the owner to be 'you', in my case:<br />
<br />
<b>icacls jmx.password /setowner apodehl</b><br />
<br />
<br />
3) remove all inherited permissions:<br />
<br />
<b>icacls jmx.password /inheritance:r</b><br />
<br />
<br />
4) grant minimal permissions to your user (read/write in this case):<br />
<br />
<b>icacls jmx.password /grant:r apodehl:(r,w)</b><br />
<br />
<div>
<br /></div>
<br />Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-82401637758914463732014-09-08T02:32:00.001-07:002014-09-08T02:32:53.087-07:00A simple disk performance testTest the I/O performance by writing several messages to the current directory<br />
The files created are named writetest.<num>.dat and should be removed after the test !!</num><br />
<br />
Usage: writetest <sync async=""> <nummsgs> <printrate> <msgsize> <numtests></numtests></msgsize></printrate></nummsgs></sync><br />
Will write <nummsgs> buffers of size <msgsize> each and print the rate every <printrate> msgs.</printrate></msgsize></nummsgs><br />
The test is repeated <numtests> times and the used time is displayed</numtests><br />
Example: writetest async 1000000 100000 100 1<br />
<br />
<br />
<br />
#include <stdlib .h=""></stdlib><br />
#include <stdio .h=""></stdio><br />
#include <signal .h=""></signal><br />
#include <string .h=""></string><br />
<br />
#ifdef WIN32<br />
#include <io .h=""></io><br />
#else<br />
#include <unistd .h=""></unistd><br />
#endif<br />
<br />
#include <sys stat.h=""> // for S_IRUSR and S_IWUSR</sys><br />
<br />
#include <fcntl .h=""></fcntl><br />
<br />
#ifndef WIN32<br />
#include <sys types.h=""></sys><br />
#include <sys time.h=""></sys><br />
#else<br />
#include <sys timeb.h=""></sys><br />
#endif<br />
<br />
#include <time .h=""></time><br />
#include <iostream></iostream><br />
<br />
#pragma warning(disable : 4996) // we want to keep the same code for Unix<br />
<br />
using namespace std;<br />
<br />
class MsgTimer<br />
{<br />
public:<br />
<br />
MsgTimer( const char* applname ) {<br />
m_startTime = 0.0;<br />
m_lastTime = 0.0;<br />
m_endTime = 0.0;<br />
m_numMsgs = 0;<br />
strcpy( m_applName, applname );<br />
}<br />
<br />
~MsgTimer() { };<br />
<br />
// --- timer starts now ---<br />
void start() {<br />
<br />
m_startTime = getTimeInSeconds();<br />
m_lastTime = m_startTime;<br />
<br />
cerr << "\n" << m_applName << ": ###### MsgTimer started" << endl;<br />
printTimestamp();<br />
}<br />
<br />
// --- specify the number of processed messages ---<br />
void stop( long numMsgs ) {<br />
<br />
m_endTime = getTimeInSeconds();<br />
m_lastTime = m_endTime;<br />
m_numMsgs = numMsgs;<br />
<br />
cerr << "\n" << m_applName << ": ###### MsgTimer stopped after processing " << numMsgs << " messages " << endl;<br />
printTimestamp();<br />
printResult();<br />
}<br />
<br />
// --- specify the number of processed messages ---<br />
// returns current msgs/sec<br />
double current( long numMsgs ) {<br />
<br />
double currentTime = getTimeInSeconds();<br />
<br />
double elapsedTime, mps;<br />
<br />
elapsedTime = currentTime - m_startTime;<br />
if( elapsedTime > 0.0 ) mps = (double)numMsgs / elapsedTime;<br />
else mps = 0.0;<br />
cerr << m_applName << ": ###### Accumulated Number of msgs : " << m_numMsgs << endl;<br />
cerr << m_applName << ": ###### Accumulated Elapsed time in sec: " << elapsedTime << endl;<br />
cerr << m_applName << ": ###### Accumulated Msgs-per-sec: " << mps << endl;<br />
<br />
elapsedTime = currentTime - m_lastTime;<br />
if( elapsedTime > 0.0 ) mps = (double)(numMsgs-m_numMsgs) / elapsedTime;<br />
else mps = 0.0;<br />
cerr << m_applName << ": ###### Number of msgs since last call: " << numMsgs - m_numMsgs << endl;<br />
cerr << m_applName << ": ###### Partial Elapsed time in sec: " << elapsedTime << endl;<br />
cerr << m_applName << ": ###### Partial Msgs-per-sec: " << mps << endl;<br />
<br />
m_numMsgs = numMsgs;<br />
m_lastTime = currentTime;<br />
return mps;<br />
}<br />
<br />
// --- prints a timestamp in a common format ---<br />
void printTimestamp() {<br />
<br />
time_t t;<br />
char* buf;<br />
t = time(NULL);<br />
//ctime_r(&t,buf);<br />
//buf[strlen(buf)-1]='\0';<br />
buf = ctime(&t);<br />
cerr << m_applName << ": ###### Timestamp: " << buf << endl;<br />
}<br />
<br />
<br />
static double getTimeInSeconds() {<br />
<br />
double t0,t1,t2;<br />
<br />
#ifndef WIN32<br />
int r;<br />
struct timeval tp;<br />
struct timezone tzp;<br />
r = gettimeofday(&tp,&tzp);<br />
if (-1 == r) {<br />
<br />
cerr << "gettimeofday() failed" << endl;<br />
exit(-1);<br />
}<br />
<br />
t1 = (double)tp.tv_sec;<br />
t2 = (double)tp.tv_usec;<br />
t0 = t1+ t2/1000000;<br />
#else<br />
struct _timeb tp;<br />
_ftime(&tp);<br />
<br />
t1 = (double)tp.time;<br />
t2 = (double)(tp.millitm/1000.0);<br />
t0 = t1+t2;<br />
#endif<br />
<br />
return (t0);<br />
}<br />
<br />
private:<br />
<br />
// --- prints the result in a common format ---<br />
void printResult() {<br />
<br />
double elapsedTime,mps;<br />
elapsedTime = m_endTime - m_startTime;<br />
<br />
if( m_numMsgs <= 0 ) {<br />
<br />
cerr << "MsgTimer::stop() was not called !" << endl;<br />
return;<br />
}<br />
<br />
if (elapsedTime > 0.0) {<br />
<br />
mps = (double)m_numMsgs / elapsedTime;<br />
<br />
}<br />
else {<br />
<br />
mps = 0.0;<br />
}<br />
<br />
cerr << m_applName << ": ###### Final Elapsed time in sec: " << elapsedTime << endl;<br />
cerr << m_applName << ": ###### Final Number of msgs : " << m_numMsgs << endl;<br />
cerr << m_applName << ": ###### Final Msgs-per-sec: " << mps << endl;<br />
}<br />
<br />
<br />
double m_startTime;<br />
double m_lastTime;<br />
double m_endTime;<br />
long m_numMsgs;<br />
char m_applName[512];<br />
};<br />
<br />
<br />
MsgTimer mt("writetest");<br />
<br />
int isSync = 0;<br />
char buffer[1024*1024];<br />
<br />
void _testWrite( int cnt, int nummsgs, int printrate, int msgsize )<br />
{<br />
char filename[30];<br />
int msgCount;<br />
int fd;<br />
<br />
sprintf( filename, "writetest.%d.dat", cnt );<br />
<br />
int oflags = O_RDWR | O_CREAT;<br />
<br />
#ifdef WIN32<br />
int pmode = _S_IREAD | _S_IWRITE;<br />
#else<br />
// this flag exists only on Unix<br />
if( isSync==1 ) oflags = oflags | O_SYNC;<br />
mode_t pmode = S_IRUSR | S_IWUSR;<br />
#endif<br />
<br />
fd = open( filename, oflags, pmode );<br />
if( fd==-1 ) {<br />
fprintf( stderr, "writetest: Could not create file %s, maybe you did not remove a former instance ?\n", filename );<br />
exit(1);<br />
}<br />
<br />
mt.start();<br />
for( msgCount=0; msgCount< nummsgs; msgCount ++ ) {<br />
<br />
if( msgCount%10000==0 ) fprintf(stderr,"writetest: written %d msgs\n", msgCount );<br />
<br />
if( msgCount%printrate==0 ) {<br />
mt.current( msgCount );<br />
}<br />
<br />
write( fd, (char*)buffer, msgsize );<br />
<br />
#ifdef WIN32<br />
// flush every message if sync writing (Windows NT only)<br />
if( isSync==1 ) _commit(fd);<br />
#endif<br />
<br />
}<br />
<br />
close(fd);<br />
mt.stop( msgCount );<br />
}<br />
<br />
void usage()<br />
{<br />
fprintf(stderr,"Test the I/O performance by writing several messages to the current directory\n");<br />
fprintf(stderr,"The files created are named writetest.<num>.dat and should be removed after the test !!\n\n");</num><br />
fprintf(stderr,"Usage: writetest <sync async=""> <nummsgs> <printrate> <msgsize> <numtests>\n");</numtests></msgsize></printrate></nummsgs></sync><br />
fprintf(stderr," Will write <nummsgs> buffers of size <msgsize> each and print the rate every <printrate> msgs.\n");</printrate></msgsize></nummsgs><br />
fprintf(stderr," The test is repeated <numtests> times and the used time is displayed\n");</numtests><br />
fprintf(stderr,"Example: writetest async 1000000 100000 100 1\n");<br />
exit(1);<br />
}<br />
<br />
int<br />
main(int argc, char **argv)<br />
{<br />
<br />
if( argc < 6 ) usage();<br />
<br />
if( !strcmp(argv[1],"sync") ) isSync = 1;<br />
else {<br />
if( !strcmp(argv[1],"async") ) isSync = 0;<br />
else usage();<br />
}<br />
<br />
int nummsgs = atoi(argv[2]); // number of messages to write<br />
int printrate = atoi(argv[3]); // number of tests to run<br />
int msgsize = atoi(argv[4]); // length of the message to write in bytes<br />
int numtests = atoi(argv[5]); // number of tests to run<br />
<br />
if( msgsize > sizeof(buffer) ) {<br />
fprintf(stderr,"writetest: msgsize can not be bigger than %ld\n", sizeof(buffer) );<br />
exit(1);<br />
}<br />
<br />
fprintf(stderr,"writetest: ARGUMENTS: sync:%d nummsgs:%d printrate:%d msgsize:%d numtests:%d\n",<br />
isSync, nummsgs, printrate, msgsize, numtests );<br />
for( int cnt=0; cnt < numtests; cnt++ ) {<br />
<br />
_testWrite( cnt, nummsgs, printrate, msgsize );<br />
}<br />
}Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-54703325205597207942014-06-02T05:56:00.001-07:002014-06-02T06:27:57.066-07:00How to use JDBC-ODBC in a 64-bit JVM with a 32-bit version of OfficeWhen using the JDBC-ODBC bridge in the JDK to access Microsoft Access files, you would set your JDBC class to sun.jdbc.odbc.JdbcOdbcDriver and your JDBC URL, for example, to:<br />
<br />
"jdbc:odbc:Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=C:\CodeStreet\sample.accdb"<br />
<br />
So far so good, but in case you are running a 64-bit JVM with a 32-bit Microsoft Office installation, your JVM and Access driver architecture don't match and you would see error messages such as:<br />
<pre>java.sql.SQLException: [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified</pre>
<pre></pre>
<pre></pre>
<pre><span style="font-family: 'Times New Roman'; white-space: normal;">or</span></pre>
<pre>"The setup routines for the Microsoft Access Driver .. could not be found."</pre>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">Fortunately, there are now 64-bit <a href="http://www.microsoft.com/en-us/download/details.aspx?id=13255">Microsoft Access drivers </a>available, but using them in this context is quite tricky. Once you install the drivers, <b>Microsoft Office stops working !</b></span><br />
<span style="font-family: inherit;"><b><br /></b></span>
<span style="font-family: inherit;">Opening an Excel file, for example, tries to find the 64-bit version of Office, which you don't have :-(</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">Instead of opening a file, you will see "Configuration Progress" and "Configuring Microsoft Office Professional Plus 2010..." - what the heck ?
</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">But there's a neat little workaround which goes like this: </span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">1. Download the <a href="http://www.microsoft.com/en-us/download/details.aspx?id=13255" style="white-space: normal;">Microsoft Access drivers </a> </span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">2. Check this registry key:</span><br />
<pre><code><span style="font-family: inherit;"> HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Common\FilesPaths </span></code></pre>
<pre><code><span style="font-family: inherit;">
</span></code></pre>
<span style="font-family: inherit;">If if currently contains an entry with mso.dll, you are using Office 64-bit, which is ok. </span><span style="font-family: inherit;">If there is </span><b style="font-family: inherit;">NO </b><b style="font-family: inherit;">mso.dll key</b><b style="font-family: inherit;"> </b><span style="font-family: inherit;">then your Office version is 32-bit. </span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">3. Open a command prompt and install the 64-bit driver in passive mode (it won't let you do this any other way):</span><br />
<pre><span style="font-family: inherit; font-size: x-small;"> </span><span style="font-family: inherit;">AccessDatabaseEngine_X64.exe /passive</span></pre>
<pre><span style="font-family: inherit;">
</span></pre>
4. If <b>mso.dll was not </b>in your registry in step 2, then <b>remove this key</b> now from<br />
<pre><code> HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Common\FilesPaths </code></pre>
<pre></pre>
<pre></pre>
<span style="font-family: inherit;"><span style="white-space: normal;">Microsoft Office (32-bit) should start working again </span><span style="white-space: normal;">and</span><span style="white-space: normal;"> your 64-bit Access drivers are ready to go.</span>
</span><br />
<span style="font-family: inherit;"><span style="white-space: normal;"><br /></span></span>
<span style="font-family: inherit;"><span style="white-space: normal;">Good Luck!</span></span><br />
<pre> </pre>
Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-76587850371601310952012-12-03T08:07:00.000-08:002012-12-03T08:07:36.390-08:00Debugging Java Thread CPU usageIn case you want to quickly find out which of your Java threads is the culprit in a JVM's high CPU usage, this tool is truly helpful.<br />
<br />
Simply download topthreads.jar from <a href="http://lsd.luminis.nl/top-threads-plugin-for-jconsole/">http://lsd.luminis.nl/top-threads-plugin-for-jconsole/</a><br />
and run your jconsole like this:<br />
<br />
jconsole -pluginpath topthreads.jar<br /><br />
Similar to 'jtop', just a little better.<br />
<br />
<br />
<br />
<br />Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-73298451447102957342012-10-09T04:29:00.001-07:002012-10-09T04:29:06.183-07:00Eclipse and native library not foundEclipse as Java IDE is great. But there's (at least) one part of the GUI that's a bit cumbersome. One thing that frequently pops up is the problem of not finding a native library.<br />
<br />
For example if you use TIBCO Rendezvous, your project uses a tibrvj.jar file which is dependent on a native libary, for example tibrv.dll on Windows. Of course you can solve this by setting a Run Configurations 'VM argument' to -Djava.library.path=<path library="library" shared="shared" to="to">. But if you have many Run configurations, for example because you write JUnit tests around your software, it would be a pain to configure java.library.path for every JUnit Run Configuration.</path><br />
<br />
You <i>would </i>think you can do Project -> Properties -> 'default java library path' - or so.<br />
Or maybe Windows -> Preferences -> 'default VM arguments'. But neither exists :-(<br />
That's the part where I say the GUI design of Eclipse could be improved: it's not quite obvious that you can actually <b>edit properties of a jar file </b>in 'Referenced Libraries'. You add jar files and they look like non-editable references to files. But you can actually change some properties.<br />
<br />
Select your jar file and do a right-mouse-click:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGCoO6fzCeHRQTxwfd0jdsz0BBNaXnE4clcbANIyaAlwbf3EAl4X4WIHvwxByOJ0igXWPhIBAjLsria6SiSnJhHSyDEq0DreusayfGfJjx9ANZWJHLuzdCzrRHJyVqfMYN4D81CjviMSw/s1600/libpath.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGCoO6fzCeHRQTxwfd0jdsz0BBNaXnE4clcbANIyaAlwbf3EAl4X4WIHvwxByOJ0igXWPhIBAjLsria6SiSnJhHSyDEq0DreusayfGfJjx9ANZWJHLuzdCzrRHJyVqfMYN4D81CjviMSw/s1600/libpath.png" /></a></div>
<br />
In this dialog you can specify the path to a dependent native library which is then added to your java.library.path for <b>every </b>existing and future Run Configuration of this project.<br />
<br />
Another interesting jar-file setting is the 'Javadoc Location'. Once you specified the path to a Javadoc's index.htm, the online documentation will be available when you program against that API (IntelliSense and F2 work). For TIBCO Rendezvous however, there's no real Javadoc part of the product.<br />
<br />
<b>Please note</b>, though, that when you upgrade to another version of the jar file, these jar-file settings are lost. You have to repeat the steps above. It would be nice from Eclipse to be asked whether to keep Javadoc Location/Native Library settings when upgrading a jar file.<br />
<br />
<br />Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-89970650289523139612011-07-12T09:38:00.000-07:002011-07-12T09:48:22.727-07:00Eclipse doesn't start after crashAfter a crash on Windows 7, my beloved Eclipse environment wouldn't start again!
It just hung there, showing nothing but the Splash screen.
eclipse -clean didn't help, monitoring files with Sysinternal's ProcMon or 'handle.exe' didn't help - quite devastating...
But finally I found the solution <a href="http://off-topic.biz/en/eclipse-hangs-at-startup-showing-only-the-splash-screen/">here</a>:
<ol><li>cd to your Eclipse home directory</li><li>cd .metadata/.plugins</li><li>ren org.eclipse.core.resources BAK (Keep this directory around)</li><li>Restart Eclipse, ignore the error message.</li><li>Close all open editors tabs !! (in my case, playing with the .xsd file editor caused the issue)</li><li>Exit Eclipse.</li><li>del org.eclipse.core.resources (Delete the newly created directory.)</li><li>ren BAK org.eclipse.core.resources (Restore the original directory.)</li><li>Restart Eclipse.</li></ol>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com4tag:blogger.com,1999:blog-3619881695344253937.post-67596134838556095292010-12-15T01:29:00.000-08:002010-12-15T01:46:55.770-08:00How to implement a 'repeating' Swing JButton ?Sometimes you want to repeat your JButton action after the button was pressed. This is definitely not rocket-science, but I thought it's nice and small enough to share. Here's the usual code with a one-click action: a 'Next' button that selects the next row in a table.
<pre><code>
nextButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent arg0)
{
selectNextRow(...);
}});
</code></pre>
For a repeating button, you can <b><i>replace </i></b>this code with the code snippet below. Adjust the times appropriately. I wonder why this isn't standard functionality in a Swing JButton(..., startMsec, repeatMsec) ?
<pre><code>
nextButton.addMouseListener( new MouseAdapter() {
Timer repeatTimer;
public void mousePressed(MouseEvent arg0)
{
selectNextRow(...); // initial execution
repeatTimer = new Timer(100, new ActionListener() {
public void actionPerformed(ActionEvent e)
{
selectNextRow(...); // execute every 100 msec
}});
repeatTimer.setInitialDelay(1000); // start repeating only after 1 second
repeatTimer.start();
}
public void mouseReleased(MouseEvent arg0)
{
repeatTimer.stop();
}});
</code></pre>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-57286367975917203832010-05-18T02:13:00.000-07:002010-05-18T02:21:22.030-07:00Managing Oracle Tablespace (ORA-01653 error)<div>This procedure helped me a bit to manage an Oracle DB's tablespace. I was getting<span class="Apple-style-span" style="font-family:'times new roman';"><span class="Apple-style-span" style="font-size: medium;"> </span></span><span class="Apple-style-span" style="font-family:'times new roman';"><span class="Apple-style-span" style="font-size: medium;">ORA-01653: unable to extend table SYSTEM... </span></span>errors. </div><div>Maybe the following procedure is useful for others as well:</div><div><ol><li>In order to manage/see the current usage, here's a good script posted by 'bipul' on <a href="http://forums.oracle.com/forums/thread.jspa?messageID=3590569">http://forums.oracle.com/forums/thread.jspa?messageID=3590569</a></li><li>To check which data files are used: <p class="MsoListParagraph"><span class="Apple-style-span" style="font-family:'courier new';"><b><span class="Apple-style-span" style="font-family: Calibri, sans-serif; font-weight: normal; font-size: 15px; ">select name from v$datafile</span></b></span></p></li><li>To increase that setting:</li></ol><span class="Apple-style-span" style="font-family: Calibri, sans-serif; font-size: 15px; ">alter database datafile 'C:\ORACLEXE\ORADATA\XE\SYSTEM.DBF' autoextend on next 100m maxsize 2000m;</span></div><div>
</div><div>
</div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com2tag:blogger.com,1999:blog-3619881695344253937.post-86999819920136861502010-02-24T10:21:00.000-08:002010-02-24T10:23:14.331-08:00QuantsAn interesting documentation about quants, including Emanuel Derman, <div>starts in Dutch first, but in English later.</div><div>
<div><a href="http://player.omroep.nl/?aflID=10567577">http://player.omroep.nl/?aflID=10567577</a>
</div></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-3840688536842918972010-02-15T07:47:00.000-08:002010-02-15T08:01:49.191-08:00Java: Sorted ListSometimes you might have the need for a Java list, that is sorted with every element insertion. If you use a sorted TreeSet, you are stuck with iterators, no random access for elements. If you use an ArrayList, you could call Collections.sort() after every add, but that's not necessarily efficient. Collection.sort() will have to check the order for every element. If your list is long, this might take time.<div>
</div><div>But here's the trick: if you know your list was already sorted before your insertion, there's no need to re-sort everything again. You can find the proper insertion point with a quick binary search, and insert the new element at the proper position. VoilĂ , your list is always sorted.</div><div>
</div><div>Surprisingly simple to do this in Java:<pre>
<span fontsize="-2">
<code>
/** A LinkedList that efficiently sorts itself with every add.
*
* @author apodehl
*
*/
public class SortedList< T extends Comparable<? super T>> extends LinkedList<T>
{
/** Adds this element to the list at the proper sorting position.
* If the element already exists, don't do anything.
*/
@Override
public boolean add(T e)
{
if( size()==0 ) {
return super.add(e);
}
else {
// find insertion index
int idx = -Collections.binarySearch(this, e)-1;
if( idx < 0 ) {
return true; // already added
}
// add at this position
super.add(idx, e);
return true;
}
}
}
</code>
</span>
</pre>
</div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-7587940972852739282010-02-15T07:25:00.000-08:002010-02-15T07:57:20.419-08:00Java BiMap, BidiMapAfter doing some research and trying to find just the right <a href="http://tech-eureka.blogspot.com/2009/08/java-bi-directional-maps-apache-commons.html">Java Bi-Map</a>, I couldn't find any, so I had to roll my own :-( To my surprise, implementation of a Bi-Maps is not straight-forward, there's not such thing as the 'one Bi-Map functionality'.<div><div>
</div><div>The interesting case is this one: say there are mappings A->1 and B->2. What happens once you insert C->2 ? </div><div>
</div><div>Will you remove B->2 to ensure uniqueness of keys and values ? (resulting in A->1 and C->2)</div><div>or not, resulting in A->1, B->2 and C->2. Please note that in the latter case, there's no well-defined lookup for value "2". Is it B or is it C ?</div><div>
</div><div>So what I needed was uniqueness of both keys and values, in all cases, using generics. Here it goes:</div><div>
</div></div>
<span style="font-size:-2;">
<pre>
<code>
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* One possible implementation of a Bi-directional Map.
*
* Note: there are several variations of BiMap, depending on the behaviour of put(k,v)... Is it allowed to have more
* than one k for one value ?? Modify
*
* @author apodehl
*
* @param <K>
* @param <V>
*/
public class BiMap<K, V> implements Map<K, V>
{
Map<K, V> keyVal; // maps key->value
Map<V, K> valKey; // maps value->key
public BiMap()
{
keyVal = new LinkedHashMap<K, V>();
valKey = new LinkedHashMap<V, K>();
}
@Override
public V put(K key, V val)
{
// --- this implementation allows one-to-one ONLY !! ---
if (valKey.containsKey(val)) {
keyVal.remove(valKey.get(val));
}
if (keyVal.containsKey(key)) {
valKey.remove(keyVal.get(key));
}
// --- remove above if that's not what you want ---
//
valKey.put(val, key);
return keyVal.put(key, val);
}
@Override
public void putAll(Map< ? extends K, ? extends V> m)
{
for (Entry< ? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Type-safe get.
*
* @param key
* @return
*/
public K getKey(V val)
{
return valKey.get(val);
}
/**
* Type-safe get.
*
* @param key
* @return
*/
public V getVal(K key)
{
return keyVal.get(key);
}
@Override
// unfortunately Map interface isn't type-safe
public V get(Object key)
{
return getVal((K) key);
}
/**
* Type-safe contains.
*
* @param key
* @return
*/
public boolean contains(K key)
{
if (keyVal == null) return false;
return keyVal.containsKey(key);
}
/**
* Type-safe containsValue.
*
* @param key
* @return
*/
public boolean containsVal(V val)
{
if (valKey == null) return false;
return valKey.containsKey(val);
}
// unfortunately Map interface isn't type safe here ..
@Override
public boolean containsKey(Object key)
{
return contains((K) key);
}
// unfortunately Map interface isn't type safe here ..
@Override
public boolean containsValue(Object value)
{
return containsVal((V) value);
}
@Override
public Set< java.util.Map.Entry<K, V>> entrySet()
{
return keyVal.entrySet();
}
@Override
public boolean isEmpty()
{
return keyVal.isEmpty();
}
@Override
public int size()
{
return keyVal.size();
}
@Override
public Collection<V> values()
{
return keyVal.values();
}
public V removeByKey(K key)
{
V val = keyVal.remove(key);
valKey.remove(val);
return val;
}
public K removeByVal(V val)
{
K key = valKey.remove(val);
keyVal.remove(key);
return key;
}
// unfortunately Map interface isn't type-safe
@Override
public V remove(Object key)
{
return removeByKey((K) key);
}
@Override
public Set<K> keySet()
{
return keyVal.keySet();
}
@Override
public void clear()
{
keyVal.clear();
valKey.clear();
}
public String toString()
{
String s = "BiMap:\n";
s += " key->value: " + keyVal.toString();
s += "\n";
s += " value->key: " + valKey.toString();
return s;
}
public static void main(String[] args)
{
BiMap<String, Integer> biMap = new BiMap();
biMap.put("A", 1);
biMap.put("B", 2);
System.out.println(biMap); // {A=1, B=2}
biMap.put("C", 2);
System.out.println(biMap); // {A=1, C=2}
biMap.removeByVal(1);
System.out.println(biMap); // {C=2}
}
}
</code></pre></span>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com3tag:blogger.com,1999:blog-3619881695344253937.post-82992304996334572912009-12-23T06:01:00.000-08:002009-12-23T06:47:42.096-08:00Redundant home internet with a Surf Sticks and Internet Connection SharingIf you have a not-so-reliable ISP and an UMTS surf stick you can configure your home internet to be more redundant by setting up the surf stick as secondary gateway and DNS server for all your machines on a local network.<div>
</div><div>Takes a while to set up, but its worth it :-)</div><div>
</div><div>Assumption: you already have a primary internet gateway (e.g. DSL/Cable) on 192.168.0.1.</div><div>
</div><div><b>Internet host setup</b> (sometimes connected directly through a surf stick):</div><div><ol><li>Setup your dial-up connection to use 'Internet Connection Sharing' (ICS) as described, for example here: <a href="http://support.microsoft.com/kb/306126">http://support.microsoft.com/kb/306126</a></li><li>Make sure that for Internet Connection Sharing 'Settings' you have enabled DNS to be provided by this new Internet host. Possibly DHCP, but not for now.</li><li>Also make sure that the LAN network interface (on a laptop, that's usually 'Local Area Connection') now uses a static IP. Set it the way you would like your new internet gateway to appear on your home network. For example in my case, I already have a default gateway on192.168.0.1, so the new Internet-Host, should still use this primary gateway while it is not connected through the surf stick. The new internet host's ip is 192.168.0.100</li></ol></div><div><div><div><div>Ethernet adapter Local Area Connection:</div><div>
</div><div> Connection-specific DNS Suffix . :</div><div> Description . . . . . . . . . . . : Intel(R) 82567LM Gigabit Network Connection</div><div> Dhcp Enabled. . . . . . . . . . . : No</div><div> IP Address. . . . . . . . . . . . : 192.168.0.100</div><div> Subnet Mask . . . . . . . . . . . : 255.255.255.0</div><div> Default Gateway . . . . . . . . . : 192.168.0.1</div><div> DNS Servers . . . . . . . . . . . : 192.168.0.1</div><div>
</div><div>So the Internet Host (my laptop with a surf stick) will use IP and DNS from 192.168.0.1 if it currently isn't connected directly.</div><div>
</div><div><b>Client Machine Setup:</b></div><div><b>
</b></div><div>The client machines on the network 192.168.0.x must be configured to use both, the primary gateway at 192.168.0.1 and, if not available, the secondary gateway at 192.168.0.100.</div><div>
</div><div>On each client machine, go to 'Local Area Connection' -> Properties -> TCP/IP ->Properties and specify an "Alternate DNS Server" of 192.168.0.100 and click 'Advanced' to add a new Default Gateway. Click 'Add' and set the 'TCP/IP Gateway Address' to 192.168.0.100.</div><div>Now each client uses 192.168.0.1, while your DSL/Cable internet is available, and 192.168.0.100 otherwise.</div><div>
</div><div>The whole configuration with DHCP is similar, you just need to prepolute the DHCP answers with about the same settings (Default gateways and DNS: 192.168.0.1 and 192.168.0.100) .</div><div>
</div><div>
</div><div>
</div><div>
</div><div>
</div><div>
</div></div></div></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-84954711211406480842009-10-30T04:59:00.000-07:002009-10-30T05:06:37.080-07:00MAC/IP lookup<div>I was trying to figure out more about a MAC address and found an interesting lookup mechanism:</div><div><a href="http://www.8086.net/tools/mac/">http://www.8086.net/tools/mac/</a> </div><div>
</div><div>To be concise, all lookups on one page:</div><div>
</div><div>What's my browser: <a href="http://www.cyscape.com/showbrow.aspx?bhcp=1">http://www.cyscape.com/showbrow.aspx?bhcp=1</a></div><div>
</div><div>What's my IP: <a href="http://ipid.shat.net/">http://ipid.shat.net/</a> or <a href="http://ip-lookup.net/">http://ip-lookup.net/</a></div><div>
</div><div>What's my MAC: <a href="http://www.8086.net/tools/mac/">http://www.8086.net/tools/mac/</a> </div><div>or <a href="http://www.cavebear.com/archive/cavebear/Ethernet/vendor.html">http://www.cavebear.com/archive/cavebear/Ethernet/vendor.html</a></div><div>
</div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-71288857886726347262009-10-29T23:22:00.000-07:002009-10-30T04:55:13.831-07:00Automatic Restart Script for a Java serviceThis blog tries to describe a pattern on how to write a pragmatic Unix start and stop script for an automatically restarting Java service.<div>
</div><div>In many cases, you might want to write a Java service that should be up and running 24x7. Now in theory the garbage collector should deal with everything, and if programmed correctly, the process should never crash. But in praxis, things do happen. For example a web service could encounter user requests where memory use is much bigger than you ever expected. </div><div>What is a good maximum heap size anyway ?</div><div>
</div><div>A pragmatic approach is just to face the fact that your JRE could run out of memory and deal with it. There are sophisticated monitoring solutions out there to automatically restart processes (e.g. nagios/ganglia), but a poor-man's solution is to <b>automatically restart the JRE from the Unix start script</b>. </div><div>
</div><div>Please note, you <b>don't want to restart in every case</b>. A bad command line option should just stop the process and not run into the restar loop. Also, <b>there must be a clean way to manually stop it</b>.</div><div>
</div><div>Under these constraints, the best solution I could find is to create a temporary file from the Java code at exactly the point 'of no return'. If the JRE stops before this point, no restart happens. If the JRE stops after this point, automatic restart will kick in.</div>
<span style="font-size:-1;"><code><pre>... parse command line options ...
</pre><pre> // register shutdown hook Runtime.getRuntime().addShutdownHook(new ShutdownHook(...));</pre><pre> // register uncaught exception handler
Thread.currentThread().setDefaultUncaughtExceptionHandler( new UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e)
{
e.printStackTrace();
if( e.getClass()==java.lang.OutOfMemoryError.class ) {
System.err.println("FATAL: shutting down because of java.lang.OutOfMemoryError ...");
System.exit(7);
}
}} );
File restart = new File("webservice.restart"); // tell the shell to restart me
restart.createNewFile();
... start your service ...
</pre></code></span>
<div>The Unix start script below will restart the JRE if, and only if, the temporary file (webservice.restart) exists. This could go like this:</div><div>
<span style="font-size:-1;"><code><pre>#!/bin/sh
#try to start service once
${JAVA_HOME}/bin/java -DREPLAYWEB ${JOPT} com.codestreet.replay.jms.shell.web.ReplayWeb $*
# restart again (until webservice.restart file was removed)
while [ -f webservice.restart ]; do
echo "### RESTART ###"
/bin/rm -f webservice.restart # let Java decide if we really want to restart
${JAVA_HOME}/bin/java -DREPLAYWEB ${JOPT} com.codestreet.replay.jms.shell.web.ReplayWeb $*
done
</pre></code></span>So Java decides <i>if </i>the service should be restarted and the shell actually <i>performs the restart</i>. </div><div>
</div><div>An alternative would have been to <b>simply use return codes from System.exit() ?</b></div><div>But then the question would be: <i><span class="Apple-style-span" style="font-style: normal;">what's the return code with a not yet known exception ? </span></i></div><div>If someone else uses kill-9 on the jre, a shutdown hook wouldn't be invoked. <i><span class="Apple-style-span" style="font-style: normal;">And to manually stop the restarting</span></i> you would have to kill the start script as well as the JRE.</div><div>
</div><div>With this file-based approach, the stop script is pretty simple: remove the temporary file and kill the JRE. Please note that finding the proper Java process is not as simple as it seems since the classpath is usually very long and 'ps -f' potentially won't show the classname anymore because the line gets too long. On Linux you can use the --col option to see a longer output, but that doesn't work on Solaris :-(</div><div>So a little trick around this is to use a <b>dummy JRE property,</b> -DREPLAYWEB in this case. This mock up property has no meaning except that it will show up in ps before the classpath and you can make it unique enough to identify only this instance of JRE.</div><div>
</div><div>The stop script would then perform these steps:</div><div><ol><li>get the 'ps line' that contains the dummy JRE property (REPLAYWEB)</li><li>get the task id of that process (awk is good enough)</li><li>remove the temporary restart file so the start script won't restart automatically</li><li>kill the process</li></ol><div><span class="Apple-style-span" style="font-family: monospace; font-size: small; white-space: pre; ">#!/bin/sh -f</span></div></div><span style="font-size:-1;"><pre><code>
psline=`/bin/ps -aef | /bin/grep "REPLAYWEB" | /bin/grep -v grep`
echo $psline
pid=`echo $psline | /bin/awk '{ print $2}'`
if [ $pid ]
then
/bin/rm -f webservice.restart
echo "stop_webservice: killing Web Service with pid=$pid"
kill $pid
else
echo "stop_webservice: Web Service was not running"
fi
</code></pre>
<div>
</div><div>
</div><div>
</div><div>
</div><div>
</div><div>
<div>
</div><div>
<div>
</div><div>
<div>
</div><div>
<div>
</div><div>
</div></div></div></div></div></span>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-13051478046711680902009-10-15T02:55:00.000-07:002009-10-15T03:11:46.298-07:00JAX-WS inside Jetty<a href="http://www.mortbay.org/jetty/">Jetty</a> is a nice and easy to use Web Server (see previous post), but it can it handle an open standards, heavy weight JAX-WS Web Service ?<div>With a bit of luck and glue code, it can. </div><div>
</div><div>So first we need a Service Provider Implementation (SPI) from Jetty: <a href="http://docs.codehaus.org/display/JETTY/J2se6HttpServerSPI">J2se6HttpServerSPI</a></div><div>This will make the JAX-WS endpoint use the Jetty server instead its default Sun HttpServer.</div><div>To plugin different SPI's you would define this new service in a META-INF/services file, but you can also set a system property, as described <a href="http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/spi/HttpServerProvider.html#provider()">here</a>. You can even do this in code which reduces the number of files you need to worry about when refactoring.</div><div>
</div><div>So here's an example of a Jetty Server handling a JAX-WS endpoint in combination with a File system directory. </div><code><pre><span style="font-size:-1;"> Server jettyServer = new Server(port);
HandlerCollection handlerCollection = new HandlerCollection();
jettyServer.setHandler(handlerCollection);
/** 1) Publish WebService (JettyHttpServerProvider) */
String context = "/web/ws";
// 1.1) register THIS Jetty server with the JettyHttpServerProvider
new JettyHttpServerProvider().setServer(jettyServer);
// 1.2) make sure JAX-WS endpoint.publish will use our new service provider: JettyHttpServerProvider
System.setProperty("com.sun.net.httpserver.HttpServerProvider",
"org.mortbay.jetty.j2se6.JettyHttpServerProvider");
// 1.3) add an empty HandlerCollection to by setup by this provider
handlerCollection.addHandler( new HandlerCollection() );
// 1.4) use JAX-WS API to publish the endpoint (will use a JettyHttpServerProvider)
Endpoint endpoint = Endpoint.create(replayServiceImpl);
endpoint.publish("http://localhost:" + port + "/web/ws", replayServiceImpl);
/** 2) Publish WebGUI (Jetty) */
String context = "/gui";
// 2.1) configure File Resource Handler
ResourceHandler fileResourceHandler=new ResourceHandler();
fileResourceHandler.setWelcomeFiles(new String[]{"index.html"});
fileResourceHandler.setResourceBase(guiPath); // start here
// 2.2) configure 'gui' Context
ContextHandler guiHandler = new ContextHandler();
guiHandler.setContextPath(context);
guiHandler.setResourceBase(".");
guiHandler.setClassLoader(Thread.currentThread().getContextClassLoader());
guiHandler.setHandler(fileResourceHandler);
// 2.3) add this context handler
handlerCollection.addHandler(guiHandler);
/** 3) start JETTY server */
jettyServer.start();
jettyServer.join();</span></pre></code>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com19tag:blogger.com,1999:blog-3619881695344253937.post-64577635834987842292009-10-15T02:12:00.000-07:002009-10-15T02:54:56.243-07:00Embedding an HTTP daemon in Java<div>Apache Tomcat, the well-known WebServer is about 84MB after installation. Other contenders are even bigger. Does it have to be so complicated ? The following blog is about the search for a 'right size' HTTP daemon embedable in Java.</div><div>
</div>Now, what's a Web Server anyway ?
<div>To my mind, its just a piece of software that opens a socket (port 8080) where you send requests to (as defined by the HTTP protocol) and it returns data, in most cases something from the file system (HTML pages and such).
This doesn't sound too complicated. In fact, there are examples on the net of HTTP daemons that only take 10 lines of source code (compare the <a href="http://en.wikipedia.org/wiki/International_Obfuscated_C_Code_Contest">C++ obfuscation contest</a>).</div><div>But besides this little niche, there is also <a href="http://elonen.iki.fi/code/nanohttpd/">NanoHTTPD</a> which already handles some of the ugliness of HTTP. So in order to serve up a system directory, which could contain fairly complicated AJAX/Javascript/JSP files, you don't need more than this single file of Java source code.</div><div>
</div><div>If serving a file directory is all you need, go back to the previous chapter.</div><div>Unfortunately, things are not that easy ... sometimes you <i>also </i>need to publish a WebService or a Servlet or an already packaged Web application (a war file). Then you need something slightly bigger. The best thing I could find in this category is <a href="http://www.mortbay.org/jetty/">Jetty</a>. It gives up a nice stand-alone WebServer with all kinds of configuration choices and extension points, but most importantly, it is very easy to <a href="http://docs.codehaus.org/display/JETTY/Embedding+Jetty">embed</a>. By using three jar files (less then 1 MB in total) and writing very little <a href="http://jetty.mortbay.org/xref/org/mortbay/jetty/example/FileServer.html">code</a> you again have a complete HTTP daemon. Oh, and also, Jetty is already used inside Eclipse and GWT.</div><div>
</div><div>
</div><div>
<div>
</div></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-62518255045649391872009-10-15T02:05:00.000-07:002009-10-15T02:15:09.388-07:00Publishing a JAX-WS Endpoint not just to localhost<div>Interestingly enough, publishing the JAX-WS Endpoint with endpoint.publish(url) as described in a previous post, publishes the WSDL to http://localhost:8080 but this socket is not accessible from any other host ! Strange default, but there you go ...</div><div>
</div><div>So in order to really make a JAX-WS Endpoint public to the world, you can revert to the slightly more cumbersome API of com.sun.net.httpserver.HttpServer (at least on Sun's JDK):</div><div>
</div>
<code><pre><span style="font-size:-1;">
HttpServer server = HttpServer.create(new InetSocketAddress(port), 3 ); // backlog
server.start();
endpoint.setExecutor( Executors.newFixedThreadPool(10) );
// publish WebService
HttpContext wsContext = server.createContext( "/" + service );
//wsContext.setAuthenticator(new TestBasicAuthenticator("test"));
endpoint.publish( wsContext );</span></pre></code>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com2tag:blogger.com,1999:blog-3619881695344253937.post-50165508283349762732009-10-04T04:11:00.000-07:002009-10-04T04:16:33.063-07:00QuarkbaseEver wondered who are the key people behind a website ?<div>
<div>Check out <a href="http://www.quarkbase.com/">http://www.quarkbase.com/</a> and enter the URL to check.</div><div>Not always perfect, though ... ;-)</div></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-48622705713601929372009-09-07T01:44:00.000-07:002009-10-15T02:11:25.547-07:0060 seconds on SOAP-based Web ServicesAccording to this <a href="http://oreilly.com/catalog/9780596002695/">book</a>, there are 74 distinct initiatives trying to define what a Web Service should look like. And I bet, there are at least 74 different abbreviations around to describe different parts of it. So it's surprising how simple it is to create a Web Service with JDK 1.6. First you define an interface with a bunch of annotations:
<span class="Apple-style-span" style="font-size:small;"><pre><code>
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
@WebService
// use RPC or 'wrapped' document-style
@SOAPBinding(style = Style.RPC)
//@SOAPBinding(style = Style.DOCUMENT,
// parameterStyle = SOAPBinding.ParameterStyle.WRAPPED )
public interface Example
{
@WebMethod public String getGreeting( @WebParam(name="myname") String myname);
}
</code></pre></span>
And then implement the service itself. Use a thread-pool if your application is multi-threaded. <div><div>
<span class="Apple-style-span" style="font-size:small;"><pre><code>
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
@WebService(endpointInterface = "simple.Example")
public class ExampleImpl implements Example
{
@Override
public String getGreeting(String myname)
{
return "hello, " + myname;
}
public static void main(String[] args)
{
String url = "http://127.0.0.1:9876/example";
System.out.println("Starting WebService at: " + url);
System.out.println("WSDL available at: " + url + "?wsdl");
Endpoint endpoint = Endpoint.create(new ExampleImpl());
//endpoint.setExecutor( Executors.newFixedThreadPool(10) );
endpoint.publish(url);
}
}
</code></pre></span>
<span class="Apple-style-span" style=" white-space: normal; font-family:Georgia, serif;font-size:16px;">The resulting WSDL definition of this service can be looked at http://127.0.0.1:9876/example?wsdl (use Firefox or IE, not Chrome!). Any SOAP-based client (such as the XML Test Utility in General Interface, see previous post) can now invoke this service. </span><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; font-family:Georgia, serif;font-size:16px;"><div>REST-style would be harder to implement, but easier to test. In the REST case, you can simply do: http://127.0.0.1:9876/rs?name="Pete" for invocations. The difference to the previous solution is that with REST-style, the client uses an HTTP GET to invoke a method and the return value can be any kind of XML, not just a SOAP envelope. </div><div>
</div><div>The code above implements the main 'standard', but its also the most bloated possibilty. More efficient, but less standardized, is a message encoding of JSON instead of XML, or better, use the <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>. If you are in full control of the server and the client and don't need to follow standards, GWT seems an excellent choice right now.</div></span></span></div></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-87108422846858160552009-09-04T01:43:00.000-07:002009-09-09T05:31:04.772-07:00General Interface PrimerThere's now an open-source Ajax GUI builder called <a href="http://www.generalinterface.org/">General Interface</a> which looks quite promising. A bunch of components that are somewhat familiar to Swing developers. Of course, all logic is Javascript :-(<div>
</div><div>After installation, here some tips that could come in handy:</div><div><ol><li>Look at the <a href="http://www.generalinterface.org/docs/display/DOC/Tutorials">video tutorials</a>, the best one for tables(matrix) isn't actually on that page, but <a href="http://media.tibco.com/gi/gi32_webcast/index.html">here</a>.</li><li>Defining Javascript functions doesn't always work, even after Save/Reload. Try to restart and reload the page before giving up.</li><li>You might sneeze at the package names, but trust me, in Javascript conflicts are easy to create. Here's a simple example on a function called<b> jmsw.doAction(index)</b> :
<code></code><pre><code>
// name of the package to create: jmsw
jsx3.lang.Package.definePackage( "jmsw", function(jmsw) {
jmsw.doAction = function(index) // define doAction(index)
{
...
};
});
</code></pre>
</li><li>Do <b>Control+Click</b> on a component (such as a button) to locate its definition in the 'Component Hierachy'.</li><li>Float a Palette to get more space or to move it to the bottom panel: Upper Right Corner of a Palette View, click on 'Floating'. Use Menu->Palettes to enable/disable a palette.</li><li>The 'XML Mapping Tool' always starts with the same default of ..Address.wsdl, but this will not be the one you will be working with. To change the default of this initial URI, edit <span class="Apple-style-span" style="font-size: small;">C:\TIBCO\gi-3.8-max\GI_Builder\plugins\jsx3.ide.mapping\components\Inputs\wsdl.xml</span> and search for 'Address, comment it out and add your own WebService URI:
<pre><code>
<!-- use different default for XML Mapping Tool:
<strings jsxname="jsx_schema_wsdlurl" jsxvalue="jsxplugin://jsx3.ide.mapping/samples/Address.wsdl" jsxwidth="100%"/>
-->
<strings jsxname="jsx_schema_wsdlurl" jsxvalue="http://127.0.0.1:9876/rs?wsdl" jsxwidth="100%"/>
</code></pre>
</li></ol></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-49166771129974584962009-08-31T03:51:00.001-07:002009-08-31T04:29:08.746-07:00Java Bi-directional Maps: Apache commons or Google collections ?<span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">In case you need a bi-directional Map in Java, you can, of course, easily roll your own.</span></span><div><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">But this is a common problem, so others have done this work already. </span></span></div><div><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">Here's a simple how-to on bi-directional maps with Apache and Google commons.</span></span><div><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">
</span></span></div><div><a href="http://commons.apache.org/collections/"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">Apache commons</span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;"> collections offers a </span></span><a href="http://commons.apache.org/collections/apidocs/org/apache/commons/collections/BidiMap.html"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">BidiMap </span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">interface with various implementations. </span></span></div><div><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">For example:</span></span></div><div><code><pre>public class TestBidiMap
{
static BidiMap bimap = new DualHashBidiMap();
public static void main(String[] args)
{
bimap.put("D", 3);
System.out.println("D -> " + bimap.get("D") );
System.out.println("3 -> " + bimap.inverseBidiMap().get(3) );
}
}
</pre></code>
<span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">Nice and easy, but unfortunately this version of BidiMap doesn't use Java generics, which I have learned to like over time (bimpap.get(3) will not show a compile error) !</span></span></div><pre><span class="Apple-style-span" style=" white-space: normal;"><a href="http://code.google.com/p/google-collections/"><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;">Google collections</span></span></a><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;"> has an alternative that uses generics: </span></span><span class="Apple-style-span" style="font-family:georgia;"><span class="Apple-style-span" style="font-size:small;"><a href="http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/BiMap.html">BiMap</a></span></span></span></pre><pre><code>public class TestBiMap
{
static BiMap<String,Integer> bimap = HashBiMap.create();
public static void main(String[] args)
{
bimap.put("D", 3);
System.out.println("D -> " + bimap.get("D") );
System.out.println("3 -> " + bimap.inverse().get(3) );
}
}
</code></pre><pre><span class="Apple-style-span" style=" white-space: normal; font-family:georgia, serif;"><span class="Apple-style-span" style="font-size:medium;">Also nice and easy, what's interesting is the factory method HashBiMap.create(). This removes the need to duplicate the generics type specification of String, Integer. </span><span class="Apple-style-span" style="font-size:medium;"><a href="http://en.wikipedia.org/wiki/KISS_principle">Kiss</a> rules</span><span class="Apple-style-span" style="font-size:medium;">, so two points for Google collections here.</span></span></pre><pre><span class="Apple-style-span" style="font-family:georgia, serif;"><span class="Apple-style-span" style=" white-space: normal;"><span class="Apple-style-span" style="font-size:medium;">Summary: any mid-size Java project probably shouldn't live without Apache commons, but as a useful addition, Google collections is definitely worth a look.</span></span></span></pre><pre><span class="Apple-style-span" style="font-family:georgia, serif;font-size:130%;"><span class="Apple-style-span" style=" white-space: normal;font-size:16px;">
</span></span></pre><code></code></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-66409762330132627772009-08-17T03:02:00.000-07:002009-08-17T03:05:35.725-07:00Amazon S3 introStarted to look into Amazon EC2/S3 recently (technology you should know about).<div>Here's a short S3 intro, one with lots of screenshots and therefore easy to digest:</div><div>
</div><div><a href="http://www.hongkiat.com/blog/amazon-s3-the-beginners-guide/">http://www.hongkiat.com/blog/amazon-s3-the-beginners-guide/</a></div><div>
</div><div>
</div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0tag:blogger.com,1999:blog-3619881695344253937.post-11351112246067802922009-08-10T06:36:00.000-07:002009-08-10T07:03:07.188-07:00Spring and Pipes&Filters<span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><code></code><pre><span class="Apple-style-span" style="white-space: pre-wrap;"><span class="Apple-style-span" style="font-family:Georgia, serif;color:#333333;"><span class="Apple-style-span" style="line-height: 20px; white-space: normal;"><span class="Apple-style-span" style="font-size:small;">Finally an IoC container that almost everyone agrees on: </span><a href="http://www.springframework.org/" style="color: rgb(85, 136, 170); text-decoration: none; "><span class="Apple-style-span" style="font-size:small;">Spring</span></a><span class="Apple-style-span" style="font-size:small;">.
After many inhouse, half-baked attempts and 'framework wars' a default solution. TIBCO folks might remember the days of TAF (1996), GAF, XXTAF or PushBeans. Also
</span><a href="https://www.openadaptor.org/" style="color: rgb(85, 136, 170); text-decoration: none; "><span class="Apple-style-span" style="font-size:small;">openadaptor.org</span></a><span class="Apple-style-span" style="font-size:small;"> should be noted here.
Now Spring certainly is nice and generic, but for building EAI adapters or 'message processing pipelines' it is not ideal. In case of these pipelines, the message path is very structured: from a source bean through several transformation beans until it finally reaches one (or many) sinks. XML already imposes an 'order of things' so an ideal configuration language of a little adapter might look something like this:</span></span></span></span></pre><code><span class="Apple-style-span" style="font-size:x-small;"><pipelines>
<pipe name="Jdbc2JMSPipe">
<jdbc dbref="db1" querysql="select * from table1">
<concatenate method1="getFirstName" method2="getLastName" result="setName">
<jmsObject2MapMessage>
<jmsPublish topic="PERSON">
</pipe>
</pipelines>
</span></code><span class="Apple-style-span" style="font-size:small;">
<span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">This example configures a processing pipeline to get rows from a JDBC connection, invoke the concatenation filter, convert an object to a JMS MapMessage and finally publish the result to a JMS topic named 'T.PERSON'.</span>
</span><pre><span class="Apple-style-span" style="font-family:Georgia, serif;color:#333333;"><span class="Apple-style-span" style=" line-height: 20px; white-space: normal;"><span class="Apple-style-span" style="font-size:small;">
The configuration above is easy to understand and manipulate. One can imagine extensions with for-loops, if-clauses etc. For example:<span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace, serif;font-size:x-small;"><pipelines></span></span></span></span></pre><code><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style="font-size:x-small;"> <pipe name="Jdbc2JMSPipe">
<jdbc dbref="db1" querysql="select * from table1">
<concatenate method1="getFirstName" method2="getLastName" result="setName">
<jmsObject2MapMessage>
<if method="getName" startswith="a-z">
<jmsPublish topic="PERSON.LOWERCASE">
<else>
<jmsPublish topic="PERSON.UPPERCASE">
</else>
</if>
</pipe>
</pipelines></span>
</span></code><span class="Apple-style-span" style="font-size:small;">
<span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">Configuring the first example pipeline with Spring could look like this:</span><span class="Apple-style-span" style="font-size:x-small;">
</span></span><code><span class="Apple-style-span" style="font-size:x-small;">
<beans>
<bean id="1" class="com.abc.jdbc" dbRef="db1" querySql="select * from table1">
<property name="next"> <ref bean="2"> </property>
</bean>
<bean id="2" class="com.abc.concatenate" method1="getFirstName" method2="getLastName" result="setName">
<property name="next"> <ref bean="3"> </property>
</bean>
<bean id="3" class="com.abc.jmsObject2MapMessage>
<property name="next"> <ref bean="4"> </property>
</bean>
<bean id="4" class="com.abc.jmsPublish" topic="T.PERSON"/>
</beans>
</span></code><span class="Apple-style-span" style="font-size:small;">
<span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">The order of beans is defined explicitly although XML already provides an order.</span></span></span></span><div><span class="Apple-style-span" style="font-family:monospace, serif;"><span class="Apple-style-span" style=" white-space: pre-wrap;font-size:small;">
</span></span></div><div><span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">A nasty user of this system could even reorder the beans in a different order :-(</span></span></span></span></div><div><span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">So maybe some XSLT to transform from a 'pipes&filters' language to a Spring configuration ?</span></span></span></span></div><div><span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">Or extend Spring with this kind of processing step ?</span></span></span></span></div><div><span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">Or how about a XML-aware bean reference:<span class="Apple-style-span" style="font-size:x-small;">
</span></span></span><code><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style="font-size:x-small;"><beans>
<bean id="1" class="com.abc.jdbc" dbRef="db1" querySql="select * from table1">
<property name="next"> <ref bean="following-sibling"> </property>
</bean>
<bean id="2" class="com.abc.concatenate" method1="getFirstName" method2="getLastName" result="setName">
<property name="next"> <ref bean="following-sibling"> </property>
</bean>
<bean id="3" class="com.abc.jmsObject2MapMessage>
<property name="next"> <ref bean="following-sibling"> </property>
</bean>
<bean id="4" class="com.abc.jmsPublish" topic="T.PERSON"/>
</beans>
</span><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">
</span></span></code></span></span></div><div><span class="Apple-style-span" style=" color: rgb(51, 51, 51); line-height: 20px; font-size:11px;"><span class="Apple-style-span" style="color: rgb(0, 0, 0); line-height: normal; white-space: pre-wrap; font-family:monospace;font-size:13px;"><code><span class="Apple-style-span" style="font-size:small;"><span class="Apple-style-span" style=" white-space: normal; color: rgb(51, 51, 51); line-height: 20px; font-family:Georgia, serif;">Currently, I think XSLT would be the best solution since it keeps the clarity of the pipes&filters language.</span></span></code></span></span></div>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com2tag:blogger.com,1999:blog-3619881695344253937.post-38087741517669537522009-08-10T05:30:00.001-07:002009-08-10T07:10:24.597-07:00Good books on EAI and Agile Programming<span class="Apple-style-span" style=" color: rgb(51, 51, 51); font-size:small;"><h3 class="post-title" style="margin-top: 0.25em; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 4px; padding-left: 0px; font-weight: normal; line-height: 1.4em; color: rgb(204, 102, 0); font-size:18px;"><span class="Apple-style-span" style="color: rgb(51, 51, 51); line-height: 20px; font-size:small;"><ul><li><span style=" ;font-size:11px;">Head First "Design Patterns" - 26 pages on the Decorator ? Finally the space it deserves ! If you found the "Consequences, Forces, Solutions, Implementation, Related Patterns"structure of other Patterns book too rigid. This one is for you. Finally an entertaining read on Patterns. "Patterns in Java" and even the GoFs books loose their glory after reading this one.</span></li><li><span style=" ;font-size:11px;">"Client/Server Survival Guide" - Very old, but I liked the writing. (use Steven's "Unix Network Programming" as a reference)</span></li><li><span style=" ;font-size:11px;">"Extreme Programming Explained" - there you go, little Agile programmer. Also some good tips on how to convince upper management of the benefits.</span></li><li><span style=" ;font-size:11px;">"Java Extreme Programming Cookbook" - nice intro to a all the good technologies (as of 2005).</span></li><li><span style=" ;font-size:11px;">"Java Message Service" (Monson-Haefel&Chappel) - no thrills, but all you really need. Writing concise books is a great virtue.</span></li></ul></span></h3></span>Axel Podehlhttp://www.blogger.com/profile/10752349929649267813noreply@blogger.com0