All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.connectors.deployment.annotation.handlers.ConfigPropertyHandler Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2012 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 packager/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.
 */

package com.sun.enterprise.connectors.deployment.annotation.handlers;

import com.sun.enterprise.deployment.annotation.context.RarBundleContext;
import com.sun.enterprise.deployment.annotation.handlers.*;
import com.sun.enterprise.deployment.*;
import com.sun.enterprise.util.LocalStringManagerImpl;

import jakarta.resource.spi.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.IllegalStateException;
import java.util.Locale;
import java.util.Set;
import java.util.List;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.apf.impl.HandlerProcessingResultImpl;
import org.glassfish.apf.impl.AnnotationUtils;
import org.glassfish.apf.*;
import org.jvnet.hk2.annotations.Service;

/**
 * @author Jagadish Ramu
 */
@Service
@AnnotationHandlerFor(ConfigProperty.class)
public class ConfigPropertyHandler extends AbstractHandler {

    protected final static LocalStringManagerImpl localStrings =
            new LocalStringManagerImpl(ConfigPropertyHandler.class);

    protected final static Logger logger = AnnotationUtils.getLogger();

    private static final String SUCCESS = "success";

    public HandlerProcessingResult processAnnotation(AnnotationInfo element) throws AnnotationProcessorException {
        AnnotatedElementHandler aeHandler = element.getProcessingContext().getHandler();
        ConfigProperty configProperty = (ConfigProperty) element.getAnnotation();

        if (aeHandler instanceof RarBundleContext) {

            RarBundleContext rbc = (RarBundleContext) aeHandler;
            ConnectorDescriptor desc = rbc.getDescriptor();
            handleConfigPropertyAnnotation(configProperty, element, desc);
        } else {
            return getFailureResult(element, "not a rar bundle context", true);
        }
        return getDefaultProcessedResult();
    }

    public HandlerProcessingResult handleConfigPropertyAnnotation(
            ConfigProperty configProperty, AnnotationInfo element, ConnectorDescriptor desc){
                    String defaultValue = configProperty.defaultValue();
            String[] description = configProperty.description();
            boolean ignore = configProperty.ignore();
            boolean supportsDynamicUpdates = configProperty.supportsDynamicUpdates();
            boolean confidential = configProperty.confidential();

            Class type ;

            if (element.getElementType().equals(ElementType.METHOD)) {
                Method m = (Method) element.getAnnotatedElement();
                String result = validateMethod(m, configProperty);

                if(!result.equals(SUCCESS)){
                    return getFailureResult(element, result, true);
                }

                type = getType(configProperty, m.getParameterTypes()[0]);
                //XXX: Siva: For now use the first provided description
                String firstDesc = "";
                if (description.length > 0) firstDesc = description[0];
                ConnectorConfigProperty ep = getConfigProperty(defaultValue, firstDesc, ignore,
                        supportsDynamicUpdates, confidential, type, m.getName().substring(3));

                handleConfigPropertyAnnotation(element, desc, ep, m.getDeclaringClass());

            } else if (element.getElementType().equals(ElementType.FIELD)) {
                Field f = (Field) element.getAnnotatedElement();
                String result = validateField(f, configProperty);


                if(!result.equals(SUCCESS)){
                    return getFailureResult(element, result, true);
                }

                type = getType(configProperty, f.getType());

                if(defaultValue == null || defaultValue.equals("")){
                    defaultValue = deriveDefaultValueOfField(f);
                }
                //XXX: Siva: For now use the first provided description
                String firstDesc = "";
                if (description.length > 0) firstDesc = description[0];
                ConnectorConfigProperty ep = getConfigProperty(defaultValue,firstDesc, ignore,
                        supportsDynamicUpdates, confidential,  type, f.getName());

                handleConfigPropertyAnnotation(element, desc, ep, f.getDeclaringClass());
            }
        return getDefaultProcessedResult();
    }

