XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Archive for the ‘Java’ Category

Alpha Version of FIT Decorator released

Wednesday, December 7th, 2005

While it would take some more time for the fit decorator code to be available in the fitlibrary, I have created a project under sourceforge called fitdecorator.

The alpha release is available at
http://sourceforge.net/projects/fitdecorator/

This is my first open source project.

Looking forward to your feedback

FIT Decorators

Monday, November 7th, 2005

Rick Mugridge has agreed to include the fit decorators I had written into the fit library. He plans to package them under fitlibrary.decorator.*

I have written an abstract class called FixtureDecorator. Anyone who wants to decorate any fit fixtures will have to extend the FixtureDecorator class and override the following 3 methods

protected void setupDecorator(Parse table) throws InvalidInputException;
If you need to pass any parameters to your decorator fixture, then write the logic in this method. Throws an InvalidInputException if your fixture expects some data, but in the fit test you have not set it.

protected void run(Fixture fixture, Parse table);
This is the main method which does the real decoration stuff and calls the super.run(fixture, table); when you want to execute the enclosed fixture.

protected void updateColumnsBasedOnResults(Parse table);
After execution, you might want to evaluate the results and mark the cells in your decorator as right or wrong. You can do that in this method.

Few things might change here and there, but this would be the essence of the fixture decorator.

This would be released under the GNU Public License. Oh yes! GPL.

Ideas/suggestions welcomed.

Performance testing in FIT/Fitnesse

Sunday, November 6th, 2005

Acceptance tests are a nice way to capture requirements and test them against the system. I was wondering if we can associate some kind of performance criteria with our acceptance tests.

On my current project we are using fitnesse for writing/running our acceptance tests. We are using fitnesse to demonstrate completion of requirements to the customer. As of now we do not have specific performance parameters defined by the customer. But we want to write some tests that can keep a check on time taken for each user call.

We started using JUnitPerf for this. JUnitPerf needs end-to-end JUnit tests, which it can decorates to check the execution time. Soon we found we had a lot of code duplication in these end-to-end JUnit tests and in our fit fixtures. At this stage we started considering FIT to do this kind of execution/performance test.

So far I have written a spike implementation. In the current implementation, I have written a new fixture which is kind of a decorator around any fixture. Following is an example of how it can be used.

com.perfit.MaxTime 10
eg.Division

numerator denominator quotient()
10 2 5
12.6 3 4.2
100 4 25

Going forward I‘m planning to write other decorators which can run the same fixture multiple time and so forth. Here is an example

com.perfit.Loop 5
com.perfit.MaxTime 10
eg.Division

numerator denominator quotient()
10 2 5
12.6 3 4.2
100 4 25

Ideas/suggestions welcomed

This code, with a few other decorators have been released as an open source project. For more details look at the fitdecorator project.

Can offshore development centers get business critical App development projects?

Saturday, October 15th, 2005

I‘m wondering how would CIOs or IT Managers feel off shoring business critical apps to an offshore development centers?

Could this answer why majority of software projects in India are not business critical?

If so, how do we make our work more challenging and interesting?

Constants and JUnit

Tuesday, October 11th, 2005

On my current project we have a requirement that we need to log every method entry and exit. For purpose of this post let‘s consider a simpler example.

public class Calculator{
private final LoggerWrapper logger;
static final String ADD_METHOD_SIGNATURE = “add(int, int)“;

Calculator(LoggerWrapper logger){
this.logger = logger;
}

public int add(int firstOperand, int secondOperand) {
logger.logMethodEntry(ADD_METHOD_SIGNATURE);
int result = firstOperand + secondOperand;
logger.logMethodExit(ADD_METHOD_SIGNATURE);
return result;
}

}

Since logging method entry and exit is a requirement we need to write unit tests to make sure we are logging the correct messages in the right sequence.

We started using easymocks and constructor based dependency injections.

Since our unit tests reside in the same package as the classes, [in a different source tree of course], we use package protected constructors to inject the logger.

//test class
import org.easymock.MockControl;
import junit.framework.TestCase;

