knox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kmin...@apache.org
Subject [1/2] KNOX-23: Generate audit log of all gateway activity
Date Mon, 27 Jan 2014 17:00:38 GMT
Updated Branches:
  refs/heads/master a9e7deaee -> f69acbbc4


http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/CorrelationServiceFactory.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/CorrelationServiceFactory.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/CorrelationServiceFactory.java
new file mode 100644
index 0000000..534edb0
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/CorrelationServiceFactory.java
@@ -0,0 +1,44 @@
+/**
+ * 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.hadoop.gateway.audit.api;
+
+import org.apache.hadoop.gateway.audit.log4j.correlation.Log4jCorrelationService;
+
+
+public abstract class CorrelationServiceFactory {
+
+  // The global correlation service instance.
+  private static CorrelationService correlationService = null;
+
+  // To prevent instantiation.
+  private CorrelationServiceFactory() {
+  }
+
+  /**
+   * Provides access to the default correlation service implementation.
+   * @return The default correlation service implementation.  Will not be null.
+   */
+  public static CorrelationService getCorrelationService() {
+    // Race condition acceptable and will only result in multiple service instantiations.
+    if( correlationService == null ) {
+      correlationService = new Log4jCorrelationService();
+    }
+    return correlationService;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/ResourceType.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/ResourceType.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/ResourceType.java
new file mode 100644
index 0000000..08b1ab4
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/api/ResourceType.java
@@ -0,0 +1,28 @@
+/**
+ * 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.hadoop.gateway.audit.api;
+
+public abstract class ResourceType {
+  private ResourceType() {
+  }
+
+  public static final String URI = "uri";
+  public static final String TOPOLOGY = "topology";
+  public static final String PRINCIPAL = "principal";
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmQueue.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmQueue.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmQueue.java
new file mode 100644
index 0000000..0a3f766
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmQueue.java
@@ -0,0 +1,171 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.appender;
+
+import jdbm.RecordManager;
+import jdbm.RecordManagerFactory;
+import jdbm.htree.HTree;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Properties;
+
+public class JdbmQueue<E> {
+
+  private static final String STAT_NAME = "stat";
+  private static final String DATA_TREE = "data";
+
+  private RecordManager db;
+  private long stat;
+  private HTree data;
+  private boolean open;
+
+  public JdbmQueue( File file ) throws IOException {
+    Properties props = new Properties();
+    db = RecordManagerFactory.createRecordManager( file.getAbsolutePath(), props );
+    stat = findStat();
+    data = getData();
+    db.commit();
+    open = true;
+  }
+
+  public synchronized void enqueue( E e ) throws IOException {
+    boolean committed = false;
+    try {
+      Stat stat = getStat();
+      stat.lastEnqueue++;
+      setStat( stat );
+      data.put( stat.lastEnqueue, e );
+      db.commit();
+      committed = true;
+      notify();
+    } finally {
+      if( !committed ) {
+        db.rollback();
+      }
+    }
+  }
+
+  public synchronized E dequeue() throws InterruptedException, IOException {
+    boolean committed = false;
+    try {
+      Stat s = getStat();
+      while( open && s.size() == 0 ) {
+        wait();
+        if( !open ) {
+          return null;
+        }
+        s = getStat();
+      }
+      s.nextDequeue++;
+      Long key = Long.valueOf( s.nextDequeue );
+      @SuppressWarnings("unchecked")
+      E e = (E)data.get( key );
+      data.remove( key );
+      db.update( stat, s );
+      db.commit();
+      committed = true;
+      return e;
+    } finally {
+      if( !committed && open ) {
+        db.rollback();
+      }
+    }
+  }
+
+  public synchronized boolean process( Consumer<E> consumer ) throws IOException {
+    boolean committed = false;
+    try {
+      E e = dequeue();
+      boolean consumed = consumer.consume( e );
+      if( consumed && open ) {
+        db.commit();
+        committed = true;
+      }
+    } catch( RuntimeException e ) {
+      throw e;
+    } catch( IOException e ) {
+      throw e;
+    } catch( Throwable t ) {
+      throw new RuntimeException( t );
+    } finally {
+      if( !committed && open ) {
+        db.rollback();
+      }
+    }
+    return committed;
+  }
+
+  public synchronized void close() {
+    open = false;
+    notifyAll();
+  }
+
+  long findStat() throws IOException {
+    long recid = db.getNamedObject( STAT_NAME );
+    if ( recid == 0 ) {
+      recid = db.insert( new Stat() );
+      db.setNamedObject( STAT_NAME, recid );
+    }
+    return recid;
+  }
+
+  Stat getStat() throws IOException {
+    Stat stat;
+    long recid = db.getNamedObject( STAT_NAME );
+    if ( recid == 0 ) {
+      stat = new Stat();
+      db.setNamedObject( STAT_NAME, db.insert( stat ) );
+    } else {
+      stat = (Stat)db.fetch( recid );
+    }
+    return stat;
+  }
+
+  void setStat( Stat update ) throws IOException {
+    db.update( stat, update );
+  }
+
+  HTree getData() throws IOException {
+    HTree tree;
+    long recid = db.getNamedObject( DATA_TREE );
+    if ( recid != 0 ) {
+      tree = HTree.load( db, recid );
+    } else {
+      tree = HTree.createInstance( db );
+      db.setNamedObject( DATA_TREE, tree.getRecid() );
+    }
+    return tree;
+  }
+
+  private static final class Stat implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    private long lastEnqueue = 0;
+    private long nextDequeue = 0;
+    private long size() {
+      return lastEnqueue - nextDequeue;
+    }
+  }
+
+  public interface Consumer<E> {
+    boolean consume( E e );
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmStoreAndForwardAppender.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmStoreAndForwardAppender.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmStoreAndForwardAppender.java
new file mode 100644
index 0000000..6aa11c8
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/appender/JdbmStoreAndForwardAppender.java
@@ -0,0 +1,115 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.appender;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+public class JdbmStoreAndForwardAppender extends AppenderSkeleton {
+
+  private File file;
+  private Thread forwarder;
+  private JdbmQueue<LoggingEvent> queue;
+  private Logger forward;
+  private boolean fetchLocationInfo = true;
+
+  @Override
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  public void setFile( String file ) {
+    this.file = new File( file );
+  }
+
+  public void setFetchLocationInfo( boolean fetchLocationInfo ) {
+    this.fetchLocationInfo = fetchLocationInfo;
+  }
+
+  public boolean isFetchLocationInfo() {
+    return fetchLocationInfo;
+  }
+
+  @Override
+  public void activateOptions() {
+    try {
+      queue = new JdbmQueue<LoggingEvent>( file );
+    } catch ( IOException e ) {
+      throw new IllegalStateException( e );
+    }
+    forward = Logger.getLogger( "audit.forward" );
+    forward.setAdditivity( false );
+    forwarder = new Forwarder();
+    forwarder.setDaemon( true );
+    forwarder.start();
+  }
+
+  @Override
+  protected void append( LoggingEvent event ) {
+    try {
+      if( fetchLocationInfo ) {
+        event.getLocationInformation();
+      }
+      queue.enqueue( event );
+    } catch ( IOException e ) {
+      throw new RuntimeException( e );
+    }
+  }
+
+  @Override
+  public void close() {
+    queue.close();
+  }
+
+  private class Forwarder extends Thread {
+
+    public void run() {
+      final AtomicBoolean done = new AtomicBoolean( false );
+      while( !done.get() ) {
+        try {
+          queue.process( new JdbmQueue.Consumer<LoggingEvent>() {
+            @Override
+            public boolean consume( LoggingEvent event ) {
+              try {
+                if( event == null ) {
+                  done.set( true );
+                } else {
+                  forward.callAppenders( event );
+                }
+                return true;
+              } catch ( Exception e ) {
+                e.printStackTrace();
+                return false;
+              }
+            }
+          } );
+        } catch ( ThreadDeath e ) {
+          throw e;
+        } catch ( Throwable t ) {
+          t.printStackTrace();
+        }
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/AuditConstants.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/AuditConstants.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/AuditConstants.java
new file mode 100644
index 0000000..3ab4dfb
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/AuditConstants.java
@@ -0,0 +1,35 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.audit;
+
+public class AuditConstants {
+  //resource
+  public static final String MDC_RESOURCE_TYPE_KEY = "resource_type";
+  public static final String MDC_RESOURCE_NAME_KEY = "resource_name";
+  
+  //Action details
+  public static final String MDC_ACTION_KEY = "action";
+  public static final String MDC_OUTCOME_KEY = "outcome";
+  
+  public static final String MDC_SERVICE_KEY = "service_name";
+  public static final String MDC_COMPONENT_KEY = "component_name";
+  
+  public static final String DEFAULT_AUDITOR_NAME = "audit";
+  public static final String KNOX_SERVICE_NAME = "knox";
+  public static final String KNOX_COMPONENT_NAME = "knox";
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditContext.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditContext.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditContext.java
new file mode 100644
index 0000000..03c2f75
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditContext.java
@@ -0,0 +1,116 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.audit;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+
+public class Log4jAuditContext implements Serializable, AuditContext {
+
+  private static final long serialVersionUID = 1L;
+
+  private String username;
+  private String proxyUsername;
+  private String systemUsername;
+  private Set<String> groups;
+  private String targetServiceName;
+  private String remoteIp;
+  private String remoteHostname;
+
+  @Override
+  public String getUsername() {
+    return username;
+  }
+
+  @Override
+  public void setUsername( String username ) {
+    this.username = username;
+  }
+
+  @Override
+  public String getProxyUsername() {
+    return proxyUsername;
+  }
+
+  @Override
+  public void setProxyUsername( String proxyUsername ) {
+    this.proxyUsername = proxyUsername;
+  }
+
+  @Override
+  public String getSystemUsername() {
+    return systemUsername;
+  }
+
+  @Override
+  public void setSystemUsername( String systemUsername ) {
+    this.systemUsername = systemUsername;
+  }
+  
+  @Override
+  public String getTargetServiceName() {
+    return targetServiceName;
+  }
+  
+  @Override
+  public void setTargetServiceName( String targetServiceName ) {
+    this.targetServiceName = targetServiceName;
+  }
+
+  @Override
+  public String getRemoteIp() {
+    return remoteIp;
+  }
+
+  @Override
+  public void setRemoteIp( String remoteIp ) {
+    this.remoteIp = remoteIp;
+  }
+  
+  @Override
+  public String getRemoteHostname() {
+    return remoteHostname;
+  }
+
+  @Override
+  public void setRemoteHostname( String remoteHostname ) {
+    this.remoteHostname = remoteHostname;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append( "[" );
+    sb.append( "username=" ).append( username );
+    sb.append( ", proxy_username=" ).append( proxyUsername );
+    sb.append( ", system_username=" ).append( systemUsername );
+    sb.append( ", groups=" ).append( groups );
+    sb.append( ", targetServiceName=" ).append( targetServiceName );
+    sb.append( ", remoteIp=" ).append( remoteIp );
+    sb.append( ", remoteHostname=" ).append( remoteHostname );
+    sb.append( "]" );
+    return sb.toString();
+  }
+
+  @Override
+  public void destroy() {   
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditService.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditService.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditService.java
new file mode 100644
index 0000000..5d3a815
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditService.java
@@ -0,0 +1,84 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.audit;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.log4j.MDC;
+
+public class Log4jAuditService implements AuditService {
+
+  public static final String MDC_AUDIT_CONTEXT_KEY = "audit_context";
+  private Map<String, Auditor> auditors = new ConcurrentHashMap<String, Auditor>();
+
+  @Override
+  public AuditContext createContext() {
+    AuditContext context = getContext();
+    if ( context == null ) {
+      context = new Log4jAuditContext();
+      attachContext( context );
+    }
+    return context;
+  }
+
+  @Override
+  public AuditContext getContext() {
+    return (Log4jAuditContext) MDC.get( MDC_AUDIT_CONTEXT_KEY );
+  }
+
+  @Override
+  public void attachContext(AuditContext context) {
+    if ( context != null ) {
+      MDC.put( MDC_AUDIT_CONTEXT_KEY, context );
+    }
+  }
+
+  @Override
+  public AuditContext detachContext() {
+    AuditContext context = (AuditContext) MDC.get( MDC_AUDIT_CONTEXT_KEY );
+    MDC.remove( MDC_AUDIT_CONTEXT_KEY );
+    return context;
+  }
+
+  @Override
+  public <T> T execute( AuditContext context, Callable<T> callable ) throws Exception {
+    try {
+      attachContext( context );
+      return callable.call();
+    } finally {
+      detachContext();
+    }
+  }
+  
+  @Override
+  public synchronized Auditor getAuditor( String auditorName, String componentName, String serviceName ) {
+    String key = auditorName + componentName + serviceName;
+    Auditor auditor = auditors.get( key );
+    if( auditor == null ) {
+      auditor = new Log4jAuditor( auditorName, componentName, serviceName );
+      auditors.put( key, auditor );
+    }
+    return auditor;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditor.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditor.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditor.java
new file mode 100644
index 0000000..bbcf9ad
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/audit/Log4jAuditor.java
@@ -0,0 +1,111 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.audit;
+
+
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+import org.apache.hadoop.gateway.audit.api.CorrelationService;
+import org.apache.hadoop.gateway.audit.log4j.correlation.Log4jCorrelationService;
+import org.apache.log4j.Logger;
+import org.apache.log4j.MDC;
+
+public class Log4jAuditor implements Auditor {
+
+  private Logger logger;
+  private String componentName;
+  private String serviceName;
+  private AuditService auditService = new Log4jAuditService();
+  private CorrelationService correlationService = new Log4jCorrelationService();
+
+  public Log4jAuditor( String loggerName, String componentName, String serviceName ) {
+    logger = Logger.getLogger( loggerName );
+    logger.setAdditivity( false );
+    this.componentName = componentName;
+    this.serviceName = serviceName;
+  }
+
+  @Override
+  public void audit( CorrelationContext correlationContext, AuditContext auditContext, String action, String resourceName, String resourceType, String outcome, String message ) {
+    CorrelationContext previousCorrelationContext = null;
+    AuditContext previousAuditContext = null;
+    try {
+      previousCorrelationContext = correlationService.getContext();
+      previousAuditContext = auditService.getContext();
+      auditService.attachContext( auditContext );
+      correlationService.attachContext( correlationContext );
+      auditLog( action, resourceName, resourceType, outcome, message );
+    } finally {
+      if ( previousAuditContext != null ) {
+        auditService.attachContext( previousAuditContext );
+      }
+      if ( previousCorrelationContext != null ) {
+        correlationService.attachContext( previousCorrelationContext );
+      }
+    }
+  }
+
+  @Override
+  public void audit( String action, String resourceName, String resourceType, String outcome, String message ) {
+    auditLog( action, resourceName, resourceType, outcome, message );
+  }
+  
+  @Override
+  public void audit( String action, String resourceName, String resourceType, String outcome ) {
+    auditLog( action, resourceName, resourceType, outcome, null );
+  }
+
+  private void auditLog( String action, String resourceName, String resourceType, String outcome, String message ) {
+    if ( logger.isInfoEnabled() ) {
+      MDC.put( AuditConstants.MDC_ACTION_KEY, action );
+      MDC.put( AuditConstants.MDC_RESOURCE_NAME_KEY, resourceName );
+      MDC.put( AuditConstants.MDC_RESOURCE_TYPE_KEY, resourceType );
+      MDC.put( AuditConstants.MDC_OUTCOME_KEY, outcome );
+      MDC.put( AuditConstants.MDC_SERVICE_KEY, serviceName );
+      MDC.put( AuditConstants.MDC_COMPONENT_KEY, componentName );
+      
+      logger.info( message );
+      
+      MDC.remove( AuditConstants.MDC_ACTION_KEY );
+      MDC.remove( AuditConstants.MDC_RESOURCE_NAME_KEY );
+      MDC.remove( AuditConstants.MDC_RESOURCE_TYPE_KEY );
+      MDC.remove( AuditConstants.MDC_OUTCOME_KEY );
+      MDC.remove( AuditConstants.MDC_SERVICE_KEY );
+      MDC.remove( AuditConstants.MDC_COMPONENT_KEY );
+    }
+  }
+
+  @Override
+  public String getComponentName() {
+    return componentName;
+  }
+
+  @Override
+  public String getServiceName() {
+    return serviceName;
+  }
+
+  @Override
+  public String getAuditorName() {
+    return logger.getName();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationContext.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationContext.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationContext.java
new file mode 100644
index 0000000..c923ac7
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationContext.java
@@ -0,0 +1,105 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.correlation;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+
+public class Log4jCorrelationContext implements Externalizable, CorrelationContext {
+
+  private byte versionUID = 1;
+  private String requestId;
+  private String parentRequestId;
+  private String rootRequestId;
+
+  public Log4jCorrelationContext() {
+  }
+
+  public Log4jCorrelationContext( String requestId, String parentRequestId,
+      String rootRequestId ) {
+    this.requestId = requestId;
+    this.parentRequestId = parentRequestId;
+    this.rootRequestId = rootRequestId;
+  }
+
+  @Override
+  public String getRequestId() {
+    return requestId;
+  }
+
+  @Override
+  public void setRequestId( String requestId ) {
+    this.requestId = requestId;
+  }
+
+  @Override
+  public String getParentRequestId() {
+    return parentRequestId;
+  }
+
+  @Override
+  public void setParentRequestId( String parentRequestId ) {
+    this.parentRequestId = parentRequestId;
+  }
+
+  @Override
+  public String getRootRequestId() {
+    return rootRequestId;
+  }
+
+  @Override
+  public void setRootRequestId( String rootRequestId ) {
+    this.rootRequestId = rootRequestId;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append( "[" );
+    sb.append( "request_id=" ).append( requestId );
+    sb.append( ", parent_request_id=" ).append( parentRequestId );
+    sb.append( ", root_request_id=" ).append( rootRequestId );
+    sb.append( "]" );
+    return sb.toString();
+  }
+
+  @Override
+  public void writeExternal(ObjectOutput out) throws IOException {
+    out.writeByte( versionUID );
+    out.writeObject( requestId );
+    out.writeObject( parentRequestId );
+    out.writeObject( rootRequestId );
+  }
+
+  @Override
+  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+    versionUID = in.readByte();
+    requestId = (String)in.readObject();
+    parentRequestId = (String)in.readObject();
+    rootRequestId = (String)in.readObject();
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationService.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationService.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationService.java
new file mode 100644
index 0000000..030e2ec
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/correlation/Log4jCorrelationService.java
@@ -0,0 +1,128 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.correlation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.concurrent.Callable;
+
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+import org.apache.hadoop.gateway.audit.api.CorrelationService;
+import org.apache.log4j.MDC;
+
+public class Log4jCorrelationService implements CorrelationService {
+  
+  public static final String MDC_CORRELATION_CONTEXT_KEY = "correlation_context";
+  
+  @Override
+  public CorrelationContext createContext() {
+    CorrelationContext context = getContext();
+    if ( context == null ) {
+      context = new Log4jCorrelationContext();
+      attachContext( context );
+    }
+    return context;
+  }
+
+  @Override
+  public CorrelationContext getContext() {
+    return (CorrelationContext) MDC.get( MDC_CORRELATION_CONTEXT_KEY );
+  }
+
+  @Override
+  public void attachContext( CorrelationContext context ) {
+    if ( context != null ) {
+      MDC.put( MDC_CORRELATION_CONTEXT_KEY, context );
+    }
+  }
+
+  @Override
+  public CorrelationContext detachContext() {
+    CorrelationContext context = (CorrelationContext) MDC.get( MDC_CORRELATION_CONTEXT_KEY );
+    MDC.remove( MDC_CORRELATION_CONTEXT_KEY );
+    return context;
+  }
+  
+  @Override
+  public <T> T execute( CorrelationContext context, Callable<T> callable ) throws Exception {
+    try {
+      attachContext( context );
+      return callable.call();
+    } finally {
+      detachContext();
+    }
+  }
+
+  @Override
+  public CorrelationContext attachExternalizedContext(byte[] externalizedContext) {
+    CorrelationContext context = readExternalizedContext( externalizedContext );
+    attachContext( context );
+    return context;
+  }
+
+  @Override
+  public byte[] detachExternalizedContext() {
+    byte[] result = getExternalizedContext();
+    detachContext();
+    return result;
+  }
+
+  @Override
+  public CorrelationContext readExternalizedContext(byte[] externalizedContext) {
+    ByteArrayInputStream bais = new ByteArrayInputStream( externalizedContext );
+    ObjectInput oi = null;
+    CorrelationContext context = null;
+    try {
+      oi = new ObjectInputStream( bais );
+      context = (CorrelationContext) oi.readObject();
+    } catch ( IOException e ) {
+      throw new IllegalArgumentException( e );
+    } catch ( ClassNotFoundException e ) {
+      throw new IllegalArgumentException( e );
+    } finally {
+      try {
+        bais.close();
+        if ( oi != null) {
+          oi.close();
+        }
+      } catch ( IOException e ) {
+        throw new IllegalArgumentException( e );
+      }
+    }
+    return context;
+  }
+
+  @Override
+  public byte[] getExternalizedContext() {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try {
+    ObjectOutputStream oos = new ObjectOutputStream( baos );
+    oos.writeObject( getContext() );
+    oos.close();
+    } catch ( IOException e ) {
+      throw new RuntimeException( e );
+    }
+    return baos.toByteArray();
+  }
+  
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/layout/AuditLayout.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/layout/AuditLayout.java b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/layout/AuditLayout.java
new file mode 100644
index 0000000..09f827c
--- /dev/null
+++ b/gateway-util-common/src/main/java/org/apache/hadoop/gateway/audit/log4j/layout/AuditLayout.java
@@ -0,0 +1,78 @@
+/**
+ * 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.hadoop.gateway.audit.log4j.layout;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.audit.log4j.audit.Log4jAuditService;
+import org.apache.hadoop.gateway.audit.log4j.correlation.Log4jCorrelationService;
+import org.apache.log4j.helpers.DateLayout;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * Formats audit record to following output:
+ * date time root_request_id|parent_request_id|request_id|channel|target_service|username|proxy_username|system_username|action|resource_type|resource_name|outcome|message
+ */
+public class AuditLayout extends DateLayout {
+  
+  private static final String DATE_FORMAT = "yy/MM/dd HH:mm:ss";
+  private static final String SEPARATOR = "|";
+  private StringBuffer sb = new StringBuffer();
+  
+  @Override
+  public void activateOptions() {
+    setDateFormat( DATE_FORMAT );
+  }
+
+  @Override
+  public String format( LoggingEvent event ) {
+    sb.setLength( 0 );
+    dateFormat( sb, event );
+    CorrelationContext cc = (CorrelationContext)event.getMDC( Log4jCorrelationService.MDC_CORRELATION_CONTEXT_KEY );
+    AuditContext ac = (AuditContext)event.getMDC( Log4jAuditService.MDC_AUDIT_CONTEXT_KEY );
+    appendParameter( cc == null ? null : cc.getRootRequestId() );
+    appendParameter( cc == null ? null : cc.getParentRequestId() );
+    appendParameter( cc == null ? null : cc.getRequestId() );
+    appendParameter( event.getLoggerName() );
+    appendParameter( ac == null ? null : ac.getTargetServiceName() );
+    appendParameter( ac == null ? null : ac.getUsername() );
+    appendParameter( ac == null ? null : ac.getProxyUsername() );
+    appendParameter( ac == null ? null : ac.getSystemUsername() );
+    appendParameter( (String)event.getMDC( AuditConstants.MDC_ACTION_KEY ) );
+    appendParameter( (String)event.getMDC( AuditConstants.MDC_RESOURCE_TYPE_KEY ) );
+    appendParameter( (String)event.getMDC( AuditConstants.MDC_RESOURCE_NAME_KEY ) );
+    appendParameter( (String)event.getMDC( AuditConstants.MDC_OUTCOME_KEY ) );
+    String message = event.getRenderedMessage();
+    sb.append( message == null ? "" : message ).append( LINE_SEP );
+    return sb.toString();
+  }
+
+  @Override
+  public boolean ignoresThrowable() {
+    return true;
+  }
+  
+  private void appendParameter( String parameter ) {
+    if ( parameter != null ) {
+      sb.append( parameter );
+    }
+    sb.append( SEPARATOR );
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditLayoutTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditLayoutTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditLayoutTest.java
new file mode 100644
index 0000000..8a74289
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditLayoutTest.java
@@ -0,0 +1,141 @@
+/**
+ * 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.hadoop.gateway.audit;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.AuditServiceFactory;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+import org.apache.hadoop.gateway.audit.api.CorrelationService;
+import org.apache.hadoop.gateway.audit.api.CorrelationServiceFactory;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.audit.log4j.layout.AuditLayout;
+import org.apache.hadoop.test.log.CollectAppender;
+import org.apache.log4j.spi.LoggingEvent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AuditLayoutTest {
+  private static AuditService auditService = AuditServiceFactory.getAuditService();
+  private static CorrelationService correlationService = CorrelationServiceFactory.getCorrelationService();
+  private static Auditor auditor = auditService.getAuditor( "audit.forward", AuditConstants.KNOX_COMPONENT_NAME, AuditConstants.KNOX_SERVICE_NAME );
+  private static AuditLayout layout = new AuditLayout();
+  
+  private static final String USERNAME = "username";
+  private static final String PROXYUSERNAME = "proxy_username";
+  private static final String SYSTEMUSERNAME = "system_username";
+  private static final String HOST_NAME = "hostname";
+  private static final String HOST_ADDRESS = "hostaddress";
+  private static final String ACTION = "action";
+  private static final String OUTCOME = "outcome";
+  private static final String RESOURCE_NAME = "resource_name";
+  private static final String RESOURCE_TYPE = "resource_type";
+  private static final String TARGET_SERVICE = "WEBHDFS";
+  private static final String MESSAGE = "message";
+  private static final String ROOT_REQUEST_ID = "1";
+  private static final String PARENT_REQUEST_ID = "2";
+  private static final String REQUEST_ID = "3";
+  private static final String EMPTY = "";
+  private static final String RECORD_PATTERN = "%s %s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s%s";
+
+  static {
+    layout.activateOptions();
+  }
+
+  @Before
+  public void setup() {
+    CollectAppender.queue.clear();
+  }
+
+  @After
+  public void cleanup() {
+    auditService.detachContext();
+    correlationService.detachContext();
+  }
+
+  @Test
+  public void testFullyFilledAuditEvent() {
+    AuditContext auditContext = auditService.createContext();
+    auditContext.setProxyUsername( PROXYUSERNAME );
+    auditContext.setSystemUsername( SYSTEMUSERNAME );
+    auditContext.setUsername( USERNAME );
+    auditContext.setRemoteHostname( HOST_NAME );
+    auditContext.setRemoteIp( HOST_ADDRESS );
+    auditContext.setTargetServiceName( TARGET_SERVICE );
+    
+    CorrelationContext correlationContext = correlationService.createContext();
+    correlationContext.setRequestId( REQUEST_ID );
+    correlationContext.setParentRequestId( PARENT_REQUEST_ID );
+    correlationContext.setRootRequestId( ROOT_REQUEST_ID );
+    auditor.audit( ACTION, RESOURCE_NAME, RESOURCE_TYPE, OUTCOME, MESSAGE );
+    
+    assertThat( CollectAppender.queue.size(), is( 1 ) );
+    LoggingEvent event = CollectAppender.queue.iterator().next();
+    SimpleDateFormat format = new SimpleDateFormat( "yy/MM/dd HH:mm:ss" );
+    String formatedDate = format.format( new Date( event.getTimeStamp() ) );
+    //14/01/24 12:40:24 1|2|3|audit.forward|WEBHDFS|username|proxy_username|system_username|action|resource_type|resource_name|outcome|message
+    String expectedOutput = String.format(
+        RECORD_PATTERN, formatedDate,
+        ROOT_REQUEST_ID, PARENT_REQUEST_ID, REQUEST_ID, "audit.forward",
+        TARGET_SERVICE, USERNAME, PROXYUSERNAME, SYSTEMUSERNAME, ACTION,
+        RESOURCE_TYPE, RESOURCE_NAME, OUTCOME, MESSAGE, AuditLayout.LINE_SEP );
+    String auditOutput = layout.format( event );
+    assertThat( auditOutput, is( expectedOutput ) );
+    
+  }
+
+  @Test
+  public void testAuditEventWithoutContexts() {
+    auditor.audit( ACTION, RESOURCE_NAME, RESOURCE_TYPE, OUTCOME, MESSAGE );
+    assertThat( CollectAppender.queue.size(), is( 1 ) );
+    LoggingEvent event = CollectAppender.queue.iterator().next();
+    SimpleDateFormat format = new SimpleDateFormat( "yy/MM/dd HH:mm:ss" );
+    String formatedDate = format.format( new Date( event.getTimeStamp() ) );
+    //14/01/24 12:41:47 |||audit.forward|||||action|resource_type|resource_name|outcome|message
+    String expectedOutput = String.format( RECORD_PATTERN, formatedDate,
+        EMPTY, EMPTY, EMPTY, "audit.forward",
+        EMPTY, EMPTY, EMPTY, EMPTY, ACTION, RESOURCE_TYPE, RESOURCE_NAME, OUTCOME, MESSAGE, AuditLayout.LINE_SEP );
+    String auditOutput = layout.format( event );
+    assertThat( auditOutput, is( expectedOutput ) );
+    
+  }
+
+  @Test
+  public void testAuditEventWithoutMessage() {
+    auditor.audit( ACTION, RESOURCE_NAME, RESOURCE_TYPE, OUTCOME );
+    assertThat( CollectAppender.queue.size(), is( 1 ) );
+    LoggingEvent event = CollectAppender.queue.iterator().next();
+    SimpleDateFormat format = new SimpleDateFormat( "yy/MM/dd HH:mm:ss" );
+    String formatedDate = format.format( new Date( event.getTimeStamp() ) );
+    //14/01/24 12:41:47 |||audit.forward|||||action|resource_type|resource_name|outcome|
+    String expectedOutput = String.format( RECORD_PATTERN, formatedDate,
+        EMPTY, EMPTY, EMPTY, "audit.forward",
+        EMPTY, EMPTY, EMPTY, EMPTY, ACTION, RESOURCE_TYPE, RESOURCE_NAME, OUTCOME, EMPTY, AuditLayout.LINE_SEP );
+    String auditOutput = layout.format( event );
+    assertThat( auditOutput, is( expectedOutput ) );
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditServiceTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditServiceTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditServiceTest.java
new file mode 100644
index 0000000..ff9fecf
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/AuditServiceTest.java
@@ -0,0 +1,167 @@
+/**
+ * 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.hadoop.gateway.audit;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNull.nullValue;
+
+import java.util.Iterator;
+import java.util.UUID;
+
+import org.apache.hadoop.gateway.audit.api.AuditContext;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.AuditServiceFactory;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.audit.api.CorrelationContext;
+import org.apache.hadoop.gateway.audit.api.CorrelationService;
+import org.apache.hadoop.gateway.audit.api.CorrelationServiceFactory;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.audit.log4j.audit.Log4jAuditService;
+import org.apache.hadoop.gateway.audit.log4j.correlation.Log4jCorrelationService;
+import org.apache.hadoop.test.log.CollectAppender;
+import org.apache.log4j.spi.LoggingEvent;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AuditServiceTest {
+  private static AuditService auditService = AuditServiceFactory.getAuditService();
+  private static CorrelationService correlationService = CorrelationServiceFactory.getCorrelationService();
+  private static Auditor auditor = AuditServiceFactory.getAuditService().getAuditor( "audit.forward", AuditConstants.KNOX_COMPONENT_NAME, AuditConstants.KNOX_SERVICE_NAME );
+  
+  private String username = "user";
+  private String proxyUsername = "proxyuser";
+  private String remoteIp = "127.0.0.1";
+  private String remoteHostname = "localhost";
+  private String targetServiceName = "service";
+  
+  @Before
+  public void cleanAppenderDb() {
+    CollectAppender.queue.clear();
+  }
+  
+  @Test
+  public void testMultipleRequestEvents() {
+    int iterations = 1000;
+    
+    AuditContext ac = auditService.createContext();
+    ac.setUsername( username );
+    ac.setProxyUsername( proxyUsername );
+    ac.setRemoteIp( remoteIp );
+    ac.setRemoteHostname( remoteHostname );
+    ac.setTargetServiceName( targetServiceName );
+    
+    CorrelationContext cc = correlationService.createContext();
+    cc.setRequestId( UUID.randomUUID().toString() );
+    cc.setParentRequestId( UUID.randomUUID().toString() );
+    cc.setRootRequestId( UUID.randomUUID().toString() );
+    
+    CollectAppender.queue.clear();
+    for( int i = 0; i < iterations; i++ ) {
+      auditor.audit( "action" + i, "resource" + i, "resource type" + i, "outcome" + i, "message" + i );
+    }
+    
+    auditService.detachContext();
+    correlationService.detachContext();
+    assertThat( CollectAppender.queue.size(), is( iterations ) );
+    
+    //Verify events number and audit/correlation parameters in each event
+    Iterator<LoggingEvent> iterator = CollectAppender.queue.iterator();
+    int counter = 0;
+    while(iterator.hasNext()) {
+      LoggingEvent event = iterator.next();
+      checkLogEventContexts( event, cc, ac );
+      
+      assertThat( (String)event.getMDC( AuditConstants.MDC_ACTION_KEY ), is( "action" + counter ) );
+      assertThat( (String)event.getMDC( AuditConstants.MDC_RESOURCE_NAME_KEY ), is( "resource" + counter ) );
+      assertThat( (String)event.getMDC( AuditConstants.MDC_RESOURCE_TYPE_KEY ), is( "resource type" + counter ) );
+      assertThat( (String)event.getMDC( AuditConstants.MDC_OUTCOME_KEY ), is( "outcome" + counter ) );
+      assertThat( (String)event.getMDC( AuditConstants.MDC_SERVICE_KEY ), is( AuditConstants.KNOX_SERVICE_NAME ) );
+      assertThat( (String)event.getMDC( AuditConstants.MDC_COMPONENT_KEY ), is( AuditConstants.KNOX_COMPONENT_NAME ) );
+      assertThat( (String)event.getRenderedMessage(), is( "message" + counter ) );
+
+      counter++;
+    }
+    assertThat( auditService.getContext(), nullValue() );
+    assertThat( correlationService.getContext(), nullValue() );
+    
+  }
+
+  @Test
+  public void testSequentialRequests() {
+    AuditContext ac = auditService.createContext();
+    ac.setUsername( username );
+    ac.setProxyUsername( proxyUsername );
+    ac.setRemoteIp( remoteIp );
+    ac.setRemoteHostname( remoteHostname );
+    ac.setTargetServiceName( targetServiceName );
+    
+    CorrelationContext cc = correlationService.createContext();
+    cc.setRequestId( UUID.randomUUID().toString() );
+    cc.setParentRequestId( UUID.randomUUID().toString() );
+    cc.setRootRequestId( UUID.randomUUID().toString() );
+
+    auditor.audit( "action", "resource", "resource type", "outcome", "message" );
+    
+    auditService.detachContext();
+    correlationService.detachContext();
+    
+    assertThat( CollectAppender.queue.size(), is( 1 ) );
+    LoggingEvent event = CollectAppender.queue.iterator().next();
+    checkLogEventContexts( event, cc, ac );
+    
+    CollectAppender.queue.clear();
+    
+    ac = auditService.createContext();
+    ac.setUsername( username + "1" );
+    ac.setProxyUsername( proxyUsername + "1" );
+    ac.setRemoteIp( remoteIp + "1" );
+    ac.setRemoteHostname( remoteHostname + "1" );
+    ac.setTargetServiceName( targetServiceName + "1" );
+    
+    cc = correlationService.createContext();
+    cc.setRequestId( UUID.randomUUID().toString() );
+    cc.setParentRequestId( UUID.randomUUID().toString() );
+    cc.setRootRequestId( UUID.randomUUID().toString() );
+    
+    auditor.audit( "action", "resource", "resource type", "outcome", "message" );
+    
+    auditService.detachContext();
+    correlationService.detachContext();
+    
+    assertThat( CollectAppender.queue.size(), is( 1 ) );
+    event = CollectAppender.queue.iterator().next();
+    checkLogEventContexts( event, cc, ac );
+  }
+  
+  private void checkLogEventContexts( LoggingEvent event, CorrelationContext expectedCorrelationContext, AuditContext expectedAuditContext ) {
+    AuditContext context = (AuditContext) event.getMDC( Log4jAuditService.MDC_AUDIT_CONTEXT_KEY );
+    assertThat( context.getUsername(), is( expectedAuditContext.getUsername() ) );
+    assertThat( context.getProxyUsername(), is( expectedAuditContext.getProxyUsername() ) );
+    assertThat( context.getSystemUsername(), is( expectedAuditContext.getSystemUsername() ) );
+    assertThat( context.getRemoteIp(), is( expectedAuditContext.getRemoteIp() ) );
+    assertThat( context.getRemoteHostname(), is( expectedAuditContext.getRemoteHostname() ) );
+    assertThat( context.getTargetServiceName(), is( expectedAuditContext.getTargetServiceName() ) );
+         
+    CorrelationContext correlationContext =  (CorrelationContext)event.getMDC( Log4jCorrelationService.MDC_CORRELATION_CONTEXT_KEY );
+    assertThat( correlationContext.getRequestId(), is( expectedCorrelationContext.getRequestId() ) );
+    assertThat( correlationContext.getRootRequestId(), is( expectedCorrelationContext.getRootRequestId() ) );
+    assertThat( correlationContext.getParentRequestId(), is( expectedCorrelationContext.getParentRequestId() ) );
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/JdbmQueueTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/JdbmQueueTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/JdbmQueueTest.java
new file mode 100644
index 0000000..4447221
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/JdbmQueueTest.java
@@ -0,0 +1,280 @@
+/**
+ * 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.hadoop.gateway.audit;
+
+import org.apache.hadoop.gateway.audit.log4j.appender.JdbmQueue;
+import org.apache.log4j.LogManager;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JdbmQueueTest {
+
+  private File file;
+  private JdbmQueue<String> queue;
+
+  @Before
+  public void setup() throws IOException {
+    LogManager.getCurrentLoggers();
+    file = new File( "target/JdbmQueueTest" );
+    cleanup();
+    queue = new JdbmQueue<String>( file );
+  }
+
+  public void cleanup() {
+    File db = new File( file.getAbsolutePath() + ".db" );
+    if( db.exists() ) {
+      assertThat( db.delete(), is( true ) );
+    }
+    File lg = new File( file.getAbsolutePath() + ".lg" );
+    if( lg.exists() ) {
+      assertThat( lg.delete(), is( true ) );
+    }
+  }
+
+  @Test
+  public void testSimple() throws IOException, InterruptedException {
+    String one = UUID.randomUUID().toString();
+    String two = UUID.randomUUID().toString();
+    String three = UUID.randomUUID().toString();
+    String four = UUID.randomUUID().toString();
+    queue.enqueue( one );
+    assertThat( queue.dequeue(), is( one ) );
+    queue.enqueue( two );
+    queue.enqueue( three );
+    assertThat( queue.dequeue(), is( two ) );
+    assertThat( queue.dequeue(), is( three ) );
+
+    final AtomicInteger counter = new AtomicInteger( 0 );
+    queue.enqueue( four );
+    queue.process( new JdbmQueue.Consumer<String>() {
+      @Override
+      public boolean consume( String s ) {
+        counter.incrementAndGet();
+        return true;
+      }
+    } );
+    assertThat( counter.get(), is( 1 ) );
+  }
+
+  @Ignore
+  @Test
+  public void testPerformanceAndStorageFootprint() throws IOException, InterruptedException {
+
+    String fill = createFillString( 100 );
+    File dbFile = new File( file.getAbsolutePath() + ".db" );
+    File lgFile = new File( file.getAbsolutePath() + ".lg" );
+
+    String s = null;
+    long writeCount = 0;
+    long writeTime = 0;
+    long before;
+
+    int iterations = 10000;
+
+    for( int i=0; i<iterations; i++ ) {
+      s = UUID.randomUUID().toString() + ":" + fill;
+      before = System.currentTimeMillis();
+      queue.enqueue( s );
+      writeTime += ( System.currentTimeMillis() - before );
+      writeCount++;
+    }
+
+    System.out.println( String.format( "Line: len=%d", s.length() ) );
+    System.out.println( String.format( "Perf: avg=%.4fs, tot=%.2fs, cnt=%d", ( (double)writeTime / (double)writeCount / 1000.0 ),  (double)writeTime/1000.0, writeCount ) );
+    System.out.println( String.format(
+        "File: db=%s, lg=%s, tot=%s, per=%s",
+        humanReadableSize( dbFile.length() ),
+        humanReadableSize( lgFile.length() ),
+        humanReadableSize( dbFile.length() + lgFile.length() ),
+        humanReadableSize( ( ( dbFile.length() + lgFile.length() ) / writeCount ) ) ) );
+  }
+
+  @Ignore
+  @Test
+  public void testFileGrowth() throws IOException, InterruptedException {
+
+    String fill = createFillString( 100 );
+    File dbFile = new File( file.getAbsolutePath() + ".db" );
+    File lgFile = new File( file.getAbsolutePath() + ".lg" );
+
+    String s = null;
+    long writeCount = 0;
+    long writeTime = 0;
+    long before;
+
+    int iterations = 10000;
+
+    for( int i=0; i<iterations; i++ ) {
+      s = UUID.randomUUID().toString() + ":" + fill;
+      before = System.currentTimeMillis();
+      queue.enqueue( s );
+      assertThat( queue.dequeue(), is( s ) );
+      writeTime += ( System.currentTimeMillis() - before );
+      writeCount++;
+    }
+
+    System.out.println( String.format( "Line: len=%d", s.length() ) );
+    System.out.println( String.format( "Perf: avg=%.4fs, tot=%.2fs, cnt=%d", ( (double)writeTime / (double)writeCount / 1000.0 ),  (double)writeTime/1000.0, writeCount ) );
+    System.out.println( String.format(
+        "File: db=%s, lg=%s, tot=%s, per=%s",
+        humanReadableSize( dbFile.length() ),
+        humanReadableSize( lgFile.length() ),
+        humanReadableSize( dbFile.length() + lgFile.length() ),
+        humanReadableSize( ( ( dbFile.length() + lgFile.length() ) / writeCount ) ) ) );
+  }
+
+  @Test( timeout = 80000 )
+  public void testConcurrentConsumer() throws InterruptedException {
+    int iterations = 1000;
+    HashSet<String> consumed = new HashSet<String>();
+    Consumer consumer = new Consumer( consumed );
+    consumer.start();
+    Producer producer1 = new Producer( iterations );
+    producer1.start();
+    Producer producer2 = new Producer( iterations );
+    producer2.start();
+    producer1.join();
+    producer2.join();
+    while (consumed.size() < iterations * 2) {
+      Thread.sleep( 5 );
+    }
+    queue.close();
+    consumer.join();
+    assertThat( consumed.size(), is( iterations * 2 ) );
+  }
+
+  @Test( timeout=80000 )
+  public void testConcurrentProcessor() throws InterruptedException {
+    int iterations = 1000;
+    HashSet<String> consumed = new HashSet<String>();
+    Processor consumer = new Processor( consumed );
+    consumer.start();
+    Producer producer1 = new Producer( iterations );
+    producer1.start();
+    Producer producer2 = new Producer( iterations );
+    producer2.start();
+    producer1.join();
+    producer2.join();
+    while (consumed.size() < iterations * 2) {
+      Thread.sleep( 5 );
+    }
+    queue.close();
+    //consumer.join();
+    assertThat( consumed.size(), is( iterations * 2 ) );
+  }
+
+  public class Producer extends Thread {
+    public int iterations;
+    public Producer( int iterations ) {
+      this.iterations = iterations;
+    }
+    public void run() {
+      try {
+        for( int i = 0; i < iterations; i++ ) {
+          queue.enqueue( UUID.randomUUID().toString() );
+        }
+      } catch ( Throwable t ) {
+        t.printStackTrace();
+      }
+    }
+  }
+
+  public class Consumer extends Thread {
+    public Set<String> consumed;
+    public Consumer( Set<String> consumed ) {
+      this.consumed = consumed;
+    }
+    public void run() {
+      try {
+        while( true ) {
+          String s = queue.dequeue();
+          if( s == null ) {
+            return;
+          } else if( consumed.contains( s ) ) {
+            System.out.println( "DUPLICATE " + s );
+            System.exit( 1 );
+          } else {
+            consumed.add( s );
+          }
+        }
+      } catch ( Throwable t ) {
+        t.printStackTrace();
+      }
+    }
+  }
+
+  public class Processor extends Thread {
+    public Set<String> consumed;
+    public Processor( Set<String> consumed ) {
+      this.consumed = consumed;
+    }
+    public void run() {
+      try {
+        final AtomicBoolean done = new AtomicBoolean( false );
+        while( !done.get() ) {
+          queue.process( new JdbmQueue.Consumer<String>() {
+            @Override
+            public boolean consume( String s ) {
+              if( s == null ) {
+                done.set( true );
+              } else if( consumed.contains( s ) ) {
+                System.out.println( "DUPLICATE " + s );
+                System.exit( 1 );
+              } else {
+                consumed.add( s );
+              }
+              return true;
+            }
+          } );
+        }
+      } catch ( Throwable t ) {
+        t.printStackTrace();
+      }
+    }
+  }
+
+  public static String humanReadableSize( long size ) {
+    if(size <= 0) return "0";
+    final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
+    int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
+    return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
+  }
+
+  public static String createFillString( int size ) {
+    StringBuilder s = new StringBuilder();
+    for( int i=0; i<size; i++ ) {
+      s.append( UUID.randomUUID().toString() );
+      s.append( "+" );
+    }
+    return s.toString();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/StoreAndForwardAppenderTest.java
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/StoreAndForwardAppenderTest.java b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/StoreAndForwardAppenderTest.java
new file mode 100644
index 0000000..524cb1d
--- /dev/null
+++ b/gateway-util-common/src/test/java/org/apache/hadoop/gateway/audit/StoreAndForwardAppenderTest.java
@@ -0,0 +1,61 @@
+/**
+ * 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.hadoop.gateway.audit;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.hadoop.test.log.CollectAppender;
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+import org.junit.Before;
+import org.junit.Test;
+
+public class StoreAndForwardAppenderTest {
+
+  @Before
+  public void setup() throws IOException {
+    String absolutePath = "target/audit";
+    File db = new File( absolutePath + ".db" );
+    if( db.exists() ) {
+      assertThat( db.delete(), is( true ) );
+    }
+    File lg = new File( absolutePath + ".lg" );
+    if( lg.exists() ) {
+      assertThat( lg.delete(), is( true ) );
+    }
+  }
+  
+  @Test(timeout = 500000)
+  public void testAppender() throws Exception {
+    int iterations = 1000;
+    Logger logger = Logger.getLogger( "audit.store" );
+    for( int i = 1; i <= iterations; i++ ) {
+      logger.info( Integer.toString( i ) );
+    }
+    while( CollectAppender.queue.size() < iterations ) {
+    }
+    assertThat( CollectAppender.queue.size(), is( iterations ) );
+    Appender appender = (Appender)logger.getAllAppenders().nextElement();
+    appender.close();
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/gateway-util-common/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/gateway-util-common/src/test/resources/log4j.properties b/gateway-util-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..050f695
--- /dev/null
+++ b/gateway-util-common/src/test/resources/log4j.properties
@@ -0,0 +1,25 @@
+# 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.
+
+log4j.rootLogger=ERROR
+
+log4j.logger.audit.store = INFO, audit-store
+log4j.logger.audit.forward = INFO, audit-forward
+
+log4j.appender.audit-store = org.apache.hadoop.gateway.audit.log4j.appender.JdbmStoreAndForwardAppender
+log4j.appender.audit-store.file = target/audit
+
+log4j.appender.audit-forward = org.apache.hadoop.test.log.CollectAppender
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/f69acbbc/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 7b600dc..1cdad96 100644
--- a/pom.xml
+++ b/pom.xml
@@ -797,7 +797,11 @@
                 <artifactId>shrinkwrap-descriptors-impl-javaee</artifactId>
                 <version>2.0.0-alpha-4</version>
             </dependency>
-
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-jdbm</artifactId>
+                <version>2.0.0-M5</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.directory.server</groupId>
                 <artifactId>apacheds-all</artifactId>


Mime
View raw message