+/**
+* Copyright (C) 2020 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package org.springframework.remoting.httpinvoker;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.log4j.Logger;
+import org.springframework.remoting.support.RemoteInvocation;
+import org.springframework.remoting.support.RemoteInvocationResult;
+
+import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteController;
+import eu.etaxonomy.cdm.api.application.CdmApplicationState;
+import eu.etaxonomy.cdm.api.service.ITermService;
+import eu.etaxonomy.cdm.api.service.UpdateResult;
+import eu.etaxonomy.taxeditor.service.IRemoteInvocationTermCacher;
+import eu.etaxonomy.taxeditor.session.ICdmEntitySessionManager;
+
+/**
+ * Extension of the <code>HttpInvokerProxyFactoryBean</code> to support caching of general CDM entities and terms.
+ * <p>
+ * <b>Performance measurement:</b></br>
+ * Supports measuring the request processing time at the client side. Setting "{@value #PROP_KEY_MEASURE_DURATION}"
+ * as system parameter enables the measurement. To make the measurements appear in the log, the log <code>Level</code>
+ * for this class needs to be set to at least {@link org.apache.log4j.Level#INFO}
+ *
+ *
+ * @author a.kohlbecker
+ * @since Feb 4, 2020
+ *
+ */
+public class CachingHttpInvokerProxyFactoryBean extends HttpInvokerProxyFactoryBean {
+
+ private static final String PROP_KEY_MEASURE_DURATION = "remoting.httpinvoker.measureDuration";
+
+ private static final Logger logger = Logger.getLogger(CachingHttpInvokerProxyFactoryBean.class);
+
+ private ICdmEntitySessionManager cdmEntitySessionManager;
+
+ private IRemoteInvocationTermCacher remoteInvocationTermCacher;
+
+ protected final static Set<String> persistingMethods = new HashSet<String>();
+
+ protected static boolean measureDuration = false;
+
+ static {
+ persistingMethods.add("merge");
+ persistingMethods.add("save");
+ persistingMethods.add("findWithUpdate");
+ persistingMethods.add("loadWithUpdate");
+ measureDuration = System.getProperty(PROP_KEY_MEASURE_DURATION) != null;
+ }
+
+ @Override
+ protected RemoteInvocationResult executeRequest(
+ RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {
+
+ // logger.setLevel(Level.INFO);
+
+ if (!(invocation.getMethodName() == null) && !(getServiceUrl() == null) && logger.isDebugEnabled()){
+ logger.debug("Remote invoking : " + getServiceUrl() + "#" + invocation.getMethodName());
+ }
+
+ if(CdmApplicationState.getCurrentAppConfig() == null){
+ logger.debug("No application context yet, no point caching!");
+ return doExecuteRequest(invocation, originalInvocation);
+ }
+
+ // A) if this is a term service call for term lists try to get the terms from the cache
+ if(ITermService.class.isAssignableFrom(originalInvocation.getMethod().getDeclaringClass())){
+ return handleTermRequest(invocation, originalInvocation);
+ }
+ // B) handle other service calls
+ else {
+ return handleGeneralRequest(invocation, originalInvocation);
+ }
+ }
+
+ /**
+ * @param invocation
+ * @param originalInvocation
+ * @return
+ * @throws Exception
+ */
+ private RemoteInvocationResult handleGeneralRequest(RemoteInvocation invocation,
+ MethodInvocation originalInvocation) throws Exception {
+ RemoteInvocationResult invocationResult = doExecuteRequest(invocation, originalInvocation);
+ if(invocationResult.getValue() != null && !invocationResult.hasException()) {
+
+ if(persistingMethods.contains(invocation.getMethodName())) {
+ invocationResult = new RemoteInvocationResult(cdmEntitySessionManager().load(invocationResult.getValue(), true));
+ logger.debug("Entity cached with updating cached data");
+ } else if(invocationResult.getValue() instanceof UpdateResult){
+ UpdateResult result = (UpdateResult)invocationResult.getValue();
+ if(result.isOk()){
+ logger.debug("Entity from UpdateResult stored in cache with updating cached data" );
+ cdmEntitySessionManager().load(result.getCdmEntity(), true);
+ }
+ } else {
+ invocationResult = new RemoteInvocationResult(cdmEntitySessionManager().load(invocationResult.getValue(), false));
+ logger.debug("Entity cached without updating cached data" );
+ }
+
+ }
+ return invocationResult;
+ }
+
+ /**
+ * @param invocation
+ * @param originalInvocation
+ * @return
+ * @throws Exception
+ */
+ private RemoteInvocationResult handleTermRequest(RemoteInvocation invocation, MethodInvocation originalInvocation)
+ throws Exception {
+ RemoteInvocationResult invocationResult = null;
+ if(remoteInvocationTermCacher != null){
+ invocationResult = remoteInvocationTermCacher.termsFromCache(invocation);
+ if(invocationResult == null) {
+ invocationResult = doExecuteRequest(invocation, originalInvocation);
+ remoteInvocationTermCacher.cacheTerms(invocation, invocationResult);
+ logger.debug("Term list loaded and cached");
+ } else {
+ logger.debug("Term list found in cache, not loaded");
+ }
+ } else {
+ logger.warn("Term list: No IRemoteInvocationTermCacher configured");
+ invocationResult = doExecuteRequest(invocation, originalInvocation);
+ }
+ return invocationResult;
+ }
+
+ /**
+ * @param invocation
+ * @param originalInvocation
+ * @return
+ * @throws Exception
+ */
+ private RemoteInvocationResult doExecuteRequest(RemoteInvocation invocation, MethodInvocation originalInvocation)
+ throws Exception {
+
+ double startTime = 0;
+ if(measureDuration){
+ startTime = System.currentTimeMillis();
+ }
+ RemoteInvocationResult result = super.executeRequest(invocation, originalInvocation);
+ if(measureDuration){
+ double duration = System.currentTimeMillis() - startTime;
+ logger.info(getServiceUrl() + "#" + invocation.getMethodName() + " [" + duration + " ms]");
+ }
+ return result;
+ }
+
+ /**
+ * @return the remoteInvocationTermCacher
+ */
+ public IRemoteInvocationTermCacher getRemoteInvocationTermCacher() {
+ return remoteInvocationTermCacher;
+ }
+
+ /**
+ * @param remoteInvocationTermCacher the remoteInvocationTermCacher to set
+ */
+ public void setRemoteInvocationTermCacher(IRemoteInvocationTermCacher remoteInvocationTermCacher) {
+ this.remoteInvocationTermCacher = remoteInvocationTermCacher;
+ }
+
+ private ICdmEntitySessionManager cdmEntitySessionManager(){
+ if(cdmEntitySessionManager == null) {
+ cdmEntitySessionManager =
+ ((CdmApplicationRemoteController)CdmApplicationState.getCurrentAppConfig()).getCdmEntitySessionManager();
+ }
+ return cdmEntitySessionManager;
+ }
+
+}