public class CalculatorTest extends TestCase {

public void testAdding3To5Returns8() throws Exception {
MockControl loggerControl = MockControl. createStrictControl (LoggerWrapper.class);
LoggerWrapper mockLogger = (LoggerWrapper) loggerControl.getMock();
Calculator calculator = new Calculator(mockLogger);

mockLogger.logMethodEntry(Calculator. ADD_METHOD_SIGNATURE);
mockLogger.logMethodExit(Calculator. ADD_METHOD_SIGNATURE);

loggerControl.replay();
assertEquals(8, calculator.add(3,5));
loggerControl.verify();
}

}

This works perfectly fine. This test makes sure that logMethodEntry and logMethodExit is called in the same order and with the correct parameters. [method signature in this case].

So if you notice, we have defined a constant called ADD_METHOD_SIGNATURE in the Calculator class. We are using this constant in the test and making sure the logger methods are called with the correct method signature.

Using constants in this fashion surely has advantages. For Ex. someday we change the add method signature, we just need to update this constant and the tests will still pass. But the same advantage can turn into a big disadvantage if the constant value goes out of sink with the actual method signature. So, if we change the method signature and don‘t update the constant, the test would still pass, which is wrong. The tests should be checking for the absolute value in case of logging.

We have 2 approaches to tackle this.
1. Don‘t use the constant in the test and use the actual value. So if you change the method signature and don‘t update the constant, you‘ll have a failing test.
2. The second and the better approach is to use the constant in the test, but also add another test which checks for the absolute value of the constant.

public void testConstantValue() throws Exception {
assertEquals(“add(int, int)”, Calculator.ADD_METHOD_SIGNATURE);
}

Now you know, what‘s keeping me busy whole day 😉

To FitNesse or Not to FitNesse!

Thursday, October 6th, 2005

Background:
According to the FitNesse website, FitNesse is a fully integrated standalone wiki, and acceptance testing framework.

In simple words, FitNesse is essentially a wiki wrapper for FIT. FitNesse uses Ward Cunningham‘s FIT, which is the engine that actually runs the acceptance tests. The FitServer which comes with FitNesse, interfaces with FIT by converting all the wiki pages to HTML tables and passing them to the WikiRunner. FitNesse is not just limited to running only Java tests. Given an implementation of FIT and the corresponding FitServer, FitNesse can execute tests in any language.

As per the website, following languages have FIT/FitServer implementations.

  • Java : Included in the FitNesse.jar.
  • .NET : Included in the standard FitNesse distribution.
  • C++ : C++ FIT and FitServer
  • Delphi : Delphi FIT and FitServer
  • Python : Python FIT and FitServer
  • Ruby : Ruby FIT and FitServer
  • Smalltalk : Visual Works Smalltalk FIT and FitServer
  • Perl : Perl FitServer

Topic of the blog:
I have been using FitNesse for about 2 months now. FitNesse has a simple but not elegant wiki interface. [Well, it‘s not meant to be a full fledge fancy wiki]. But I‘m using it as a project wiki as well.

