Friday, December 19, 2008

Using iText to convert TIFF to PDF and to combine multiple PDFs into one PDF

We recently had the requirement to take a Microsoft Excel file as well as one or more PDF "attachments" and 1) convert the Excel file to PDF, and 2) combine all the PDFs into a single PDF.

Unfortunately we were unable to find an open source Java solution for converting the Excel file to PDF.  If you want to extract information from the Excel file you can do so using Apache POI.  You can then generate a PDF file from this data using iText.  This wasn't an appropriate solution for us.  An alternative solution in the interim was to print the Excel file to TIFF using the Microsoft Document Image Writer and then convert the TIFF to PDF using iText.  The results look alright, not ideal, but workable.

Here are two Java methods to do it.  Let me know if you find a free Java solution to converting Excel to PDF.

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.PageSize;
import com.lowagie.text.pdf.PRAcroForm;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.pdf.SimpleBookmark;

public class FileConversions {

    /**
     * Convert a TIFF file to a PDF.
     * 
     * @param tiffFile
     * @return
     * @throws DocumentException
     * @throws MalformedURLException
     * @throws IOException
     */
    public static byte[] convertTiffToPdf(byte[] tiffFile) 
            throws DocumentException, MalformedURLException, IOException {

        ByteArrayOutputStream outfile = new ByteArrayOutputStream();
        Document document = new Document(PageSize.A4.rotate());
        PdfWriter writer = PdfWriter.getInstance(document, outfile);
        writer.setStrictImageSequence(true);
        document.open();
        Image tiff = Image.getInstance(tiffFile);
        tiff.scaleToFit(800, 600);
        document.add(tiff);
        document.close();
        outfile.flush();
        return outfile.toByteArray();
    }

    /**
     * Combine multiple PDFs into a single PDF.
     *
     * @param pdfs
     * @param combinedPdfFile TODO
     * @throws IOException
     * @see http://itext.ugent.be/library/com/lowagie/examples/general/copystamp/Concatenate.java
     */
    public static void combinePdfFiles(List<byte[]> pdfs, File combinedPdfFile) throws Exception {

        PdfReader reader = null;
        Document document = null;
        PdfCopy  writer = null;
        ArrayList master = new ArrayList();
        int pageOffset = 0;

        for (byte[] pdf : pdfs) {
            int size = pdf.length;
            reader = new PdfReader(pdf);
            reader.consolidateNamedDestinations();
            int n = reader.getNumberOfPages();
            List bookmarks = SimpleBookmark.getBookmark(reader);
            if (bookmarks != null) {
                if (pageOffset != 0) {
                    SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null);
                }
                master.addAll(bookmarks);
            }
            pageOffset += n;

            if (document == null) {
                // step 1: creation of a document-object
                document = new Document(reader.getPageSizeWithRotation(1));
                // step 2: we create a writer that listens to the document
                writer = new PdfCopy(document, new FileOutputStream(combinedPdfFile));
                // step 3: we open the document
                document.open();
            }
            // step 4: we add content
            PdfImportedPage page;
            for (int i = 0; i < n; ) {
                ++i;
                page = writer.getImportedPage(reader, i);
                writer.addPage(page);
            }
            PRAcroForm form = reader.getAcroForm();
            if (form != null) {
                writer.copyAcroForm(reader);
            }
        }
        if (!master.isEmpty()) {
            writer.setOutlines(master);
        }
        if (document != null) {
            document.close();
        }
    }
}

Thursday, December 18, 2008

Simple request benchmarking of a Ruby on Rails application using ApacheBenchmarker

You can use ApacheBenchmarker which comes with your default Apache install.  You can find the ab.exe executable in C:\Program Files\Apache Group\Apache2\bin on Windows.

