Index: api/src/main/java/org/hippoecm/frontend/service/render/AbstractRenderService.java =================================================================== --- api/src/main/java/org/hippoecm/frontend/service/render/AbstractRenderService.java (revision 55514) +++ api/src/main/java/org/hippoecm/frontend/service/render/AbstractRenderService.java (working copy) @@ -59,6 +59,8 @@ import org.hippoecm.frontend.service.ServiceTracker; import org.hippoecm.frontend.session.UserSession; import org.hippoecm.frontend.util.WebApplicationHelper; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.hippoecm.repository.api.HippoNodeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -404,15 +406,29 @@ * {@inheritDoc} */ public void render(PluginRequestTarget target) { - if (redraw) { - if (target != null && isActive()) { - target.add(this); + Task renderTask = null; + + try { + if (HDC.isStarted()) { + renderTask = HDC.getCurrentTask().startSubtask("AbstractRenderService.render"); + renderTask.setAttribute("pluginConfig", getPluginConfig().getName()); + renderTask.setAttribute("pluginClass", getClass().getName()); } - } - for (Map.Entry entry : children.entrySet()) { - for (IRenderService service : entry.getValue().getChildren()) { - service.render(target); + + if (redraw) { + if (target != null && isActive()) { + target.add(this); + } } + for (Map.Entry entry : children.entrySet()) { + for (IRenderService service : entry.getValue().getChildren()) { + service.render(target); + } + } + } finally { + if (renderTask != null) { + renderTask.stop(); + } } } @@ -534,16 +550,50 @@ return service.getModel(); } } + return null; } @Override protected void onBeforeRender() { - redraw = false; - super.onBeforeRender(); + Task beforeRenderTask = null; + + try { + if (HDC.isStarted()) { + beforeRenderTask = HDC.getCurrentTask().startSubtask("AbstractRenderService.onBeforeRender"); + beforeRenderTask.setAttribute("pluginConfig", getPluginConfig().getName()); + beforeRenderTask.setAttribute("pluginClass", getClass().getName()); + } + + redraw = false; + super.onBeforeRender(); + } finally { + if (beforeRenderTask != null) { + beforeRenderTask.stop(); + } + } } @Override + protected void onAfterRender() { + Task afterRenderTask = null; + + try { + if (HDC.isStarted()) { + afterRenderTask = HDC.getCurrentTask().startSubtask("AbstractRenderService.onAfterRender"); + afterRenderTask.setAttribute("pluginConfig", getPluginConfig().getName()); + afterRenderTask.setAttribute("pluginClass", getClass().getName()); + } + + super.onAfterRender(); + } finally { + if (afterRenderTask != null) { + afterRenderTask.stop(); + } + } + } + + @Override public void onComponentTagBody(MarkupStream markupStream, final ComponentTag openTag) { int beginOfBodyIndex = markupStream.getCurrentIndex(); Response response = new StringResponse(); Index: engine/pom.xml =================================================================== --- engine/pom.xml (revision 55514) +++ engine/pom.xml (working copy) @@ -73,6 +73,10 @@ + + org.apache.commons + commons-proxy + Index: engine/src/main/java/org/hippoecm/frontend/Main.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/Main.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/Main.java (working copy) @@ -87,6 +87,7 @@ import org.apache.wicket.util.string.StringValueConversionException; import org.apache.wicket.util.string.Strings; import org.apache.wicket.util.time.Duration; +import org.hippoecm.frontend.diagnosis.DiagnosisRequestCycleListener; import org.hippoecm.frontend.model.JcrHelper; import org.hippoecm.frontend.model.JcrNodeModel; import org.hippoecm.frontend.model.UserCredentials; @@ -95,6 +96,8 @@ import org.hippoecm.frontend.plugin.config.impl.JcrApplicationFactory; import org.hippoecm.frontend.session.PluginUserSession; import org.hippoecm.frontend.session.UserSession; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.hippoecm.repository.HippoRepository; import org.hippoecm.repository.HippoRepositoryFactory; import org.hippoecm.repository.api.HippoNodeType; @@ -605,7 +608,7 @@ * Adds the default built-in {@link IRequestCycleListener} or configured custom {@link IRequestCycleListener}s. *

* If no custom {@link IRequestCycleListener}s are configured, then this simply registers the default built-in - * {@link RepositoryRuntimeExceptionHandlingRequestCycleListener}. + * listeners such as {@link DiagnosisRequestCycleListener} and {@link RepositoryRuntimeExceptionHandlingRequestCycleListener}. * Otherwise, this registers only the custom configured {@link IRequestCycleListener}s. *

*/ @@ -614,6 +617,7 @@ RequestCycleListenerCollection requestCycleListenerCollection = getRequestCycleListeners(); if (listenerClassNames == null || listenerClassNames.length == 0) { + requestCycleListenerCollection.add(new DiagnosisRequestCycleListener()); requestCycleListenerCollection.add(new RepositoryRuntimeExceptionHandlingRequestCycleListener()); } else { for (String listenerClassName : listenerClassNames) { Index: engine/src/main/java/org/hippoecm/frontend/PluginPage.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/PluginPage.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/PluginPage.java (working copy) @@ -47,6 +47,8 @@ import org.hippoecm.frontend.session.PluginUserSession; import org.hippoecm.frontend.session.UserSession; import org.hippoecm.frontend.util.WebApplicationHelper; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; public class PluginPage extends Home implements IServiceTracker { @@ -67,39 +69,52 @@ } public PluginPage(IApplicationFactory appFactory) { - pageId = ((PluginUserSession) UserSession.get()).getPageId(); + Task pageInitTask = null; - add(new EmptyPanel("root")); + try { + if (HDC.isStarted()) { + pageInitTask = HDC.getCurrentTask().startSubtask("PluginPage.init"); + } - mgr = new PluginManager(this); - context = new PluginContext(mgr, new JavaPluginConfig("home")); - context.connect(null); + pageId = ((PluginUserSession) UserSession.get()).getPageId(); - context.registerTracker(this, "service.root"); + add(new EmptyPanel("root")); - pluginConfigService = new PluginConfigFactory(UserSession.get(), appFactory); - context.registerService(pluginConfigService, IPluginConfigService.class.getName()); + mgr = new PluginManager(this); + context = new PluginContext(mgr, new JavaPluginConfig("home")); + context.connect(null); - obRegistry = new ObservableRegistry(context, null); - obRegistry.startObservation(); + context.registerTracker(this, "service.root"); - dialogService = new DialogServiceFactory("dialog"); - dialogService.init(context, IDialogService.class.getName()); - add(dialogService.getComponent()); + pluginConfigService = new PluginConfigFactory(UserSession.get(), appFactory); + context.registerService(pluginConfigService, IPluginConfigService.class.getName()); - context.registerService(this, Home.class.getName()); - registerGlobalBehaviorTracker(); + obRegistry = new ObservableRegistry(context, null); + obRegistry.startObservation(); - add(menuBehavior = new ContextMenuBehavior()); + dialogService = new DialogServiceFactory("dialog"); + dialogService.init(context, IDialogService.class.getName()); + add(dialogService.getComponent()); - IClusterConfig pluginCluster = pluginConfigService.getDefaultCluster(); - IClusterControl clusterControl = context.newCluster(pluginCluster, null); - clusterControl.start(); + context.registerService(this, Home.class.getName()); + registerGlobalBehaviorTracker(); - IController controller = context.getService(IController.class.getName(), IController.class); - if (controller != null) { - WebRequest request = (WebRequest) RequestCycle.get().getRequest(); - controller.process(request.getRequestParameters()); + add(menuBehavior = new ContextMenuBehavior()); + + IClusterConfig pluginCluster = pluginConfigService.getDefaultCluster(); + IClusterControl clusterControl = context.newCluster(pluginCluster, null); + clusterControl.start(); + + IController controller = context.getService(IController.class.getName(), IController.class); + + if (controller != null) { + WebRequest request = (WebRequest) RequestCycle.get().getRequest(); + controller.process(request.getRequestParameters()); + } + } finally { + if (pageInitTask != null) { + pageInitTask.stop(); + } } } @@ -128,10 +143,23 @@ @Override public void renderHead(final IHeaderResponse response) { - super.renderHead(response); - CoreLibrariesContributor.contribute(Application.get(), response); - if (WebApplicationHelper.isDevelopmentMode()) { - addDevelopmentModeCssClassToHtml(response); + Task pageRenderHeadTask = null; + + try { + if (HDC.isStarted()) { + pageRenderHeadTask = HDC.getCurrentTask().startSubtask("PluginPage.renderHead"); + } + + super.renderHead(response); + CoreLibrariesContributor.contribute(Application.get(), response); + + if (WebApplicationHelper.isDevelopmentMode()) { + addDevelopmentModeCssClassToHtml(response); + } + } finally { + if (pageRenderHeadTask != null) { + pageRenderHeadTask.stop(); + } } } @@ -159,8 +187,15 @@ * Notify refreshables and listeners in the page for which events have been received. */ public void processEvents() { - refresh(); + Task pageProcessEventsTask = null; + try { + if (HDC.isStarted()) { + pageProcessEventsTask = HDC.getCurrentTask().startSubtask("PluginPage.processEvents"); + } + + refresh(); + // re-evaluate models context.getServices(IRefreshable.class.getName(), IRefreshable.class).forEach(org.hippoecm.frontend.model.event.IRefreshable::refresh); @@ -167,16 +202,84 @@ // process JCR events JcrObservationManager.getInstance().processEvents(); } finally { + if (pageProcessEventsTask != null) { + pageProcessEventsTask.stop(); + } + setFlag(FLAG_RESERVED1, false); } } + @Override + protected void onInitialize() { + Task initTask = null; + + try { + if (HDC.isStarted()) { + initTask = HDC.getCurrentTask().startSubtask("PluginPage.onInitialize"); + } + + super.onInitialize(); + } finally { + if (initTask != null) { + initTask.stop(); + } + } + } + + @Override + protected void onBeforeRender() { + Task beforeRenderTask = null; + + try { + if (HDC.isStarted()) { + beforeRenderTask = HDC.getCurrentTask().startSubtask("PluginPage.onBeforeRender"); + } + + super.onBeforeRender(); + } finally { + if (beforeRenderTask != null) { + beforeRenderTask.stop(); + } + } + } + + @Override + protected void onAfterRender() { + Task afterRenderTask = null; + + try { + if (HDC.isStarted()) { + afterRenderTask = HDC.getCurrentTask().startSubtask("PluginPage.onAfterRender"); + } + + super.onAfterRender(); + } finally { + if (afterRenderTask != null) { + afterRenderTask.stop(); + } + } + } + public void render(PluginRequestTarget target) { - if (root != null) { - root.render(target); + Task pageRenderTask = null; + + try { + if (HDC.isStarted()) { + pageRenderTask = HDC.getCurrentTask().startSubtask("PluginPage.render"); + } + + if (root != null) { + root.render(target); + } + + dialogService.render(target); + menuBehavior.checkMenus(target); + } finally { + if (pageRenderTask != null) { + pageRenderTask.stop(); + } } - dialogService.render(target); - menuBehavior.checkMenus(target); } public void focus(IRenderService child) { @@ -232,8 +335,20 @@ @Override public void onDetach() { - context.detach(); - super.onDetach(); + Task detachTask = null; + + try { + if (HDC.isStarted()) { + detachTask = HDC.getCurrentTask().startSubtask("PluginPage.onDetach"); + } + + context.detach(); + super.onDetach(); + } finally { + if (detachTask != null) { + detachTask.stop(); + } + } } public void showContextMenu(IContextMenu active) { Index: engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisDaemonModule.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisDaemonModule.java (nonexistent) +++ engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisDaemonModule.java (working copy) @@ -0,0 +1,114 @@ +/* + * Copyright 2015-2015 Hippo B.V. (http://www.onehippo.com) + * + * Licensed 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.hippoecm.frontend.diagnosis; + +import java.util.HashSet; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.lang.StringUtils; +import org.hippoecm.repository.util.JcrUtils; +import org.onehippo.cms7.services.HippoServiceRegistry; +import org.onehippo.repository.modules.AbstractReconfigurableDaemonModule; +import org.onehippo.repository.modules.ProvidesService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ProvidesService(types = DiagnosisService.class) +public class DiagnosisDaemonModule extends AbstractReconfigurableDaemonModule { + + private static Logger log = LoggerFactory.getLogger(DiagnosisDaemonModule.class); + + private static final String ENABLED = "enabled"; + + private static final String THRESHOLD_MILLISEC = "thresholdMillisec"; + + private static final String DEPTH = "depth"; + + private static final String ALLOWED_ADDRESSES = "allowedAddresses"; + + private boolean enabled; + + private long thresholdMillisec = -1L; + + private long depth = -1; + + private Set allowedAddresses; + + private final Object configurationLock = new Object(); + + private DiagnosisService service; + + @Override + protected void doConfigure(final Node moduleConfig) throws RepositoryException { + synchronized (configurationLock) { + enabled = JcrUtils.getBooleanProperty(moduleConfig, ENABLED, false); + thresholdMillisec = JcrUtils.getLongProperty(moduleConfig, THRESHOLD_MILLISEC, -1L); + depth = JcrUtils.getLongProperty(moduleConfig, DEPTH, -1L); + allowedAddresses = new HashSet<>(); + + String [] addrValues = JcrUtils.getMultipleStringProperty(moduleConfig, ALLOWED_ADDRESSES, null); + + if (addrValues != null) { + for (String addrValue : addrValues) { + if (StringUtils.isNotBlank(addrValue)) { + allowedAddresses.add(StringUtils.trim(addrValue)); + } + } + } + + log.info("Reconfiguring diagnostic daemon module. enabled: {}, thresholdMillisec: {}, depth: {}, allowedAddresses: {}", + enabled, thresholdMillisec, depth, allowedAddresses); + } + } + + @Override + protected void doInitialize(final Session session) throws RepositoryException { + HippoServiceRegistry.registerService(service = new DiagnosisService() { + @Override + public boolean isEnabledFor(String clientAddress) { + if (!enabled) { + return false; + } + + if (!allowedAddresses.isEmpty() && !allowedAddresses.contains(clientAddress)) { + return false; + } + + return true; + } + + @Override + public long getThresholdMillisec() { + return thresholdMillisec; + } + + @Override + public int getDepth() { + return (int) depth; + } + }, DiagnosisService.class); + } + + @Override + protected void doShutdown() { + HippoServiceRegistry.unregisterService(service, DiagnosisService.class); + } + +} Index: engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisRequestCycleListener.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisRequestCycleListener.java (nonexistent) +++ engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisRequestCycleListener.java (working copy) @@ -0,0 +1,115 @@ +/* + * Copyright 2015-2015 Hippo B.V. (http://www.onehippo.com) + * + * Licensed 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.hippoecm.frontend.diagnosis; + +import javax.servlet.ServletRequest; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.wicket.Application; +import org.apache.wicket.request.cycle.AbstractRequestCycleListener; +import org.apache.wicket.request.cycle.IRequestCycleListener; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.http.WebRequest; +import org.hippoecm.frontend.Main; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; +import org.hippoecm.hst.diagnosis.TaskLogFormatUtils; +import org.onehippo.cms7.services.HippoServiceRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The default {@link IRequestCycleListener} implementation to set diagnosis context and report monitoring logs. + */ +public class DiagnosisRequestCycleListener extends AbstractRequestCycleListener { + + private static Logger log = LoggerFactory.getLogger(DiagnosisRequestCycleListener.class); + + @Override + public void onBeginRequest(RequestCycle cycle) { + final DiagnosisService diagnosisService = HippoServiceRegistry.getService(DiagnosisService.class); + + if (diagnosisService != null) { + final Main application = (Main) Application.get(); + final String remoteAddr = getFarthestRemoteAddr(cycle); + + if (diagnosisService.isEnabledFor(remoteAddr)) { + if (HDC.isStarted()) { + log.error("HDC was not cleaned up properly in previous request cycle for some reason. So clean up HDC to start new one."); + HDC.cleanUp(); + } + + Task rootTask = HDC.start(application.getPluginApplicationName()); + rootTask.setAttribute("request", cycle.getRequest().getUrl().toString()); + } + } + } + + @Override + public void onEndRequest(RequestCycle cycle) { + if (HDC.isStarted()) { + try { + final Task rootTask = HDC.getRootTask(); + rootTask.stop(); + + final DiagnosisService diagnosisService = HippoServiceRegistry.getService(DiagnosisService.class); + final long threshold = diagnosisService != null ? diagnosisService.getThresholdMillisec() : -1; + final int depth = diagnosisService != null ? diagnosisService.getDepth() : -1; + + if (threshold > -1L && rootTask.getDurationTimeMillis() < threshold) { + log.debug("Skipping task '{}' because took only '{}' ms.", + rootTask.getName(), rootTask.getDurationTimeMillis()); + } else { + log.info("Diagnosis Summary:\n{}", TaskLogFormatUtils.getTaskLog(rootTask, depth)); + } + } finally { + HDC.cleanUp(); + } + } + } + + protected String getFarthestRemoteAddr(final RequestCycle requestCycle) { + String [] remoteAddrs = getRemoteAddrs(requestCycle); + + if (ArrayUtils.isNotEmpty(remoteAddrs)) { + return remoteAddrs[0]; + } + + return null; + } + + private String [] getRemoteAddrs(final RequestCycle requestCycle) { + WebRequest request = (WebRequest) requestCycle.getRequest(); + + String xff = request.getHeader("X-Forwarded-For"); + + if (xff != null) { + String [] addrs = xff.split(","); + + for (int i = 0; i < addrs.length; i++) { + addrs[i] = addrs[i].trim(); + } + + return addrs; + } else if (request.getContainerRequest() instanceof ServletRequest) { + ServletRequest servletRequest = (ServletRequest) request.getContainerRequest(); + return new String [] { servletRequest.getRemoteAddr() }; + } + + return ArrayUtils.EMPTY_STRING_ARRAY; + } + +} Index: engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisService.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisService.java (nonexistent) +++ engine/src/main/java/org/hippoecm/frontend/diagnosis/DiagnosisService.java (working copy) @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2015 Hippo B.V. (http://www.onehippo.com) + * + * Licensed 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.hippoecm.frontend.diagnosis; + +import org.onehippo.cms7.services.SingletonService; + +/** + * Diagnostics Service. + */ +@SingletonService +public interface DiagnosisService { + + /** + * Return true if diagnostics is enabled for the client at the address. + * @param clientAddress client remote address + * @return true if diagnostics is enabled + */ + public boolean isEnabledFor(String clientAddress); + + /** + * Return threshold time to report diagnostics in milliseconds. + * @return threshold time to report diagnostics in milliseconds + */ + public long getThresholdMillisec(); + + /** + * Return diagnostics reporting depth. + * @return diagnostics reporting depth + */ + public int getDepth(); + +} Index: engine/src/main/java/org/hippoecm/frontend/model/JcrSessionModel.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/model/JcrSessionModel.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/model/JcrSessionModel.java (working copy) @@ -28,6 +28,8 @@ import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException; import org.hippoecm.frontend.Main; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.hippoecm.repository.HippoRepository; import org.hippoecm.repository.api.HippoNodeType; import org.hippoecm.repository.api.NodeNameCodec; @@ -69,21 +71,33 @@ } protected void flush() { - Session session = getObject(); - if (session != null) { - if (session.isLive()) { - if (saveOnExit) { - try { - session.save(); - } catch (RepositoryException e) { - log.error("Failed to save session before logging out", e); + Task flushTask = null; + + try { + if (HDC.isStarted()) { + flushTask = HDC.getCurrentTask().startSubtask("JcrSessionModel.flush"); + } + + Session session = getObject(); + if (session != null) { + if (session.isLive()) { + if (saveOnExit) { + try { + session.save(); + } catch (RepositoryException e) { + log.error("Failed to save session before logging out", e); + } } + session.logout(); + logHippoEvent(false, session.getUserID(), "logout", true); } - session.logout(); - logHippoEvent(false, session.getUserID(), "logout", true); + transientJcrSessionWrapper = null; + super.detach(); } - transientJcrSessionWrapper = null; - super.detach(); + } finally { + if (flushTask != null) { + flushTask.stop(); + } } } @@ -157,20 +171,45 @@ } public static Session login(UserCredentials credentials) throws LoginException, RepositoryException { - Main main = (Main) Application.get(); - HippoRepository repository = main.getRepository(); - return repository.getRepository().login(credentials.getJcrCredentials()); + Task loginTask = null; + + try { + if (HDC.isStarted()) { + loginTask = HDC.getCurrentTask().startSubtask("JcrSessionModel.login"); + } + + Main main = (Main) Application.get(); + HippoRepository repository = main.getRepository(); + return repository.getRepository().login(credentials.getJcrCredentials()); + } finally { + if (loginTask != null) { + loginTask.stop(); + } + } } private void logHippoEvent(final boolean login, final String user, final String message, boolean success) { final HippoEventBus eventBus = HippoServiceRegistry.getService(HippoEventBus.class); + if (eventBus != null) { - final String action = login ? "login" : "logout"; - final HippoEvent event = new HippoSecurityEvent("cms").success(success).action(action) - .category(HippoEventConstants.CATEGORY_SECURITY).user(user).set("remoteAddress", getRemoteAddr()) - .message(message); - event.sealEvent(); - eventBus.post(event); + Task logEventTask = null; + + try { + if (HDC.isStarted()) { + logEventTask = HDC.getCurrentTask().startSubtask("JcrSessionModel.logHippoEvent"); + } + + final String action = login ? "login" : "logout"; + final HippoEvent event = new HippoSecurityEvent("cms").success(success).action(action) + .category(HippoEventConstants.CATEGORY_SECURITY).user(user).set("remoteAddress", getRemoteAddr()) + .message(message); + event.sealEvent(); + eventBus.post(event); + } finally { + if (logEventTask != null) { + logEventTask.stop(); + } + } } } @@ -200,6 +239,7 @@ if (result.getNodes().hasNext()) { return result.getNodes().nextNode(); } + return null; } Index: engine/src/main/java/org/hippoecm/frontend/plugin/impl/PluginContext.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/plugin/impl/PluginContext.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/plugin/impl/PluginContext.java (working copy) @@ -39,6 +39,8 @@ import org.hippoecm.frontend.plugin.config.IPluginConfig; import org.hippoecm.frontend.plugin.config.impl.ClusterConfigDecorator; import org.hippoecm.frontend.service.IRenderService; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,45 +77,59 @@ } public IClusterControl newCluster(IClusterConfig template, IPluginConfig parameters) { - String name = template.getName(); - if (name != null) { - name = name.replace(':', '_'); - } else { - name = ""; - } - String clusterIdBase = config.getName() + ".cluster." + name; - String clusterId = clusterIdBase; - int counter = 0; - while (children.containsKey(clusterId)) { - clusterId = clusterIdBase + counter++; - } - IClusterConfig decorator = new ClusterConfigDecorator(template, clusterId); - ClusterControl cluster = new ClusterControl(manager, this, decorator, clusterId); + Task newClusterTask = null; + ClusterControl cluster = null; - for (String service : template.getServices()) { - String serviceId = clusterId + ".service." + service.replace(':', '_'); - decorator.put(service, serviceId); - if (parameters != null && parameters.getString(service) != null) { - cluster.forward(serviceId, parameters.getString(service)); + try { + if (HDC.isStarted()) { + newClusterTask = HDC.getCurrentTask().startSubtask("PluginContext.newCluster"); } - } - for (String reference : template.getReferences()) { - String serviceId; - if (!template.getServices().contains(reference)) { - serviceId = clusterId + ".reference." + reference.replace(':', '_'); - decorator.put(reference, serviceId); + + String name = template.getName(); + if (name != null) { + name = name.replace(':', '_'); } else { - serviceId = decorator.getString(reference); + name = ""; } - if (parameters != null && parameters.getString(reference) != null) { - cluster.forward(parameters.getString(reference), serviceId); + String clusterIdBase = config.getName() + ".cluster." + name; + String clusterId = clusterIdBase; + int counter = 0; + while (children.containsKey(clusterId)) { + clusterId = clusterIdBase + counter++; } - } - for (String property : template.getProperties()) { - if (parameters != null && parameters.get(property) != null) { - decorator.put(property, parameters.get(property)); + IClusterConfig decorator = new ClusterConfigDecorator(template, clusterId); + cluster = new ClusterControl(manager, this, decorator, clusterId); + + for (String service : template.getServices()) { + String serviceId = clusterId + ".service." + service.replace(':', '_'); + decorator.put(service, serviceId); + if (parameters != null && parameters.getString(service) != null) { + cluster.forward(serviceId, parameters.getString(service)); + } } + for (String reference : template.getReferences()) { + String serviceId; + if (!template.getServices().contains(reference)) { + serviceId = clusterId + ".reference." + reference.replace(':', '_'); + decorator.put(reference, serviceId); + } else { + serviceId = decorator.getString(reference); + } + if (parameters != null && parameters.getString(reference) != null) { + cluster.forward(parameters.getString(reference), serviceId); + } + } + for (String property : template.getProperties()) { + if (parameters != null && parameters.get(property) != null) { + decorator.put(property, parameters.get(property)); + } + } + } finally { + if (newClusterTask != null) { + newClusterTask.stop(); + } } + return cluster; } @@ -287,32 +303,48 @@ // DO NOT CALL THIS API @SuppressWarnings("unchecked") public void connect(IPlugin plugin) { - if (initializing) { - this.plugin = plugin; - Map listeners = new LinkedHashMap(this.listeners); - initializing = false; + Task connectTask = null; - log.debug("registering trackers for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - for (Map.Entry entry : listeners.entrySet()) { - List> list = entry.getValue(); - for (IServiceTracker listener : list) { - manager.registerTracker(listener, entry.getKey()); + try { + if (HDC.isStarted()) { + connectTask = HDC.getCurrentTask().startSubtask("PluginContext.connect"); + + if (plugin != null) { + connectTask.setAttribute("pluginClass", plugin.getClass().getName()); } } - log.debug("registering services for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - for (ServiceRegistration registration : registrationOrder) { - registration.notifyTrackers(); - } - registrationOrder.clear(); - registrations.clear(); + if (initializing) { + this.plugin = plugin; + Map listeners = new LinkedHashMap(this.listeners); + initializing = false; - if (plugin != null) { - log.debug("starting plugin {}", plugin.getClass().getName()); - plugin.start(); + log.debug("registering trackers for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + for (Map.Entry entry : listeners.entrySet()) { + List> list = entry.getValue(); + for (IServiceTracker listener : list) { + manager.registerTracker(listener, entry.getKey()); + } + } + + log.debug("registering services for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + for (ServiceRegistration registration : registrationOrder) { + registration.notifyTrackers(); + } + registrationOrder.clear(); + registrations.clear(); + + if (plugin != null) { + log.debug("starting plugin {}", plugin.getClass().getName()); + plugin.start(); + } + } else { + log.warn("context was already initialized"); } - } else { - log.warn("context was already initialized"); + } finally { + if (connectTask != null) { + connectTask.stop(); + } } } @@ -325,55 +357,84 @@ } PluginContext start(IPluginConfig plugin) { - return manager.start(plugin); + Task startTask = null; + + try { + if (HDC.isStarted()) { + startTask = HDC.getCurrentTask().startSubtask("PluginContext.start"); + if (plugin != null) { + startTask.setAttribute("pluginConfig", plugin.getName()); + String pluginClassName = plugin.getString(IPlugin.CLASSNAME); + startTask.setAttribute("pluginClass", pluginClassName != null ? pluginClassName : "unknown"); + } + } + + return manager.start(plugin); + } finally { + if (startTask != null) { + startTask.stop(); + } + } } void stop() { - if (!stopping) { - stopping = true; + Task stopTask = null; - if (plugin != null) { - plugin.stop(); + try { + if (HDC.isStarted()) { + stopTask = HDC.getCurrentTask().startSubtask("PluginContext.stop"); } - log.debug("stopping clusters for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - ClusterControl[] controls = children.values().toArray(new ClusterControl[children.size()]); - for (ClusterControl control : controls) { - control.stop(); - } + if (!stopping) { + stopping = true; - if (!initializing) { - log.debug("unregistering trackers for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - for (Map.Entry>> entry : listeners.entrySet()) { - for (IServiceTracker service : entry.getValue()) { - manager.unregisterTracker(service, entry.getKey()); + if (plugin != null) { + plugin.stop(); + } + + log.debug("stopping clusters for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + ClusterControl[] controls = children.values().toArray(new ClusterControl[children.size()]); + for (ClusterControl control : controls) { + control.stop(); + } + + if (!initializing) { + log.debug("unregistering trackers for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + for (Map.Entry>> entry : listeners.entrySet()) { + for (IServiceTracker service : entry.getValue()) { + manager.unregisterTracker(service, entry.getKey()); + } } } - } - listeners.clear(); + listeners.clear(); - log.debug("releasing service factory instances for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - for (Map.Entry, IClusterable> entry : instances.entrySet()) { - entry.getKey().releaseService(this, entry.getValue()); - } - instances.clear(); + log.debug("releasing service factory instances for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + for (Map.Entry, IClusterable> entry : instances.entrySet()) { + entry.getKey().releaseService(this, entry.getValue()); + } + instances.clear(); - if (!initializing) { - log.debug("unregistering services for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); - for (Map.Entry> entry : services.entrySet()) { - final String key = entry.getKey(); - for (IClusterable service : entry.getValue()) { - manager.unregisterService(service, key); + if (!initializing) { + log.debug("unregistering services for plugin {}", plugin != null ? plugin.getClass().getName() : "unknown"); + for (Map.Entry> entry : services.entrySet()) { + final String key = entry.getKey(); + for (IClusterable service : entry.getValue()) { + manager.unregisterService(service, key); + } } + } else { + for (ServiceRegistration registration : registrationOrder) { + registration.cleanup(); + } + registrationOrder.clear(); + registrations.clear(); } - } else { - for (ServiceRegistration registration : registrationOrder) { - registration.cleanup(); - } - registrationOrder.clear(); - registrations.clear(); + services.clear(); } - services.clear(); + } finally { + if (stopTask != null) { + stopTask.stop(); + } } } @@ -383,7 +444,7 @@ initializing = true; stopping = false; } - + public void detach() { for (Map.Entry, IClusterable> entry : instances.entrySet()) { IClusterable service = entry.getValue(); @@ -416,7 +477,7 @@ private void writeObject(ObjectOutputStream output) throws IOException { if (stopping && Application.exists()) { if (Application.get().getConfigurationType().equals(RuntimeConfigurationType.DEVELOPMENT)) { - throw new WicketRuntimeException("Stopped plugin is still being referenced: " + plugin.getClass().getName()); + throw new WicketRuntimeException("Stopped plugin is still being referenced: " + (plugin != null ? plugin.getClass().getName() : "unknown")); } } output.defaultWriteObject(); Index: engine/src/main/java/org/hippoecm/frontend/service/restproxy/RestProxyServicePlugin.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/service/restproxy/RestProxyServicePlugin.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/service/restproxy/RestProxyServicePlugin.java (working copy) @@ -27,11 +27,9 @@ import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.MediaType; -import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; - +import org.apache.commons.proxy.Interceptor; +import org.apache.commons.proxy.Invocation; +import org.apache.commons.proxy.ProxyFactory; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.jaxrs.client.JAXRSClientFactory; import org.apache.cxf.jaxrs.client.WebClient; @@ -43,10 +41,17 @@ import org.hippoecm.frontend.service.restproxy.custom.json.deserializers.AnnotationJsonDeserializer; import org.hippoecm.frontend.session.PluginUserSession; import org.hippoecm.frontend.session.UserSession; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.onehippo.sso.CredentialCipher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; + /** * Creates proxies for HST REST services. Plugin configuration properties: *
    @@ -136,9 +141,11 @@ * @param additionalProviders {@link java.util.List} of additional providers to configure client proxies with * @return a proxy to the REST service represented by the given class, or null if no proxy could be created. */ + @SuppressWarnings("unchecked") @Override public T createRestProxy(final Class restServiceApiClass, final List additionalProviders) { - return JAXRSClientFactory.create(restUri, restServiceApiClass, getProviders(additionalProviders)); + T cxfProxy = JAXRSClientFactory.create(restUri, restServiceApiClass, getProviders(additionalProviders)); + return createHDCEnabledJaxrsClientInterceptorProxy(cxfProxy, restServiceApiClass); } /** @@ -167,7 +174,8 @@ .accept(MediaType.WILDCARD_TYPE); // default time out is 60000 ms; - return clientProxy; + + return createHDCEnabledJaxrsClientInterceptorProxy(clientProxy, restServiceApiClass); } protected Subject getSubject() { @@ -248,4 +256,27 @@ return true; } + @SuppressWarnings({ "unchecked" }) + private T createHDCEnabledJaxrsClientInterceptorProxy(final T cxfProxy, final Class restServiceApiClass) { + return (T) new ProxyFactory().createInterceptorProxy(cxfProxy, new Interceptor() { + @Override + public Object intercept(Invocation invocation) throws Throwable { + Task jaxrsClientTask = null; + + try { + if (HDC.isStarted()) { + jaxrsClientTask = HDC.getCurrentTask().startSubtask("JAXRSClient"); + jaxrsClientTask.setAttribute("class", restServiceApiClass.getName()); + jaxrsClientTask.setAttribute("method", invocation.getMethod().getName()); + } + + return invocation.proceed(); + } finally { + if (jaxrsClientTask != null) { + jaxrsClientTask.stop(); + } + } + } + }, new Class [] { restServiceApiClass }); + } } Index: engine/src/main/java/org/hippoecm/frontend/session/PluginUserSession.java =================================================================== --- engine/src/main/java/org/hippoecm/frontend/session/PluginUserSession.java (revision 55514) +++ engine/src/main/java/org/hippoecm/frontend/session/PluginUserSession.java (working copy) @@ -48,6 +48,9 @@ import org.hippoecm.frontend.plugin.IPlugin; import org.hippoecm.frontend.plugin.config.IPluginConfigService; import org.hippoecm.frontend.plugin.config.impl.IApplicationFactory; +import org.hippoecm.frontend.session.LoginException.Cause; +import org.hippoecm.hst.diagnosis.HDC; +import org.hippoecm.hst.diagnosis.Task; import org.hippoecm.repository.HippoRepository; import org.hippoecm.repository.api.HippoNode; import org.hippoecm.repository.api.HippoSession; @@ -56,8 +59,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.hippoecm.frontend.session.LoginException.Cause; - /** * A Wicket {@link org.apache.wicket.Session} that maintains a reference to a JCR {@link javax.jcr.Session}. It is * available to plugins as a threadlocal variable during request processing. @@ -340,16 +341,28 @@ } public void logout() { - releaseJcrSession(); - jcrSessionModel = null; + Task logoutTask = null; - JcrObservationManager.getInstance().cleanupListeners(this); - pageId = 0; + try { + if (HDC.isStarted()) { + logoutTask = HDC.getCurrentTask().startSubtask("PluginUserSession.logout"); + } - getHttpSession().removeAttribute("hippo:username"); + releaseJcrSession(); + jcrSessionModel = null; - invalidate(); - dirty(); + JcrObservationManager.getInstance().cleanupListeners(this); + pageId = 0; + + getHttpSession().removeAttribute("hippo:username"); + + invalidate(); + dirty(); + } finally { + if (logoutTask != null) { + logoutTask.stop(); + } + } } public Credentials getCredentials() { @@ -429,11 +442,23 @@ } public void flush() { - JcrObservationManager.getInstance().cleanupListeners(this); - if (Application.exists()) { - clear(); - } else { - log.info("There is no wicket application attached to the current thread."); + Task flushTask = null; + + try { + if (HDC.isStarted()) { + flushTask = HDC.getCurrentTask().startSubtask("PluginUserSession.flush"); + } + + JcrObservationManager.getInstance().cleanupListeners(this); + if (Application.exists()) { + clear(); + } else { + log.info("There is no wicket application attached to the current thread."); + } + } finally { + if (flushTask != null) { + flushTask.stop(); + } } } Index: engine/src/main/resources/hippo-configuration/hippo-modules/diagnosis-module.xml =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/xml Index: engine/src/main/resources/hippo-configuration/hippo-modules/diagnosis-module.xml =================================================================== --- engine/src/main/resources/hippo-configuration/hippo-modules/diagnosis-module.xml (nonexistent) +++ engine/src/main/resources/hippo-configuration/hippo-modules/diagnosis-module.xml (working copy) Property changes on: engine/src/main/resources/hippo-configuration/hippo-modules/diagnosis-module.xml ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/xml \ No newline at end of property Index: engine/src/main/resources/hippoecm-extension.xml =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/xml Index: engine/src/main/resources/hippoecm-extension.xml =================================================================== --- engine/src/main/resources/hippoecm-extension.xml (nonexistent) +++ engine/src/main/resources/hippoecm-extension.xml (working copy) Property changes on: engine/src/main/resources/hippoecm-extension.xml ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/xml \ No newline at end of property Index: pom.xml =================================================================== --- pom.xml (revision 55514) +++ pom.xml (working copy) @@ -106,6 +106,7 @@ 1.7.0 2.6 1.4 + 1.0 1.1.1 @@ -332,6 +333,11 @@ jackson-core ${jackson2.version} + + org.apache.commons + commons-proxy + ${commons-proxy.version} + junit