I must admit that I‘m pretty satisfied with FitNesse.
Reasons:
1. It has a decent menu on the left hand, which helps you declare a wiki page as a Test or a Test Suite. This means you can click one button on this menu and run any test or the whole suite.
2. It‘s very easy to download and install. Just a zip file which we need to unzip. There is a run.bat file, which needs the JAVA_HOME to be set. Double click the run.bat file, and you have FitNesse running on the specified port. [There is a bunch of simple command line arguments which can come very handy]
3. You can define a hierarchy of pages prefixing the link with ^ [caret character]. This hierarchy of pages is very useful when you want to organize the tests and run them together as a suite.
4. Classpath and other properties are pretty simple to setup. One needs to go to the root page and add them on this page. [http://localhost:8080/root]
5. It has a css file, which can be used to define custom format attributes.
6. Has a simple mapping of the pages to the file system. Under the FitNesse installation folder, there is a folder called FitNesseRoot, which will contain a folder per page created under the root folder. Each of these folders would contain a content.txt and properties.xml file. The content.txt file contains the actual contents of the page. The properties,xml file defines what menu buttons should be shown on the page. So it‘s pretty configurable.

Some pain points that I ran into with FitNesse are:
1. I want a good automatically backup strategy. It is a bit painful to just backup the whole FitnessRoot folder. I want a different backup strategy for the fixtures and the rest of the pages. FitNesse also contains a lot of backup zip file. Everytime, I change any page on the wiki, FitNesse zips up the contents.txt and the properties.xml file, before making any change. If you have lots of changes been done to the wiki there might be a huge number of these backup zip files.
2. Any wiki is not good at handling concurrent changes. I want to be able to manage concurrent changes with features like visual merge and rollbacks.
3. I want some kind of a configuration management on the tests. One should be able to tag the FitNesse fixtures against a build version and be able to keep it together with the build. FitNesse does not have a direct integration with the version control system I‘m using. [Might be a good idea to write a plugin]
4. Currently FitNesse comes with a build in web server. I‘m not aware of any easy way of pulling the fit server and wiki out and running it on any web server of our choice.
5. The CruiseControl war does not work with FitNesse. It needs another web container to run. It kind of gets annoying having so many web servers running on the build machine.

At this point I‘m trying to do all kinds of wacky stuff to get around these. Eventually I might just plan to throw out FitNesse and just go with simple FIT. There are a lot of alternatives for the wiki part of FitNesse. So breaking them up into a separate project wiki and a bunch of html files for FIT and maintaining them differently seems to make more sense.

Playing with EasyMock

Sunday, September 25th, 2005

I just started using EasyMock on my current project. So far, I have not used it much, but I think it would be helpful to some people to put some basic notes on my approach to using easymock.

I started playing around with JDBCFixture (A set of useful fixtures for Fitnesse). I was happy to see JDBCFixture using easymock for some of the JDBC stuff. I think this would be a good starting point for someone to understand easymock.

Lets consider a simple DeleteFixture. Following is the behavior that is expected on a DeleteFixture

1.Given a tableName, it should delete all the rows from the table

fitnesse.jdbc.DeleteFixture aTable

2.Given a tableName and where clause, it should execute the delete statement

fitnesse.jdbc.DeleteFixture aTable
where id=1

The core functionality of the DeleteFixture is basically to extract the correct data (tableName and the where clause) and form the corresponding SQL, which it can then execute on the database through the JDBC calls.

So we are interested to test the following sequence for the first scenario: (just TableName is given)
1.get a connection instance
2.connection.prepareStatement(“delete from tableName“) is called. We are not interested in how the sql is formed
3.preparedStatement .executeUpdate() is called
4.preparedStatement.close() is called
5.connection.close() is called

So what would a unit test for DeleteFixture look like?
Unfortunately with simple JUnit this kind of testing is not possible. So what do we do? You guessed it right, we use Mocks.

Using easyMock, the test class would like this:

package fitnesse.jdbc;

import fit.Fixture;
import fit.Parse;

import junit.framework.TestCase;

import org.easymock.MockControl;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class DeleteFixtureTest extends TestCase {
private Connection connection;
private MockControl mockConnectionControl;
private MockControl mockStatementControl;
private PreparedStatement statement;

protected void setUp() throws Exception {
super.setUp();
JdbcConnectionFixture.driver = “org.gjt.mm.mysql.Driver“;

mockConnectionControl = MockControl.createControl(Connection.class);
connection = (Connection) mockConnectionControl.getMock();

mockStatementControl = MockControl.createControl(PreparedStatement.class);
statement = (PreparedStatement) mockStatementControl.getMock();
}

protected void tearDown() throws Exception {
mockConnectionControl = null;
mockStatementControl = null;
connection = null;
statement = null;
super.tearDown();
}

public void testJdbcCallSequenceForJustTableName() throws Exception {
JdbcConnectionFixture.setConnection(connection);

connection.prepareStatement(“delete from tableName“);
mockConnectionControl.setReturnValue(statement);

statement.executeUpdate();
mockStatementControl.setReturnValue(1);

statement.close();
connection.close();

replay();

Parse table = new Parse(”

fitnesse.jdbc.DeleteFixture tableName

“);
Fixture fixture = new Fixture();
fixture.doTables(table);

verify();
}

private void replay() {
mockConnectionControl.replay();
mockStatementControl.replay();
}

private void verify() {
mockConnectionControl.verify();
mockStatementControl.verify();
}

}

Let‘s start from the setUp() method:

MockControl mockConnectionControl = MockControl.createControl(Connection.class);
Connection connection = (Connection) mockConnectionControl.getMock();

MockControl.createControl() takes a parameter of type Class. We can pass Class, Abstract Class or even Interfaces. This would return the MockControl object.

mockConnectionControl.getMock() returns the actual mock object.

Now let‘s look at our actual test method

public void testJdbcCallSequenceForJustTableName() throws Exception {
JdbcConnectionFixture.setConnection(connection);

connection.prepareStatement(“delete from tableName“);
mockConnectionControl.setReturnValue(statement);

statement.executeUpdate();
mockStatementControl.setReturnValue(1);

statement.close();
connection.close();

replay();

Parse table = new Parse(”

fitnesse.jdbc.DeleteFixture tableName

“);
Fixture fixture = new Fixture();
fixture.doTables(table);

verify();
}

In the first statement JdbcConnectionFixture.setConnection(connection); we are injecting our mock connection into the system.

In the following calls we are setting the expections:

connection.prepareStatement(“delete from tableName“);
mockConnectionControl.setReturnValue(statement);

statement.executeUpdate();
mockStatementControl.setReturnValue(1);

statement.close();
connection.close();

Please note that this is the sequence of JDBC calls we expect. One can this of this as if we are recording a macro.

The replay() call tells the system that we have finished recording, following will be the actual execution. Hence after the replay() we call the actual method call that we want to test.

Parse table = new Parse(”

fitnesse.jdbc.DeleteFixture tableName

”);
Fixture fixture = new Fixture();
fixture.doTables(table);

Finally we call the verify() method, which checks if the macro recording sequence was same as the actual method call sequence with the expected paramter values.

Testing with Mocks using EasyMock can be as simple as this.

Crystal Reports PLUS Struts

Monday, February 14th, 2005

<b>Steps to integrate crystal reports with Strut’s app</b>

1. Provide a link from your web-app, which will trigger the report. Usually we have a form, which will do this.
html:form action=”/generateReport” target=”_blank”

2. Update the struts-congif.xml file to redirect “/generateReport” to the appropriate action class

3. Write the following GenerateReportAction class to handle the report generation logic.

That’s it. You will see the report.

package com.xyz.abc.web.servlet;

import com.crystaldecisions.report.web.viewer.CrystalReportViewer;
import com.crystaldecisions.reports.reportengineinterface.JPEReportSourceFactory;
import com.crystaldecisions.sdk.occa.report.data.Fields;
import com.crystaldecisions.sdk.occa.report.data.ParameterField;
import com.crystaldecisions.sdk.occa.report.lib.ReportSDKExceptionBase;
import com.crystaldecisions.sdk.occa.report.reportsource.IReportSource;
import com.crystaldecisions.sdk.occa.report.reportsource.IReportSourceFactory2;

import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

public class GenerateReportAction extends Action {
public ActionForward actionExecuted(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, ActionErrors errors) throws IOException, ServletException {
String reportName = System.getProperty(REPORT_NAME, “Report1.rpt”);
final ActionForm reportForm = (ReportForm) form;
List paramList = getReportParams(reportForm);
CrystalReportViewer reportViewer = createReportViewer(reportName, paramList, request.getLocale());
try {
reportViewer.processHttpRequest(request, response, getServlet().getServletContext(), response.getWriter());
} catch (ReportSDKExceptionBase reportSDKExceptionBase) {
reportSDKExceptionBase.printStackTrace();
} finally {
reportViewer.dispose();
}
return (mapping.findForward(“success”));
}

private CrystalReportViewer createReportViewer(String reportName, List params, Locale locale) {
CrystalReportViewer reportViewer = new CrystalReportViewer();
try {
IReportSourceFactory2 rptSrcFactory = new JPEReportSourceFactory();
IReportSource reportSource = (IReportSource) rptSrcFactory.createReportSource(reportName, locale);
reportViewer.setReportSource(reportSource);
Fields fields = createParamterFields(params);
reportViewer.setParameterFields(fields);
setReportViewerProperties(reportViewer);

return reportViewer;
} catch (ReportSDKExceptionBase e) {
throw new IllegalStateException(e.getMessage());
}
}

private Fields createParamterFields(List params) {
Fields fields = new Fields();
for (Iterator iterator = params.iterator(); iterator.hasNext();) {
ReportParams reportParam = (ReportParams) iterator.next();
ParameterField parameterField = newParameterField(reportParam.getParamName(), reportParam.getParamValue());
fields.add(parameterField);
}
return fields;
}

private void setReportViewerProperties(CrystalReportViewer reportViewer) {
reportViewer.setHasRefreshButton(false);
reportViewer.setHasExportButton(true);
reportViewer.setEnableParameterPrompt(true);
reportViewer.setEnableLogonPrompt(true);
reportViewer.setHasLogo(false);
}

private ArrayList getReportParams(ActionForm ReportForm) {
ArrayList outParamList = new ArrayList();
outParamList.add(new ReportParams(“param1”, ReportForm.getParamOne()));

return outParamList;
}

public static ParameterField newParameterField(String name, String dataValue) {
ParameterField field = new ParameterField();
field.setName(name);
field.setReportName(“”);
field.getCurrentValues().add(dataValue);
return field;
}
}

Things to watch out:

1. html:form action=”/generateReport” target=”_blank”
Since crystal report has it’s own menu, it’s better to open the report in a separate blank page. If we don’t do so, when we click on any of the menu items, actually a form submission takes place and struts will interfere with it.

2. While creating parameter fields, make sure you do the following field.setReportName(“”);
Though, it does not make any sense to set the reportName to blank string, it is a must, else the parameters will not be passed to the report. If inside the report you try to access these parameters, then Crystal Reports will throw the following exception and redirect you to a page, which asks for the missing parameters.
05 Feb 2005 16:17:01 [http80-Processor25] ERROR com.crystaldecisions.reports.formatter.formatter.objectformatter – com.crystaldecisions.reports.dataengine.al: Some parameters are missing values
05 Feb 2005 16:17:01 [http80-Processor25] ERROR com.crystaldecisions.reports.reportengineinterface – Error formatting page
com.crystaldecisions.reports.formatter.formatter.c: Some parameters are missing values
at com.crystaldecisions.reports.formatter.formatter.objectformatter.bf.<init>(Unknown Source)
at com.crystaldecisions.reports.formatter.formatter.objectformatter.bf.a(Unknown Source)
at com.crystaldecisions.reports.formatter.formatter.d.j.<init>(Unknown Source)
at com.crystaldecisions.reports.formatter.formatter.d.j.if(Unknown Source)
at com.crystaldecisions.reports.reportengineinterface.Engine.getPage(Unknown Source)
at com.crystaldecisions.reports.reportengineinterface.JPEReportSource.getPage(Unknown Source)
at com.crystaldecisions.report.web.viewer.ReportAgent.a(Unknown Source)
at com.crystaldecisions.report.web.viewer.CrystalReportViewer.goto(Unknown Source)
at com.crystaldecisions.report.web.ServerControl.a(Unknown Source)
at com.crystaldecisions.report.web.ServerControl.getHtmlContent(Unknown Source)
at com.thoughtworks.clearinghouse.web.servlet.GenerateSummaryReportAction.actionExecuted(GenerateSummaryReportAction.java:51)
at com.thoughtworks.clearinghouse.web.servlet.AbstractAction.execute(AbstractAction.java:40)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:437)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:264)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1109)
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:470)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:763)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:284)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:204)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256)
at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:245)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:199)
at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:195)
at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:164)
at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:149)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:156)
at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:972)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:211)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:805)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:696)
at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:605)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:677)
at java.lang.Thread.run(Thread.java:534)
Caused by: com.crystaldecisions.reports.dataengine.al: Some parameters are missing values
at com.crystaldecisions.reports.dataengine.a0.a(Unknown Source)
… 42 more
Even after setting reportName to blank string, we still get this error, but the values are passed correctly to the report.

