org.glassfish.gmbal.logex.WrapperGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of webservices-rt Show documentation
Show all versions of webservices-rt Show documentation
This module contains the Metro runtime code.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2008-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.glassfish.gmbal.logex;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ResourceBundle;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.glassfish.gmbal.generic.OperationTracer;
/** Given an annotated interface, return a Proxy that implements that interface.
* Interface must be annotated with @ExceptionWrapper( String idPrefix, String loggerName ).
* id prefix defaults to empty, loggerName defaults to the package name of the annotated
* class.
*
* The behavior of the implementation of each method on the interface is determined
* in part by its return type as follows:
*
* - void. Such a method can only log a message.
* - String. Such a method may log a message, and also returns the message.
* - A subclass of Exception. Such a method may log a message, and also returns
* an exception containing the message.
*
*
* Each method may be annotated as follows:
*
*
* - @Message( String value ). This defines the message to be placed in a resource
* bundle (generated at build time by a separate tool). The key to the resource
* bundle is
.. The message is prepended with the
* idPrefix and the id from the @Log annotation (if @Log is present, otherwise nothing
* is prepended to the message). If this annotation is not present, a default message
* is created from the method name and the arguments.
* - @Log( LogLevel level, int id ). The presence of this annotation indicates that
* a log record must be generated, and logger IF the appropriate logger is enabled at
* the given level (note that LogLevel is an enum used for the annotation, each member
* of which returns the java.util.logging.Level from a getLevel() method).
*
*
* In addition, the @Chain annotation may be used on a method parameter (whose type
* must be a subclass of Throwable) of a method that returns an exception
* to indicate that the parameter should be the cause of the returned exception.
* All other method parameters are used as arguments in formatting the message.
*
* @author ken
*/
public class WrapperGenerator {
private WrapperGenerator() {}
// Find the outer index in pannos for which the element array
// contains an annotation of type cls.
private static int findAnnotatedParameter( Annotation[][] pannos,
Class extends Annotation> cls ) {
for (int ctr1=0; ctr1= 0) {
Object[] result = new Object[args.length-1] ;
int rindex = 0 ;
for (int ctr=0; ctr 0) {
try {
rb = ResourceBundle.getBundle( name ) ;
} catch (Exception exc) {
Logger.getLogger( "org.glassfish.gmbal.logex" )
.fine( "Could not load resource bundle") ;
}
}
return rb ;
}
private static String getMessage( ResourceBundle rb,
Logger logger, Method method,
int numParams, String idPrefix, int logId ) {
String msg = getTranslatedMessage( rb, logger, method) ;
if (msg == null) {
final Message message = method.getAnnotation( Message.class ) ;
final StringBuilder sb = new StringBuilder() ;
sb.append( idPrefix ) ;
sb.append( logId ) ;
sb.append( ": " ) ;
if (message == null) {
sb.append( method.getName() ) ;
sb.append( ' ' ) ;
for (int ctr=0; ctr0) {
sb.append( ", " ) ;
}
sb.append( "arg" ) ;
sb.append( ctr ) ;
sb.append( "={" + ctr + "}" ) ;
}
} else {
sb.append( message.value() ) ;
}
msg = sb.toString() ;
}
return msg ;
}
private static void inferCaller( LogRecord lrec ) {
// Private method to infer the caller's class and method names
// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();
StackTraceElement frame = null ;
String wcname = "$Proxy" ; // Is this right? Do we always have Proxy$n here?
String baseName = WrapperGenerator.class.getName() ;
String nestedName = WrapperGenerator.class.getName() + "$1" ;
// The top of the stack should always be a method in the wrapper class,
// or in this base class.
// Search back to the first method not in the wrapper class or this class.
int ix = 0;
while (ix < stack.length) {
frame = stack[ix];
String cname = frame.getClassName();
if (!cname.contains(wcname) && !cname.equals(baseName)
&& !cname.equals(nestedName)) {
break;
}
ix++;
}
// Set the class and method if we are not past the end of the stack
// trace
if (ix < stack.length) {
lrec.setSourceClassName(frame.getClassName());
lrec.setSourceMethodName(frame.getMethodName());
}
}
private static Exception makeException( ReturnType rtype,
boolean forceStackTrace, Class> rclass, Throwable cause, String msg ) {
Exception exc = null ;
try {
if (rtype == ReturnType.NULL) {
if (forceStackTrace) {
exc = new RuntimeException( "StackTrace" ) ;
}
} else if (rtype ==ReturnType.EXCEPTION) {
Constructor cons = rclass.getConstructor(String.class);
exc = (Exception)cons.newInstance(msg);
}
} catch (InstantiationException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
} catch (IllegalAccessException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
} catch (IllegalArgumentException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
} catch (InvocationTargetException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
} catch (NoSuchMethodException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
} catch (SecurityException ex) {
exc = new RuntimeException( "No (String) constructor available in "
+ rclass + ": " + ex ) ;
}
if ((exc != null) && (cause != null)) {
exc.initCause( cause ) ;
}
return exc ;
}
private static String getTranslatedMessage( ResourceBundle rb,
Logger logger, Method method ) {
// Check to see if we should fetch a possibly translated message from
// a ResourceBundle
String result = null ;
if (rb != null) {
try {
String key = logger.getName() + "." + method.getName() ;
result = rb.getString( key ) ;
} catch (Exception exc) {
Logger.getLogger( "org.glassfish.gmbal.logex" )
.fine(
"Could not find translated message in bundle " + rb
+ " for logger " + logger
+ " and method " + method.getName() ) ;
}
}
return result ;
}
private static String handleMessageOnly( ResourceBundle rb,
Method method, Logger logger,
Object[] messageParams ) {
// Just format the message: no exception ID or log level
// This code is adapted from java.util.logging.Formatter.formatMessage
String msg = (String)method.getAnnotation( Message.class ).value() ;
String transMsg = getTranslatedMessage( rb, logger, method ) ;
if (transMsg == null) {
transMsg = msg ;
}
String result ;
if (transMsg.indexOf( "{0" ) >= 0 ) {
result = java.text.MessageFormat.format( transMsg, messageParams ) ;
} else {
result = transMsg ;
}
return result ;
}
private enum ReturnType { EXCEPTION, STRING, NULL } ;
private static ReturnType classifyReturnType( Method method ) {
Class> rtype = method.getReturnType() ;
if (Exception.class.isAssignableFrom(rtype)) {
return ReturnType.EXCEPTION ;
} else if (rtype.equals( String.class)) {
return ReturnType.STRING ;
} else if (rtype.equals( void.class ) ) {
return ReturnType.NULL ;
} else {
throw new RuntimeException( "Method " + method
+ " has an illegal return type" ) ;
}
}
private static LogRecord makeLogRecord( Level level, String key,
Object[] args, boolean forceStackTrace, Logger logger ) {
LogRecord result = new LogRecord( level, key ) ;
if (args != null && args.length > 0) {
result.setParameters( args ) ;
}
result.setLoggerName( logger.getName() ) ;
result.setResourceBundle( logger.getResourceBundle() ) ;
if (forceStackTrace || level != Level.INFO) {
inferCaller( result ) ;
}
return result ;
}
// Note: This is used ONLY to format the message used in the method
// result, not in the actual log handler.
// We define this class simply to re-use the code in formatMessage.
static class ShortFormatter extends Formatter {
@Override
public String format( LogRecord record ) {
StringBuilder sb = new StringBuilder() ;
sb.append(record.getLevel().getLocalizedName());
sb.append(": ");
String message = formatMessage( record ) ;
sb.append(message);
return sb.toString() ;
}
}
private final static ShortFormatter formatter = new ShortFormatter() ;
private static Object handleFullLogging( ResourceBundle rb,
Log log, Method method, Logger logger,
String idPrefix, Object[] messageParams, Throwable cause ) {
final Level level = log.level().getLevel() ;
final ReturnType rtype = classifyReturnType( method ) ;
final boolean forceStackTrace = method.isAnnotationPresent(
StackTrace.class ) ;
final int len = messageParams == null ? 0 : messageParams.length ;
final String msgString = getMessage( rb, logger, method, len, idPrefix,
log.id() ) ;
final LogRecord lrec = makeLogRecord( level, msgString,
messageParams, forceStackTrace, logger ) ;
final String message = formatter.format( lrec ) ;
Exception exc = makeException( rtype, forceStackTrace,
method.getReturnType(), cause, message ) ;
if (exc != null) {
if (forceStackTrace || (level != Level.INFO)) {
lrec.setThrown( exc ) ;
}
}
if (logger.isLoggable(level)) {
final String context = OperationTracer.getAsString() ;
String newMsg = msgString ;
if (context.length() > 0) {
newMsg += "\nCONTEXT:" + context ;
lrec.setMessage( newMsg ) ;
}
logger.log( lrec ) ;
}
switch (rtype) {
case EXCEPTION : return exc ;
case STRING : return message ;
default : return null ;
}
}
private static String getLoggerName( Class> cls ) {
ExceptionWrapper ew = cls.getAnnotation( ExceptionWrapper.class ) ;
String str = ew.loggerName() ;
if (str.length() == 0) {
str = cls.getPackage().getName() ;
}
return str ;
}
public static T makeWrapper( final Class cls ) {
// Must have an interface to use a Proxy.
if (!cls.isInterface()) {
throw new IllegalArgumentException( "Class " + cls +
"is not an interface" ) ;
}
ExceptionWrapper ew = cls.getAnnotation( ExceptionWrapper.class ) ;
final String idPrefix = ew.idPrefix() ;
final String name = getLoggerName( cls ) ;
final ResourceBundle rb = getResourceBundle( ew.resourceBundle() ) ;
InvocationHandler inh = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
final Annotation[][] pannos = method.getParameterAnnotations() ;
final int chainIndex = findAnnotatedParameter( pannos,
Chain.class ) ;
Throwable cause = null ;
final Object[] messageParams = getWithSkip( args, chainIndex ) ;
if (chainIndex >= 0) {
cause = (Throwable)args[chainIndex] ;
}
final Logger logger = Logger.getLogger( name ) ;
final Class> rtype = method.getReturnType() ;
final Log log = method.getAnnotation( Log.class ) ;
if (log == null) {
if (!rtype.equals( String.class ) ) {
throw new IllegalArgumentException(
"No @Log annotation present on "
+ cls.getName() + "." + method.getName() ) ;
}
return handleMessageOnly( rb, method, logger,
messageParams ) ;
} else {
return handleFullLogging( rb, log, method, logger, idPrefix,
messageParams, cause ) ;
}
}
} ;
// Load the Proxy using the same ClassLoader that loaded the interface
ClassLoader loader = cls.getClassLoader() ;
Class[] classes = { cls } ;
return (T)Proxy.newProxyInstance(loader, classes, inh ) ;
}
}