From 6bb0606505be3e99021c5ff9771c719eb1e1f2e7 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 22 Feb 2012 15:39:39 +0000 Subject: [PATCH] Improve secure logging git-svn-id: https://svn.argeo.org/commons/trunk@5111 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../src/main/java/org/argeo/ArgeoLogger.java | 31 ++ .../org.argeo.osgi.ui.explorer/plugin.xml | 14 +- .../org.argeo.security.ui.admin/plugin.xml | 14 +- .../META-INF/spring/commands.xml | 4 - .../META-INF/spring/monitor.xml | 3 +- .../META-INF/spring/osgi.xml | 3 +- .../META-INF/spring/views.xml | 17 + .../org.argeo.security.ui/icons/adminLog.gif | Bin 0 -> 617 bytes .../org.argeo.security.ui/icons/log.gif | Bin 0 -> 572 bytes .../icons/maintenance.gif | Bin 0 -> 583 bytes .../plugins/org.argeo.security.ui/plugin.xml | 45 ++- .../security/ui/MaintenancePerspective.java | 30 ++ .../security/ui/UserHomePerspective.java | 9 +- .../argeo/security/ui/views/AdminLogView.java | 79 +++++ .../security/ui/views/LogContentProvider.java | 69 +++- .../org/argeo/security/ui/views/LogView.java | 22 +- .../org/argeo/security/SecurityUtils.java | 46 +++ .../argeo/security/log4j/SecureLogger.java | 306 ++++++++++++++---- .../org.argeo.jcr.ui.explorer/plugin.xml | 15 +- .../commands/AddRemoteRepository.java | 5 +- 20 files changed, 563 insertions(+), 149 deletions(-) create mode 100644 basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java create mode 100644 security/plugins/org.argeo.security.ui/META-INF/spring/views.xml create mode 100644 security/plugins/org.argeo.security.ui/icons/adminLog.gif create mode 100644 security/plugins/org.argeo.security.ui/icons/log.gif create mode 100644 security/plugins/org.argeo.security.ui/icons/maintenance.gif create mode 100644 security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java create mode 100644 security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java diff --git a/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java new file mode 100644 index 000000000..c72c537ff --- /dev/null +++ b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/ArgeoLogger.java @@ -0,0 +1,31 @@ +package org.argeo; + +/** + * Logging framework agnostic identifying a logging service, to which one can + * register + */ +public interface ArgeoLogger { + /** + * Register for events by threads with the same authentication (or all + * threads if admin) + */ + public void register(ArgeoLogListener listener, + Integer numberOfPreviousEvents); + + /** + * For admin use only: register for all users + * + * @param listener + * the log listener + * @param numberOfPreviousEvents + * the number of previous events to notify + * @param everything + * if true even anonymous is logged + */ + public void registerForAll(ArgeoLogListener listener, + Integer numberOfPreviousEvents, boolean everything); + + public void unregister(ArgeoLogListener listener); + + public void unregisterForAll(ArgeoLogListener listener); +} diff --git a/osgi/plugins/org.argeo.osgi.ui.explorer/plugin.xml b/osgi/plugins/org.argeo.osgi.ui.explorer/plugin.xml index 27947067b..848025bbd 100644 --- a/osgi/plugins/org.argeo.osgi.ui.explorer/plugin.xml +++ b/osgi/plugins/org.argeo.osgi.ui.explorer/plugin.xml @@ -36,20 +36,8 @@ - - - - - - - - - diff --git a/security/plugins/org.argeo.security.ui.admin/plugin.xml b/security/plugins/org.argeo.security.ui.admin/plugin.xml index 9d0987958..bcb80ebd9 100644 --- a/security/plugins/org.argeo.security.ui.admin/plugin.xml +++ b/security/plugins/org.argeo.security.ui.admin/plugin.xml @@ -126,21 +126,9 @@ - - - - - - - - - diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml index 1dc8d53ce..3b2e6ccb0 100644 --- a/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml +++ b/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml @@ -4,8 +4,4 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - - - diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/monitor.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/monitor.xml index f2faeb2fd..93a370de4 100644 --- a/security/plugins/org.argeo.security.ui/META-INF/spring/monitor.xml +++ b/security/plugins/org.argeo.security.ui/META-INF/spring/monitor.xml @@ -5,9 +5,8 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - - + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/views.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/views.xml new file mode 100644 index 000000000..e5363dcc0 --- /dev/null +++ b/security/plugins/org.argeo.security.ui/META-INF/spring/views.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/security/plugins/org.argeo.security.ui/icons/adminLog.gif b/security/plugins/org.argeo.security.ui/icons/adminLog.gif new file mode 100644 index 0000000000000000000000000000000000000000..6ef3bca66b88efa91777bdbb7b5a9b5697144fb1 GIT binary patch literal 617 zcmZ?wbhEHb6krfwc*el+Ofs_Z$H{3tn@c4AYYk9xOzQu~4ui1RM;q&W3;<1%R$5xyE zx*PTJl+(vk&d;_!jYtQ?4IQxSd{Q{dA+`vrSSTcd32crG9L-(ywFMM`tPR z>k&OLQT)g>xg*mR4s?q=SuA;|U;5Dk$%pf$wv}@4sFvSRBfq0sVMn#%=1RrKZJY;7 zg$|YoA1D&pQy{W7p6zKR`^E_N|Ns9p5CV!nSr{1@N*HuN3PEwgz<#WuM4r2)wT)Z8 zq>G2Wx38a_r>lgQLvQ+w863Qm`9$<|wHUOtXU*y2=hS0kVP@3#NsNn4^XU>3)zi?> z(QuFUGPZJwa4!*%UCqYI7$4+oayX)}L{LFbMO~dKJITq+BOs@sL|j!*Q%;#H!_U^p z!Z|X(L`Y3d>APaO|9i_Lc`pi1OkiX_$dkG3O~=8-9MjAuixkc@+0oK1W{|w-%pFNX0S0RT3Bd~u literal 0 HcmV?d00001 diff --git a/security/plugins/org.argeo.security.ui/icons/log.gif b/security/plugins/org.argeo.security.ui/icons/log.gif new file mode 100644 index 0000000000000000000000000000000000000000..e3ecc5535cd4d72cbb9cce9843f054714992babe GIT binary patch literal 572 zcmZ?wbhEHb6krfwc*el+^XH!*KmY#t`6sb(R%Yd*uiyWqS1ibDSW(=%wxVle!=#$il~ZV0ViA6l~}v}$E&<+9MqrP=*^t7qTaBgaDX>Dt7 zvr_Nw=~cIC*5H{uWh#$GvnH?8oVjy(HJi1BoE(`LnB*9l7zMPObp@UHxj9&v9n@F_ zb(-~son#LkJ|wKyEHAN{pNo^7jagj8pxIE;Nyg4j=BA9jsA02_l9MW*m>AzHF)5>F x6J=!;Woa8_TkDU;&6cMB{wrj$teN;Ypie+CN5rIp+1Z{ zte(79BW{s;%v>eEHucD<=9Q;4qo%8e_FLAR(G2a?4C!)dyW-Mv*?+>_=-JO>WYf52F?(b)t($C)REt}>W?h)U~(e5ClnktUkMT;2@HKh{v35>(HjH|ZVEAy7f|8hCNI=rY + icon="icons/log.gif" + restorable="false"> + + + + + + + + + + + + + + + + + + + diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java new file mode 100644 index 000000000..0cfdda7a4 --- /dev/null +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/MaintenancePerspective.java @@ -0,0 +1,30 @@ +package org.argeo.security.ui; + +import org.argeo.security.ui.views.AdminLogView; +import org.argeo.security.ui.views.UserProfile; +import org.eclipse.ui.IFolderLayout; +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.IPerspectiveFactory; + +/** Home perspective for the current user */ +public class MaintenancePerspective implements IPerspectiveFactory { + public final static String ID = SecurityUiPlugin.PLUGIN_ID + + ".adminMaintenancePerspective"; + + public void createInitialLayout(IPageLayout layout) { + String editorArea = layout.getEditorArea(); + layout.setEditorAreaVisible(true); + layout.setFixed(false); + + IFolderLayout bottom = layout.createFolder("bottom", + IPageLayout.BOTTOM, 0.50f, editorArea); + bottom.addView(AdminLogView.ID); + + IFolderLayout left = layout.createFolder("left", IPageLayout.LEFT, + 0.30f, editorArea); + left.addView(UserProfile.ID); + // left.addView(RolesView.ID); + + } + +} diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/UserHomePerspective.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/UserHomePerspective.java index 1ce7364e8..1cf4dd386 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/UserHomePerspective.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/UserHomePerspective.java @@ -8,7 +8,8 @@ import org.eclipse.ui.IPerspectiveFactory; /** Home perspective for the current user */ public class UserHomePerspective implements IPerspectiveFactory { - public final static String ID = "org.argeo.security.ui.userHomePerspective"; + public final static String ID = SecurityUiPlugin.PLUGIN_ID + + ".userHomePerspective"; public void createInitialLayout(IPageLayout layout) { String editorArea = layout.getEditorArea(); @@ -18,11 +19,7 @@ public class UserHomePerspective implements IPerspectiveFactory { IFolderLayout left = layout.createFolder("left", IPageLayout.LEFT, 0.30f, editorArea); left.addView(UserProfile.ID); - // left.addView(RolesView.ID); - - IFolderLayout bottom = layout.createFolder("bottom", - IPageLayout.BOTTOM, 0.30f, editorArea); - bottom.addView(LogView.ID); + left.addView(LogView.ID); } } diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java new file mode 100644 index 000000000..63908cf8a --- /dev/null +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/AdminLogView.java @@ -0,0 +1,79 @@ +package org.argeo.security.ui.views; + +import java.util.ArrayList; + +import org.argeo.security.log4j.SecureLogger; +import org.argeo.security.ui.SecurityUiPlugin; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.part.ViewPart; + +/** + * Display log lines for all users with a virtual table. + */ +public class AdminLogView extends ViewPart { + public static String ID = SecurityUiPlugin.PLUGIN_ID + ".adminLogView"; + + private TableViewer viewer; + + private LogContentProvider logContentProvider; + private SecureLogger argeoLogger; + + private Font font; + + @Override + public void createPartControl(Composite parent) { + // FIXME doesn't return a monospace font in RAP + font = JFaceResources.getTextFont(); +// if (font == JFaceResources.getDefaultFont()) { +// Set keySet = JFaceResources.getFontRegistry().getKeySet(); +// for (Object key : keySet) { +// System.out.println(key); +// } +// } + + viewer = new TableViewer(parent, SWT.VIRTUAL | SWT.MULTI | SWT.H_SCROLL + | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); + viewer.getTable().setFont(font); + viewer.setLabelProvider(new LabelProvider()); + logContentProvider = new LogContentProvider(viewer) { + + @Override + protected StringBuffer prefix(String username, Long timestamp, + String level, String category, String thread) { + return super + .prefix(username, timestamp, level, category, thread) + .append(norm(level, 5)) + .append(' ') + .append(norm(username != null ? username + : "", 16)).append(' '); + } + }; + viewer.setContentProvider(logContentProvider); + // viewer.setUseHashlookup(true); + viewer.setInput(new ArrayList()); + + if (argeoLogger != null) + argeoLogger.registerForAll(logContentProvider, 1000, true); + } + + @Override + public void setFocus() { + viewer.getTable().setFocus(); + } + + @Override + public void dispose() { + if (argeoLogger != null) + argeoLogger.unregisterForAll(logContentProvider); + } + + public void setArgeoLogger(SecureLogger argeoLogger) { + this.argeoLogger = argeoLogger; + } + +} diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogContentProvider.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogContentProvider.java index 08cca0d0a..fb1ee13bb 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogContentProvider.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogContentProvider.java @@ -2,6 +2,7 @@ package org.argeo.security.ui.views; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -25,7 +26,7 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener { private final Integer maxLineBufferSize = 10 * 1000; private final TableViewer viewer; - private final LinkedList lines; + private LinkedList lines; public LogContentProvider(TableViewer viewer) { this.viewer = viewer; @@ -36,6 +37,7 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener { public synchronized void dispose() { lines.clear(); + lines = null; } @SuppressWarnings("unchecked") @@ -62,12 +64,12 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener { return; String message = msg.toString(); - StringBuffer buf = new StringBuffer(""); - buf.append(dateFormat.format(new Date(timestamp))).append(" "); - buf.append(level).append(" "); int count = 0; + String prefix = prefix(username, timestamp, level, category, thread) + .toString(); + // String suffix = suffix(username, timestamp, level, category, thread); for (String line : message.split("\n")) { - addLine(count == 0 ? buf + line : line); + addLine(count == 0 ? prefix + line : line); count++; } @@ -76,24 +78,57 @@ class LogContentProvider implements ILazyContentProvider, ArgeoLogListener { addLine(ste); } } - viewer.getTable().getDisplay().syncExec(new Runnable() { + + viewer.getTable().getDisplay().asyncExec(new Runnable() { public void run() { + if (lines == null) + return; viewer.setItemCount(lines.size()); - // viewer.reveal(lines.peekLast()); - Table table = viewer.getTable(); - // table.setTopIndex(lines.size()-1); - System.out.println("topIndex=" + table.getTopIndex() - + ", tableSize=" + lines.size()); - // table.select(lines.size() - 1); - // table.showSelection(); - TableItem ti = table.getItem(lines.size() - 1); - if (ti == null) - System.out.println("tableItem is null"); - table.showItem(ti); + // doesn't work with syncExec + scrollToLastLine(); } }); } + protected StringBuffer prefix(String username, Long timestamp, + String level, String category, String thread) { + StringBuffer buf = new StringBuffer(""); + buf.append(dateFormat.format(new Date(timestamp))).append(" "); + // buf.append(level).append(" "); + return buf; + } + + /** Normalize string to the given size */ + protected String norm(String str, Integer size) { + int length = str.length(); + if (length == size) + return str; + else if (length > size) + return str.substring(0, size); + else { + char[] arr = new char[size - length]; + Arrays.fill(arr, ' '); + return str + new String(arr); + } + } + + // protected String suffix(String username, Long timestamp, String level, + // String category, String thread) { + // return ""; + // } + + /** Scroll to the last line */ + protected void scrollToLastLine() { + // we try to show last line with two methods + // viewer.reveal(lines.peekLast()); + + Table table = viewer.getTable(); + TableItem ti = table.getItem(lines.size() - 1); + if (ti == null) + System.out.println("tableItem is null"); + table.showItem(ti); + } + protected synchronized LogLine addLine(String line) { // check for maximal size and purge if necessary while (lines.size() >= maxLineBufferSize) { diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogView.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogView.java index f432ad348..a612bc9f7 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogView.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/views/LogView.java @@ -3,14 +3,13 @@ package org.argeo.security.ui.views; import java.util.ArrayList; import org.argeo.ArgeoLogListener; +import org.argeo.ArgeoLogger; import org.argeo.security.ui.SecurityUiPlugin; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.part.ViewPart; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; /** * Display log lines with a virtual table. Register and unregisters a @@ -22,8 +21,7 @@ public class LogView extends ViewPart { private TableViewer viewer; private LogContentProvider logContentProvider; - - private ServiceRegistration serviceRegistration; + private ArgeoLogger argeoLogger; @Override public void createPartControl(Composite parent) { @@ -31,11 +29,12 @@ public class LogView extends ViewPart { | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); viewer.setLabelProvider(new LabelProvider()); logContentProvider = new LogContentProvider(viewer); - serviceRegistration = getBundleContext().registerService( - ArgeoLogListener.class.getName(), logContentProvider, null); viewer.setContentProvider(logContentProvider); - //viewer.setUseHashlookup(true); + // viewer.setUseHashlookup(true); viewer.setInput(new ArrayList()); + + if (argeoLogger != null) + argeoLogger.register(logContentProvider, 1000); } @Override @@ -45,11 +44,12 @@ public class LogView extends ViewPart { @Override public void dispose() { - if (serviceRegistration != null) - serviceRegistration.unregister(); + if (argeoLogger != null) + argeoLogger.unregister(logContentProvider); } - private BundleContext getBundleContext() { - return SecurityUiPlugin.getDefault().getBundle().getBundleContext(); + public void setArgeoLogger(ArgeoLogger argeoLogger) { + this.argeoLogger = argeoLogger; } + } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java new file mode 100644 index 000000000..d50c9b78a --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java @@ -0,0 +1,46 @@ +package org.argeo.security; + +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; + +/** Static utilities */ +public class SecurityUtils { + + private SecurityUtils() { + } + + /** Whether the current thread has the admin role */ + public static boolean hasCurrentThreadAuthority(String authority) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + for (GrantedAuthority ga : authentication.getAuthorities()) + if (ga.getAuthority().equals(authority)) + return true; + } + } + return false; + } + + /** + * @return the authenticated username or null if not authenticated / + * anonymous + */ + public static String getCurrentThreadUsername() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + if (authentication instanceof AnonymousAuthenticationToken) { + return null; + } + return authentication.getName(); + } + } + return null; + } +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java index 70945f07c..a03d2b6d2 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java @@ -16,25 +16,31 @@ package org.argeo.security.log4j; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; +import java.util.Map; import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; +import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.spi.LoggingEvent; import org.argeo.ArgeoException; import org.argeo.ArgeoLogListener; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContext; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; +import org.argeo.ArgeoLogger; +import org.argeo.security.SecurityUtils; /** Not meant to be used directly in standard log4j config */ -public class SecureLogger { - private List listeners; +public class SecureLogger implements ArgeoLogger { private Boolean disabled = false; @@ -47,6 +53,18 @@ public class SecureLogger { private AppenderImpl appender; + private final List everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + /** Marker to prevent stack overflow */ private ThreadLocal dispatching = new ThreadLocal() { @@ -58,16 +76,18 @@ public class SecureLogger { public void init() { try { + events = new LinkedBlockingQueue(); + // if (layout != null) // setLayout(layout); // else // setLayout(new PatternLayout(pattern)); appender = new AppenderImpl(); - - if (configuration != null) - PropertyConfigurator.configure(configuration); - + reloadConfiguration(); Logger.getRootLogger().addAppender(appender); + + logDispatcherThread = new LogDispatcherThread(); + logDispatcherThread.start(); } catch (Exception e) { throw new ArgeoException("Cannot initialize log4j"); } @@ -75,12 +95,71 @@ public class SecureLogger { public void destroy() throws Exception { Logger.getRootLogger().removeAppender(appender); + allUsersListeners.clear(); + for (List lst : userListeners.values()) + lst.clear(); + userListeners.clear(); + + events.clear(); + events = null; + logDispatcherThread.interrupt(); } // public void setLayout(Layout layout) { // this.layout = layout; // } + public synchronized void register(ArgeoLogListener listener, + Integer numberOfPreviousEvents) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (username == null) + throw new ArgeoException( + "Only authenticated users can register a log listener"); + + if (!userListeners.containsKey(username)) { + List lst = Collections + .synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List lastEvents = logDispatcherThread.getLastEvents(username, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + dispatchEvent(listener, evt); + } + + public synchronized void registerForAll(ArgeoLogListener listener, + Integer numberOfPreviousEvents, boolean everything) { + if (everything) + everythingListeners.add(listener); + else + allUsersListeners.add(listener); + List lastEvents = logDispatcherThread.getLastEvents(null, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + if (everything || evt.getUsername() != null) + dispatchEvent(listener, evt); + } + + public synchronized void unregister(ArgeoLogListener listener) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (!userListeners.containsKey(username)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + if (!userListeners.get(username).contains(listener)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + userListeners.get(username).remove(listener); + if (userListeners.get(username).isEmpty()) + userListeners.remove(username); + + } + + public synchronized void unregisterForAll(ArgeoLogListener listener) { + everythingListeners.remove(listener); + allUsersListeners.remove(listener); + } + /** For development purpose, since using regular logging is not easy here */ static void stdOut(Object obj) { System.out.println(obj); @@ -98,14 +177,87 @@ public class SecureLogger { this.level = level; } - public void setListeners(List listeners) { - this.listeners = listeners; - } - public void setConfiguration(Properties configuration) { this.configuration = configuration; } + public void updateConfiguration(Properties configuration) { + setConfiguration(configuration); + reloadConfiguration(); + } + + public Properties getConfiguration() { + return configuration; + } + + /** Reloads configuration (if the configuration {@link Properties} is set) */ + protected void reloadConfiguration() { + if (configuration != null) { + LogManager.resetConfiguration(); + PropertyConfigurator.configure(configuration); + } + } + + protected synchronized void processLoggingEvent(LogEvent event) { + if (disabled) + return; + + if (dispatching.get()) + return; + + if (level != null && !level.trim().equals("")) { + if (log4jLevel == null || !log4jLevel.toString().equals(level)) + try { + log4jLevel = Level.toLevel(level); + } catch (Exception e) { + System.err + .println("Log4j level could not be set for level '" + + level + "', resetting it to null."); + e.printStackTrace(); + level = null; + } + + if (log4jLevel != null + && !event.getLoggingEvent().getLevel() + .isGreaterOrEqual(log4jLevel)) { + return; + } + } + + try { + // admin listeners + Iterator everythingIt = everythingListeners + .iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners + .iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator userIt = userListeners.get( + event.getUsername()).iterator(); + while (userIt.hasNext()) + dispatchEvent(userIt.next(), event); + } + } + } catch (Exception e) { + stdOut("Cannot process logging event"); + e.printStackTrace(); + } + } + + protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { + LoggingEvent event = evt.getLoggingEvent(); + logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event + .getLevel().toString(), event.getLoggerName(), event + .getThreadName(), event.getMessage(), event + .getThrowableStrRep()); + } + private class AppenderImpl extends AppenderSkeleton { public boolean requiresLayout() { return false; @@ -116,61 +268,93 @@ public class SecureLogger { @Override protected void append(LoggingEvent event) { - if (disabled) - return; + if (events != null) { + try { + String username = SecurityUtils.getCurrentThreadUsername(); + events.put(new LogEvent(username, event)); + } catch (InterruptedException e) { + // silent + } + } + } - if (dispatching.get()) - return; + } - if (level != null && !level.trim().equals("")) { - if (log4jLevel == null || !log4jLevel.toString().equals(level)) - try { - log4jLevel = Level.toLevel(level); - } catch (Exception e) { - System.err - .println("Log4j level could not be set for level '" - + level + "', resetting it to null."); - e.printStackTrace(); - level = null; - } - - if (log4jLevel != null - && !event.getLevel().isGreaterOrEqual(log4jLevel)) { - return; + private class LogDispatcherThread extends Thread { + /** encapsulated in order to simplify concurrency management */ + private LinkedList lastEvents = new LinkedList(); + + public LogDispatcherThread() { + super("Argeo Logging Dispatcher Thread"); + } + + public void run() { + while (events != null) { + try { + LogEvent loggingEvent = events.take(); + processLoggingEvent(loggingEvent); + addLastEvent(loggingEvent); + } catch (InterruptedException e) { + if (events == null) + return; } } + } - try { - Thread currentThread = Thread.currentThread(); - String username = null; - - // find username - SecurityContext securityContext = SecurityContextHolder - .getContext(); - if (securityContext != null) { - Authentication authentication = securityContext - .getAuthentication(); - if (authentication != null) { - if (authentication instanceof AnonymousAuthenticationToken) { - username = null; - } else { - username = authentication.getName(); - } - } - } + protected synchronized void addLastEvent(LogEvent loggingEvent) { + if (lastEvents.size() >= maxLastEventsCount) + lastEvents.poll(); + lastEvents.add(loggingEvent); + } - // Spring OSGi safe - Iterator it = listeners.iterator(); - while (it.hasNext()) { - ArgeoLogListener logListener = it.next(); - logListener.appendLog(username, event.getTimeStamp(), event - .getLevel().toString(), event.getLoggerName(), - currentThread.getName(), event.getMessage(), event.getThrowableStrRep()); + public synchronized List getLastEvents(String username, + Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator it = lastEvents.listIterator(lastEvents + .size()); + int count = 0; + while (it.hasPrevious() && (count < maxCount)) { + LogEvent evt = it.previous(); + if (username == null || username.equals(evt.getUsername())) { + evts.push(evt); + count++; } - } catch (Exception e) { - stdOut("Cannot process logging event"); - e.printStackTrace(); } + return evts; + } + } + + private class LogEvent { + private final String username; + private final LoggingEvent loggingEvent; + + public LogEvent(String username, LoggingEvent loggingEvent) { + super(); + this.username = username; + this.loggingEvent = loggingEvent; + } + + @Override + public int hashCode() { + return loggingEvent.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return loggingEvent.equals(obj); + } + + @Override + public String toString() { + return username + "@ " + loggingEvent.toString(); + } + + public String getUsername() { + return username; + } + + public LoggingEvent getLoggingEvent() { + return loggingEvent; } } diff --git a/server/plugins/org.argeo.jcr.ui.explorer/plugin.xml b/server/plugins/org.argeo.jcr.ui.explorer/plugin.xml index 74c69707e..09dffb8a9 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/plugin.xml +++ b/server/plugins/org.argeo.jcr.ui.explorer/plugin.xml @@ -261,21 +261,8 @@ - - - - - - - - - - diff --git a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java index e691b8fb6..209dcdc6a 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java +++ b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java @@ -191,11 +191,10 @@ public class AddRemoteRepository extends AbstractHandler implements "Repository Added", "Remote repository '" + username.getText() + "@" + uri.getText() + "' added"); + super.okPressed(); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + ErrorFeedback.show("Cannot add remote repository", e); } - super.okPressed(); } /** Creates label and text. */ -- 2.30.2