Does someone have a solution, how to get rid of this exception?

Art of amazing object programming!

Monday, February 7th, 2005
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
public class ReportServerControl extends ServerControl {
    private static final String ag = "RptVwSt";
    private static final String N = "SelectionFormula";
    private static final String R = "VTSelectionFormula";
    protected static final String B = "ActionParams";
    private static final String ak = "RptSrc_Chgd";
    private static final String aj = "ActionNavigateTo";
    private static final String G = "uri";
    private static final String al = "DataContext";
    private static final String X = "ObjectName";
    private static final String A = "PendingEvent";
    private static final String aa = "UserParams";
    private static final String J = "UserLogons";
    private static final String D = "TriedServerCap";
    private static final String Z = "ServerCaps";
    protected static final String Y = "ExportFormats";
    private boolean ab;
    private boolean T;
    private boolean C;
    private boolean x;
    private ReportAgentBase am;
    protected static final double L = 9.0;
    private static double V;
    private Hashtable H;
    private static Object S;
    private String O;
    private Hashtable ah;
    private Hashtable P;
    private boolean an;
    private String K;
    private String F;
    private String M;
    private String af;
    private boolean U;
    private boolean y;
    private Fields I;
    private ConnectionInfos Q;
    private boolean ae;
    private boolean z;
    private boolean ac;
    private boolean W;
    private boolean ai;
    private PropertyBag E;
    private static int ad;
 