Usage: ab [options] [http://]hostname[:port]/path
Options are:
  -n requests     Number of requests to perform
  -c concurrency  Number of multiple requests to make
  -t timelimit    Seconds to max. wait for responses
  -p postfile     File containing data to POST
  -T content-type Content-type header for POSTing
  -v verbosity    How much troubleshooting info to print
  -w              Print out results in HTML tables
  -i              Use HEAD instead of GET
  -x attributes   String to insert as table attributes
  -y attributes   String to insert as tr attributes
  -z attributes   String to insert as td or th attributes
  -C attribute    Add cookie, eg. 'Apache=1234. (repeatable)
  -H attribute    Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
                  Inserted after all normal header lines. (repeatable)
  -A attribute    Add Basic WWW Authentication, the attributes
                  are a colon separated username and password.
  -P attribute    Add Basic Proxy Authentication, the attributes
                  are a colon separated username and password.
  -X proxy:port   Proxyserver and port number to use
  -V              Print version number and exit
  -k              Use HTTP KeepAlive feature
  -d              Do not show percentiles served table.
  -S              Do not show confidence estimators and warnings.
  -g filename     Output collected data to gnuplot format file.
  -e filename     Output CSV file with percentages served
  -h              Display usage information (this message)

My simple benchmarking tests for my Ruby on Rails website.  I wanted to compare the performance of RoR over CGI with a new server instance created on each request versus requests over CGI proxied to a single long-running mongrel_rails server.  These tests do 10 individual requests, then 100 requests, 5 concurrently. Results are output in HTML.

ab -n 10 -c 1 -w http://new.varzyfamily.com/ > 10-requests.html ab -n 100 -c 5 -w http://new.varzyfamily.com/ > 100-5-concurrent-requests.html

For your information I'm running my mongrel_rails using God on port 3000 and I am proxying requests using the standard RoR .htaccess file as follows:

RewriteEngine On RewriteCond %{HTTP_HOST} ^new.varzyfamily.com$ RewriteRule ^(.*)$ http://127.0.0.1:3000%{REQUEST_URI} [P,QSA,L]

I have to adopt such an arcane setup because my host HostGator only supports RoR over CGI, which is not very performant.

The test results basically tell me what I already: never, ever run a RoR app over CGI where you're starting the server on every request! Holy smokes! For 100 requests (5 concurrent) over CGI average total request time was 16437 ms (ouch!!!) serving 0.3 reqs/sec. Talking to a proxied mongrel server fared much better with average total request time being 565 ms serving 8.61 reqs/sec.

Here is the output from the latter test.


This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright (c) 2006 The Apache Software Foundation, http://www.apache.org/

..done

Server Software:Mongrel
Server Hostname:new.varzyfamily.com
Server Port:80
Document Path:/
Document Length:4174 bytes
Concurrency Level:5
Time taken for tests:0.11609 seconds
Complete requests:100
Failed requests:0
Total transferred:457200 bytes
HTML transferred:417400 bytes
Requests per second:8.61
Transfer rate:39.38 kb/s received
Connnection Times (ms)
 minavgmax
Connect: 62 107 3062
Processing: 188 458 703
Total: 250 565 3765

Monday, December 15, 2008

Starting tomcat from Eclipse gives error "Can't load server.xml"

I am using Eclipse Gannymede and I am trying to start a Tomcat 5.5 and/or Tomcat 6.0 server.

Problem: When trying to start the Tomcat server from within Eclipse you get an error stating that Eclipse cannot load the server.xml file:

WARNING: Can't load server.xml from C:\projects\workspace-gannymede\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\conf\server.xml
Dec 15, 2008 3:52:51 PM org.apache.catalina.startup.Catalina load
WARNING: Can't load server.xml from C:\projects\workspace-gannymede\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\conf\server.xml
Dec 15, 2008 3:52:51 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 0 ms
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)
Caused by: java.lang.NullPointerException
at org.apache.catalina.startup.Catalina.await(Catalina.java:616)
at org.apache.catalina.startup.Catalina.start(Catalina.java:576)
... 6 more

If I try to look at the server.xml file that Eclipse is trying to read, it does not exist on the filesystem.

Solution: I tried a number of things such as deleting all my server configurations as well as the contents of the .metadata\.plugins\org.eclipse.wst.server.core\ directory.  Nothing worked.  The solution is to create a new Server Runtime Environment (Window -> Preferences -> Server -> Runtime Environments).  Then in your Servers view (Window -> Show View -> Servers) add a new server configuration (right-click and select New -> Server) but DO NOT ADD ANY PROJECTS TO IT.  Double click the new server configuration to bring up the Server Overview page which looks like this:

Under Server Locations select Use Tomcat installation.  Save.  You should be able to start your server now (assuming you can start it fine from the command line).  Now add your project to the server and you're ready to go.

Friday, December 12, 2008

OC4J 10.1.3.* does not support JSF 1.2

I spent a very frustrating day wading through the version soup that is JavaServer Faces and trying to deploy a JSF 1.2 app on on OC4J 10.1.3 container.  To save you a lot of time, OC4J DOES NOT SUPPORT JSF 1.2.

This technical paper explains that OC4J 10.1.3 (all versions) supports Servlet 2.4, JSP 2.0 and JSF 1.1.

And this handy webpage explains that "JSF 1.2 is the latest release and it works with servlet 2.5 and jsp 2.1". So obviously it's not compatible.  It also details all the versions of Java technologies that each JSF version relies upon.

So don't waste your time like I did.

Wednesday, November 26, 2008

Custom Scriptaculous Ajax InPlaceEditor

The following code demonstrates how to customize the Scriptaculous library's InPlaceEditor to make it look and feel exctly how you want.  It also demonstrates the general techniques that can be used to extend any of the Scriptaculous Event classes as well as the Prototype base classes (as I will demonstrate in a later post).  All of my customizations are controlled by options passed in when creating your IPE instance and do not alter the original behaviour of the IPE classes.

My intention here is to isolate my customizations so that the libraries can be updated at will and my modifications will continue to work.

I will post a sample of the code in action and describe what I am doing here at a later time. For now, here's the code.

// Custom controls.  Must be loaded after prototype & scriptaculous.

Object.extend(Ajax.InPlaceEditor.DefaultOptions, {
  showHighlight: true,  // false/true
  showSaving: true,     // false/true
  lockoutOnSave: true   // false/true
});

/**
 * The following fixes are from http://www.mail-archive.com/rubyonrails-spinoffs@googlegroups.com/msg11429.html  
 * They fix the cancelling/escape issues with the IPE.
 *
 */

/**
 * Removed the checkForEscapeOrReturn binding on keydown because it doesn't
 * do anything attached to the span element.
 */
Ajax.InPlaceEditor.Listeners = {
  click: 'enterEditMode',
  mouseover: 'enterHover',
  mouseout: 'leaveHover'
};
 
Ajax.InPlaceEditor.prototype.initialize = Ajax.InPlaceEditor.prototype.initialize.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  //proceed(args[0], args[1], args[2] ? args[2] : null);
  proceed.apply(null, args);
  this._boundKeyListener = this.checkForEscapeOrReturn.bindAsEventListener(this);
});

Ajax.InPlaceEditor.prototype.enterEditMode = Ajax.InPlaceEditor.prototype.enterEditMode.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  var e = args[0] ? args[0] : null;
  //proceed(); // args[0] ? args[0] : null 
  proceed.apply(null, args);
  // The form won't exist if we are saving, so check before setting the key listener
  if (this._controls.editor) {
    this._controls.editor.observe('keydown', this._boundKeyListener);
    if (e.rangeOffset != undefined) {
      setCaretTo(this._controls.editor, e.rangeOffset);
    }
    
  }
});

Ajax.InPlaceEditor.prototype.removeForm = Ajax.InPlaceEditor.prototype.removeForm.wrap( function(){
  var args = $A(arguments), proceed = args.shift();
  if (this._controls.editor) {
    this._controls.editor.stopObserving('keydown', this._boundKeyListener);
  }
  proceed.apply(null, args);
});

/**
 * Don't submit the form if nothing has changed.
 *
 */
 
Ajax.InPlaceEditor.prototype.handleFormSubmission = Ajax.InPlaceEditor.prototype.handleFormSubmission.wrap( function() {
  var args = $A(arguments), proceed = args.shift();
  var e = args[0] ? args[0] : null;
  //proceed(e);
  //return;
  //alert($A(arguments));
  var value = $F(this._controls.editor);
  var original_value = this.getText();
  //alert("Original value is: " + original_value + ", new value is " + value + ", values are equal? " + (original_value == value));
  if (original_value == value) {
    alert("nothing changed");
    // Stop the event and return.  Prepare submission removes the form and does
    // some cleanup that we need to do.
    this.prepareSubmission();
    if (e) Event.stop(e);
    return;
  } else {
    alert("submitting ajax request");
    proceed.apply(null, args);
  }
});

