dev@glassfish.java.net

Re: message catalog synchronization

From: Ken Cavanaugh <Ken.Cavanaugh_at_Sun.COM>
Date: Wed, 15 Jul 2009 15:50:47 -0700

Bill Shannon wrote:
> Does anyone have any tools or techniques for keeping message catalogs
> (LocalStrings.properties) synchronized with the code that uses them?
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_glassfish.dev.java.net
> For additional commands, e-mail: dev-help_at_glassfish.dev.java.net
>
>
I have two different approaches, one in CORBA, and the evolution of it
in Gmbal. I'll give a quick sketch
of the approaches here:

For CORBA, the only messages that I need to place into .properties files
are for SystemException
message strings. These are all represented in message catalogs as S
expressions:

("com.sun.corba.se.impl.logging" "IORSystemException" IOR
    (
    (INTERNAL
        (ORT_NOT_INITIALIZED 1 WARNING "ObjectReferenceTemplate is not
initialized")
        (NULL_POA 2 WARNING "Null POA")
        (BAD_MAGIC 3 WARNING "Bad magic number {0} in ObjectKeyTemplate")
        (STRINGIFY_WRITE_ERROR 4 WARNING "Error while stringifying an
object reference"))
    (BAD_OPERATION
        (ADAPTER_ID_NOT_AVAILABLE 1 WARNING "Adapter ID not available")
        (SERVER_ID_NOT_AVAILABLE 2 WARNING "Server ID not available")
        (ORB_ID_NOT_AVAILABLE 3 WARNING "ORB ID not available")
        (OBJECT_ADAPTER_ID_NOT_AVAILABLE 4 WARNING "Object adapter ID
not available"))
    (BAD_PARAM
        (INVALID_TAGGED_PROFILE 2 WARNING "Error in reading IIOP
TaggedProfile")
        (BAD_IIOP_ADDRESS_PORT 3 WARNING "Attempt to create IIOPAdiress
with port {0}, which is out of range"))
    (INV_OBJREF
        (IOR_MUST_HAVE_IIOP_PROFILE 1 WARNING "IOR must have at least
one IIOP profile"))))

These get turned into Java classes so that each exception is constructed
and logged from a Java method
(here's an example for the last message above):

    public INV_OBJREF iorMustHaveIiopProfile( CompletionStatus cs, Throwable t ) {

        INV_OBJREF exc = new INV_OBJREF( IOR_MUST_HAVE_IIOP_PROFILE, cs ) ;

        if (t != null)

            exc.initCause( t ) ;

        

        if (getLogger().isLoggable( Level.WARNING )) {

            Object[] parameters = null ;

            doLog( Level.WARNING, "IOR.iorMustHaveIiopProfile",

                parameters, IORSystemException.class, exc ) ;

        }

        

        return exc ;

    }

   
This also generates the entries in resource files for the messages:

IOR.iorMustHaveIiopProfile="IOP00511201: (INV_OBJREF) IOR must have at
least one IIOP profile{0}"

and the CORBA build gathers these entries into the .properties file.
Note that this also manages the assignment
of exception message identifiers automatically.

==================

Gmbal uses a different, more flexible system based on annotated
interfaces. To use it, I write an interface with
annotations:

@ExceptionWrapper( idPrefix="GMBAL_TYPELIB" )
public interface Exceptions {
    static final Exceptions self = WrapperGenerator.makeWrapper(
        Exceptions.class ) ;

    // Allow 100 exceptions per class
    static final int EXCEPTIONS_PER_CLASS = 100 ;

    // TypeEvaluator
    static final int TYPE_EVALUATOR_START = 1 ;

    @Message( "Internal error in TypeEvaluator" )
    @Log( id=TYPE_EVALUATOR_START + 0 )
    IllegalStateException internalTypeEvaluatorError( @Chain Exception exc ) ;

    @Message( "evaluateType should not be called with a Method ({0})" )
    @Log( id=TYPE_EVALUATOR_START + 1 )
    IllegalArgumentException evaluateTypeCalledWithMethod( Object type ) ;

    @Message( "evaluateType should not be called with an unknown type ({0})" )
    @Log( id=TYPE_EVALUATOR_START + 2 )
    IllegalArgumentException evaluateTypeCalledWithUnknownType( Object type ) ;

}


To use it, simply call (for example)

throw Exceptions.self.internalTypeEvaluatorError( exc )

and this will construct the message, place the message in the exception,
generate a log (if required), and
return the exception for use in the program. Note that the return type
can also be void (just log) or
a String (return an I18N message). The @Log annotation is optional, in
case no log generation is required.
You can also annotate a parameter with @Chain to chain that argument
into the generated exception.
Of course, everything is fully typechecked here, other than (as usual)
the {n} fields in the messages themselves.
Each generated log has an associated ID, generated from the idPrefix in
the @ExceptionWrapper
annotation and the id from the @Log annotation. So the ID for
internalTypeEvaluatorError is
"GMBAL_TYPELIB101".

This works by having WrapperGenerator generate a proxy for the
interface, which looks up the required
information for each method from the annotations on the interface
method. No actual implementation of
the exception interface is every written by hand. When logs are
generated, they either go to a logger
named in the LogWrapper annotation, or by default the logger named by
the package of the Exceptions
class. Typically I use an Exceptions interface in each implementation
package and just default the logger
name to the package name.

Adding new methods is really simple in NetBeans: just write the method
name you want, then tell NB to fix
the resulting error by generating the method in the Exceptions
interface. Then add the appropriate annotations
to the new method in the interface.

The next thing I need to add here is resource file generation, but
that's pretty simple: just scan the classes
for annotated method, and generate the messages. I'll be doing that
soon, but right now I'm buried in CORBA
issues.

Ken.