    public ReportServerControl() { }
 
    protected void a(String batchId, Object other) { }
 
    public void addReportPartBookmarkNavigationEventListener(IReportPartBookmarkNavigationEventListener iReportPartBookmarkNavigationEventListener) throws TooManyListenersException {    }
 
    public void addReportSourceChangeEventListener(ReportSourceChangeEventListener reportSourceChangeEventListener) {    }
 
    private void C() { }
 
    protected ReportAgentBase x() { }
 
    public void dispose() { }
 
    void a(ReportPartBookmarkNavigationEventArgs reportPartBookmarkNavigationEventArgs) { }
 
    protected Hashtable m() { }
 
    public ConnectionInfos getDatabaseLogonInfos() throws ReportSDKExceptionBase { }
 
    public Object getEnterpriseLogon() { }
 
    protected String a(Exception e) { }
 
    protected Hashtable s() { }
 
    protected String z() { }
 
    public Fields getParameterFields() throws ReportSDKExceptionBase { }
 
    private String u() throws ReportSDKExceptionBase { }
 
    private String l() throws ReportSDKExceptionBase { }
 
    private String t() throws ReportSDKExceptionBase { }
 
    protected Hashtable B() { }
 
    protected ReportAgentBase o() { }
 
    public IReportSource getReportSource() throws ReportSDKExceptionBase { }
 
