001package co.codewizards.cloudstore.ls.core.invoke; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004import static co.codewizards.cloudstore.core.util.ReflectionUtil.*; 005 006import java.util.Collections; 007import java.util.HashMap; 008import java.util.Iterator; 009import java.util.LinkedList; 010import java.util.List; 011import java.util.Map; 012import java.util.SortedSet; 013import java.util.Timer; 014import java.util.TimerTask; 015import java.util.TreeSet; 016import java.util.concurrent.Executor; 017import java.util.concurrent.Executors; 018import java.util.concurrent.atomic.AtomicInteger; 019 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023import co.codewizards.cloudstore.core.Uid; 024import co.codewizards.cloudstore.ls.core.invoke.filter.ExtMethodInvocationRequest; 025import co.codewizards.cloudstore.ls.core.invoke.filter.InvocationFilterRegistry; 026 027public class InvokeMethodExecutor { 028 029 private static final Logger logger = LoggerFactory.getLogger(InvokeMethodExecutor.class); 030 031 private static final AtomicInteger nextInstanceId = new AtomicInteger(); 032 private final int instanceId = nextInstanceId.getAndIncrement(); 033 034 private final Executor executor = Executors.newCachedThreadPool(); 035 private final Map<Uid, InvocationRunnable> delayedResponseId2InvocationRunnable = Collections.synchronizedMap(new HashMap<Uid, InvocationRunnable>()); 036 private final SortedSet<DelayedResponseIdScheduledEviction> delayedResponseIdScheduledEvictions = Collections.synchronizedSortedSet(new TreeSet<DelayedResponseIdScheduledEviction>()); 037 038 private final Timer evictOldDataTimer = new Timer(String.format("InvokeMethodExecutor[%d].evictOldDataTimer", instanceId), true); 039 private final TimerTask evictOldDataTimerTask = new TimerTask() { 040 @Override 041 public void run() { 042 try { 043 final List<Uid> delayedResponseIdsToEvict = new LinkedList<>(); 044 synchronized (delayedResponseIdScheduledEvictions) { 045 for (final Iterator<DelayedResponseIdScheduledEviction> it = delayedResponseIdScheduledEvictions.iterator(); it.hasNext(); ) { 046 final DelayedResponseIdScheduledEviction delayedResponseIdScheduledEviction = it.next(); 047 048 if (System.currentTimeMillis() < delayedResponseIdScheduledEviction.getScheduledEvictionTimestamp()) 049 break; 050 051 delayedResponseIdsToEvict.add(delayedResponseIdScheduledEviction.getDelayedResponseId()); 052 it.remove(); 053 } 054 } 055 056 synchronized (delayedResponseId2InvocationRunnable) { 057 for (Uid delayedResponseId : delayedResponseIdsToEvict) 058 delayedResponseId2InvocationRunnable.remove(delayedResponseId); 059 } 060 } catch (final Throwable t) { 061 logger.error("evictOldDataTimerTask.run: " + t, t); 062 } 063 } 064 }; 065 066 public InvokeMethodExecutor() { 067 evictOldDataTimer.schedule(evictOldDataTimerTask, 60000L, 60000L); 068 } 069 070 public MethodInvocationResponse execute(final ExtMethodInvocationRequest extMethodInvocationRequest) throws Exception { 071 assertNotNull(extMethodInvocationRequest, "extMethodInvocationRequest"); 072 073 InvocationFilterRegistry.getInstance().assertCanInvoke(extMethodInvocationRequest); 074 075 final InvocationRunnable invocationRunnable = new InvocationRunnable(extMethodInvocationRequest); 076 executor.execute(invocationRunnable); 077 078 synchronized (invocationRunnable) { 079 MethodInvocationResponse methodInvocationResponse = invocationRunnable.getMethodInvocationResponse(); 080 if (methodInvocationResponse != null) 081 return methodInvocationResponse; 082 083 Throwable error = invocationRunnable.getError(); 084 if (error != null) 085 throwError(error); 086 087 try { 088 invocationRunnable.wait(45000L); 089 } catch (InterruptedException e) { 090 logger.debug("performMethodInvocation: " + e, e); 091 } 092 093 methodInvocationResponse = invocationRunnable.getMethodInvocationResponse(); 094 if (methodInvocationResponse != null) 095 return methodInvocationResponse; 096 097 error = invocationRunnable.getError(); 098 if (error != null) 099 throwError(error); 100 101 final Uid delayedResponseId = invocationRunnable.getDelayedResponseId(); 102 assertNotNull(delayedResponseId, "delayedResponseId"); 103 delayedResponseId2InvocationRunnable.put(delayedResponseId, invocationRunnable); 104 105 return new DelayedMethodInvocationResponse(delayedResponseId); 106 } 107 } 108 109 public MethodInvocationResponse getDelayedResponse(final Uid delayedResponseId) throws Exception { 110 assertNotNull(delayedResponseId, "delayedResponseId"); 111 112 long schedEvTiSt = System.currentTimeMillis() + 240000; // scheduled eviction in 4 minutes 113 final InvocationRunnable invocationRunnable = delayedResponseId2InvocationRunnable.get(delayedResponseId); 114 if (invocationRunnable == null) 115 throw new IllegalArgumentException("delayedResponseId unknown: " + delayedResponseId); 116 117 synchronized (invocationRunnable) { 118 MethodInvocationResponse methodInvocationResponse = invocationRunnable.getMethodInvocationResponse(); 119 Throwable error = invocationRunnable.getError(); 120 121 if (methodInvocationResponse != null) { 122 delayedResponseIdScheduledEvictions.add(new DelayedResponseIdScheduledEviction(schedEvTiSt, delayedResponseId)); 123 return methodInvocationResponse; 124 } 125 if (error != null) { 126 delayedResponseIdScheduledEvictions.add(new DelayedResponseIdScheduledEviction(schedEvTiSt, delayedResponseId)); 127 throwError(error); 128 } 129 130 try { 131 invocationRunnable.wait(45000L); 132 } catch (InterruptedException e) { 133 logger.debug("performMethodInvocation: " + e, e); 134 } 135 136 schedEvTiSt = System.currentTimeMillis() + 240000; // scheduled eviction in 4 minutes 137 138 methodInvocationResponse = invocationRunnable.getMethodInvocationResponse(); 139 if (methodInvocationResponse != null) { 140 delayedResponseIdScheduledEvictions.add(new DelayedResponseIdScheduledEviction(schedEvTiSt, delayedResponseId)); 141 return methodInvocationResponse; 142 } 143 error = invocationRunnable.getError(); 144 if (error != null) { 145 delayedResponseIdScheduledEvictions.add(new DelayedResponseIdScheduledEviction(schedEvTiSt, delayedResponseId)); 146 throwError(error); 147 } 148 149 return new DelayedMethodInvocationResponse(delayedResponseId); 150 } 151 } 152 153 private static void throwError(final Throwable error) throws Exception { 154 assertNotNull(error, "error"); 155 if (error instanceof RuntimeException) 156 throw (RuntimeException) error; 157 else if (error instanceof Error) 158 throw (Error) error; 159 else 160 throw new RuntimeException(error); 161 } 162 163 private class InvocationRunnable implements Runnable { 164 private final Logger logger = LoggerFactory.getLogger(InvocationRunnable.class); 165 166 private final ExtMethodInvocationRequest extMethodInvocationRequest; 167 private MethodInvocationResponse methodInvocationResponse; 168 private Throwable error; 169 private Uid delayedResponseId; 170 171 public InvocationRunnable(final ExtMethodInvocationRequest extMethodInvocationRequest) { 172 this.extMethodInvocationRequest = assertNotNull(extMethodInvocationRequest, "extMethodInvocationRequest"); 173 } 174 175 @Override 176 public void run() { 177 final ObjectManager objectManager = extMethodInvocationRequest.getObjectManager(); 178 final MethodInvocationRequest methodInvocationRequest = extMethodInvocationRequest.getMethodInvocationRequest(); 179 final ClassManager classManager = extMethodInvocationRequest.getObjectManager().getClassManager(); 180 final Class<?> clazz = extMethodInvocationRequest.getTargetClass(); 181 final Object object = methodInvocationRequest.getObject(); 182 final String methodName = methodInvocationRequest.getMethodName(); 183 184 final String[] argumentTypeNames = methodInvocationRequest.getArgumentTypeNames(); 185 final Class<?>[] argumentTypes = argumentTypeNames == null ? null : classManager.getClassesOrFail(argumentTypeNames); 186 187 final Object[] arguments = methodInvocationRequest.getArguments(); 188 189 objectManager.getReferenceCleanerRegistry().preInvoke(extMethodInvocationRequest); 190 191 Throwable error = null; 192 Object resultObject = null; 193 try { 194 195 final InvocationType invocationType = methodInvocationRequest.getInvocationType(); 196 switch (invocationType) { 197 case CONSTRUCTOR: 198 resultObject = invokeConstructor(clazz, arguments); 199 break; 200 case OBJECT: 201 resultObject = invoke(object, methodName, argumentTypes, arguments); 202 break; 203 case STATIC: 204 resultObject = invokeStatic(clazz, methodName, arguments); 205 break; 206 default: 207 throw new IllegalStateException("Unknown InvocationType: " + invocationType); 208 } 209 210 } catch (final Throwable x) { 211 resultObject = null; 212 error = x; 213 synchronized (this) { 214 this.error = x; 215 } 216 logger.debug("run: " + x, x); 217 } finally { 218 objectManager.getReferenceCleanerRegistry().postInvoke(extMethodInvocationRequest, resultObject, error); 219 } 220 221 synchronized (this) { 222 if (this.error == null) { 223 methodInvocationResponse = MethodInvocationResponse.forInvocation(resultObject, filterWritableArguments(arguments)); 224 assertNotNull(methodInvocationResponse, "methodInvocationResponse"); 225 } 226 227 this.notifyAll(); // note: other threads only continue running, *after* this synchronized block is finished entirely! 228 229 if (delayedResponseId != null) { 230 // We give the client 15 minutes to fetch the result. 231 delayedResponseIdScheduledEvictions.add(new DelayedResponseIdScheduledEviction( 232 System.currentTimeMillis() + 900L * 1000L, delayedResponseId)); 233 } 234 } 235 } 236 237 public synchronized MethodInvocationResponse getMethodInvocationResponse() { 238 return methodInvocationResponse; 239 } 240 241 public synchronized Throwable getError() { 242 return error; 243 } 244 245 public synchronized Uid getDelayedResponseId() { 246 if (delayedResponseId == null) 247 delayedResponseId = new Uid(); 248 249 return delayedResponseId; 250 } 251 } 252 253 private Object[] filterWritableArguments(final Object[] arguments) { 254 if (arguments == null || arguments.length == 0) 255 return null; 256 257 boolean atLeastOneWritable = false; 258 final Object[] result = new Object[arguments.length]; 259 for (int i = 0; i < arguments.length; ++i) { 260 final Object argument = arguments[i]; 261 if (argument != null && argument.getClass().isArray()) { 262 result[i] = argument; 263 atLeastOneWritable = true; 264 } 265 } 266 return atLeastOneWritable ? result : null; 267 } 268}