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