]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java
First tests with LDAP
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / security / log4j / SecureLogger.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.security.log4j;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.Map;
26 import java.util.Properties;
27 import java.util.concurrent.BlockingQueue;
28 import java.util.concurrent.LinkedBlockingQueue;
29
30 import org.apache.log4j.AppenderSkeleton;
31 import org.apache.log4j.Level;
32 import org.apache.log4j.LogManager;
33 import org.apache.log4j.Logger;
34 import org.apache.log4j.PropertyConfigurator;
35 import org.apache.log4j.spi.LoggingEvent;
36 import org.argeo.ArgeoException;
37 import org.argeo.ArgeoLogListener;
38 import org.argeo.ArgeoLogger;
39 import org.argeo.security.SecurityUtils;
40
41 /** Not meant to be used directly in standard log4j config */
42 public class SecureLogger implements ArgeoLogger {
43
44 private Boolean disabled = false;
45
46 private String level = null;
47
48 private Level log4jLevel = null;
49 // private Layout layout;
50
51 private Properties configuration;
52
53 private AppenderImpl appender;
54
55 private final List<ArgeoLogListener> everythingListeners = Collections
56 .synchronizedList(new ArrayList<ArgeoLogListener>());
57 private final List<ArgeoLogListener> allUsersListeners = Collections
58 .synchronizedList(new ArrayList<ArgeoLogListener>());
59 private final Map<String, List<ArgeoLogListener>> userListeners = Collections
60 .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
61
62 private BlockingQueue<LogEvent> events;
63 private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
64
65 private Integer maxLastEventsCount = 10 * 1000;
66
67 /** Marker to prevent stack overflow */
68 private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
69
70 @Override
71 protected Boolean initialValue() {
72 return false;
73 }
74 };
75
76 public void init() {
77 try {
78 events = new LinkedBlockingQueue<LogEvent>();
79
80 // if (layout != null)
81 // setLayout(layout);
82 // else
83 // setLayout(new PatternLayout(pattern));
84 appender = new AppenderImpl();
85 reloadConfiguration();
86 Logger.getRootLogger().addAppender(appender);
87
88 logDispatcherThread = new LogDispatcherThread();
89 logDispatcherThread.start();
90 } catch (Exception e) {
91 throw new ArgeoException("Cannot initialize log4j");
92 }
93 }
94
95 public void destroy() throws Exception {
96 Logger.getRootLogger().removeAppender(appender);
97 allUsersListeners.clear();
98 for (List<ArgeoLogListener> lst : userListeners.values())
99 lst.clear();
100 userListeners.clear();
101
102 events.clear();
103 events = null;
104 logDispatcherThread.interrupt();
105 }
106
107 // public void setLayout(Layout layout) {
108 // this.layout = layout;
109 // }
110
111 public synchronized void register(ArgeoLogListener listener,
112 Integer numberOfPreviousEvents) {
113 String username = SecurityUtils.getCurrentThreadUsername();
114 if (username == null)
115 throw new ArgeoException(
116 "Only authenticated users can register a log listener");
117
118 if (!userListeners.containsKey(username)) {
119 List<ArgeoLogListener> lst = Collections
120 .synchronizedList(new ArrayList<ArgeoLogListener>());
121 userListeners.put(username, lst);
122 }
123 userListeners.get(username).add(listener);
124 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username,
125 numberOfPreviousEvents);
126 for (LogEvent evt : lastEvents)
127 dispatchEvent(listener, evt);
128 }
129
130 public synchronized void registerForAll(ArgeoLogListener listener,
131 Integer numberOfPreviousEvents, boolean everything) {
132 if (everything)
133 everythingListeners.add(listener);
134 else
135 allUsersListeners.add(listener);
136 List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null,
137 numberOfPreviousEvents);
138 for (LogEvent evt : lastEvents)
139 if (everything || evt.getUsername() != null)
140 dispatchEvent(listener, evt);
141 }
142
143 public synchronized void unregister(ArgeoLogListener listener) {
144 String username = SecurityUtils.getCurrentThreadUsername();
145 if (!userListeners.containsKey(username))
146 throw new ArgeoException("No user listeners " + listener
147 + " registered for user " + username);
148 if (!userListeners.get(username).contains(listener))
149 throw new ArgeoException("No user listeners " + listener
150 + " registered for user " + username);
151 userListeners.get(username).remove(listener);
152 if (userListeners.get(username).isEmpty())
153 userListeners.remove(username);
154
155 }
156
157 public synchronized void unregisterForAll(ArgeoLogListener listener) {
158 everythingListeners.remove(listener);
159 allUsersListeners.remove(listener);
160 }
161
162 /** For development purpose, since using regular logging is not easy here */
163 static void stdOut(Object obj) {
164 System.out.println(obj);
165 }
166
167 // public void setPattern(String pattern) {
168 // this.pattern = pattern;
169 // }
170
171 public void setDisabled(Boolean disabled) {
172 this.disabled = disabled;
173 }
174
175 public void setLevel(String level) {
176 this.level = level;
177 }
178
179 public void setConfiguration(Properties configuration) {
180 this.configuration = configuration;
181 }
182
183 public void updateConfiguration(Properties configuration) {
184 setConfiguration(configuration);
185 reloadConfiguration();
186 }
187
188 public Properties getConfiguration() {
189 return configuration;
190 }
191
192 /** Reloads configuration (if the configuration {@link Properties} is set) */
193 protected void reloadConfiguration() {
194 if (configuration != null) {
195 LogManager.resetConfiguration();
196 PropertyConfigurator.configure(configuration);
197 }
198 }
199
200 protected synchronized void processLoggingEvent(LogEvent event) {
201 if (disabled)
202 return;
203
204 if (dispatching.get())
205 return;
206
207 if (level != null && !level.trim().equals("")) {
208 if (log4jLevel == null || !log4jLevel.toString().equals(level))
209 try {
210 log4jLevel = Level.toLevel(level);
211 } catch (Exception e) {
212 System.err
213 .println("Log4j level could not be set for level '"
214 + level + "', resetting it to null.");
215 e.printStackTrace();
216 level = null;
217 }
218
219 if (log4jLevel != null
220 && !event.getLoggingEvent().getLevel()
221 .isGreaterOrEqual(log4jLevel)) {
222 return;
223 }
224 }
225
226 try {
227 // admin listeners
228 Iterator<ArgeoLogListener> everythingIt = everythingListeners
229 .iterator();
230 while (everythingIt.hasNext())
231 dispatchEvent(everythingIt.next(), event);
232
233 if (event.getUsername() != null) {
234 Iterator<ArgeoLogListener> allUsersIt = allUsersListeners
235 .iterator();
236 while (allUsersIt.hasNext())
237 dispatchEvent(allUsersIt.next(), event);
238
239 if (userListeners.containsKey(event.getUsername())) {
240 Iterator<ArgeoLogListener> userIt = userListeners.get(
241 event.getUsername()).iterator();
242 while (userIt.hasNext())
243 dispatchEvent(userIt.next(), event);
244 }
245 }
246 } catch (Exception e) {
247 stdOut("Cannot process logging event");
248 e.printStackTrace();
249 }
250 }
251
252 protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
253 LoggingEvent event = evt.getLoggingEvent();
254 logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event
255 .getLevel().toString(), event.getLoggerName(), event
256 .getThreadName(), event.getMessage(), event
257 .getThrowableStrRep());
258 }
259
260 private class AppenderImpl extends AppenderSkeleton {
261 public boolean requiresLayout() {
262 return false;
263 }
264
265 public void close() {
266 }
267
268 @Override
269 protected void append(LoggingEvent event) {
270 if (events != null) {
271 try {
272 String username = SecurityUtils.getCurrentThreadUsername();
273 events.put(new LogEvent(username, event));
274 } catch (InterruptedException e) {
275 // silent
276 }
277 }
278 }
279
280 }
281
282 private class LogDispatcherThread extends Thread {
283 /** encapsulated in order to simplify concurrency management */
284 private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
285
286 public LogDispatcherThread() {
287 super("Argeo Logging Dispatcher Thread");
288 }
289
290 public void run() {
291 while (events != null) {
292 try {
293 LogEvent loggingEvent = events.take();
294 processLoggingEvent(loggingEvent);
295 addLastEvent(loggingEvent);
296 } catch (InterruptedException e) {
297 if (events == null)
298 return;
299 }
300 }
301 }
302
303 protected synchronized void addLastEvent(LogEvent loggingEvent) {
304 if (lastEvents.size() >= maxLastEventsCount)
305 lastEvents.poll();
306 lastEvents.add(loggingEvent);
307 }
308
309 public synchronized List<LogEvent> getLastEvents(String username,
310 Integer maxCount) {
311 LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
312 ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents
313 .size());
314 int count = 0;
315 while (it.hasPrevious() && (count < maxCount)) {
316 LogEvent evt = it.previous();
317 if (username == null || username.equals(evt.getUsername())) {
318 evts.push(evt);
319 count++;
320 }
321 }
322 return evts;
323 }
324 }
325
326 private class LogEvent {
327 private final String username;
328 private final LoggingEvent loggingEvent;
329
330 public LogEvent(String username, LoggingEvent loggingEvent) {
331 super();
332 this.username = username;
333 this.loggingEvent = loggingEvent;
334 }
335
336 @Override
337 public int hashCode() {
338 return loggingEvent.hashCode();
339 }
340
341 @Override
342 public boolean equals(Object obj) {
343 return loggingEvent.equals(obj);
344 }
345
346 @Override
347 public String toString() {
348 return username + "@ " + loggingEvent.toString();
349 }
350
351 public String getUsername() {
352 return username;
353 }
354
355 public LoggingEvent getLoggingEvent() {
356 return loggingEvent;
357 }
358
359 }
360 }