Nelz's Blog

10 April 2008

Which Filesystem JAR Is A Class From?

Filed under: Java — nelz9999 @ 14:04

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.

9 April 2008

Migrating from JUnit to TestNG

Filed under: Java — nelz9999 @ 12:09

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."

8 April 2008

Log4J Runtime Configuration

Filed under: Java — nelz9999 @ 13:25

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:&nbsp;&nbsp;

        <input name="logNameFilter" type="text" size="50" value="<%=(logNameFilter == null ? "":logNameFilter)%>"

               class="filterText"/>
        <input name="logNameFilterType" type="submit" value="<%=beginsWithFilter%>" class="filterButton"/>&nbsp;

        <input name="logNameFilterType" type="submit" value="<%=containsFilter%>" class="filterButton"/>&nbsp;

        <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 : "") + "&logNameFilterType=" + (logNameFilterType != null ? logNameFilterType : "");

                    if (logger.getLevel() == Level.toLevel(logLevels[cnt]) || logger.getEffectiveLevel() == Level.toLevel(logLevels[cnt])) {

            %>
            [<%=logLevels[cnt].toUpperCase()%>]

            <%
            } else {
            %>
            <a href='<%=url%>'>[<%=logLevels[cnt]%>]</a>&nbsp;

            <%
                    }
                }
            %>
        </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:

  1. You will need to have a base log4j.configuration file set up at startup. A log4j.properties file is fine for basic setups, but a log4j.xml file is reportedly more flexible and powerful.
  2. Standard JSP security is advised… You may need/want to hid this JSP behind whatever MVC framework you use… You DEFINITELY don’t want this JSP exposed to just anyone hitting your server!
  3. Towards the bottom of this file, there’s a hard-coded URL back to this page… If you change the name/url, make sure to update this… (I haven’t looked to hard at it, but I assume there’s a way to access the REQUEST object to find out the base URL for self reference.)

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!

Update (20008-09-09): Sudheer pointed out a problem with the url that is created. I have updated the code with his suggestion. Thank you Sudheer!

3 April 2008

More Tools to Look Into…

Filed under: Java — nelz9999 @ 12:58

Logback

  • A log4j successor (and wrapper?) that is supposed to have a servlet component for dynamic run-time updating of logging levels.

ie7.js

  • A JavaScript library that is intended to make Internet Explorer work as a standards-compliant browser.

Note: I haven’t yet tried these… YMMV! No InterWiki reference defined in properties for Wiki called ‘Note’!)

1 April 2008

Front-Line Learnings

Filed under: Java — nelz9999 @ 15:37

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:

  • Primary Keys
    • Primary Keys should be numeric types. This ends up showing itself once you’ve got ~1 million rows joining with another ~1 million rows.
    • I haven’t proved this out with time metrics, but when I do I will try to share them.
  • IntelliJ IDEA
    • Yeah, I’ve heard all the fanatics rave about it, but as a long-time Eclipse user, I feel like I’m working through molasses. I know I’m only 1 week in really, and I expect my productivity to get better. I just hope it is soon!
  • Quartz Scheduler
    • This framework looks really cool for scheduling. I’m hoping I get to put my hands on it really soon.
  • Ehcache
    • Around here, Ehcache has a reputation for being the slowest (read: worst) distributed caching technology out there.
    • All I can tell you is their documentation is VERY yawn-inspiring.

26 March 2008

GraphViz and ant2dot

Filed under: Java — nelz9999 @ 14:46

So, in trying to understand the ANT build here at my new company Widgetbox, I pulled out an old friend from my toolbox.

Using the ant2dot.xsl file, I was able to transform the build.xml file into a "build.dot" file. Then using a MacOSX port of GraphViz, I was able to tranform the "build.dot" file into a build.png.

Seeing the build dependencies represented graphically can really help with comprehension of a new build.

24 October 2007

Java equals(…) Question

Filed under: Java — nelz9999 @ 00:27

So, I’m playing around with the idea of creating a utility to test adherence to the Java "Equals Contract", and I came across something that I find ambiguous in the JavaDoc of java.lang.Object.equals(...).

It has to do with transitivity. The JavaDoc says this:

  • For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