    private static Class getWrapperClass(String primitive){
        if(primitive.equalsIgnoreCase("int")){
            return java.lang.Integer.class;
        }else if(primitive.equalsIgnoreCase("long")){
            return java.lang.Long.class;
        }else if(primitive.equalsIgnoreCase("short")){
            return java.lang.Short.class;
        }else if(primitive.equalsIgnoreCase("char")){
            return Character.class;
        }else if(primitive.equalsIgnoreCase("byte")){
            return java.lang.Byte.class;
        }else if(primitive.equalsIgnoreCase("boolean")){
            return java.lang.Boolean.class;
        }else if(primitive.equalsIgnoreCase("float")){
            return java.lang.Float.class;
        }else if(primitive.equalsIgnoreCase("double")){
            return java.lang.Double.class;
        }else{
            throw new IllegalArgumentException("Could not determine Wrapper class for primitive type ["+primitive+"]");
        }
    }

    private static String deriveDefaultValueOfField(Field f){
        Class declaringClass = f.getDeclaringClass();
        String fieldName = f.getName();
        String value = null;
        try {
            Object o = declaringClass.newInstance();
            String getterMethod = "get" + getCamelCasedPropertyName(fieldName);

            if(Boolean.class.isAssignableFrom(f.getType())){
                getterMethod = "is" + getCamelCasedPropertyName(fieldName);                
            }
            Method m = declaringClass.getDeclaredMethod(getterMethod);
            m.setAccessible(true);
            Object result = m.invoke(o);
            if(result != null) {
                value = result.toString();
            }
        } catch (Exception e) {
            Object[] args = {fieldName, declaringClass.getName(), e.getMessage()};
            String localString = localStrings.getLocalString(
                    "enterprise.deployment.annotation.handlers.configpropertyfieldreadfailure",
                    "failed to read the value of field [{0}] on class [{1}], reason : {2}", args);
            logger.log(Level.WARNING, localString, e);
        }
        return value;
    }

    /**
     * Returns camel-cased version of a propertyName. Used to construct
     * correct accessor and mutator method names for a give property.
     */
    private static String getCamelCasedPropertyName(String propertyName) {
        return propertyName.substring(0, 1).toUpperCase(Locale.getDefault()) +
                propertyName.substring(1);
    }


    private static ConnectorConfigProperty getConfigProperty(String defaultValue, String description, boolean ignore,
                                                      boolean supportsDynamicUpdates, boolean confidential,
                                                      Class type, String propertyName) {
        ConnectorConfigProperty ep = new ConnectorConfigProperty();
        //use description if specified
        if (!description.equals("")) {
            ep.setDescription(description);
        }
        //use default value if specified
        if ( defaultValue!= null && !defaultValue.equals("")) {
            ep.setValue(defaultValue);
        }
        ep.setType(type.getName());

        ep.setName(propertyName);

        if (!ep.isSetIgnoreCalled()) {
            ep.setIgnore(ignore);
        }
        if (!ep.isSetConfidentialCalled()) {
            ep.setConfidential(confidential);
        }
        if (!ep.isSupportsDynamicUpdates()) {
            ep.setSupportsDynamicUpdates(supportsDynamicUpdates);
        }
        return ep;
    }