/**
 * Don't prevent the user from editing the field while the value is being saved.
 */

Ajax.InPlaceEditor.prototype.prepareSubmission = Ajax.InPlaceEditor.prototype.prepareSubmission.wrap( function() {
  var args = $A(arguments), proceed = args.shift();
  proceed.apply(null, args);
  if (!this.options.lockoutOnSave) {
    this._saving = false;
  }
});


/**
 * Don't show "Saving..." if showSaving is turned off.
 */

Ajax.InPlaceEditor.prototype.showSaving = Ajax.InPlaceEditor.prototype.showSaving.wrap(function() {
  if (!this.options.showSaving) {
    this._boundWrapperHandler();
    return;
  } else {
    var args = $A(arguments), proceed = args.shift();
    proceed.apply(null, args);
  }
});
  
/**
 * Don't highlight if highlighting is turned off.
 */
 
var checkIfHighlighting = function() {
  //alert("check if highlighting: " + this.options.hoverClassName);
  if (!this.options.showHighlight) return;
  var args = $A(arguments), proceed = args.shift();
  proceed.apply(null, args);
};

Debugging applications running in OC4J in Eclipse

Being able to breakpoint your Java code and step through it in Eclipse is essential for timely development.  But every time I go to create a new OC4J debugger instance in Eclipse I don't know where to start! And nothing on the web helped me out.  So here are my settings which work for me.  Hopefully they will work for you too.

A note on versions. I am using Eclipse 3.3.2 and OC4J 10.1.3.2.0 which I have installed at C:\appservers\oc4j10.1.3.2.0.  My Java version is 1.5.0_11.

Click Run -> Open Debug Dialog.  Create a new Java Application and give it a name.  Now configure each tab according to the screenshots.  The project I'm debugging is called regis. I don't know if you have to supply the project or not, but obviously whereever you see regis you'll want to replace that with your own project.

(Click on the screenshots to see a larger version.)

Some of the arguments here are optional. -Xmx512M just gives more memory to the process.

The libraries I have on the Source tab are also optional.  They're useful though when you want to step into Struts method calls etc.

Hopefully after duplicating my settings you are now ready to debug!

Wednesday, October 29, 2008

SVN tips

I'm going to keep track of some SVN tips here. For instance I always get confused about when I'm supposed to use a URL and when I'm supposed to use a path with SVN. And I always have to recreate those annoying trunk/ branch/ and tag/ directories every time I want to import a project and sometimes if you do it wrong you're truly screwed. Hopefully this will help you (and me) out in future.

Create a repository and import a project

svnadmin create --fs-type fsfs /home/kjvarga/svnroot svn import newproject file:///home/kjvarga/svnroot/newproject/
Some things to notice
  • The triple slash on the local file URL.
  • Specifying the file store type as fsfs. I'm on a shared host and apparently it's really not a good idea to use the BerkleyDB in this scenario. I found out the hard way when after about 4 months of no problems I was suddenly unable to access my repository (something about a BDB version mismatch).
  • In this scenario newproject has the structure newproject/trunk tags branches.
  • Don't forget to append the directory name to the SVNROOT in the import. Otherwise the contents of newproject/ will be imported to the root of your SVNROOT, which isn't very useful.
Verify your import with svn list file:///home/kjvarga/svnroot/newproject/

Checkout your import

The next logical step is to checkout the project you just checked in.
svn checkout file:///home/kjvarga/svnroot/newproject/trunk svnproject

Don't forget that you're checking out the trunk/ directory!

If you're not checking out the project locally you will need to supply a URL pointing to the repository resource.

I'm on a shared host which doesn't allow any direct access to SVN so I have to connect using ssh+svn. In taht case your checkout would look something like this:

svn checkout svn+ssh://kjvarga@varzyfamily.com/home/kjvarga/svnroot/newproject/trunk newproject

Friday, October 17, 2008

CSS Tips and Tricks

Every time I get back into CSS I forget some of the basic rules about inline and block level elements, positioning and box models. I'm not a fan of browser hacks, so you won't find any of those here.

Inline Elements

  • Can have width, padding-left, padding-right, margin-left and margin-right applied. However height, padding-top, padding-bottom, margin-top and margin-bottom are not applied.
  • To set the height you can use the line-height property.