That’s all well and good. But I’m trying to figure out the inverse of this rule.

Is it this?:

  • For any non-null reference values x, y, and z, if x.equals(y) returns false and y.equals(z) returns true, then x.equals(z) should return false.

… Or is it this?:

  • For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns false, then x.equals(z) should return false.

… Or are those two logically equivalent?

9 October 2007

Lucene – Ranged Numerical Search

Filed under: Java — nelz9999 @ 13:51

(This is the 2nd part of my ongoing series on Lucene, which I have been working with as of late. Also see the first part: Lucene Overview.)

I was psyched when I read that Lucene did ranged searches. This was going to be a big help when searching on member age, or even with latitude/longitude searches. The thing I glazed over (on the first reading, at least) was that Lucene does lexicographical sorting. (Which can also colloquially be referred to as alphabetical sorting.)

To see an example of some of the problems with lexicographical sorting, here is a quick Java/TestNG method that can display a lexicographically sorted set of strings:

@Test
public void example() {
final List<String> list = new ArrayList<String>();
list.add("<string to sort>");
...
Collections.sort(list);
for (String tmp : list) {System.out.println(tmp);}
}

Magnitudes of Numbers

Using the method above, what happens when we sort the four following strings {“1.00″, “2.00″, “11.00″, “12.00″}? We get the following ordering:

  1. “1.00″
  2. “11.00″
  3. “12.00″
  4. “2.00″

That doesn’t really meet our needs, but there is an answer to this issue, and it has to do with formatting. If you can figure out the entire conceivable range of values, write your entry into the index using a padded formatter. Example: For a weight range in Kg (but also calculated from Lbs), we will probably see a values from about 45Kg to 150Kg. Let’s go for 3 digits before the decimal place, and 4 decimal digits of precision. Then you will probably want to use the following:

public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("000.0000");

The great thing about this formatting trick is that it is automatically going to be understood when you convert the String back to a Double.

Negative Numbers

Using the method above again, notice what happens when we sort the four following strings {“-11.00″, “-01.00″, “02.00″, “12.00″}? We get the following ordering:

  1. “-01.00″
  2. “-11.00″
  3. “02.00″
  4. “12.00″

Now, we get negative numbers first, in descending (value, not lexicographical) order, and then the positive numbers in ascending (value) order.

We have two options here:

  • Either make our query builder smart enough to recognize when a query would bridge the negative to positive value ranges, and break the query into two pieces.
  • Add an offset to your values so that even the smallest (most negative) possible value can still be represented as a positive value. Again, this requires adding intelligence to your query builder.

In the end, we chose the latter of the two options… We figured it was going to be easier in the end to have a single query search over a contiguous range rather than conditionally breaking a query into separate positive and negative pieces.

For example, when we were trying to index on Longitudes, the possible values are -180 degrees to +180 degrees. Since we’re dealing in trigonometric degree measurement, we opted to use 360 degrees as our offset. (Since trig-wise -179 deg = -179 deg + 360 deg = 181 deg…)

Remember the trade-off though. You now have to add the offset at two times, once when you are creating the index, and once when you are creating your query against this index. This is far from an insurmountable problem, but you have to keep this additional point of synchronization in mind.


Well, that’s all I’ve got for today. Let me know if you have any questions.

1 October 2007

Lucene Overview

Filed under: Java — nelz9999 @ 11:23

What Is It?

Lucene (The Website) is an open-source suite of index-based search software projects hosted by the Apache Software Foundation. Lucene (The Project) is the base project at the center of all of the other projects presented on Lucene (The Website).

My Perspective

One could use Lucene to search on any type of text, be it from HTML pages, from Excel or Word documents, or from a database. I am using Lucene to search out the members of a website, based on information entered by those members. This information is primarily stored within a database.

Why is Lucene Better than Database Searching?

Imagine that you want to search for the word "balloon" across three different columns in your database. "Balloon" doesn’t need to be in all three columns, but it must be in at least one column. Yeah, you say to yourself "That wouldn’t be too hard of an SQL query to write."

What if I then tell you that you need to come up with rankings of the search results based on frequency of the occurrence of the word "balloon"? Oh, and did I metion that we need the ability to weight the results from one column heavier than another column? This whole searching thing becomes a much harder task…

