1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.logging;
21  
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.net.SocketAddress;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.concurrent.CountDownLatch;
29  
30  import junit.framework.TestCase;
31  
32  import org.apache.log4j.AppenderSkeleton;
33  import org.apache.log4j.Level;
34  import org.apache.log4j.spi.LoggingEvent;
35  import org.apache.mina.common.ConnectFuture;
36  import org.apache.mina.common.DefaultIoFilterChainBuilder;
37  import org.apache.mina.common.IdleStatus;
38  import org.apache.mina.common.IoBuffer;
39  import org.apache.mina.common.IoFilterAdapter;
40  import org.apache.mina.common.IoHandlerAdapter;
41  import org.apache.mina.common.IoSession;
42  import org.apache.mina.filter.codec.ProtocolCodecFactory;
43  import org.apache.mina.filter.codec.ProtocolCodecFilter;
44  import org.apache.mina.filter.codec.ProtocolDecoder;
45  import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
46  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
47  import org.apache.mina.filter.codec.ProtocolEncoder;
48  import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
49  import org.apache.mina.filter.codec.ProtocolEncoderOutput;
50  import org.apache.mina.filter.executor.ExecutorFilter;
51  import org.apache.mina.filter.statistic.ProfilerTimerFilter;
52  import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
53  import org.apache.mina.transport.socket.nio.NioSocketConnector;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  /**
58   * Tests {@link MdcInjectionFilter} in variuos scenarios.
59   *
60   * @author The Apache MINA Project (dev@mina.apache.org)
61   * @version $Rev: 600461 $, $Date: 2007-12-03 02:55:52 -0700 (Mon, 03 Dec 2007) $
62   */
63  public class MdcInjectionFilterTest extends TestCase {
64  
65      private static Logger logger = LoggerFactory.getLogger(MdcInjectionFilterTest.class);
66      private static final int PORT = 7475;
67      private static final int TIMEOUT = 5000;
68  
69      private MyAppender appender = new MyAppender();
70      private NioSocketAcceptor acceptor;
71  
72      @Override
73      protected void setUp() throws Exception {
74          super.setUp();
75          // comment out next line if you want to see normal logging
76          org.apache.log4j.Logger.getRootLogger().removeAllAppenders();
77          org.apache.log4j.Logger.getRootLogger().setLevel(Level.DEBUG);
78          org.apache.log4j.Logger.getRootLogger().addAppender(appender);
79          acceptor = new NioSocketAcceptor();
80      }
81  
82  
83      @Override
84      protected void tearDown() throws Exception {
85          acceptor.dispose();
86          super.tearDown();
87      }
88  
89      public void testSimpleChain() throws IOException, InterruptedException {
90          DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
91          chain.addFirst("mdc-injector", new MdcInjectionFilter());
92          chain.addLast("dummy", new DummyIoFilter());
93          chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
94          test(chain);
95      }
96  
97      public void testExecutorFilterAtTheEnd() throws IOException, InterruptedException {
98          DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
99          MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
100         chain.addFirst("mdc-injector1", mdcInjectionFilter);
101         chain.addLast("dummy", new DummyIoFilter());
102         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
103         chain.addLast("executor" , new ExecutorFilter());
104         chain.addLast("mdc-injector2", mdcInjectionFilter);
105         test(chain);
106     }
107 
108     public void testExecutorFilterAtBeginning() throws IOException, InterruptedException {
109         DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
110         MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
111         chain.addLast("executor" , new ExecutorFilter());
112         chain.addLast("mdc-injector", mdcInjectionFilter);
113         chain.addLast("dummy", new DummyIoFilter());
114         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
115         test(chain);
116     }
117 
118     public void testExecutorFilterBeforeProtocol() throws IOException, InterruptedException {
119         DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
120         MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
121         chain.addLast("executor" , new ExecutorFilter());
122         chain.addLast("mdc-injector", mdcInjectionFilter);
123         chain.addLast("dummy", new DummyIoFilter());
124         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
125         test(chain);
126     }
127 
128     public void testMultipleFilters() throws IOException, InterruptedException {
129         DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
130         MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
131         chain.addLast("executor" , new ExecutorFilter());
132         chain.addLast("mdc-injector", mdcInjectionFilter);
133         chain.addLast("profiler", new ProfilerTimerFilter());
134         chain.addLast("dummy", new DummyIoFilter());
135         chain.addLast("logger", new LoggingFilter());
136         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
137         test(chain);
138     }
139 
140 
141     public void testTwoExecutorFilters() throws IOException, InterruptedException {
142         DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
143         MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
144         chain.addLast("executor1" , new ExecutorFilter());
145         chain.addLast("mdc-injector1", mdcInjectionFilter);
146         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
147         chain.addLast("dummy", new DummyIoFilter());
148         chain.addLast("executor2" , new ExecutorFilter());
149         chain.addLast("mdc-injector2", mdcInjectionFilter);
150         test(chain);
151     }
152 
153     public void testOnlyRemoteAddress() throws IOException, InterruptedException {
154         DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder();
155         chain.addFirst("mdc-injector", new MdcInjectionFilter(
156             MdcInjectionFilter.MdcKey.remoteAddress));
157         chain.addLast("dummy", new DummyIoFilter());
158         chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory()));
159         SimpleIoHandler simpleIoHandler = new SimpleIoHandler();
160         acceptor.setHandler(simpleIoHandler);
161         acceptor.bind(new InetSocketAddress(PORT));
162         acceptor.setFilterChainBuilder(chain);
163         // create some clients
164         NioSocketConnector connector = new NioSocketConnector();
165         connector.setHandler(new IoHandlerAdapter());
166         SocketAddress remoteAddressClients[] = new SocketAddress[2];
167         remoteAddressClients[0] = connectAndWrite(connector,0);
168         remoteAddressClients[1] = connectAndWrite(connector,1);
169         // wait until Iohandler has received all events
170         simpleIoHandler.messageSentLatch.await();
171         simpleIoHandler.sessionIdleLatch.await();
172         simpleIoHandler.sessionClosedLatch.await();
173         // make a copy to prevent ConcurrentModificationException
174         List<LoggingEvent> events = new ArrayList<LoggingEvent>(appender.events);
175         // verify that all logging events have correct MDC
176         for (LoggingEvent event : events) {
177             for (MdcInjectionFilter.MdcKey mdcKey : MdcInjectionFilter.MdcKey.values()) {
178               String key = mdcKey.name();
179               Object value = event.getMDC(key);
180               if (mdcKey == MdcInjectionFilter.MdcKey.remoteAddress) {
181                   assertNotNull(
182                       "MDC[remoteAddress] not set for [" + event.getMessage() + "]", value);
183               } else {
184                   assertNull("MDC[" + key + "] set for [" + event.getMessage() + "]", value);
185               }
186             }
187         }
188     }
189 
190     private void test(DefaultIoFilterChainBuilder chain) throws IOException, InterruptedException {
191         // configure the server
192         SimpleIoHandler simpleIoHandler = new SimpleIoHandler();
193         acceptor.setHandler(simpleIoHandler);
194         acceptor.bind(new InetSocketAddress(PORT));
195         acceptor.setFilterChainBuilder(chain);
196         // create some clients
197         NioSocketConnector connector = new NioSocketConnector();
198         connector.setHandler(new IoHandlerAdapter());
199         SocketAddress remoteAddressClients[] = new SocketAddress[2];
200         remoteAddressClients[0] = connectAndWrite(connector,0);
201         remoteAddressClients[1] = connectAndWrite(connector,1);
202         // wait until Iohandler has received all events
203         simpleIoHandler.messageSentLatch.await();
204         simpleIoHandler.sessionIdleLatch.await();
205         simpleIoHandler.sessionClosedLatch.await();
206 
207         // make a copy to prevent ConcurrentModificationException
208         List<LoggingEvent> events = new ArrayList<LoggingEvent>(appender.events);
209 
210         // verify that all logging events have correct MDC
211         for (LoggingEvent event : events) {
212             if (!ExecutorFilter.class.getName().equals(event.getLoggerName())) {
213                 Object remoteAddress = event.getMDC("remoteAddress");
214                 assertNotNull(
215                     "MDC[remoteAddress] not set for [" + event.getMessage() + "]",
216                     remoteAddress);
217                 assertNotNull(
218                     "MDC[remotePort] not set for [" + event.getMessage() + "]",
219                     event.getMDC("remotePort"));
220                 assertEquals(
221                     "every event should have MDC[handlerClass]",
222                     SimpleIoHandler.class.getName(),
223                     event.getMDC("handlerClass") );
224             }
225         }
226         // asert we have received all expected logging events for each client
227         for (int i = 0; i < remoteAddressClients.length; i++) {
228             SocketAddress remoteAddressClient = remoteAddressClients[i];
229             assertEventExists(events, "sessionCreated", remoteAddressClient, null);
230             assertEventExists(events, "sessionOpened", remoteAddressClient, null);
231             assertEventExists(events, "decode", remoteAddressClient, null);
232             assertEventExists(events, "messageReceived-1", remoteAddressClient, null);
233             assertEventExists(events, "messageReceived-2", remoteAddressClient, "user-" + i);
234             assertEventExists(events, "encode", remoteAddressClient, null);
235             assertEventExists(events, "exceptionCaught", remoteAddressClient, "user-" + i);
236             assertEventExists(events, "messageSent-1", remoteAddressClient, "user-" + i);
237             assertEventExists(events, "messageSent-2", remoteAddressClient, null);
238             assertEventExists(events, "sessionIdle", remoteAddressClient, "user-" + i);
239             assertEventExists(events, "sessionClosed", remoteAddressClient, "user-" + i);
240             assertEventExists(events, "sessionClosed", remoteAddressClient, "user-" + i);
241             assertEventExists(events, "DummyIoFilter.sessionOpened", remoteAddressClient, "user-" + i);
242         }
243     }
244 
245     private SocketAddress connectAndWrite(NioSocketConnector connector, int clientNr) {
246         ConnectFuture connectFuture = connector.connect(new InetSocketAddress("localhost",PORT));
247         connectFuture.awaitUninterruptibly(TIMEOUT);
248         IoBuffer message = IoBuffer.allocate(4).putInt(clientNr).flip();
249         IoSession session = connectFuture.getSession();
250         session.write(message).awaitUninterruptibly(TIMEOUT);
251         return session.getLocalAddress();
252     }
253 
254     private void assertEventExists(List<LoggingEvent> events,
255                                    String message,
256                                    SocketAddress address,
257                                    String user) {
258         InetSocketAddress remoteAddress = (InetSocketAddress) address;
259         for (LoggingEvent event : events) {
260             if (event.getMessage().equals(message) &&
261                 event.getMDC("remoteAddress").equals(remoteAddress.toString()) &&
262                 event.getMDC("remoteIp").equals(remoteAddress.getAddress().getHostAddress()) &&
263                 event.getMDC("remotePort").equals(remoteAddress.getPort()+"") ) {
264                 if (user == null && event.getMDC("user") == null) {
265                     return;
266                 }
267                 if (user != null && user.equals(event.getMDC("user"))) {
268                     return;
269                 }
270                 return;
271             }
272         }
273         fail("No LoggingEvent found from [" + remoteAddress +"] with message [" + message + "]");
274     }
275 
276     private static class SimpleIoHandler extends IoHandlerAdapter {
277 
278         CountDownLatch sessionIdleLatch = new CountDownLatch(2);
279         CountDownLatch sessionClosedLatch = new CountDownLatch(2);
280         CountDownLatch messageSentLatch = new CountDownLatch(2);
281 
282         @Override
283         public void sessionCreated(IoSession session) throws Exception {
284             logger.info("sessionCreated");
285             session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 1);
286         }
287 
288         @Override
289         public void sessionOpened(IoSession session) throws Exception {
290             logger.info("sessionOpened");
291         }
292 
293         @Override
294         public void sessionClosed(IoSession session) throws Exception {
295             logger.info("sessionClosed");
296             sessionClosedLatch.countDown();
297         }
298 
299         @Override
300         public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
301             logger.info("sessionIdle");
302             sessionIdleLatch.countDown();
303             session.close();
304         }
305 
306         @Override
307         public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
308             logger.info("exceptionCaught", cause);
309         }
310 
311         @Override
312         public void messageReceived(IoSession session, Object message) throws Exception {
313             logger.info("messageReceived-1");
314             // adding a custom property to the context
315             String user = "user-" + message;
316             MdcInjectionFilter.setProperty(session, "user", user);
317             logger.info("messageReceived-2");
318             session.write(message);
319             throw new RuntimeException("just a test, forcing exceptionCaught");
320         }
321 
322         @Override
323         public void messageSent(IoSession session, Object message) throws Exception {
324             logger.info("messageSent-1");
325             MdcInjectionFilter.removeProperty(session, "user");
326             logger.info("messageSent-2");
327             messageSentLatch.countDown();
328         }
329     }
330 
331     private static class DummyProtocolCodecFactory implements ProtocolCodecFactory {
332 
333         public ProtocolEncoder getEncoder(IoSession session) throws Exception {
334             return new ProtocolEncoderAdapter() {
335                 public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
336                     logger.info("encode");
337                     IoBuffer buffer = IoBuffer.allocate(4).putInt(123).flip();
338                     out.write(buffer);
339                 }
340             };
341         }
342 
343         public ProtocolDecoder getDecoder(IoSession session) throws Exception {
344             return new ProtocolDecoderAdapter() {
345                 public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
346                     if (in.remaining() >= 4) {
347                         int value = in.getInt();
348                         logger.info("decode");
349                         out.write(value);
350                     }
351                 }
352             };
353         }
354     }
355 
356     private static class MyAppender extends AppenderSkeleton {
357 
358         List<LoggingEvent> events = Collections.synchronizedList(new ArrayList<LoggingEvent>());
359 
360         @Override
361         protected void append(final LoggingEvent loggingEvent) {
362             loggingEvent.getMDCCopy();
363             events.add(loggingEvent);
364         }
365 
366         @Override
367         public boolean requiresLayout() {
368             return false;
369         }
370 
371         @Override
372         public void close() {
373         }
374     }
375 
376     private static class DummyIoFilter extends IoFilterAdapter {
377         @Override
378         public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
379             logger.info("DummyIoFilter.sessionOpened");
380             nextFilter.sessionOpened(session);
381         }
382     }
383 
384 }