Vertically Centered Text

  • An easy way center text vertically within an element is to use the line-height property. If the line-height is greater than the font size, the extra space is distributed evenly above and below the text.

Horzontally Centered Elements

  • Set a width and margin: 0 auto; on an element to have it horizontally centered.

Clearing Floats

  • Clearing floats with clear: left; clear: right; or clear: both; will clear all floats that precede the element in the document model. So use floats wisely because you will probably end up clearing floats on other parts of the page which you did not intent to clear.

Absolute vs Relative Positioned Elements

  • A relatively positioned element's margins will collapse (the margin will be the maximum of the two adjacent element's margins). However an absolutely positioned element will start a new block formatting context and its margins will not collapse with adjoining elements.
  • An absolutely positioned element is taken out of the document flow and positioned relative to the first positioned (rel or abs) ancestor.

100% Height Divs

  • Assigning height: 100% to a div will have no effect without a height defined on the parent.

Tuesday, October 7, 2008

How to determine your hourly rate as a contractor

This is a useful bit of information I pulled off of salary.com about how to derive your hourly rate as a contractor based on your current salary:

Independent contractor fees. If you work as an independent contractor without going through an agency, you have some leeway in establishing your professional fees, but you should charge close to market. To calculate this rate, start with the prevailing full-time salary for that job. Then divide by 2080, the number of work hours in a year (2080 = 52 X 40). This is the hourly rate for your job if benefits are being paid for by the employer.

But as a contractor, you need to pay for your own benefits, as well as additional Social Security contributions, so the number needs to be higher. Salary.com uses an adjustment factor of 30 percent to convert an hourly wage for a salaried employee to an hourly wage for a contract employee. Multiply your unadjusted hourly rate by (1 + 0.3) to get your adjusted hourly rate. For example, if your unadjusted hourly rate comes out to $20 per hour, your contract rate should be $20 * (1.3) = $26.

An example shows how this works for a senior-level web designer in Kansas City. A Web designer III working in Kansas City makes $66,244. The unadjusted hourly rate for this position is $66,244/2,080, or $31.85. Adjusted by 30 percent, the contract rate comes to $41.40.

Contract fee for a Web designer III in Kansas City

Salary$66,244
Hourly rate, unadjusted$31.85
Adjustment factor30%
Hourly rate, adjusted$41.40

Source: Salary.com, May 2002.

Tuesday, August 12, 2008

BPEL fails with WSIF exception on multiple simultaneous requests

Occasionally we run into what appear to be concurrency issues with BPEL whereby WSIF calls to our EJB are failing sporadically, reporting that the method we are trying to call cannot be found. Which is obviously incorrect because the deployed EJB has not been modified, and subsequent BPEL processes are not faulted. Usually we get this error when we are processing multiple records simultaneously.

Today I discovered the conditions that manifest the problem. If one of the BPEL processes is modfied and deployed and then - without refreshing the WSDL cache - two requests are processed concurrently, the first will always fail. If the WSDL cache is cleared, both are processed without problems.

So it appears to be a problem related to caching that only comes up if multiple requests are processed concurrently. So always refresh your WSDL cache after deploying BPEL processes to avoid any such issues.

<2008-08-12 13:19:50,125> <DEBUG> <default.collaxa.cube.ws> <WSIFInvocationHandler::invoke> Fault happened
org.collaxa.thirdparty.apache.wsif.WSIFException: No method named 'findSearchFile' found that match the parts specified
 at com.collaxa.cube.ws.wsif.providers.ejb.WSIFOperation_EJB.executeRequestResponseOperation(WSIFOperation_EJB.java:938)
 at com.collaxa.cube.ws.WSIFInvocationHandler.invoke(WSIFInvocationHandler.java:435)
 at com.collaxa.cube.ws.WSInvocationManager.invoke2(WSInvocationManager.java:443)
 at com.collaxa.cube.ws.WSInvocationManager.invoke(WSInvocationManager.java:251)
 at com.collaxa.cube.engine.ext.wmp.BPELInvokeWMP.__invoke(BPELInvokeWMP.java:727)
 at com.collaxa.cube.engine.ext.wmp.BPELInvokeWMP.__executeStatements(BPELInvokeWMP.java:366)
 at com.collaxa.cube.engine.ext.wmp.BPELActivityWMP.perform(BPELActivityWMP.java:195)