    public String getReportSourceClassFactoryName() { }
 
    public String getSelectionFormula() throws ReportSDKExceptionBase { }
 
    protected void a() { }
 
    protected PropertyBag v() { }
 
    double y() { }
 
    private synchronized void w() { }
 
    public String getStyleSheetFileName() { }
 
    protected void a(String batchId) throws ReportSDKExceptionBase { }
 
    private void p() throws ReportSDKExceptionBase { }
 
    protected void a(ReportSDKException e) throws ReportSDKExceptionBase { }
 
    protected void a(ReportSDKException e, String batchId) throws ReportSDKExceptionBase { }
 
    private void a(ReportSDKException e, String batchId, boolean b) throws ReportSDKExceptionBase { }
 
    public boolean isEnableLogonPrompt() { }
 
   public boolean isEnableParameterPrompt() { }
 
   protected boolean F() { }
 
    protected boolean q() { }
 
    boolean A() { }
 
    public boolean isReuseParameterValuesOnRefresh() { }
 
    protected void a(Object other) throws ReportSDKExceptionBase { }
 
    public void navigateTo(String batchId, String batchId1) { }
 
    private void a(String batchId, String batchId1, String batchId2) throws ReportSDKExceptionBase { }
 
    protected void a(String batchId, String batchId1, String batchId2, boolean[] booleans) throws ReportSDKExceptionBase { }
 
    protected void d() { }
 
    private void E() throws ReportSDKExceptionBase, UnsupportedEncodingException { }
 
    private void r() throws ReportSDKExceptionBase, UnsupportedEncodingException { }
 
    public void refresh() { }
 
    protected void D() throws ReportSDKExceptionBase { }
 
    public void removeReportPartBookmarkNavigationEventListener() { }
 
    public void removeReportPartBookmarkNavigationEventListenerr() { }
 
    public void removeReportSourceChangeEventListener() { }
 
    protected void a(Writer writer) throws IOException, ReportSDKExceptionBase { }
 
    protected Object c() throws ReportSDKExceptionBase { }
 
