Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Friday, February 06, 2009

If It Ain't Broken, Fix It!

For already more than three years I'm the Java developer of some Oracle-internal sales application. Up to that point the application had been maintained by several people who obviously had different development styles.

I'm more a kind of guy that practices principles like coding by intention, responsibility driven design, and refactoring with the main goal to achieve a simple design that is easy to understand and therefore better to maintain. Those principles had not been on the top list of my predecessors. The result being that, to implement a change request or bug fix, you almost always had to debug the code and do extensive code reviews before you even knew where to start. I'm not joking when I say that the same kind of functionality was never implemented twice the same way. There was hardly any pattern to discover, except anti-patterns and a lot of bad practices:
  • lengthly methods, often of 100+ lines
  • names for classes, attributes and methods that do not resemble what it does
  • classes and methods having too many responsibilities
  • no proper layering and separation of concerns
  • and so on, and so forth
At a specific point in time I had (and took) the opportunity to start refactoring key parts of the application, and never stopped since. I did refactor for the sake of refactoring, but restricted myself to the code that I needed to touch anyway. While doing so I tried to apply best practices and extrapolated these to other parts of the application to keep the solution consistent.

The result being that after less than half a year, newcomers needed only half the time to understand the application good enough to do their job. And those parts that had been refactored properly, required only half the time to change. Cross my heart: I dare to state that any investment in this making the application code base simpler, has paid back itself several times already!

Many developers will recognize this situation, and may want to do something about it, but run against a brick wall called "project manager", who is just a plain idiot, not understanding the value of good coding practices, but only focusing on getting the job done in as less time as possible, right? Wrong! The key responsibility stake of the project manager is exactly that: delivering on time and within budget, and most of them are doing that job pretty well.

Unlike what you may think the project manager is not the key stakeholder, but the customer is. So rather than trying to convince the project manager, you must make the customer aware of how a simple design:
  • will reduce their maintenance costs (which still is about 80% of the original development time) and perhaps even more important,
  • will make their IT better aligned with the business, because it will be quicker to change a well designed system than spaghetti.
Try to convince the customer that to achieve this, a simple design is an absolute must, and therefore from time to time you need to clean the kitchen because otherwise at some point the kitchen will get so dirty that almost any meal you make in there, will be a serious health hazard. That cleaning the kitchen is what refactoring is all about.

Even when the project plan is already finished and the budget already fixed, it is likely that there still are plenty of opportunities to help the project manager with reducing the remaining development time, if only you take the time to find them. The biggest problem for applying best development practices is probably you not spotting these opportunities or not being able to communicate them well.

Friday, December 05, 2008

Dynamically Switching Rules Dictionary Versions

Suppose that you are using Oracle Business Rules in a Java application. And suppose you want to be able to change and test your rule set without bothering other people. Wouldn't it be nice to be able to work in a copy of a dictionary and activate that only for your user session? This can be achieved as follows.

I assume one dictionary of which there can be multiple versions. Of course the same principle can be applied to multiple dictionaries.

First implement some mechanism for storing the default version of the dictionary, and that will be retrieved every time before calling the rules repository. I use a properties file for that, because you can change a properties file dynamically. Then have some page from which the user can pick an available version from a list. Once a version has been chosen that differs from the default, that will be the version used for that user session.

Now how to get the versions of a dictionary from a repository? That can be done using the following Java code snippet, which is based on a WebDAV repository:


// repositoryURL is a String containing the URL to your repository
// rulesDictionaryName is a String containing the name of the dictionary

RepositoryType repositoryType =
RepositoryManager.getRegisteredRepositoryType("oracle.rules.sdk.store.webdav");
RuleRepository rulesRepository =
RepositoryManager.createRuleRepositoryInstance(repositoryType);
RepositoryContext repositoryContext = new RepositoryContext();
repositoryContext.setProperty("oracle.rules.sdk.store.webdav.url", repositoryURL);
rulesRepository.init(repositoryContext);

dictionaryVersions = rulesRepository.getMarkerNames(rulesDictionaryName);
for (int i=0; i < dictionaryVersions.size(); i++)
{
dictionaryVersions.set(i, ((String)dictionaryVersions.get(i)));
}