Friday, August 1, 2008

Extract the original entity from a (Hibernate) CGLIB proxy class

Ed: this is a rewrite of another post that deals with the same problem but with a less technical description.

If you have a polymorphic class structure then when Hibernate instantiates instances of your sub-classes and wraps them in a proxy class, the proxy is actually an instance of the root class in the hierarchy. This introduces some problems because you can no longer rely on instanceof and class casts because they will not work. You can implement a visitor pattern to work around these shortcomings.

The following code shows you how to retrieve the persisted concrete instance of your sub-class from the proxy class. This is useful for those occasions when you want access to the concrete instance, but don't want to implement a full visitor pattern:

if (proxy instanceof HibernateProxy){ 
   return ((HibernateProxy)proxy).getHibernateLazyInitializer().getImplementation(); 
}

Tuesday, July 29, 2008

Access the target object from a Hibernate CGLIB proxy

Sometimes in Hibernate when you have Lazy Loaded lists of objects you will end up interacting with a proxied instance of the object rather than the object itself, even if you access members of the object so that you know it's been loaded from the database.

This situation can be tricky because your object walks like a duck and talks like a duck...but it's a proxy. You want the duck! You can do something like the following to lure it out:

if (asset instanceof org.hibernate.proxy.HibernateProxy) {
    BankAccount ss = (BankAccount) ((HibernateProxy)asset).getHibernateLazyInitializer().getImplementation();
    String desc = asset.getDescriptionTxt();
}

Friday, July 25, 2008

Pass an encoded message resource in a redirect URL

Often you need to display a message/error on a JSP page but you want to redirect to the page so that it can be refreshed without reposting the form.  If you simply set redirect="true" in your struts-config.xml action forward you end up losing the request-scoped message when struts issues the redirect.

So what you need to do is 1) construct the message from a resource file, 2) encode the message, 3) issue a redirect and 4) display the message in the target JSP.

Access the MessageResources.properties bundle

org.apache.struts.util.MessageResources mr = getResources(request);
String message = mr.getMessage("the.message.key", optionalInterpolationArgument);

URL encode the message and issue a redirect

message = "message=" + java.net.URLEncoder.encode(message, "UTF-8");
response.sendRedirect(request.getContextPath() + "/targetAction.do?" + message);

Display the message

Note the use of <c:out /> here. If you just write out the message without doing any HTML escaping on it you open yourself up to an HTML-injection attack.
<c:if test="${!empty param.message}">
  <c:out value="${param.message}" />
</c:if>

Lookup message resources in a JSP file

Sometimes it may be better to just pass the message key itself and have the target JSP lookup the message and display it. The following snippet shows you how to lookup a message key:
<bean:message key="simple.message.key"/>
<bean:message key="interpolated.message.key" arg0="argument.value"/>

Tuesday, July 22, 2008

Installing MySQL and Apache as Windows services

To install Apache as a service

From the Apache bin directory (usually C:\Program Files\Apache Group\Apache2\bin):
C:\Program Files\Apache Group\Apache2\bin>Apache.exe -k install -n "This is the service name"
Other options are
-k uninstall to uninstall the service,
-k start,
-k stop and
-k restart to control the apache server.

To install MySQL as a service

From the MySQL bin directory (usually C:\Program Files\MySQL\MySQL Server 5.0\bin):
C:\Program Files\MySQL\MySQL Server 5.0\bin>mysqld --install "This is the service name" --defaults-file=C:/custom/path/to/my.ini
The example above sets the --defaults-file option which allows you to specify a non-standard options file location. The server will read options from the [mysqld] group.

To control the server you can use mysqladmin --port=3306 -u root shutdown to shutdown the server. 3306 is the default port. I put in the --port option as an example for when you need to specify a non-standard port. mysqld --remove removes the service. There are many other useful commands, to see them use the --help option.

Shut down Windows XP from the command line

You can use following built-in utility to shutdown Windows and then the machine itself:
C:\WINDOWS\SYSTEM32\TSSHUTDN.EXE 0 /POWERDOWN /DELAY:secs

