Ruminations on Development
Applying the Java Keyword 'final' To Classes
I was ponderin' the usages of the final keyword as applied to classes in Java.
I have heard before that all classes should be defined as final by default. The theory goes that unless you are specifically designing (and testing) your classes as potentially extensible, the final class modifier is a nice 'pertinent negative'.
In practice, this theory falls apart.
If you are exposing your concrete classes to other clients, a final class can neuter the consumer's ability to mock your class for testing purposes. (I ran into this at one point when using Lucene
at work, and tried to mock out a class.)
Another problem that arises, even if you have hidden all your concrete classes behind a nice API, is that final classes are not accessible by AOP
. (At least, not in the framework
that I looked into.)
Posted at 06:11PM May 21, 2008 by Nelson "Nelz" Carpentier in Java | Comments[1]
Revelation of the Day
This revelation hit me (and my coworkers) today: Caching is a cross-cutting concern...
You many not understand the implications of that phrase, but it leads to some interesting stuff
...
Posted at 11:36PM May 13, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Tomcat ROOT Context
I've been fighting
with Tomcat
a bunch this week.
My specific challenges have been around configuring a webapp to have the context root of "/", without having to name it ROOT.war...
The documentation page on context configuration
says that "Context elements may be explicitly defined":
In my experience this week, the option of creating the file $CATALINA_HOME/conf/[enginename]/[hostname]/ROOT.xml didn't work for me.
But, one of my coworkers (Chris) found some other resource somewhere that said you could create a $CATALINA_HOME/webapps/ROOT.xml that looked like this:
<?xml version='1.0' encoding='utf-8'?> <Context docBase="/absolute/path/to/your/webapp/dir"> </Context>
Chris tried this setup, and it seemed to work for him. Here's my caveats:
I will update if I find that this actually does work.
Posted at 11:04AM May 11, 2008 by Nelson "Nelz" Carpentier in Java | Comments[3]
Java 5 FOREACH Syntax
For those that don't currently use the new Java 5 "foreach" syntax, you are missing out.
I got to a point today where I wanted to verify that the construct is doing the intelligent thing, so I wrote the following unit test:
@Test
public void forEachLoop() {
for (String str : doHeavyWeigthMethod()) {
System.out.println(str);
}
}
private List<String> doHeavyWeigthMethod() {
System.out.println("heavyMethod");
final List<String> results = new ArrayList<String>();
results.add("one");
results.add("two");
results.add("three");
return results;
}
I was psyched to see the output:
heavyMethod one two three
Basically, this shows that the foreach construct only evaluates the collection once and caches the value to be used in each iteration.
However, the construct is not without some pitfalls. I would have expected the new construct to be programmed more defensively, but it is still susceptible to NullPointerException. Running the following throws the NPE:
@Test
public void forEachLoop() {
for (String str : doHeavyWeigthMethod()) {
System.out.println(str);
}
}
private List<String> doHeavyWeigthMethod() {
return null;
}
So, unless you know you are in control of the method you are calling in the expression part of the "foreach" construct, you still need to defensively check for null. And, we all know how much I love that
, right?
Posted at 09:35PM Apr 16, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Heavyweight Syntax
Method chaining seems like such a good idea. And Java supports it, always has. However, if you have been bitten in the butt too often by code you can't (or don't) control, you learn the value of defensive programming
.
Whereas, I want to write code like this:
public String getDeepString(ComplexObjectA a) {
return a.getComplexObjectB().toString();
}
... I can't. Or at least it's not a good idea to write it like that. To protect myself, or classes that utilize my method, I have to write it like this:
public String getDeepString(ComplexObjectA a) {
String result = null;
if (a != null) {
ComplexObjectB b = a.getComplexObjectB();
if (b != null) {
result = b.toString();
}
}
return result;
}
Wash. Rinse. Repeat. Over and over and over, I'm checking for null. And, I'm getting kind of tired of it.
But wait, what light over yon hill breaks? Is it Groovy
?!? Groovy provides a fantastic "?.
" construct, which can reduce the above noisy code to nearly what I was hoping for in my first example:
public String getDeepString(ComplexObjectA a) {
return a?.getComplexObjectB()?.toString();
}
I showed the "Unadulterated Java is so groovy
" post to some of my coworkers, and the "?." construct is the single most talked about benefit of potentially adopting Groovy.
No, we haven't adopted Groovy yet. We've got some work to do, including moving toward a Maven
(2) build system. But from what I hear the Groovy plugins to Maven
and IntelliJ
make dropping in Groovy transparent and easy. We may become a Groovy shop yet!
Posted at 11:33AM Apr 11, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Which Filesystem JAR Is A Class From?
In my environment, a test was blowing up. In my coworkers, it was working fine. On inspection, it seems my environment was expecting a different version of a class. How do I figure out what JAR the new/incorrect requirements are coming from?
I found this blog post, and it helped me greatly:
From which Jar a Class was loaded?
I used this info to write the following utility:
package net.nelz.utils;
import javax.activation.*;
import java.security.*;
import java.net.*;
public class ClassFinder {
public static String findClassOnFilesystem(final String className) {
try {
final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
return ClassFinder.findClassOnFilesystem(clazz);
} catch (ClassNotFoundException ex) {
return "Class:" + className + " - CLASS NOT FOUND!";
}
}
public static String findClassOnFilesystem(final Class clazz) {
final ProtectionDomain pDomain = clazz.getProtectionDomain();
final CodeSource cSource = pDomain.getCodeSource();
final URL loc = cSource.getLocation();
final String result = clazz.toString() + " - " + loc;
System.out.println(result);
return result;
}
}
Additionally, after finding this solution, a coworker pointed me to the Dependency Finder
as another tool that can help diagnose classpath issues.
Posted at 02:04PM Apr 10, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Migrating from JUnit to TestNG
I found an interesting and helpful bit of info, that I'd like to share.
My company has a bunch of older tests set up using JUnit
V.3.8.1. I am trying to convert it over to TestNG
.
TestNG has a page explaining the migration utility
. One of the things that freaked me out was all the exceptions blown by the utility before actually adding the TestNG annotations.
Luckily, I found this article on TestNG migration
this morning. The following quote explained what I was seeing:
"Note that the command will likely spit out a whole bunch of errors about classes that aren’t found; these can be safely ignored since they are emitted by javadoc which is used internally by the utility, and so cannot be suppressed. Your sources will now be annotated, and you will have a testng.xml in the src/test directory."
Posted at 12:09PM Apr 09, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Log4J Runtime Configuration
Ok... I know that in past projects, my teams have been able to put together a JSP to modify Log4J
configuration at runtime. However, doing a google search really only found a couple of descriptions of HOW to do it, but I wanted a more cut & paste solution.
So, after finding one in some code, I thought I would re-post it here. I give you "log4jAdmin.jsp":
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ page import="org.apache.log4j.Level"%>
<%@ page import="org.apache.log4j.LogManager"%>
<%@ page import="org.apache.log4j.Logger"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Enumeration"%>
<%@ page import="java.util.Set"%>
<%@ page import="java.util.Arrays"%>
<% long beginPageLoadTime = System.currentTimeMillis();%>
<html>
<head>
<title>Log4J Administration</title>
<style type="text/css">
<!--
#content { margin: 0px; padding: 0px; text-align: center; background-color: #ccc; border: 1px solid #000; width: 100%;}
body { position: relative; margin: 10px; padding 0px; color: #333; }
h1 { margin-top: 20px; font: 1.5em Verdana, Arial, Helvetica sans-serif;}
h2 { margin-top: 10px; font: 0.75em Verdana, Arial, Helvetica sans-serif; text-align: left; }
a, a:link, a:visited, a:active { color: red; text-decoration: none; text-transform: uppercase; }
table { width: 100%; background-color: #000; padding: 3px; border: 0px;}
th { font-size: 0.75em; background-color: #ccc; color: #000; padding-left: 5px; text-align: center; border: 1px solid #ccc; white-space: nowrap; }
td { font-size: 0.75em; background-color: #fff; white-space: nowrap;}
td.center { font-size: 0.75em; background-color: #fff; text-align: center; white-space: nowrap;}
.filterForm { font-size: 0.9em; background-color: #000; color: #fff; padding-left: 5px; text-align: left; border: 1px solid #000; white-space: nowrap;}
.filterText { font-size: 0.75em; background-color: #fff; color: #000; text-align: left; border: 1px solid #ccc; white-space: nowrap;}
.filterButton { font-size: 0.75em; background-color: #000; color: #fff; padding-left: 5px; padding-right: 5px; text-align: center; border: 1px solid #ccc; width: 100px; white-space: nowrap;}
-->
</style>
</head>
<body onLoad="javascript:document.logFilterForm.logNameFilter.focus();">
<%
String containsFilter = "Contains";
String beginsWithFilter = "Begins With";
String[] logLevels = { "debug", "info", "warn", "error", "fatal", "off" };
String targetOperation = (String)request.getParameter("operation");
String targetLogger = (String)request.getParameter("logger");
String targetLogLevel = (String)request.getParameter("newLogLevel");
String logNameFilter = (String)request.getParameter("logNameFilter");
String logNameFilterType = (String)request.getParameter("logNameFilterType");
%>
<div id="content">
<h1>Log4J Administration</h1>
<div class="filterForm">
<form action="log4jAdmin.jsp" name="logFilterForm">Filter Loggers:
<input name="logNameFilter" type="text" size="50" value="<%=(logNameFilter == null ? "":logNameFilter)%>" class="filterText" />
<input name="logNameFilterType" type="submit" value="<%=beginsWithFilter%>" class="filterButton" />
<input name="logNameFilterType" type="submit" value="<%=containsFilter%>" class="filterButton" />
<input name="logNameClear" type="button" value="Clear" class="filterButton" onmousedown='javascript:document.logFilterForm.logNameFilter.value="";' />
<input name="logNameReset" type="reset" value="Reset" class="filterButton" />
<param name="operation" value="changeLogLevel" />
</form>
</div>
<table cellspacing="1">
<tr>
<th width="25%">Logger</th>
<th width="25%">Parent Logger</th>
<th width="15%">Effective Level</th>
<th width="35%">Change Log Level To</th>
</tr>
<%
Enumeration loggers = LogManager.getCurrentLoggers();
HashMap loggersMap = new HashMap(128);
Logger rootLogger = LogManager.getRootLogger();
if(!loggersMap.containsKey(rootLogger.getName()))
{
loggersMap.put(rootLogger.getName(), rootLogger);
}
while(loggers.hasMoreElements())
{
Logger logger = (Logger)loggers.nextElement();
if(logNameFilter == null || logNameFilter.trim().length() == 0)
{
loggersMap.put(logger.getName(), logger);
}
else if(containsFilter.equals(logNameFilterType))
{
if(logger.getName().toUpperCase().indexOf(logNameFilter.toUpperCase()) >= 0)
{
loggersMap.put(logger.getName(), logger);
}
}
else
{
// Either was no filter in IF, contains filter in ELSE IF, or begins with in ELSE
if(logger.getName().startsWith(logNameFilter))
{
loggersMap.put(logger.getName(), logger);
}
}
}
Set loggerKeys = loggersMap.keySet();
String[] keys = new String[loggerKeys.size()];
keys = (String[])loggerKeys.toArray(keys);
Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
for(int i=0; i<keys.length; i++)
{
Logger logger = (Logger)loggersMap.get(keys[i]);
// MUST CHANGE THE LOG LEVEL ON LOGGER BEFORE GENERATING THE LINKS AND THE
// CURRENT LOG LEVEL OR DISABLED LINK WON'T MATCH THE NEWLY CHANGED VALUES
if("changeLogLevel".equals(targetOperation) && targetLogger.equals(logger.getName()))
{
Logger selectedLogger = (Logger)loggersMap.get(targetLogger);
selectedLogger.setLevel(Level.toLevel(targetLogLevel));
}
String loggerName = null;
String loggerEffectiveLevel = null;
String loggerParent = null;
if(logger != null)
{
loggerName = logger.getName();
loggerEffectiveLevel = String.valueOf(logger.getEffectiveLevel());
loggerParent = (logger.getParent() == null ? null : logger.getParent().getName());
}
%>
<tr>
<td><%=loggerName%></td>
<td><%=loggerParent%></td>
<td><%=loggerEffectiveLevel%></td>
<td class="center">
<%
for(int cnt=0; cnt<logLevels.length; cnt++)
{
String url = "/log4jAdmin.jsp?operation=changeLogLevel&logger=" + loggerName + "&newLogLevel=" + logLevels[cnt] + "&logNameFilter=" + (logNameFilter != null ? logNameFilter : "");
if(logger.getLevel() == Level.toLevel(logLevels[cnt]) || logger.getEffectiveLevel() == Level.toLevel(logLevels[cnt]))
{
%>
[<%=logLevels[cnt].toUpperCase()%>]
<%
}
else
{
%>
<a href='<%=url%>'>[<%=logLevels[cnt]%>]</a>
<%
}
}
%>
</td>
</tr>
<%
}
%>
</table>
<h2>
Revision: 1.0<br />
Page Load Time (Millis): <%=(System.currentTimeMillis() - beginPageLoadTime)%>
</h2>
</div>
</body>
</html>
If you've got a basic Log4J setup running, this should be a trivial add, allowing you to modify logging levels at runtime.
Here are a couple of caveats:
Good Luck!
Update (20008-04-10): Jon Mann pointed out that there are two hard-coded URLs in the JSP. I updated them both to be "log4jAdmin.jsp" on lines 51 and 140. Thanks Jon!
Posted at 01:25PM Apr 08, 2008 by Nelson "Nelz" Carpentier in Java | Comments[2]
More Tools to Look Into...
successor (and wrapper?) that is supposed to have a servlet component for dynamic run-time updating of logging levels.
Note: I haven't yet tried these... YMMV! No InterWiki reference defined in properties for Wiki called 'Note'!)
Posted at 12:58PM Apr 03, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]
Front-Line Learnings
Okay... I want to try to be a bit more open about what my day-to-day involves... So, I'm going to try to post little learnings I make throughout my job to this here blog.
Here's a couple I've seen recently:
Package
Posted at 03:37PM Apr 01, 2008 by Nelson "Nelz" Carpentier in Java | Comments[0]