When the test was satisfactory, the chosen version can be stored in the properties file, making the default for every new user session.

Thursday, August 23, 2007

White Paper Business Rules in ADF Business Components Revamped!

Finally, the white paper Business Rules in ADF Business Components has been revamped!

As usual it took much more work than anticipated, especially as I made the 'mistake' to ask two subject experts (Steve Muench and Sandra Muller from the JHeadstart Team) to review the first draft. If I had not done that, the paper could have been published a month ago! But no, I could not help myself, I had to be thorough, I had to be me, and of course they provided me with insights that have had a significant impact on its contents. But all for the better, otherwise some of it already would have been obsolete the minute it hit the street.

"So what?", those of you who have not seen it before, might ask yourself. Well let me try to explain without copying too much of what already is explained in the paper itself.

First of all it is our (Oracle Consulting's) experience that analyzing, and implementing business rules takes a significant part of the total effort of creating the average application. Although being a very productive persistence framework as it is, this still holds true for ADF Business Components (which is part of Oracle's ADF), or ADF BC for short. Moreover, despite all our efforts trying to create the ultimate application, most resources still are being put in maintenance rather than in creating the original. So there is a lot to be gained for business rules in this regard, and that is what the white paper intends to address.

Does the paper present 'the right way' for ADF BC? Well, perhaps there is a better way, I don't know. But it is a good way, as it is consistent and makes use of the best that ADF BC has to offer. And because it is consistent (and well documented) maintenance also becomes easier, as when the developers that created the original application used it (and then ran away like lightning to build the next hip thing), they will not have left behind a maintenance nightmare. At least not what the business rules are concerned.

"So, what's new?", those of you who have sleep with the previous version of the paper under their pillow, might ask yourself.

Let me give you this short list and for the rest of it refer you to the paper itself:
  • Capturing business rules using UML class model
    Why? Because plenty of people still want to capture requirements (including business rules) before there are tables and entity objects and therefore cannot make use of an ADF Business Components diagram (see also the article UML Rules! I posted some time ago).
  • Setting up framework extension classes
    Doing so makes introducing generic functionality later on so much easier and therefore is strongly advised in general.
  • Deprecated custom authorization in ADF BC
    This in particular concerns the horizontal authorization rules (restricting the set of rows of an entity you are allowed to insert, update or delete). The reason to do so is that you probably rather use Virtual Private Database for that.
  • 'Other Attribute Rules' dropped
    As I discussed in the article How to Pimp ADF BC Exception Handling, there are no compelling arguments anymore for not using built-in validators or method validators, making that the category Other Attributes Rules could be dropped, and we now suggest implementing them using these validators.
  • New built-in attribute validators
    ADF BC provides the Length, and Regular Expression validators for attributes.
  • 'Other Instance Rules' dropped, 'Delete Rules' added
    The category Other Instance Rules is dropped for the same reason the Other Attribute Rules category has been dropped. This with the exception of rules that make use of the delete() method, which rules are now in the new category 'Delete Rules'.
  • Registered Rules
    Using Registered Rules you can create generic method validators you can use for multiple entities. An example is a reoccurring validation of an end date that must be on or after a begin date.
  • UniqueKey Validator
    Compared with just checking 'Primary Key' for all primary key attributes, this one and only entity-level built-in validator helps to make validation of the primary key predictable and consistent, and also supports providing a user-friendlier error message.
  • 'Change History' and 'Cascade Delete' added
    These two categories are subcategories of Change Event Rules with DML. When using JAAS/JAZN ADF BC offers built-in support for recording date/user created/updated information, which has been documented in the Change History category. As a result of introducing this category, the 'Derivation' category has been renamed to 'Other Derivation'. Furthermore, ADF BC also supports Cascade Delete by defining an association as being a 'composition'.
  • Sending an email using JavaMail API
    The previous version of the paper here and there referred to some 'clex' library which was part of the now long-gone Oracle9iAS MVC Framework for J2EE. If you remember that you really are an Oracle veteran! Anyway, the classical example of a 'Change Event Rule without DML' is sending an email when somebody changes something in the database, which example now is based on the JavaMail API
  • Message handling
    Especially this subject has been revised significantly. Among other things you can specify error message together with the validator, which message will end up in an entity-specific message bundle. By creating custom exceptions you also can use one single message bundle, as I explained in the article How to Pimp ADF BC Exception Handling.