    public void setDatabaseLogonInfos(ConnectionInfos connectionInfos) { }
 
    protected void a(ConnectionInfos connectionInfos) { }
 
    public void setEnableLogonPrompt(boolean b) { }
 
    public void setEnableParameterPrompt(boolean b) { }
 
    public void setEnterpriseLogon(Object other) { }
 
    public void setParameterFields(Fields fields) { }
 
    protected void a(Fields fields) { }
 
    protected void a(ReportAgentBase reportAgentBase) { }
 
    public void setReportSource(Object other) throws ReportSDKExceptionBase { }
 
    public void setReportSourceClassFactoryName(String batchId) { }
 
    public void setReuseParameterValuesOnRefresh(boolean b) { }
 
    public void setSelectionFormula(String batchId) { }
 
    public void setStyleSheetFileName(String batchId) { }
 
    public void setURI(String batchId) { }
 
    public void setViewTimeSelectionFormula(String batchId) { }
 
    protected void a(Writer writer, DeviceAdaptor deviceAdaptor) throws IOException { }
 
    protected void a(InputStream inputStream) throws ReportSDKException { }
 
    private String n() { }
}

No prizes for guessing it’s project right.

How Tomcat Windows Service can ruin your weekend!

Monday, February 7th, 2005

Context: We are currently using Tomcat 5.0.19. Since we use ANT script to do all the project setup, tomcat is installed from the zip distribution. Once we unzip the tomcat installation zip (that’s the installation process), we use a separate bat file to install tomcat as a windows service. Following is the bat file contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@echo off
if "%OS%" == "Windows_NT" setlocal
rem ---------------------------------------------------------------------------
rem NT Service Install/Uninstall script
rem
rem Options
rem install                Install the service using Tomcat5 as service name.
rem                        Service is installed using default settings.
rem remove                 Remove the service from the System.
rem
rem name        (optional) If the second argument is present it is considered
rem                        to be new service name
rem CATALINA_HOME       &lt;Root_Dir&gt;\Tomcat
rem JAVA_HOME           E:\jdk_1.3.0
rem JRE Type           hotspot
rem
rem ---------------------------------------------------------------------------
 
rem Guess CATALINA_HOME if not defined
set CURRENT_DIR=%cd%
set CATALINA_HOME=%3
if exist "%CATALINA_HOME%\bin\tomcat.exe" goto okHome
:gotHome
echo The tomcat.exe was not found...
echo The CATALINA_HOME environment variable is not defined correctly.
echo This environment variable is needed to run this program
goto end
:okHome
 
set EXECUTABLE=%CATALINA_HOME%\bin\tomcat.exe
 
rem Set default Service name
set SERVICE_NAME=TomcatClearinghousePortal
 
if "%1" == "" goto displayUsage
if "%2" == "" goto setServiceName
if "%3" == "" goto displayUsage
if "%4" == "" goto displayUsage
if "%5" == "" goto displayUsage
set SERVICE_NAME=%2
:setServiceName
if %1 == install goto doInstall
if %1 == remove goto doRemove
echo Unknown parameter "%1"
:displayUsage
echo
echo Usage: service.bat install/remove [service_name] CATALINA_HOME JDK_HOME JRE_TYPE
goto end
 
:doRemove
rem Remove the service
"%EXECUTABLE%" //DS//%SERVICE_NAME%
echo The service '%SERVICE_NAME%' has been removed
goto end
 
:doInstall
rem Install the service
%EXECUTABLE% //IS//%SERVICE_NAME% --DisplayName "%2" --Description "Indemand Clearinghouse Portal"
%EXECUTABLE% //US//%SERVICE_NAME% --Install %EXECUTABLE%
%EXECUTABLE% //US//%SERVICE_NAME% --ImagePath "%JAVA_HOME%\lib\tools.jar;%CATALINA_HOME%\bin\bootstrap.jar"
%EXECUTABLE% //US//%SERVICE_NAME% --Java "%4\jre\bin\%5\jvm.dll"
%EXECUTABLE% //US//%SERVICE_NAME% --StartupClass org.apache.catalina.startup.Bootstrap;main;start --ShutdownClass org.apache.catalina.startup.Bootstrap;main;stop --Startup auto
rem Set extra parameters
%EXECUTABLE% //US//%SERVICE_NAME% --JavaOptions -Dcatalina.home=""%CATALINA_HOME%""#-Djava.endorsed.dirs=""%CATALINA_HOME%\common\endorsed""#-Djava.io.tmpdir=""%CATALINA_HOME%\temp""#-Xms32m#-Xmx256m#-Xrs
%EXECUTABLE% //US//%SERVICE_NAME% --StdOutputFile "%CATALINA_HOME%\logs\stdout.log"
%EXECUTABLE% //US//%SERVICE_NAME% --StdErrorFile "%CATALINA_HOME%\logs\stderr.log"
%EXECUTABLE% //US//%SERVICE_NAME% --WorkingPath "%CATALINA_HOME%\bin"
echo The service '%SERVICE_NAME%' has been installed
 
