Great, thanks for the review. As you said, let's deal with lookups later.

Gary

On Wed, Sep 14, 2016 at 9:31 PM, Ralph Goers <[email protected]>
wrote:

> This looks good to me. The only thing missing is the lookup and that can
> be added under a separate Jira issue.
>
> Ralph
>
> On Sep 14, 2016, at 7:12 PM, Gary Gregory <[email protected]> wrote:
>
> FYI: I just pushed site docs updates.
>
> Gary
>
> On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <[email protected]>
> wrote:
>
>> And I am not worried about concurrency around the log event.
>>
>> Gary
>>
>> On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <[email protected]>
>> wrote:
>>
>>> OK, I've just reworked the code. Bindings are no longer shared, only the
>>> ConcurrentMap is shared. Please review.
>>>
>>> Gary
>>>
>>> On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <[email protected]
>>> > wrote:
>>>
>>>> And obviously the Bindings variable should not be a class member.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 4:33 PM, Ralph Goers <[email protected]>
>>>> wrote:
>>>>
>>>> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <[email protected]>
>>>> wrote:
>>>>
>>>> That's not how I have now. I'll revisit...
>>>>
>>>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <[email protected]>
>>>> wrote:
>>>>
>>>>> A new Binding is created for every script execution so thread safety
>>>>> is not a problem.
>>>>>
>>>>> Ralph
>>>>>
>>>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <[email protected]>
>>>>> wrote:
>>>>>
>>>>> If the Binding is shared between threads (and therefore not
>>>>> thread-safe), you could put the LogEvent in a ThreadLocal.
>>>>>
>>>>> Sent from my iPhone
>>>>>
>>>>> On 2016/09/15, at 6:24, Gary Gregory <[email protected]> wrote:
>>>>>
>>>>> Ralph: Thank you for the guidance on this topic.
>>>>>
>>>>> I'll tackle the documentation update tonight.
>>>>>
>>>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on
>>>>> that, I can take a look.
>>>>>
>>>>> I am wondering in general about the performance difference from an app
>>>>> POV between a plain appender and one appender nested in a RoutingAppender.
>>>>>
>>>>> I am also wondering about any concurrency issue passing a LogEvent to
>>>>> a Script. Can a LogEvent be skipped when multiple threads use the same
>>>>> RoutingAppender?
>>>>>
>>>>> Gary
>>>>>
>>>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Sounds good.
>>>>>>
>>>>>> When documenting that you should make it clear that the Map is shared
>>>>>> so that every thread is seeing the same Map. Users need to be aware that
>>>>>> they cannot put things in the map and expect it to only be available for
>>>>>> that single event.
>>>>>>
>>>>>> The last, and probably trickiest part, is going to be making it so
>>>>>> variables in the Map can be accessed via a Lookup. To be honest, I 
>>>>>> haven’t
>>>>>> really figured out how to do that.
>>>>>>
>>>>>> Ralph
>>>>>>
>>>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <[email protected]>
>>>>>> wrote:
>>>>>>
>>>>>> The RoutingAppender Scripts now share a Bindings instance which
>>>>>> contains a ConcurrentMap keyed under "staticVariables". The Bindings
>>>>>> instance is tracked as a RoutingAppender and Routes ivar.
>>>>>>
>>>>>> I created an abstract superclass for (private) ScriptRunner
>>>>>> implementations which holds on to the ConcurrentMap. The map can act as a
>>>>>> set of static/global variables for that script and can be shared through 
>>>>>> a
>>>>>> Bindings instance. The private ScriptRunner has new method
>>>>>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>>>>>> script specific data added to the Bindings, but there could be in the
>>>>>> future.
>>>>>>
>>>>>> I'll add LogEvent support next...
>>>>>>
>>>>>> Gary
>>>>>>
>>>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> OK - It wasn’t in there when I looked last night.
>>>>>>>
>>>>>>> A couple other things. A ConcurrentMap should be created and passed
>>>>>>> to the init script and the routing script so that the init script can 
>>>>>>> pass
>>>>>>> variables to the routing script. Also, the routing script really needs 
>>>>>>> to
>>>>>>> be passed the logEvent so it can route based on data within it.
>>>>>>>
>>>>>>> Ralph
>>>>>>>
>>>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <[email protected]>
>>>>>>> wrote:
>>>>>>>
>>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Gary,
>>>>>>>>
>>>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense
>>>>>>>> for the Routes class to execute the script in the call to getPattern 
>>>>>>>> and
>>>>>>>> return the result if there is a script?
>>>>>>>>
>>>>>>>
>>>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>>>
>>>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>>       * @return the pattern.
>>>>>>>       */
>>>>>>>      public String getPattern() {
>>>>>>> +        if (patternScript != null) {
>>>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>> +            bindings.put("configuration", configuration);
>>>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>>>> +            final Object object = configuration.getScriptManager
>>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>>> +            return Objects.toString(object, null);
>>>>>>> +        }
>>>>>>>          return pattern;
>>>>>>>      }
>>>>>>>
>>>>>>> Gary
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> Ralph
>>>>>>>>
>>>>>>>> > On Sep 13, 2016, at 10:00 PM, [email protected] wrote:
>>>>>>>> >
>>>>>>>> > Repository: logging-log4j2
>>>>>>>> > Updated Branches:
>>>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>>>> >
>>>>>>>> >
>>>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>>>>>> Script
>>>>>>>> > in a Routes element.
>>>>>>>> >
>>>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4
>>>>>>>> j2/repo
>>>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>>>>>> 2/commit/e0f29d9a
>>>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>>> tree/e0f29d9a
>>>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>>> diff/e0f29d9a
>>>>>>>> >
>>>>>>>> > Branch: refs/heads/master
>>>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>>>> > Parents: 3846e2a
>>>>>>>> > Author: Gary Gregory <[email protected]>
>>>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>>>> > Committer: Gary Gregory <[email protected]>
>>>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>>>> >
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>>>>>> ++++++++++++-----
>>>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>>>>>> +++++++++++++++++++
>>>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> >
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/Routes.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > index c95b64a..33fccd7 100644
>>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>>>>>> appender.routing;
>>>>>>>> >
>>>>>>>> > import java.util.Objects;
>>>>>>>> >
>>>>>>>> > +import javax.script.SimpleBindings;
>>>>>>>> > +
>>>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>>>> > import org.apache.logging.log4j.core.config.plugins.Plugin;
>>>>>>>> > import org.apache.logging.log4j.core.
>>>>>>>> config.plugins.PluginAttribute;
>>>>>>>> > import org.apache.logging.log4j.core.
>>>>>>>> config.plugins.PluginBuilderFactory;
>>>>>>>> > +import org.apache.logging.log4j.core.
>>>>>>>> config.plugins.PluginConfiguration;
>>>>>>>> > import org.apache.logging.log4j.core.
>>>>>>>> config.plugins.PluginElement;
>>>>>>>> > +import org.apache.logging.log4j.core.
>>>>>>>> config.plugins.validation.constraints.Required;
>>>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>>>> >
>>>>>>>> > /**
>>>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>>>> >
>>>>>>>> >     public static class Builder implements
>>>>>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>>>> >
>>>>>>>> > +        @PluginConfiguration
>>>>>>>> > +        private Configuration configuration;
>>>>>>>> > +
>>>>>>>> >         @PluginAttribute("pattern")
>>>>>>>> >         private String pattern;
>>>>>>>> >
>>>>>>>> > -        @PluginElement("Routes")
>>>>>>>> > +        @PluginElement("Script")
>>>>>>>> > +        private AbstractScript patternScript;
>>>>>>>> > +
>>>>>>>> > +        @PluginElement("Routes")
>>>>>>>> > +        @Required
>>>>>>>> >         private Route[] routes;
>>>>>>>> >
>>>>>>>> >         @Override
>>>>>>>> >         public Routes build() {
>>>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>>>> > -                LOGGER.error("No routes configured");
>>>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> > -            return new Routes(pattern, routes);
>>>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>>>> > +                LOGGER.warn("In a Routes element, you must
>>>>>>>> configure either a Script element or a pattern attribute.");
>>>>>>>> > +            }
>>>>>>>> > +            if (patternScript != null) {
>>>>>>>> > +                if (configuration == null) {
>>>>>>>> > +                    LOGGER.error("No Configuration defined for
>>>>>>>> Routes; required for Script");
>>>>>>>> > +                } else {
>>>>>>>> > +                    configuration.getScriptManager
>>>>>>>> ().addScript(patternScript);
>>>>>>>> > +                }
>>>>>>>> > +            }
>>>>>>>> > +            return new Routes(configuration, patternScript,
>>>>>>>> pattern, routes);
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Configuration getConfiguration() {
>>>>>>>> > +            return configuration;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> >         public String getPattern() {
>>>>>>>> >             return pattern;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>>>> > +            return patternScript;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> >         public Route[] getRoutes() {
>>>>>>>> >             return routes;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>>> String pattern) {
>>>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>>>>>> final Configuration configuration) {
>>>>>>>> > +            this.configuration = configuration;
>>>>>>>> > +            return this;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>>> final String pattern) {
>>>>>>>> >             this.pattern = pattern;
>>>>>>>> >             return this;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>>> Route[] routes) {
>>>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>>>>>> final AbstractScript patternScript) {
>>>>>>>> > +            this.patternScript = patternScript;
>>>>>>>> > +            return this;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>>> final Route[] routes) {
>>>>>>>> >             this.routes = routes;
>>>>>>>> >             return this;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > +    private static final Logger LOGGER =
>>>>>>>> StatusLogger.getLogger();
>>>>>>>> > +
>>>>>>>> > +    /**
>>>>>>>> > +     * Creates the Routes.
>>>>>>>> > +     * @param pattern The pattern.
>>>>>>>> > +     * @param routes An array of Route elements.
>>>>>>>> > +     * @return The Routes container.
>>>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>>> > +     */
>>>>>>>> > +    @Deprecated
>>>>>>>> > +    public static Routes createRoutes(
>>>>>>>> > +            final String pattern,
>>>>>>>> > +            final Route... routes) {
>>>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>>>> > +            LOGGER.error("No routes configured");
>>>>>>>> > +            return null;
>>>>>>>> > +        }
>>>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> >     @PluginBuilderFactory
>>>>>>>> >     public static Builder newBuilder() {
>>>>>>>> >         return new Builder();
>>>>>>>> >     }
>>>>>>>> > -
>>>>>>>> > -    private static final Logger LOGGER =
>>>>>>>> StatusLogger.getLogger();
>>>>>>>> > -
>>>>>>>> > +
>>>>>>>> > +    private final Configuration configuration;
>>>>>>>> > +
>>>>>>>> >     private final String pattern;
>>>>>>>> >
>>>>>>>> > +    private final AbstractScript patternScript;
>>>>>>>> > +
>>>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>>>> >     private final Route[] routes;
>>>>>>>> >
>>>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>>>> > +    private Routes(final Configuration configuration, final
>>>>>>>> AbstractScript patternScript, final String pattern, final Route... 
>>>>>>>> routes) {
>>>>>>>> > +        this.configuration = configuration;
>>>>>>>> > +        this.patternScript = patternScript;
>>>>>>>> >         this.pattern = pattern;
>>>>>>>> >         this.routes = routes;
>>>>>>>> >     }
>>>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>>> >      * @return the pattern.
>>>>>>>> >      */
>>>>>>>> >     public String getPattern() {
>>>>>>>> > +        if (patternScript != null) {
>>>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>>> > +            bindings.put("configuration", configuration);
>>>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>>>> > +            final Object object = configuration.getScriptManager
>>>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>>>> > +            return Objects.toString(object, null);
>>>>>>>> > +        }
>>>>>>>> >         return pattern;
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > -    public Route getRoute(String key) {
>>>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>>>> > -            final Route route = routes[i];
>>>>>>>> > +    /**
>>>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>>>> > +     * @return the optional script that decides which route to
>>>>>>>> pick. May be null.
>>>>>>>> > +     */
>>>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>>>> > +        return patternScript;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    public Route getRoute(final String key) {
>>>>>>>> > +        for (final Route route : routes) {
>>>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>>>> >                 return route;
>>>>>>>> >             }
>>>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>>>> >
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > -    /**
>>>>>>>> > -     * Creates the Routes.
>>>>>>>> > -     * @param pattern The pattern.
>>>>>>>> > -     * @param routes An array of Route elements.
>>>>>>>> > -     * @return The Routes container.
>>>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>>> > -     */
>>>>>>>> > -    @Deprecated
>>>>>>>> > -    public static Routes createRoutes(
>>>>>>>> > -            final String pattern,
>>>>>>>> > -            final Route... routes) {
>>>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>>>> > -            LOGGER.error("No routes configured");
>>>>>>>> > -            return null;
>>>>>>>> > -        }
>>>>>>>> > -        return new Routes(pattern, routes);
>>>>>>>> > -    }
>>>>>>>> > -
>>>>>>>> > }
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/RoutingAppender.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > index 4471333..78fddbc 100644
>>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >
>>>>>>>> >         @Override
>>>>>>>> >         public RoutingAppender build() {
>>>>>>>> > -            if (getName() == null) {
>>>>>>>> > -                LOGGER.error("No name defined for
>>>>>>>> RoutingAppender");
>>>>>>>> > +            final String name = getName();
>>>>>>>> > +            if (name == null) {
>>>>>>>> > +                LOGGER.error("No name defined for this
>>>>>>>> RoutingAppender");
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> >             if (routes == null) {
>>>>>>>> > -                LOGGER.error("No routes defined for
>>>>>>>> RoutingAppender");
>>>>>>>> > +                LOGGER.error("No routes defined for
>>>>>>>> RoutingAppender {}", name);
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>>> > +            return new RoutingAppender(name, getFilter(),
>>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>>> >                     configuration, purgePolicy,
>>>>>>>> defaultRouteScript);
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >     public void start() {
>>>>>>>> >         if (defaultRouteScript != null) {
>>>>>>>> >             if (configuration == null) {
>>>>>>>> > -                error("No Configuration defined for
>>>>>>>> RoutingAppender; required for DefaultRouteScript");
>>>>>>>> > +                error("No Configuration defined for
>>>>>>>> RoutingAppender; required for Script element.");
>>>>>>>> >             } else {
>>>>>>>> >                 configuration.getScriptManage
>>>>>>>> r().addScript(defaultRouteScript);
>>>>>>>> >                 final SimpleBindings bindings = new
>>>>>>>> SimpleBindings();
>>>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>>>> >         return rewritePolicy;
>>>>>>>> >     }
>>>>>>>> > +
>>>>>>>> > +    public Routes getRoutes() {
>>>>>>>> > +        return routes;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    public Configuration getConfiguration() {
>>>>>>>> > +        return configuration;
>>>>>>>> > +    }
>>>>>>>> > }
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..7d90f6b
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > @@ -0,0 +1,130 @@
>>>>>>>> > +/*
>>>>>>>> > + * Licensed to the Apache Software Foundation (ASF) under one or
>>>>>>>> more
>>>>>>>> > + * contributor license agreements. See the NOTICE file
>>>>>>>> distributed with
>>>>>>>> > + * this work for additional information regarding copyright
>>>>>>>> ownership.
>>>>>>>> > + * The ASF licenses this file to You under the Apache license,
>>>>>>>> Version 2.0
>>>>>>>> > + * (the "License"); you may not use this file except in
>>>>>>>> compliance with
>>>>>>>> > + * the License. You may obtain a copy of the License at
>>>>>>>> > + *
>>>>>>>> > + *      http://www.apache.org/licenses/LICENSE-2.0
>>>>>>>> > + *
>>>>>>>> > + * Unless required by applicable law or agreed to in writing,
>>>>>>>> software
>>>>>>>> > + * distributed under the License is distributed on an "AS IS"
>>>>>>>> BASIS,
>>>>>>>> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
>>>>>>>> or implied.
>>>>>>>> > + * See the license for the specific language governing
>>>>>>>> permissions and
>>>>>>>> > + * limitations under the license.
>>>>>>>> > + */
>>>>>>>> > +package org.apache.logging.log4j.core.appender.routing;
>>>>>>>> > +
>>>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>>>> > +
>>>>>>>> > +import java.util.List;
>>>>>>>> > +import java.util.Map;
>>>>>>>> > +
>>>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>>>> > +import org.junit.Assert;
>>>>>>>> > +import org.junit.Rule;
>>>>>>>> > +import org.junit.Test;
>>>>>>>> > +import org.junit.runner.RunWith;
>>>>>>>> > +import org.junit.runners.Parameterized;
>>>>>>>> > +
>>>>>>>> > +/**
>>>>>>>> > + *
>>>>>>>> > + */
>>>>>>>> > +@RunWith(Parameterized.class)
>>>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>>>> > +
>>>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>>>> > +    public static String[] getParameters() {
>>>>>>>> > +        return new String[] {
>>>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Rule
>>>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>>>> > +
>>>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation)
>>>>>>>> {
>>>>>>>> > +        this.loggerContextRule = new
>>>>>>>> LoggerContextRule(configLocation);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private ListAppender getListAppender() {
>>>>>>>> > +        final String key = "Service2";
>>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>>> getRoutingAppender();
>>>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>>>> > +        final Map<String, AppenderControl> appenders =
>>>>>>>> routingAppender.getAppenders();
>>>>>>>> > +        final AppenderControl appenderControl =
>>>>>>>> appenders.get(key);
>>>>>>>> > +        assertNotNull("No appender control generated for '" +
>>>>>>>> key + "'; appenders = " + appenders, appenderControl);
>>>>>>>> > +        final ListAppender listAppender = (ListAppender)
>>>>>>>> appenderControl.getAppender();
>>>>>>>> > +        return listAppender;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>>>>>> RoutingAppender.class);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private void logAndCheck() {
>>>>>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>>>>>> utesScriptAppenderTest.class);
>>>>>>>> > +        logger.error("Hello");
>>>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>>>> > +        assertNotNull("No events generated", list);
>>>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got
>>>>>>>> " + list.size(), list.size() == 1);
>>>>>>>> > +        logger.error("World");
>>>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got
>>>>>>>> " + list.size(), list.size() == 2);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>>>> > +    public void testAppenderAbsence() {
>>>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testListAppenderPresence() {
>>>>>>>> > +        // No appender until an event is routed, even thought we
>>>>>>>> initialized the default route on startup.
>>>>>>>> > +        Assert.assertNull("No appender control generated",
>>>>>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testNoPurgePolicy() {
>>>>>>>> > +        // No PurgePolicy in this test
>>>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>>>>>> getRoutingAppender().getPurgePolicy());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testNoRewritePolicy() {
>>>>>>>> > +        // No RewritePolicy in this test
>>>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>>>>>> getRoutingAppender().getRewritePolicy());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>>> getRoutingAppender();
>>>>>>>> > +        Assert.assertNull(routingAppen
>>>>>>>> der.getDefaultRouteScript());
>>>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>>>> > +        Assert.assertNotNull(routes);
>>>>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>>>> > +        getRoutingAppender();
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingPresence1() {
>>>>>>>> > +        logAndCheck();
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingPresence2() {
>>>>>>>> > +        logAndCheck();
>>>>>>>> > +    }
>>>>>>>> > +}
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>>> -script-groovy.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>>> s/log4j-routing-routes-script-groovy.xml
>>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> groovy.xml
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..83121ea
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> groovy.xml
>>>>>>>> > @@ -0,0 +1,43 @@
>>>>>>>> > +<?xml version="1.0" encoding="UTF-8"?>
>>>>>>>> > +<!--
>>>>>>>> > + Licensed to the Apache Software Foundation (ASF) under one or
>>>>>>>> more
>>>>>>>> > + contributor license agreements.  See the NOTICE file
>>>>>>>> distributed with
>>>>>>>> > + this work for additional information regarding copyright
>>>>>>>> ownership.
>>>>>>>> > + The ASF licenses this file to You under the Apache License,
>>>>>>>> Version 2.0
>>>>>>>> > + (the "License"); you may not use this file except in compliance
>>>>>>>> with
>>>>>>>> > + the License.  You may obtain a copy of the License at
>>>>>>>> > +
>>>>>>>> > +      http://www.apache.org/licenses/LICENSE-2.0
>>>>>>>> > +
>>>>>>>> > + Unless required by applicable law or agreed to in writing,
>>>>>>>> software
>>>>>>>> > + distributed under the License is distributed on an "AS IS"
>>>>>>>> BASIS,
>>>>>>>> > + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>>>>>>>> implied.
>>>>>>>> > + See the License for the specific language governing permissions
>>>>>>>> and
>>>>>>>> > + limitations under the License.
>>>>>>>> > +
>>>>>>>> > +-->
>>>>>>>> > +<Configuration status="WARN" name="RoutingTest">
>>>>>>>> > +  <Appenders>
>>>>>>>> > +    <Routing name="Routing">
>>>>>>>> > +      <Routes>
>>>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>>>> > +            return "Service2";
>>>>>>>> > +          }
>>>>>>>> > +          return "Service1";]]>
>>>>>>>> > +        </Script>
>>>>>>>> > +        <Route key="Service1">
>>>>>>>> > +          <List name="List1" />
>>>>>>>> > +        </Route>
>>>>>>>> > +        <Route key="Service2">
>>>>>>>> > +          <List name="List2" />
>>>>>>>> > +        </Route>
>>>>>>>> > +      </Routes>
>>>>>>>> > +    </Routing>
>>>>>>>> > +  </Appenders>
>>>>>>>> > +  <Loggers>
>>>>>>>> > +    <Root level="error">
>>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>>> > +    </Root>
>>>>>>>> > +  </Loggers>
>>>>>>>> > +</Configuration>
>>>>>>>> > \ No newline at end of file
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>>> -script-javascript.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>>> s/log4j-routing-routes-script-javascript.xml
>>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> javascript.xml
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..e672aea
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> javascript.xml
>>>>>>>> > @@ -0,0 +1,40 @@
>>>>>>>> > +<?xml version="1.0" encoding="UTF-8"?>
>>>>>>>> > +<!--
>>>>>>>> > + Licensed to the Apache Software Foundation (ASF) under one or
>>>>>>>> more
>>>>>>>> > + contributor license agreements.  See the NOTICE file
>>>>>>>> distributed with
>>>>>>>> > + this work for additional information regarding copyright
>>>>>>>> ownership.
>>>>>>>> > + The ASF licenses this file to You under the Apache License,
>>>>>>>> Version 2.0
>>>>>>>> > + (the "License"); you may not use this file except in compliance
>>>>>>>> with
>>>>>>>> > + the License.  You may obtain a copy of the License at
>>>>>>>> > +
>>>>>>>> > +      http://www.apache.org/licenses/LICENSE-2.0
>>>>>>>> > +
>>>>>>>> > + Unless required by applicable law or agreed to in writing,
>>>>>>>> software
>>>>>>>> > + distributed under the License is distributed on an "AS IS"
>>>>>>>> BASIS,
>>>>>>>> > + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>>>>>>>> implied.
>>>>>>>> > + See the License for the specific language governing permissions
>>>>>>>> and
>>>>>>>> > + limitations under the License.
>>>>>>>> > +
>>>>>>>> > +-->
>>>>>>>> > +<Configuration status="WARN" name="RoutingTest">
>>>>>>>> > +  <Appenders>
>>>>>>>> > +    <Routing name="Routing">
>>>>>>>> > +      <Routes>
>>>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA
>>>>>>>> [
>>>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>>>>>> "Service1";]]>
>>>>>>>> > +        </Script>
>>>>>>>> > +        <Route key="Service1">
>>>>>>>> > +          <List name="List1" />
>>>>>>>> > +        </Route>
>>>>>>>> > +        <Route key="Service2">
>>>>>>>> > +          <List name="List2" />
>>>>>>>> > +        </Route>
>>>>>>>> > +      </Routes>
>>>>>>>> > +    </Routing>
>>>>>>>> > +  </Appenders>
>>>>>>>> > +  <Loggers>
>>>>>>>> > +    <Root level="error">
>>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>>> > +    </Root>
>>>>>>>> > +  </Loggers>
>>>>>>>> > +</Configuration>
>>>>>>>> > \ No newline at end of file
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>>>>>> b/src/site/xdoc/manual/appenders.xml
>>>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >              Appender may be an appender previously configured
>>>>>>>> and may be referenced by its name or the
>>>>>>>> >              Appender can be dynamically created as needed. The
>>>>>>>> RoutingAppender should be configured after any
>>>>>>>> >              Appenders it references to allow it to shut down
>>>>>>>> properly.
>>>>>>>> > -          </p>
>>>>>>>> > +           </p>
>>>>>>>> > +           <p>
>>>>>>>> > +             You can also configure a RoutingAppender with
>>>>>>>> scripts: you can run a script when the appender starts
>>>>>>>> > +             and when a route is chosen for an log event.
>>>>>>>> > +           </p>
>>>>>>>> >           <table>
>>>>>>>> >             <caption align="top">RoutingAppender
>>>>>>>> Parameters</caption>
>>>>>>>> >             <tr>
>>>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >               <th>Description</th>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>filter</td>
>>>>>>>> > +              <td>Filter</td>
>>>>>>>> >               <td>Filter</td>
>>>>>>>> >               <td>A Filter to determine if the event should be
>>>>>>>> handled by this Appender. More than one Filter
>>>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >               <td>The name of the Appender.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>rewritePolicy</td>
>>>>>>>> > +              <td>RewritePolicy</td>
>>>>>>>> >               <td>RewritePolicy</td>
>>>>>>>> >               <td>The RewritePolicy that will manipulate the
>>>>>>>> LogEvent.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>routes</td>
>>>>>>>> > +              <td>Routes</td>
>>>>>>>> >               <td>Routes</td>
>>>>>>>> >               <td>Contains one or more Route declarations to
>>>>>>>> identify the criteria for choosing Appenders.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > +              <td>Script</td>
>>>>>>>> > +              <td>Script</td>
>>>>>>>> > +              <td>This Script runs when Log4j starts the
>>>>>>>> RoutingAppender and returns a String Route key to determine
>>>>>>>> > +                the default Route.</td>
>>>>>>>> > +            </tr>
>>>>>>>> > +            <tr>
>>>>>>>> >               <td>ignoreExceptions</td>
>>>>>>>> >               <td>boolean</td>
>>>>>>>> >               <td>The default is <code>true</code>, causing
>>>>>>>> exceptions encountered while appending events to be
>>>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >           </table>
>>>>>>>> >           <h4>Routes</h4>
>>>>>>>> >             <p>
>>>>>>>> > -              The Routes element accepts a single, required
>>>>>>>> attribute named "pattern". The pattern is evaluated
>>>>>>>> > +              The Routes element accepts a single attribute
>>>>>>>> named "pattern". The pattern is evaluated
>>>>>>>> >               against all the registered Lookups and the result
>>>>>>>> is used to select a Route. Each Route may be
>>>>>>>> >               configured with a key. If the key matches the
>>>>>>>> result of evaluating the pattern then that Route
>>>>>>>> >               will be selected. If no key is specified on a Route
>>>>>>>> then that Route is the default. Only one Route
>>>>>>>> >               can be configured as the default.
>>>>>>>> >             </p>
>>>>>>>> >             <p>
>>>>>>>> > +              The Routes element may contain a Script child
>>>>>>>> element. If specified, the Script is run for each
>>>>>>>> > +              log event and returns the String Route key to use.
>>>>>>>> > +            </p>
>>>>>>>> > +            <p>
>>>>>>>> > +              You must specify either the pattern attribute or
>>>>>>>> the Script element, but not both.
>>>>>>>> > +            </p>
>>>>>>>> > +            <p>
>>>>>>>> >               Each Route must reference an Appender. If the Route
>>>>>>>> contains a ref attribute then the
>>>>>>>> >               Route will reference an Appender that was defined
>>>>>>>> in the configuration. If the Route contains an
>>>>>>>> >               Appender definition then an Appender will be
>>>>>>>> created within the context of the RoutingAppender and
>>>>>>>> >
>>>>>>>> >
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> ------------------------------------------------------------
>>>>>>>> ---------
>>>>>>>> To unsubscribe, e-mail: [email protected]
>>>>>>>> For additional commands, e-mail: [email protected]
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> E-Mail: [email protected] | [email protected]
>>>>>>> <[email protected]>
>>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>>> <http://www.manning.com/bauer3/>
>>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>>> Blog: http://garygregory.wordpress.com
>>>>>>> Home: http://garygregory.com/
>>>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> E-Mail: [email protected] | [email protected]
>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>> <http://www.manning.com/bauer3/>
>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>> Blog: http://garygregory.wordpress.com
>>>>>> Home: http://garygregory.com/
>>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> E-Mail: [email protected] | [email protected]
>>>>> Java Persistence with Hibernate, Second Edition
>>>>> <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com
>>>>> Home: http://garygregory.com/
>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> E-Mail: [email protected] | [email protected]
>>> Java Persistence with Hibernate, Second Edition
>>> <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
>>>
>>
>>
>>
>> --
>> E-Mail: [email protected] | [email protected]
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>
>
>
> --
> E-Mail: [email protected] | [email protected]
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>
>
>


-- 
E-Mail: [email protected] | [email protected]
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Reply via email to