Do you need more in order to get you to download the new white paper? I can hardly imagine.

Friday, August 03, 2007

How to Pimp ADF BC Exception Handling

When you have been doing things in a particular way for a long time, you sometimes find that in the mean time your way has become the slow way. Not necessarily the wrong way as it works (otherwise you would not have kept doing it all the time, would you?), but you're just not fashionable anymore. Like wearing tight pants in the 60's or wide pants in the 80's. Or using vi instead of a state-of-the-art IDE like JDeveloper or Eclipse for that matter (oh yes, I dare!).

I recently discovered that I became unfashionable by using the setAttributeXXX() and validateEntity() method for implementing business rules in ADF BC instead of using Validators. Not that I didn't know of Validators, I just thought that they would not give me proper control over exception and message handling. Because one of the things I would like to have, is one single message bundle in which I could use a consistent coding of my messages, like APP-00001, APP-00002, etc. Even more important, the other thing I would like to have is that I can translate all messages to Kalaallisut the minute I need to make my application available for the Inuit people of Greenland.

As you might know, up till JDeveloper 10.1.3 ADF BC will create an entity-specific message bundle to store the messages you provide with Validators. So with many entity objects big chance you end up with many message bundles, making that keeping error codes consistent becomes a nightmare. And what about all the separate files you need to translate! You might find yourself no longer to be able to tell the messages for the bundles.

But as Steve Muench was very persistent in trying to convince me using Validators I finally gave in and tried finding a way to tackle my problem, and succeeded! No worries, don't expect rocket science from me. I hate complex or obscure code, as building maintainable Information Systems already is hard enough as it is, and therefore always try to practice Simple Design.

I assume that you have created a layer of framework extension classes as described in section 2.5 of the ADF Developers Guide and that your entity base class is called MyAppEntityImpl. If you have not yet created such a layer, do that first and come back after you finished. Chop chop!

Basically the steps are as follows:
  • Create extension classes that extend the exceptions that ADF BC will throw when validation fails
  • Override the constructor of the super class and pass in the message bundle in the call to super()
  • Override the setAttributeInternal() and validateEntity() methods in the MyAppEntityImpl.java entity base class and make they thrown your exceptions instead of the default ones.
Does that sound simple or what? No? OK, let me show you how I did it.

Attribute-level Validators will throw the AttrValException. So what I did was create a MyAppAttrValException as follows:

package myapp.model.exception;

import oracle.jbo.AttrValException;
import myapp.model.ResourceBundle;