:end
cd %CURRENT_DIR%

This will install Tomcat as a windows service and works fine.

Symptoms: Recently we had some issues integrating Tomcat with some other java service. When we started tomcat from the command prompt, everything worked fine. But as soon as we started tomcat as windows service, we’re left counting stars. Then we started searching for any error messages in the Tomcat log. Surprisingly we found that, though tomcat was writing logs, there was nothing related to our web application. We tried to reinstall the tomcat service but still no log messages.

Call the doctor: At this stage we felt that we should call the doctor. Guess who? Chris Stevenson. He’s saved us before in similar windows services related issues. Chris suggested that there’s an issue with the script that we are using to install Tomcat as a windows service. This is not the standard script that comes with the tomcat installation.

1
2
3
4
5
6
7
8
9
10
%EXECUTABLE% //IS//%SERVICE_NAME% --DisplayName "%2" --Description "Indemand Clearinghouse Portal"
%EXECUTABLE% //US//%SERVICE_NAME% --Install %EXECUTABLE%
%EXECUTABLE% //US//%SERVICE_NAME% --ImagePath "%JAVA_HOME%\lib\tools.jar;%CATALINA_HOME%\bin\bootstrap.jar"
%EXECUTABLE% //US//%SERVICE_NAME% --Java "%4\jre\bin\%5\jvm.dll"
%EXECUTABLE% //US//%SERVICE_NAME% --StartupClass org.apache.catalina.startup.Bootstrap;main;start --ShutdownClass org.apache.catalina.startup.Bootstrap;main;stop --Startup auto
rem Set extra parameters
%EXECUTABLE% //US//%SERVICE_NAME% --JavaOptions -Dcatalina.home=""%CATALINA_HOME%""#-Djava.endorsed.dirs=""%CATALINA_HOME%\common\endorsed""#-Djava.io.tmpdir=""%CATALINA_HOME%\temp""#-Xms32m#-Xmx256m#-Xrs
%EXECUTABLE% //US//%SERVICE_NAME% --StdOutputFile "%CATALINA_HOME%\logs\stdout.log"
%EXECUTABLE% //US//%SERVICE_NAME% --StdErrorFile "%CATALINA_HOME%\logs\stderr.log"
%EXECUTABLE% //US//%SERVICE_NAME% --WorkingPath "%CATALINA_HOME%\bin"

Can you smell some thing fishy?
We are installing the service once and then updating the service 8 times. After trying out different things for some time, we accidentally discovered the issue. Guess what?

1
%EXECUTABLE% //US//%SERVICE_NAME% --JavaOptions -Dcatalina.home=""%CATALINA_HOME%""#-Djava.endorsed.dirs=""%CATALINA_HOME%\common\endorsed""#-Djava.io.tmpdir=""%CATALINA_HOME%\temp""#-Xms32m#-Xmx256m#-Xrs

This line was the culprit. But as anyone would guess, it’s not the weird looking #. But it’s the tmpDir, that’s the culprit. When we checked, we didn’t have a temp dir under CATALINA_HOME. As soon as we created the temp folder, everything started working fine.

Root Cause: When tomcat is started as a service, it does not create any folders. If the temp folder is not present, it will behave strangely. Most of the things would work fine, but occasionally you might find some issues.

Lesson learnt: If tomcat is acting weird, check the logs, if the logs are not correct then there has to be some issue with the service installation script. Make sure that all the folders referred in the script are actually present.

    Licensed under
Creative Commons License