To reboot use the following option:

C:\WINDOWS\SYSTEM32\TSSHUTDN.EXE 0 /REBOOT /DELAY:secs

To see a list of command options type tsshutdn /?

DELAY is in seconds.

  • 30 minutes = 1800 seconds
  • 1 hour = 3600 seconds
  • 2 hours = 7200 seconds
  • 3 hours = 10800 seconds

Wednesday, May 28, 2008

Disable Windows Update restart reminders

So you don't think getting automatic updates is a bad thing, but you don't want that damn annoying dialogue popping up every 5 minutes telling you to restart? Follow these simple steps:
Start / Run / gpedit.msc / Local Computer Policy / Computer Configuration / Administrative Templates / Windows Components / Windows Update / Re-prompt for restart with scheduled installations Set to Disabled.
Then you need to disable the Windows Update service for this session with the following command.
sc stop wuauserv
Your changes will take effect on reboot.

Thursday, May 22, 2008

Debugging javascript errors in IE7 on WinXP

As you may have noticed, the built-in Script Debugger in WinXP cannot be used to debug javascipt errors in Internet Explorer 7.  It used to work in IE6, but doesn't work with IE7 because it can no longer bind to the browser process.

But fear not, it is possible!  You have to download Microsoft's Visual Web Developer 2008 Express Edition which is freely available.  Then you create a new project and select Debug-> Start Debugger.  This will open a new IE7 window with the debugger attached.  You can now access the site or pages that you want to debug, and because the debugger is attached, when the (ridiculously unhelpful) Error dialog pops up you can select 'Yes' to debug and you will be taken to the VWD debugger.

Check out this blog for a step-by-step guide.

Wednesday, April 30, 2008

Accessing a HostGator SVN repository via SVN+SSH on Windows

This information should be helpful to anyone trying to access an svn repository stored on a remote (shared) server which does not expose an svn server.

My host is HostGator (good speeds, reliable ssh, cgi-only, MyISAM-only, decent support, non-existent knowledgebase). HostGator runs SSH over port 2222 which presents a few problems when trying to use traditional methods to connect to an SVN repository via SSH.