public class AttrValException extends AttrValException
{
public MyAppAttrValException(String errorCode, Object[] params)
{
super(ResourceBundle.class, errorCode, params);
}

/**
* When the message contains a semicolon, return the message
* starting with the position after the semicolon and limit the
* text to the message text from the resource bundle.
*
* @return the stripped message
*/
public String getMessage()
{
String message = super.getMessage();
// strip off product code and error code
int semiColon = message.indexOf(":");
if (semiColon > 0)
{
message = message.substring(semiColon + 2);
}
return message;
}

And this is how I override the setAttributeInternal in the MyAppEntityImpl:

/**
* Overrides the setAttributeInternal of the superclass in order to
* pass in a custom message bundle to MyAppAttrValException subclass
* of the AttrValException. To be able to uniquely identify the
* entries in the message bundle, the error code is extended with the
* fully qualified class name of the Impl.
*/
protected void setAttributeInternal(int index, Object val)
{
try
{
super.setAttributeInternal(index, val);
}
catch (AttrValException e)
{
String errorCode = new StringBuffer(getClass().getName())
.append(".")
.append(e.getErrorCode())
.toString();
throw new MyAppAttrValException(errorCode, e.getErrorParameters());
}
}

In a similar way you can handle entity-instance-level Validators and Method Validator by extending the ValidationException and overriding the validateEntity() method to throw your MyAppValidationException.

As said, the message you provided when creating build-in Validators and Method Validators end up in an entity-specific message bundle. I've been told that this is going to change in JDeveloper 11g, but currently there is no stopping ADF BC from doing that.

So, at a convenient point in time (for example when most of your Validators have been implemented and tested), what you need to do is copying the error messages from the entity-specific message bundles to your custom message bundle.

To prevent duplicates in the keys of the custom message bundle, you should extend the key of each message with the fully qualified class name of the Impl.java file of the entity object where it's coming from. Otherwise duplicates might occur whenever you have two different entity objects, both with an attribute with the same name and a build-in Validator specified for them. The overriden setAttributeInternal() method assumes you did.

The entries in the message bundle would look
then similar to this:

{ "myapp.model.adfbc.businessobject.EmployeeImpl.Salary_Rule_0",
"APP-00001 Employee's Salary may not be below zero" },
...


Well that wasn't rocket science, was it?

Wednesday, July 04, 2007

How to Prevent Your Rule Gets Fired in ADF BC?

When using the Oracle ADF (Application Development Framework), implementing data-related business rules in the persistance layer is a good practice. In ADF this business layer is called ADF Business Components (aka BC4J, or Business Components for Java). This article will go briefly into this subject, just enough to let you get an appetite for the upcoming revised white paper Business Rules in ADF BC. So don't eat too much of this apetizer, to leave room for the main course to come!

Implemening business rules in ADF Business Components means implementing them in so-called entity objects. For an application that uses a relational database, to a certain extend you can compare an entity object with an EJB entity bean, as like an entity bean the purpose of the entity object is to manage storage of data in a specific table. Unlike EJB entity beans, ADF entity objects provide hooks to implement business rules that go way beyond what you can do with EJB entity beans.

I won't go into detail about these hooks. When you're interested, enough documentation about the subject can be found on the internet (to begin with the Steve Muench's web log) or read the white paper! I will let you know when it is available and where to find it.

Where I do want to go into detail is one aspect, being how to prevent that rules get fired unneccessarily, for example for reasons of performance. I assume some basic knowledge of ADF Business Components, so if you don't have that, this is where you might want to stop reading.

There are the following typical options, provided as methods on any EntityImpl:
  • The isAttributeChanged() method can be used to check if the value of an attribute has actually changed, before firing a rule that only makes sense when this is the case.
  • Furthermore there is the getEntityState() method that can be used to check the status of an entity object in the current transaction, which can either be new, changed since it has been queried, or deleted. You can use this method to make that a rule that only gets fired, for example when the entity object is new.
  • There is also the getPostState() that does a similiar thing as getEntityState() but which takes into consideration whether the change has been posted to the database.
Sometimes knowing whether or not something has changed does not suffice, for example because you need to be compare the old value of an attribute with the new one. That typically happens in case of status attributes with restrictions on the state changes. Normally you would be able to do so using the getPostedAttribute() method, that will return the original value of an attribute as read from or posted to the database. However, that won't work when you are using the beforeCommit() method to fire your rule, as at that time the change already has been posted, so the value getPostedAttribute() returns will not differ from what the getter will return.

"So what", you might think, "when do I ever want to use the beforeCommit()?". Well, you have to as soon as you are dealing with a rule that concerns two or more entity objects that could trigger the rule, as in that case the beforeCommit() is the only hook of which you can be sure that all changes made are reflected by the entity objects involved.

Suppose you have a Project with ProjectAssignments and you want to make sure that the begin and end date of the ProjectAssignments fall within the begin and end date of the Project. A classical example, I dare say. Now the events that could fire this rule are the creation of a ProjectAssignment, the update of the Project its start or end date or the update of the ProjectAssignment its start or end date.

Regarding the creation of the ProjectAssignment, that you can verify by using the getEntityState() which would return STATUS_NEW in that case. Regarding the change of any of the dates, you can check for STATUS_MODIFIED, but that also returns true when any of the other attributes have been changed.

Now suppose that, as there can be multiple ProjectAssignments for one Project, you only want to validate this rule when one of those dates actually did change. The only way to know is by comparing the old values with the new one. As explained before, as the hook you are using will be the beforeCommit() method, the getPostedAttribute() also will return the new value as that time the changes already have been posted to the database.

Bugger, what now? Well, I wouldn't have raised the question unless I would have some solution to it, would I? The solution involves a bit yet pretty straightforward coding. I will only show how to solve this for the Project, for the ProjectAssignment the problem can be solved likewise. The whole idea behind the work-around is that you will use instance variables to store the old values so that they are still available in the beforeCommit().

Open the ProjectImpl.java and add the following private instance variables:

private Date startDateOldValue;
private Date endDateOldValue;

In general you can use a convention to call this specific type of custom instance variables [attribute name]OldValue, to reflect their purpose clearly. Now you need to make that these variables are initialized at the proper moment. This will not be the validateEntity(), as that can fire more than once with unpredictable results. No, it should be the in the doDML() of the ProjectImpl, as follows:

protected void doDML(int operation, TransactionEvent e)
{
  // store old values to be able to compare them with
  // new ones in beforeCommit()
  startDateOldValue = (Date)getPostedAttribute(STARTDATE);
  endDateOldValue = (Date)getPostedAttribute(ENDDATE);

  super.doDML(operation, e);
}

Now in the beforeCommit() you can compare the startDateOldValue with what will be returned by getStartDate(), etc. Although it might not look like it at first sight, this might be the most trickiest part as you need to deal with null values as well. In JHeadstart the following convenience method has been created to tackle this, in the oracle.jheadstart.model.adfbc.AdfbcUtils class:

public static boolean valuesAreDifferent(Object firstValue
              , Object secondValue)
{
  boolean returnValue = false;
  if ((firstValue == null) || (secondValue == null))
  {
    if (( (firstValue == null) && !(secondValue == null))
        ||
        (!(firstValue == null) && (secondValue == null)))
    {
      returnValue = true;
    }
  }
  else
  {
    if (!(firstValue.equals(secondValue)))
    {
      returnValue = true;
    }
  }
  return returnValue;
}

This method is being used in the beforeCommit() of the ProjectImpl, as follows:

public void beforeCommit(TransactionEvent p0)
{
  if ( getEntityState() == STATUS_MODIFIED &&
       ( AdfbcUtils.valuesAreDifferent(startDateOldValue,
              getStartDate()) ||
         AdfbcUtils.valuesAreDifferent(endDateOldValue,
              getEndDate())
       )
    )
  {
    brProjStartEndDate();
  }
  super.beforeCommit(p0);
}

Finally, the actual rule has been implemented as the brProjStartEndDate() method on the ProjectImpl as follows:

public void brProjStartEndDate()
{
  RowIterator projAssignSet = getProjectAssignments();
  ProjectAssignmentImpl projAssign;
  while (projAssignSet.hasNext())
  {
    projAssign =
      (ProjectAssignmentImpl)projAssignSet.next();
    if (((getEndDate() == null) ||
         (projAssign.getStartDate().
              compareTo(getEndDate()) <= 0)
        ) &&
         (projAssign.getStartDate().
              compareTo(getStartDate()) >= 0)
       )
    {
      // rule is true
    }
    else
    {
      throw new JboException("Project start date must " +
        "be before start date of any Project Assignment");
    }
  }
}

Well that wasn't to difficult, was it?

Thursday, June 21, 2007

Did You Found Your Balance With Java?

When I was young I practised judo for a couple of years. Got myself a brown belt and nothing after that, as I wasn't the competition kind of guy. I could have managed getting the black belt on technique alone (by doing so-called kata's), but that would require a lot of practise and time, which I did not had. One of the reasons being that at the same time I also practiced jujutsu (got myself a green belt for that).

When I went to the university I visited a different dojo, again only for a couple of times because of lack of time, but long enough to understand what in the previous dojo the sensei failed to teach me: proper balance. Imagine that during a randori (that is sparring) you jump around too much, giving the opponent many opportunities to show you every corner of the dojo. Well, that was me.

I have not been doing martial arts for many years now, so I was way beyond any frustration about this. That was until on the recent J-Spring of the NL-JUG (spring conference of Dutch Java User Group) when I learned about the JavaBlackBelt community. As is described on the home page of their web site: "JavaBlackBelt is a community for Java and related technologies certifications. Everybody is welcome to take existing exams and build new ones." I couldn't help it, I had to check it out. Am I still jumping around, or have I found my balance with Java?

Well, I expect it will be a long way before I know, as again I don't have much time. But I managed to get at least a yellow belt! So for now I find some comfort knowing that at least I'm beyond the level of Java-newbie.