    private void handleConfigPropertyAnnotation(AnnotationInfo element, ConnectorDescriptor desc,
                                                ConnectorConfigProperty ep, Class declaringClass) {

        if ((ResourceAdapter.class.isAssignableFrom(declaringClass)
                && (!Modifier.isAbstract(declaringClass.getModifiers()))) ||
                ( declaringClass.getAnnotation(Connector.class) != null)){
            if (!processConnector(desc, ep, declaringClass)) {
                //need to book-keep the annotation for post-processing
                desc.addConfigPropertyAnnotation(declaringClass.getName(), element);
            }
        } else if (ManagedConnectionFactory.class.isAssignableFrom(declaringClass) &&
                (!Modifier.isAbstract(declaringClass.getModifiers()))) {
                //@ConnectionDefintion, @ConnectionDefinitions must be of type ManagedConnectionFactory and hence
                //the above check is sufficient to take care of JavaBean as well annotation.
                processConnectionDefinition(element, desc, ep, declaringClass);
        } else if ((ActivationSpec.class.isAssignableFrom(declaringClass) &&
                (!Modifier.isAbstract(declaringClass.getModifiers()))) ||
                (declaringClass.getAnnotation(Activation.class) != null) ) {
            processActivation(element, desc, ep, declaringClass);
        } else if (declaringClass.getAnnotation(AdministeredObject.class) != null ||
                isAdminObjectJavaBean(declaringClass, desc)){
            processAdministeredObject(element, desc, ep, declaringClass);
        }
    }

    private boolean isAdminObjectJavaBean(Class adminObjectClass, ConnectorDescriptor desc) {
        boolean isAdminObject = false;
        Set adminObjects = desc.getAdminObjects();
        Iterator adminObjectsItr = adminObjects.iterator();
        while(adminObjectsItr.hasNext()){
            AdminObject adminObject = (AdminObject)adminObjectsItr.next();
            if(adminObject.getAdminObjectClass().equals(adminObjectClass.getName())){
                isAdminObject = true;
                break;
            }
        }
        return isAdminObject;
    }

    private void processAdministeredObject(AnnotationInfo element, ConnectorDescriptor desc,
                                                           ConnectorConfigProperty ep, Class declaringClass) {

        if (declaringClass.getAnnotation(AdministeredObject.class) != null) {
            AdministeredObject ao = (AdministeredObject) declaringClass.getAnnotation(AdministeredObject.class);
            Class[] adminObjectInterfaces = ao.adminObjectInterfaces();
            if(adminObjectInterfaces.length > 0){
                for (Class adminObjectInterface : adminObjectInterfaces) {
                    handleAdministeredObject(element, desc, ep, declaringClass, adminObjectInterface);
                }
            }else{
                //handle the case where admin object interfaces are not specified via annotaiton
                List interfacesList = AdministeredObjectHandler.
                        deriveAdminObjectInterfacesFromHierarchy(declaringClass);

                //We assume that there will be only one interface (if there had been many, admin-object annotation
                //handler would have rejected it.)
                if(interfacesList.size() == 1){
                    Class intf = interfacesList.get(0);
                    handleAdministeredObject(element, desc, ep, declaringClass, intf);
                }
            }
        } else {
            Set adminObjects = desc.getAdminObjects();
            Iterator adminObjectItr = adminObjects.iterator();
            while(adminObjectItr.hasNext()){
                AdminObject adminObject = (AdminObject)adminObjectItr.next();
                if(adminObject.getAdminObjectClass().equals(declaringClass.getName())){
                    if (!(isConfigDefined(adminObject.getConfigProperties(), ep))) {
                        adminObject.addConfigProperty(ep);
                    }
                    String uniqueName = adminObject.getAdminObjectInterface() + "_" + adminObject.getAdminObjectClass();
                    if(!desc.getConfigPropertyProcessedClasses().contains(uniqueName)){
                        processParent(declaringClass.getSuperclass(), adminObject.getConfigProperties());
                        desc.addConfigPropertyProcessedClass(declaringClass.getName());
                    }
                }
            }
        }
    }

    private void handleAdministeredObject(AnnotationInfo element, ConnectorDescriptor desc,
                                          ConnectorConfigProperty ep, Class adminObjectClass, Class adminObjectIntf) {
        AdminObject adminObject = desc.getAdminObject(adminObjectIntf.getName(), adminObjectClass.getName());
        if (adminObject != null) {
            if (!(isConfigDefined(adminObject.getConfigProperties(), ep))) {
                adminObject.addConfigProperty(ep);
            }
            if(!desc.getConfigPropertyProcessedClasses().contains(adminObjectClass.getName())){
                processParent(adminObjectClass.getSuperclass(), adminObject.getConfigProperties());
                desc.addConfigPropertyProcessedClass(adminObjectClass.getName());
            }
        } else {
            // ideally adminObject should not be null as "@AdministeredObject" 
            // should have been handled before @ConfigProperty
            getFailureResult(element, "could not get adminobject of interface " +
                    "[ " + adminObjectIntf.getName() + " ]" +
                    " and class [ " + adminObjectClass.getName() + " ]", true);
        }
    }

