/* * Copyright 2000-2015 JetBrains s.r.o. * * 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 jetbrains.buildServer.clouds.vmware.vmrun.remote.server; import com.intellij.openapi.diagnostic.Logger; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import jetbrains.buildServer.clouds.vmware.vmrun.remote.RemoteTask; import jetbrains.buildServer.clouds.vmware.vmrun.remote.RemoteTaskResult; import jetbrains.buildServer.clouds.vmware.vmrun.remote.Timeout; import jetbrains.buildServer.util.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author Eugene Petrenko * Created: 08.12.2009 12:06:37 */ public class RemoteClient implements ClientState { private static final Logger LOG = Logger.getInstance(RemoteClient.class.getName()); private final String myId; private final Map myPendingTasks = new ConcurrentHashMap(); private final T myProxy; private Date myLastCommunication = new Date(); private final AtomicBoolean myIsDisposed = new AtomicBoolean(false); public RemoteClient(@NotNull final String clientInfo, final Class anInterface) { myId = clientInfo; myProxy = anInterface.cast( Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[]{anInterface, ClientState.class}, new InvocationHandler() { public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { //Proxy ClientState interface if (method.getDeclaringClass().equals(ClientState.class)) { return method.invoke(RemoteClient.this, args); } if (myIsDisposed.get()) { throw new RemoteCallException("Client is disposed."); } final RemoteTask task = new RemoteTask(method.getName(), "", args, getRequestId()); final Timeout tm = method.getAnnotation(Timeout.class); final TaskInfo ti = new TaskInfo(task, tm != null ? tm.value() : 10*1000); myPendingTasks.put(task.getRequestId(), ti); try { ti.waitForResponce(); final RemoteTaskResult result = ti.getResult(); if (result != null) { Throwable ex = result.getException(); if (ex != null) { while (ex instanceof InvocationTargetException) { ex = ((InvocationTargetException)ex).getTargetException(); } LOG.debug("Failed to perform method: " + method.getName() + ", for client: " + clientInfo + ". " + ex.getMessage(), ex); //noinspection ConstantConditions if (ex != null) { throw ex; } } else { return result.getResult(); } } LOG.warn("Failed to perform remote method: " + method.getName() + ", for client: " + clientInfo + ", result is null"); throw new RemoteCallException("Timeout: to perform remote method: " + method.getName() + ", for client: " + clientInfo); } finally { myPendingTasks.remove(task.getRequestId()); } } })); } @NotNull public Date getLastCommunicationTimestamp() { return myLastCommunication; } private static String getRequestId() { return StringUtil.generateUniqueHash(); } @NotNull public Collection getPendingTasks() { updateCommunitationTime(); return getPendingTasksInternal(); } private Collection getPendingTasksInternal() { List tasks = new ArrayList(); for (TaskInfo info : myPendingTasks.values()) { if (info.hasResult()) continue; tasks.add(info.getTask()); } return tasks; } public synchronized void acceptTasks(final Collection result) { updateCommunitationTime(); for (RemoteTaskResult remoteTaskResult : result) { final RemoteClient.TaskInfo task = myPendingTasks.get(remoteTaskResult.getRquestId()); if (task != null) { task.setResult(remoteTaskResult); } } updateCommunitationTime(); } private void updateCommunitationTime() { myLastCommunication = new Date(); } @NotNull public T proxy() { return myProxy; } public String getId() { return myId; } public boolean hasPendingTasks() { //TODO: Do not copy, just count return getPendingTasksInternal().size() > 0; } public boolean isAlive() { return !myIsDisposed.get(); } public void dispose() { myIsDisposed.set(true); } private class TaskInfo { private final Semaphore myLock = new Semaphore(1); private final RemoteTask myTask; private final long myTimeout; private volatile RemoteTaskResult myResult; private TaskInfo(@NotNull final RemoteTask task, final long timeout) { myTask = task; myTimeout = timeout; myLock.acquireUninterruptibly(); } public RemoteTask getTask() { return myTask; } public void setResult(@NotNull final RemoteTaskResult result) { myResult = result; myLock.release(); } @Nullable public RemoteTaskResult getResult() { return myResult; } public void waitForResponce() { try { myLock.tryAcquire(myTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // } } public boolean hasResult() { return myResult != null; } } }