For these steps you will need Putty. Just get the whole suite.

  1. Generate an RSA key using PuttyGen
  2. Copy the public key into your ~/.ssh/authorized_keys file on the server.
  3. Prepend your key with (so it's all on one line):
    command="/usr/bin/svnserve -t"

    This sets up svnserve to run in tunnel mode. The ramification of this is that you can no longer use this RSA key to auto-login on the shell. There is some information out there about creating an svn.mydomain.com subdomain and only having this rule apply to that subdomain, which solves that problem. On *nix machines this involves adding a simple rule to your SSH keystore that says "use key X when connecting to svn.mydomain.com and key Y when connecting to mydomain.com". Then you would have two entries in your authorized_keys file, one with the svnserve command for connections to svn.mydomain.com using key X and one without for key Y.

  4. Now load your private key into Pageant
  5. Create a new Putty connection called mydomain.com.

    • Set the the Auto-login username.
    • Ensure that SSH->Auth->Attempt authentication using Pageant is checked.
    • Create an SSH tunnel which forwards LOCAL port 22 to mydomain.com port 2222.

    SAVE the connection and then test it by opening it. You should be logged in without having to provide a password.

    SVN should now be working when you use TortoiseSVN (or a similar client I assume). However, svn from the command line will not work and will report "Can't create tunnel..."

    Now to get SVN working from the command line.

  6. Create an SVN_SSH environment variable with value (keep the quotes and use forward slashes):
    "C:/Program Files/PuTTY/plink.exe"

    SVN will now use plink to try to connect to the svn repository. This will only work if the domain of your svn root's URL is named the same as the Putty connection you created i.e. mydomain.com.

If you find that you are getting errors when running "svn info" on a URL I found that version 1.4.2 works for Windows but 1.4.6 does not.

Friday, April 25, 2008

Oracle 10g timezones and setting the Java timezone

To see your session and database timezones:
SELECT sessiontimezone, dbtimezone FROM dual;
To set them:
ALTER database SET TIME_ZONE = '-07:00';
ALTER session SET TIME_ZONE = '-07:00';
Alternatively you can set it by supplying a region which exists in the following list:
SELECT tzname, tzabbrev FROM V$TIMEZONE_NAMES;
To set the JVM timezone append the following option when calling java:
-Duser.timezone=Asia/Japan
Java stores the supported timezones in files located at:
c:\path\to\jdk1.5.0_12\jre\lib\zi\
To set the timezone within your code, which is sometimes neccessary because OC4J seems to ignore my -Duser.timezone option:
java.util.TimeZone tz = java.util.TimeZone.getTimeZone("America/Vancouver");
java.util.TimeZone.setDefault(tz);

Here is a JSP page to display some time and timezone related information:

<%@ page language=\"java\" contentType=\"text/html; charset=ISO-8859-1\"
    pageEncoding=\"ISO-8859-1\"%>
<%@ page import=\"
  java.util.*,
  java.text.DateFormat\" %>
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
<html>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">
<title>TimeZone Test</title>
</head>
<body>
Your current timezone is: <%= java.util.TimeZone.getDefault().toString() %>
<p>
The current time is: <%= (new java.util.Date()).toString() %>
<p>
The value of the user.timezone property is: <%= System.getProperty(\"user.timezone\") %>
<p>
The date using DateFormat is: <%= DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date()) %>
</body>
</html>

Wednesday, April 16, 2008

oc4j 10.1.3.2.0 is missing a required rmi class PropagatedIdentity

Today I was getting an error on the console which I have included below. The error prevents the BPEL server from calling our application server via rmi, which results in some BPEL processes not being executed.

Oc4j10.1.3.2.0 is missing the oracle.oc4j.rmi.interceptors.PropagatedIdentity class which should be in C:/appservers/oc4j10.1.3.2.0/j2ee/home/lib/oc4j-internal.jar.

The required class was found in C:\appservers\famsbpel1013\j2ee\home\oc4jclient.jar. I copied the oracle.oc4j.rmi.interceptors class folder from oc4jclient.jar to oc4j-internal.jar and everything worked fine with no errors.

If everything is running 10.1.3.3.0 you should be ok.

Here's the error:

08/04/16 18:24:20 SEVERE: RMIInterceptorManagerImpl.readInterceptorContexts unable to recover remote context:

         Missing class: oracle.oc4j.rmi.interceptors.PropagatedIdentity
       Dependent class: com.evermind.io.ClassLoaderObjectInputStream
                Loader: oc4j:10.1.3
           Code-Source: /C:/appservers/oc4j10.1.3.2.0/j2ee/home/lib/oc4j-internal.jar
         Configuration: <code-source> in META-INF/boot.xml in C:\appservers\oc4j10.1.3.2.0\j2ee\home\oc4j.jar

This load was initiated at fams.root:0.0.0 using the Class.forName() method.

The missing class is not available from any code-source or loader in the system.

08/04/16 18:24:20 SEVERE: RMIInterceptorManagerImpl.readInterceptorContexts unable to recover remote context:

         Missing class: oracle.oc4j.rmi.interceptors.PropagatedIdentity
       Dependent class: com.evermind.io.ClassLoaderObjectInputStream
                Loader: oc4j:10.1.3
           Code-Source: /C:/appservers/oc4j10.1.3.2.0/j2ee/home/lib/oc4j-internal.jar
         Configuration: <code-source> in META-INF/boot.xml in C:\appservers\oc4j10.1.3.2.0\j2ee\home\oc4j.jar

This load was initiated at fams.root:0.0.0 using the Class.forName() method.

The missing class is not available from any code-source or loader in the system.

08/04/16 18:24:20 SEVERE: RMIInterceptorManagerImpl.readInterceptorContexts unable to recover remote context:

         Missing class: oracle.oc4j.rmi.interceptors.PropagatedIdentity
       Dependent class: com.evermind.io.ClassLoaderObjectInputStream
                Loader: oc4j:10.1.3
           Code-Source: /C:/appservers/oc4j10.1.3.2.0/j2ee/home/lib/oc4j-internal.jar
         Configuration: <code-source> in META-INF/boot.xml in C:\appservers\oc4j10.1.3.2.0\j2ee\home\oc4j.jar

This load was initiated at fams.root:0.0.0 using the Class.forName() method.