    private void processActivation(AnnotationInfo element, ConnectorDescriptor desc,
                                   ConnectorConfigProperty ep, Class declaringClass) {

           InboundResourceAdapter ira = desc.getInboundResourceAdapter();
        if (declaringClass.getAnnotation(Activation.class) != null) {
            // Inbound Resource Adapter should have been defined if @Activation annotation
            // was processed successfully, before.
            if (desc.getInBoundDefined()) {
                Activation activation = (Activation) declaringClass.getAnnotation(Activation.class);
                Class[] messageListeners = activation.messageListeners();

                //messageListeners cant be 0 as we ask "@Activation" to be handled before "@ConfigProperty"
                for (Class clz : messageListeners) {
                    if (ira.hasMessageListenerType(clz.getName())) {
                        MessageListener ml = ira.getMessageListener(clz.getName());

                        //check whether the activation-spec class in the descriptor
                        //for a particular message-listener is the same as this class as it is possible
                        //that this activation-spec class may have been ignored if ra.xml is already defined with
                        //this particular message-listener-type. If so, we should not add config-property as they
                        // belong to a particular activation-spec class.
                        if (ml.getActivationSpecClass().equals(declaringClass.getName())) {
                            if (!(isConfigDefined(ml.getConfigProperties(), ep))) {
                                ml.addConfigProperty(ep);
                            }
                            if (!desc.getConfigPropertyProcessedClasses().contains(declaringClass.getName())) {
                                processParent(declaringClass.getSuperclass(), ml.getConfigProperties());
                                desc.addConfigPropertyProcessedClass(declaringClass.getName());
                            }
                        }
                    }
                }
            }
        } else {
                if(desc.getInBoundDefined()){
                    Set messageListeners = desc.getInboundResourceAdapter().getMessageListeners();
                    Iterator mlItr = messageListeners.iterator();
                    while(mlItr.hasNext()){
                        MessageListener ml = (MessageListener)mlItr.next();
                        if(ml.getActivationSpecClass().equals(declaringClass.getName())){
                            if (!(isConfigDefined(ml.getConfigProperties(), ep))) {
                                ml.addConfigProperty(ep);
                            }
                            if(!desc.getConfigPropertyProcessedClasses().contains(declaringClass.getName())){
                                processParent(declaringClass.getSuperclass(), ml.getConfigProperties());
                                desc.addConfigPropertyProcessedClass(declaringClass.getName());
                            }
                        }
                    }
                }
            }
        }

    private void processConnectionDefinition(AnnotationInfo element, ConnectorDescriptor desc,
                                             ConnectorConfigProperty ep, Class declaringClass) {
        if (desc.getOutBoundDefined()) {

            OutboundResourceAdapter ora = desc.getOutboundResourceAdapter();
            Set connectionDefinitions = ora.getConnectionDefs();

            for (Object o : connectionDefinitions) {
                ConnectionDefDescriptor cd = (ConnectionDefDescriptor) o;

                if (cd.getManagedConnectionFactoryImpl().equals(declaringClass.getName())) {

                    if (!(isConfigDefined(cd.getConfigProperties(), ep))) {
                        cd.addConfigProperty(ep);
                    }
                    //As same MCF class can be used for multiple connection-definitions
                    //store it based on connection-factory-interface class which is the unique
                    //identifier for a connection-definition
                    if(!desc.getConfigPropertyProcessedClasses().contains(cd.getConnectionFactoryIntf())){
                        processParent(declaringClass.getSuperclass(), cd.getConfigProperties());
                        desc.addConfigPropertyProcessedClass(cd.getConnectionFactoryIntf());
                    }
                }
                //ignore if connection-definition entry is not found as it is possible that
                //ra.xml has a connection-definition with the same connection-factory class
                //as this annotation.

                //it is possible that multiple ConnectionDefinitions with same MCF class, but different
                //connection-factory-interface can be defined.Hence process all connection definitions
            }

        } else {
            // if there is a @ConfigProperty annotation on any of the connection-definition (MCF), either it is
            // defined via ra.xml and hence actual @ConnectionDefinition(s) annotation is ignored
            // or
            // no clash between ra.xml and the annotation, actual annotation is considered
            // So, outbound-ra must have been defined either way.
            getFailureResult(element, "Outbound RA is not defined", true);
        }
    }

