This is an example usage of Maven's resource filtering facility. While Paolo Predonzani's excellent article got me pointed in the right direction, it didn't quite work out for me. Here is what I did to get what I needed.
Monday, October 25, 2010
Specify Database Connection via Maven Resource Filtering
Friday, October 1, 2010
Basic GWT-RPC Application
After stepping through this introduction to a simple GWT-RPC example, I had to experiment a bit to (1) get it running and (2) get some clarity around the naming hooks. What I'll present here is my own variation on that article's code examples, and the understanding I gained around what to keep straight around the names of the various classes and files.
Tip on Specifying GWT-RPC Remote Service Relative Path
Robert Hanson's excellent introduction to a GWT-RPC application got me started, but I bumped into this:
com.google.gwt.user.client.rpc.StatusCodeException:
HTTP ERROR: 404
NOT_FOUND
RequestURI=/relative-path
Thursday, August 26, 2010
iBatis Mapping for One-to-Many Relationship
Perhaps you had a problem, like I did, understanding the iBatis writeup on loading complex collection properties, i.e. populating a list-based field in a domain object where that list is the many-side of a one-to-many relationship. I'll admit it took me a few iterations to totally grasp what was being said, so for my future reference and yours, I'll write down my notes here.
Monday, August 23, 2010
Monitor Changes to a File
There are undoubtedly many options out there for file-system change monitors - i.e. a component that will notify your Java object when a given file or directory has changed, among many others I'm sure: jnotify and jpathwatch, the latter of which is based on upcoming Java 7 NIO enhancements.
I gave jpathwatch a try recently, at a time when I was under a tight deadline and didn't want to reinvent a wheel. This presents a perfectly fine API that worked out quite well and quite quickly for me in my Windows environment, but alas when I deployed to Linux, I bumped into an unsatisfied link error. The problem was around libc.so.6 and GLIBC_2.4, and I gave it a reasonable first effort to try quickly finding the resolution - assuming I'd deployed incorrectly, or my Linux box was out of date, etc. It was neither of these - OK, in fairness, it could be an out-of-date Linux box, but my experiment was to just try it out on our customers' target system - and the same problem occurred. So out-of-date becomes a moot point.
As I mentioned, I was under a tight deadline, so I began some quick prototyping to see if I could reinvent something but without relying on native libraries (as jpathwatch did). That would give me the added advantage of a smaller runtime footprint, which was another customer requirement. Since we are really talking about an Observer pattern, here's how I started:
public interface FileChangeListener {
void fileModified(String file);
void fileDeleted(String file);
void fileCreated(String file);
}That specifies the observer. Here's a simple monitor interface:
public interface AbstractFileChangeMonitor {
void watch(FileChangeListener listener, final String filename);
}This one is a bit limited, since it supports just one listener (observer) for one file. But my goal is not (yet) to provide a full-featured framework - I just need to knock out the problem at hand without any gold-plating. I'm a firm believer in doing the least I have to for a given problem - first make it work, then make it fast, then extend it, ... etc., but only if subsequent steps are called for. In either event, here's an implementation of the monitor - this one polls the file in question to detect changes, running in a thread so the client process isn't blocked:
public class PollingFileChangeMonitor implements AbstractFileChangeMonitor
{
private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
private FileChangeListener listener;
private boolean done;
private Thread watch;
private int pollingInterval;
public PollingFileChangeMonitor(int interval) {
pollingInterval = interval;
}
public void watch(FileChangeListener theListener, final String filename)
{
if (watch == null) {
listener = theListener;
watch = new Thread() {
public void run() {
try {
init(filename);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
};
watch.start();
}
}
private void init(String filename) throws Exception
{
boolean exists = false;
long modTime = -1;
File file = new File(filename);
if (file.exists())
{
exists = true;
modTime = file.lastModified();
logger.info("====> File '" + filename + "' exists; change monitor is running...");
} else
{
logger.info("====> File '" + filename + "' does NOT exist; change monitor is running...");
}
while (!done) {
try {
watch.sleep(pollingInterval);
} catch (InterruptedException e) {
// ignore for now
}
if (!exists && file.exists()) {
exists = true;
logger.info("====> File '" + filename + "' has been created; notify listener...");
listener.fileCreated(filename);
} else if (exists && !file.exists()) {
exists = false;
logger.info("====> File '" + filename + "' has been deleted; notify listener...");
listener.fileDeleted(filename);
} else if (exists && file.exists()) {
long timestamp = file.lastModified();
if (timestamp > modTime) {
modTime = timestamp;
logger.info("====> File '" + filename + "' has been modified; notify listener...");
listener.fileModified(filename);
}
}
}
}
}
What the listener does when notified is not really important in the context of this post; it can be anything. While I've to a certain extent "reinvented" something here, I've gotten away from reliance on native code and the potential deployment headaches around that, I've reduced my runtime footprint, and for that matter I've solved the problem with about the same amount of code I needed for the boiler-plate suggested by jpathwatch.
Handling Partially Failed Transactions
What's I've described of course is not strictly "transactional" - i.e. the all-or-nothing constraint is not in play, but for the application's needs, this is perfectly acceptable. And since what I describe involves removing the batch execution, the overall set of operations will take longer to execute - but again, this is acceptable for the application's goals. You won't always find these trade-offs to be acceptable, but when you do, I'll describe one mechanism for making it work - my strategy includes the following:
1 - Execute the collection of operations in a single transaction, albeit without batch
2 - Catch and deal with exceptions around operations that fail
3 - Provide return values that includes lists of successes and failures
Here are some snippets that provides these things. I start with an enumeration of the operation types I care about:
private enum OP {add, update, delete}
Next I provide a result object that can be examined by the client to identify successes and failures. I use generics for type-safety - the Key and Value types represent your application-specific database key and object classes:public class OperationResult {
private Map<Key, Value> successes = new HashMap();
private Map<Key, Exception> failures = new HashMap();
public OperationResult(Map<Key, Value> succeeded, Map<Key, Value> failed) {
successes.putAll(succeeded != null? succeeded : new HashMap());
failures.putAll(failed != null ? failed : new HashMap());
}
public Map<Key, Value> getSuccesses() {
return successes;
}
public Map<Key, Exception> getFailures() {
return failures;
}
}
Finally I provide the workhorse method that executes all operations, capturing exception details in the face of any failures, and proceeding in either event so it can commit as many successes as possible. Due to the way this API is set up, clients can execute only one operation type at a time: private OperationResult execute(Map<Key, Value> map, String query, OP operation) {
Map<Key, Value> successes = new HashMap();
Map<Key, Exception> failures = new HashMap();
try {
try {
sqlMapClient.startTransaction();
} catch (SQLException e) {
throw new IllegalStateException("None of the operations succeeded - txn could not be started",
e);
}
for (Map.Entry<Key, Value> entry : map.entrySet()) {
try {
switch (operation) {
case add:
return sqlMapClient.add(query, entry.getValue()); break;
case update:
return sqlMapClient.update(query, entry.getValue()); break;
case delete:
return sqlMapClient.delete(query, entry.getKey()); break;
}
successes.put(entry.getKey(), entry.getValue());
} catch (Exception e) {
failures.put(entry.getKey(), e);
}
}
try {
sqlMapClient.commitTransaction();
} catch (SQLException e) {
throw new IllegalStateException("None of the operations succeeded - txn could not be committed",
e);
}
} finally {
try {
sqlMapClient.endTransaction();
} catch (SQLException e) {
throw new IllegalStateException("Problem ending the txn - you should check whether the operations succeeded or not",
e);
}
return new OperationResult(successes, failures);
}
}
I'm using iBatis 2.x, as you might be able to guess from the SQL Map Client reference; it is assumed that the client passes in the correct query ID for the given operation.One guideline from my Computer Science education that always stuck with me is "make it right first, then make it fast". The solution provided here is, as noted, not going to be as fast as batch execution - but it does meet the requirements correctly. If there is still a need to make it "faster", we could consider using an asynchronous API so that the response time from the user's point of view is improved. This type of approach is in fact used in many enterprise settings, leveraging so called BASE behavior instead of the traditional ACID approach.
Tuesday, August 10, 2010
Open a PDF Resource in Browser Tab
Instead, I backed off to a more straightforward link approach, i.e. use of the outputLink control, as follows:
<ice:form>
<ice:outputLink value="doc/some-PDF-file.pdf"
target="_blank">
<ice:outputText value="Open PDF"/>
</ice:outputLink>
</ice:form>
The plain vanilla HTML is dirt-simple:<a target="_blank" href="doc/some-PDF-file.pdf">Open PDF</a>This assumes the "doc" directory location is under your webapp root context.