Now, when I tell you that this should scale to include not only "balloon", but also "giraffe" and "cotton candy" and a varying number of other phrases, we’ve basically put the whole SQL-based searching option to bed. Yeah, you could come up with a whole code-plus-SQL framework to do all these things dynamically… But, why bother? The Lucene project already solved all these issues for you.

The Two Sides of Search

Lucene is an index-based search. This means that the information to be searched upon must be converted and pre-processed into efficiently-searchable chunks of data. This data (a.k.a. index) is kept in a Lucene specific set of files on a file system. Searching the index requires knowledge of the pre-processing and conversion conventions.

This is how I say there are two sides of search. The first side is the involved process of creating the index. The second side is actually querying against the data set in the index.

It turns out that there is very little overhead requiring a connection between the two sides of searching, other than the index itself. You could, theoretically, build your index using Lucy (the loose C port of the Lucene Java library), and consume that index Java-style in your Java-based Web Application.


This is all I have time for right now. I plan to make several more Lucene-based posts over the next couple of weeks. Let me know if you have any questions on what I’ve posted so far…

24 September 2007

Manage Your Metrics Before They Manage You

Filed under: Java — nelz9999 @ 23:04

"Efficiency"

We are two sprints into a newly-adopted agile process. Somehow, the concept and term of "efficiency" made its way into our sprint planning process, with the idea being that we should pad our sprint-level expectations with a little bit of non-task time to account for day-to-day life within a project team, such as meetings and underlying architectural modifications that can’t be directly associated with a given business use case.

The Metric That Got Away

During the demo (to a large portion of the company) after our second sprint, our Project Manager dropped in a reference to the fact that our "efficiency" rating hit 75%, and that we would be basing our 3rd sprint expectations on the previous sprint’s 75% metric.

The developers had a bit of a fit (behind closed doors). In most of our experiences, "efficiency" ratings applied to iterative sprints are calculated on a 3-sprint (at least) rolling average. This 75% number was completely ignoring the fact that in first sprint, we had only achieved an "efficiency" in the low 60′s. The PM acquiesced and agreed to drop the "efficiency" rating to a 2-sprint rolling average of 70%.

When the PM then informed the CTO that the targeted efficiency rating was being dropped, all hell let loose. Evidently the C-level officers had also grabbed ahold of the 75% "efficiency" score and were bandying that number around the board room.

The Exact Wrong Thing

For me, his "efficiency" rating is not a bad internal metric to have, but only used as a general guideline. What had happened was that upper management got a hold of the numbers and were starting to look at them as a performance measure, i.e. "we need to get your efficiency up", equating efficiency with productivity. (This last sentence is paraphrased from one of Wayne Allen’s blog post concerning a parallel fear with "velocity".)

I have another problem with this "efficiency" term. It implies the other (100-X)% of time is lost to "inefficiency", which is patently false. This other time is also used for fighting the infrastructure debt that a project inevitably collects.

(I wish we could think of other terms rather than efficiency/inefficiency. A lot of agile people refer to "velocity", but I think that is a different concept of comparing actual time to estimated time, which is almost the inverse of our "efficiency" calculation. "Velocity" may have a similar application, but it is technically different from our current approach.)

The Right Things

I’ve spent some of my "inefficiency" time over the past two sprints getting a bunch of Code Quality reports running in our Maven build. Today the PM and I worked together to start publishing a quick dashboard of the metrics coming out of these Code Quality reports.

Now, if people start grabbing onto metrics, they will be the type of metrics that we actually want to drive. "Oh, you’ve got 1000 Checkstyle violations, 125 FindBugs warning, 75 TODO’s, and the Cobertura report says 66% of your code is not covered by tests? Why don’t we spend some time working those numbers down?" Now, when we take the time to refactor or clean up the code, we’ll have the metrics proving that we’re doing something positive, and not just being "inefficient".

(PS… Note how I am inverting the Cobertura numbers to reflect the amount of code that is not covered, a la this post by Andy Glover on The Disco Blog and again here on IBM DeveloperWorks…)

« Newer PostsOlder Posts »

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.