    public static boolean processConnector(ConnectorDescriptor desc, ConnectorConfigProperty ep, Class declaringClass) {
        // make sure that the RA Class considered here is the one specified in descriptor
        // If not, it will be processed once the @Connector is selected during post-processing 

        // handle the annotation specified on a ResourceAdapter JavaBean
        // make sure that the property is not already specified in DD
        if(desc.getResourceAdapterClass().equals(declaringClass.getName())){
            if (!(isConfigDefined(desc.getConfigProperties(), ep))) {
                desc.addConfigProperty(ep);
            }
            if(!desc.getConfigPropertyProcessedClasses().contains(declaringClass.getName())){
                processParent(declaringClass.getSuperclass(), desc.getConfigProperties());
                desc.addConfigPropertyProcessedClass(declaringClass.getName());
            }
            //indicate that the config-property is processed
            return true;
        }else{
            //indicate that the config-property is not processed and need to be processed during post-processing
            return false;
        }
    }

    private static String validateMethod(Method m, ConfigProperty property){

        if(!m.getName().startsWith("set")){
            return "not a standard JavaBean setter method : [" + m.getName() + " ] ";
        }

        int modifier = m.getModifiers();

        //we are not restricting protected, default methods as potentially
        //any of the sub-classes may broaden the accessibility.
        if(Modifier.isPrivate(modifier)){
            return "@ConfigProperty annotation on a " +
                    "private setter method [ "+m.getName()+" ] " +
                    "of class [ "+m.getDeclaringClass().getName()+" ]";
        }

        Class type = property.type();
        Class[] parameters = m.getParameterTypes();
        Class propertyType;
        if (parameters.length != 0) {
            if (parameters.length == 1) {
                propertyType = parameters[0];
            } else {
                return "more than one parameter for JavaBean setter method : [" + m.getName() + " ] ";
            }
        } else {
            return "no parameters for JavaBean setter method :  [" + m.getName() + " ] ";
        }
        //check compatibility between annotation type and property-type

        if (!type.equals(Object.class) && !propertyType.isAssignableFrom(type)) {
            if(type.isPrimitive()){
                type = getWrapperClass(type.getName());
            }else if(propertyType.isPrimitive()){
                propertyType = getWrapperClass(propertyType.getName());
            }

            if(!propertyType.isAssignableFrom(type)){
                return "annotation type [" + type + "] and property-type" +
                        " [" + propertyType + "] " +
                        "are not assignment compatible";
            }
        }
        return SUCCESS;
    }

    private static Class getType( ConfigProperty property, Class type){
        Class configPropertyType = property.type();

        if (configPropertyType.equals(Object.class)) {
            configPropertyType = type;
        }
        return configPropertyType;
    }

    private static String validateField(Field f, ConfigProperty property){

        Class c = f.getDeclaringClass();
        Class returnType = f.getType();
        Class type = property.type();
        if (!type.equals(Object.class)) {
            //check compatibility between annotation type and return-type
            if (!returnType.isAssignableFrom(type)) {
                return "annotation type [" + type + "] " +
                        "and return-type [" + returnType + "] " +
                        "are not assignment compatible for @ConfigProperty in " +
                        "field [ " + f.getName() + " ] of class [ " + c + " ]";
            }
        }
        return SUCCESS;
    }

    public static void processParent(Class claz, Set configProperties) {

        if(claz == null){
            return;
        }
        //process the methods
        Method[] methods = claz.getDeclaredMethods();
        for (Method m : methods) {
            ConfigProperty property = m.getAnnotation(ConfigProperty.class);
            if (property != null) {
                String result = validateMethod(m, property);
                if (!result.equals(SUCCESS)) {
                    throw new IllegalStateException(result);
                }
                String defaultValue = property.defaultValue();
                Class type = getType(property, m.getParameterTypes()[0]);
                processConfigProperty(configProperties, m.getName().substring(3), property, defaultValue, type);
            }
        }

        //process the fields
        Field[] fields = claz.getDeclaredFields();
        for (Field f : fields) {
            ConfigProperty property = f.getAnnotation(ConfigProperty.class);

            if (property != null) {
                String status = validateField(f, property);
                if(!status.equals(SUCCESS)){
                    throw new IllegalStateException(status);
                }
                String defaultValue = property.defaultValue();

                if (defaultValue == null || defaultValue.equals("")) {
                    defaultValue = deriveDefaultValueOfField(f);
                }
                processConfigProperty(configProperties, f.getName(), property, defaultValue, f.getType());
            }
        }

        //process its super-class
        if (claz.getSuperclass() != null) {
            processParent(claz.getSuperclass(), configProperties);
        }
    }

    private static void processConfigProperty(Set configProperties, String propertyName,
                                              ConfigProperty property, String defaultValue, Class declaredEntityType) {
        String description = "";
        if(property.description() != null && property.description().length > 0){
            description = property.description()[0];
        }
        Class type = getType(property, declaredEntityType);
        ConnectorConfigProperty ccp = getConfigProperty(defaultValue, description,
                property.ignore(), property.supportsDynamicUpdates(), property.confidential(),
                type, propertyName);
        if(!isConfigDefined(configProperties, ccp)){
            configProperties.add(ccp);
        }
    }

    private static boolean isConfigDefined(Set configProperties, ConnectorConfigProperty ep) {
        boolean result = false;
        for (Object o : configProperties) {
            ConnectorConfigProperty ddEnvProperty = (ConnectorConfigProperty) o;
            if (ddEnvProperty.getName().equals(ep.getName())) {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * @return a default processed result
     */
    protected HandlerProcessingResult getDefaultProcessedResult() {
        return HandlerProcessingResultImpl.getDefaultResult(
                getAnnotationType(), ResultType.PROCESSED);
    }


    public Class[] getTypeDependencies() {
        return new Class[]{Connector.class, ConnectionDefinition.class, ConnectionDefinitions.class,
                Activation.class, AdministeredObject.class};
    }

    private void debug(String s) {
        logger.log(Level.INFO, "[ConfigPropertyHandler] " + s);
    }

    private HandlerProcessingResultImpl getFailureResult(AnnotationInfo element, String message, boolean doLog) {
        HandlerProcessingResultImpl result = new HandlerProcessingResultImpl();
        result.addResult(getAnnotationType(), ResultType.FAILED);
        if (doLog) {
            Object o = element.getAnnotatedElement();
            String className = null;
            if(o instanceof Field){
                className = ((Field)o).getDeclaringClass().getName();
            }else { //else it can be only METHOD
                className = ((Method)o).getDeclaringClass().getName();
            }
            Object args[] = new Object[]{
                element.getAnnotation(),
                className,
                message,
            };
            String localString = localStrings.getLocalString(
                    "enterprise.deployment.annotation.handlers.connectorannotationfailure",
                    "failed to handle annotation [ {0} ] on class [ {1} ], reason : {2}", args);
            logger.log(Level.WARNING, localString);
        }
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy