package org.ovirt.engine.core.bll; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.VdcBllErrors; import org.ovirt.engine.core.common.errors.VdcFault; import org.ovirt.engine.core.common.interfaces.IBackendCallBackServer; import org.ovirt.engine.core.common.queries.AsyncQueryResults; import org.ovirt.engine.core.common.queries.RegisterQueryParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.NGuid; import org.ovirt.engine.core.utils.ThreadLocalParamsContainer; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; import org.ovirt.engine.core.utils.timer.OnTimerMethodAnnotation; import org.ovirt.engine.core.utils.timer.SchedulerUtilQuartzImpl; //VB & C# TO JAVA CONVERTER NOTE: There is no Java equivalent to C# namespace aliases: //using Timer=System.Timers.Timer; /** * Class, responcible to backend callbacks treatment. Contains proxies to * frontends, registerd to backend events */ public final class BackendCallBacksDirector { private static final BackendCallBacksDirector _instance = new BackendCallBacksDirector(); public static BackendCallBacksDirector getInstance() { return _instance; } private static final java.util.HashMap<String, CallBackData> _callBacks = new java.util.HashMap<String, CallBackData>(); private BackendCallBacksDirector() { SchedulerUtilQuartzImpl.getInstance().scheduleAFixedDelayJob(this, "QueriesRefreshTimer_Elapsed", new Class[0], new Object[0], Config.<Integer> GetValue(ConfigValues.SearchesRefreshRateInSeconds), Config.<Integer> GetValue(ConfigValues.SearchesRefreshRateInSeconds), TimeUnit.SECONDS); } @OnTimerMethodAnnotation("QueriesRefreshTimer_Elapsed") public void QueriesRefreshTimer_Elapsed() { CountDownLatch latch; List<String> callBacksToRemove; synchronized (_callBacks) { if (_callBacks.size() > 0) { latch = new CountDownLatch(_callBacks.size()); callBacksToRemove = Collections.synchronizedList(new ArrayList<String>()); } else { return; } refreshQueries(latch, callBacksToRemove); } try { // TODO move this to config latch.await(3, TimeUnit.MINUTES); if (callBacksToRemove.size() > 0) { synchronized (_callBacks) { for (String callBackToRemove : callBacksToRemove) { _callBacks.remove(callBackToRemove); } } } } catch (InterruptedException e) { } } private boolean refreshCallBackQuery(CallBackData callBackData) { if (callBackData != null) { boolean toRemove = false; try { int refreshCyclesSinceLastPolling = callBackData.RefreshCyclesWithoutPolling.incrementAndGet(); if (refreshCyclesSinceLastPolling < Config .<Integer> GetValue(ConfigValues.AsyncPollingCyclesBeforeRefreshSuspend)) { callBackData.RefreshQueries(); } // } else if (refreshCyclesSinceLastPolling == Config .<Integer> GetValue(ConfigValues.AsyncPollingCyclesBeforeRefreshSuspend)) log.debugFormat( "Client did not poll async queries updates for {1} cycles, suspending server side updates for session id = {0}", callBackData.getSessionId(), Config.<Integer> GetValue(ConfigValues.AsyncPollingCyclesBeforeRefreshSuspend)); else if (refreshCyclesSinceLastPolling > Config .<Integer> GetValue(ConfigValues.AsyncPollingCyclesBeforeCallbackCleanup)) // enough, // assume // client // is // gone { log.debugFormat( "Client did not poll async queries updates for {1} cycles sessionId = {0}. Callback will be removed.", callBackData.getSessionId(), Config.<Integer> GetValue(ConfigValues.AsyncPollingCyclesBeforeCallbackCleanup)); toRemove = true; } } catch (RuntimeException ex) { log.infoFormat("Communication with client has aborted sessionId = {0}. Callback will be removed.", callBackData.getSessionId()); if (Config.<Boolean> GetValue(ConfigValues.DebugSearchLogging)) { log.info("Problem with refreshing callback query", ex); } toRemove = true; } if (toRemove) { for (Guid QueryId : callBackData.getQueryIDs()) { callBackData.UnregisterQuery(QueryId); } } return toRemove; } return false; } private void refreshQueries(final CountDownLatch latch, final List<String> callBacksToRemove) { for (final CallBackData callBackData : _callBacks.values()) { if (callBackData.compareAndSetDuringRefresh()) { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { try { boolean toRemove; synchronized (callBackData) { toRemove = refreshCallBackQuery(callBackData); } if (toRemove) { callBacksToRemove.add(callBackData.getSessionId()); } } finally { callBackData.resetDuringRefresh(); latch.countDown(); } } }); } } } public void RegisterQuery(RegisterQueryParameters parameters) { CallBackData callBack = null; synchronized (_callBacks) { if (_callBacks.containsKey(ThreadLocalParamsContainer.getHttpSessionId())) { callBack = _callBacks.get(ThreadLocalParamsContainer.getHttpSessionId()); } else { IBackendCallBackServer backendCallBack = CallbackServer.Instance; if (backendCallBack != null) { callBack = new CallBackData(backendCallBack, ThreadLocalParamsContainer.getHttpSessionId()); _callBacks.put(callBack.getSessionId(), callBack); if (Config.<Boolean> GetValue(ConfigValues.DebugSearchLogging)) { log.infoFormat("Frontend {0} has registered for queries", callBack.getSessionId()); } } } } synchronized (callBack) { final QueryData queryData = QueryData.CreateQueryData(parameters.getQueryID(), parameters.getQueryType(), parameters.getQueryParams()); callBack.RegisterQuery(queryData.getQueryId(), queryData); if (Config.<Boolean> GetValue(ConfigValues.DebugSearchLogging)) { log.infoFormat("Frontend {0} has registered to query. Query Id: {1}, Query type: {2}.", callBack.getSessionId(), queryData.getQueryId(), queryData.getQueryType()); } } } public void UnregisterQuery(Guid queryID) { CallBackData callBack = null; synchronized (_callBacks) { callBack = _callBacks.get(ThreadLocalParamsContainer .getHttpSessionId()); } if (callBack != null) { synchronized (callBack) { callBack.UnregisterQuery(queryID); if (Config.<Boolean> GetValue(ConfigValues.DebugSearchLogging)) { log.infoFormat("Frontend {0} has unregistered from query. Query Id: {1}.", callBack.getSessionId(), queryID); } if (callBack.getQueriesCount() == 0) { if (Config.<Boolean> GetValue(ConfigValues.DebugSearchLogging)) { log.infoFormat("Frontend {0} unregistered from all queries - removing callback.", callBack.getSessionId()); } synchronized (_callBacks) { _callBacks.remove(ThreadLocalParamsContainer.getHttpSessionId()); } } } } } public AsyncQueryResults GetAsyncQueryResults() { try { CallBackData callBack; synchronized (_callBacks) { callBack = _callBacks.get(ThreadLocalParamsContainer.getHttpSessionId()); } // if no session, add a backend exception on session to be returned // to client NGuid FaultQueryId = null; if (callBack == null) { log.warnFormat("BackendCallbacksDirector.AsyncQueryResults - unknown callbackData for session"); callBack = new CallBackData(CallbackServer.Instance, null); VdcFault fault = new VdcFault(); fault.setError(VdcBllErrors.SESSION_ERROR); fault.setMessage("Unkown session, please login again."); FaultQueryId = CallbackServer.Instance.BackendException( VdcActionType.LoginAdminUser, fault); callBack.RegisterQuery(FaultQueryId.getValue(), null); } else { synchronized (callBack) { callBack.RefreshCyclesWithoutPolling.set(0); Guid[] guidArray = callBack.getQueryIDs(); if (guidArray.length > 0) { AsyncQueryResults results = CallbackServer.Instance.GetAsyncQueryResults(guidArray); for (int i = 0; i < results.getQueryData().length; i++) { if (results.getQueryData()[i].getValue().length == 1 && results.getQueryData()[i].getValue()[0] .getFaulted() != null) { UnregisterQuery(results.getQueryIDs()[i]); } } return results; } } } } catch (Exception ex) { log.error("Unexpected error in BackendCallBacksDirector.GetAsyncQueryResults", ex); throw new RuntimeException(ex); } return new AsyncQueryResults(); } public void RegisterFaultQuery(Guid FaultQueryId, String sessionId) { try { CallBackData callBack; synchronized (_callBacks) { callBack = _callBacks.get(sessionId); } if (callBack != null) { synchronized (callBack) { callBack.RegisterQuery(FaultQueryId, null); } } } catch (Exception ex) { log.error("Unexpected error in BackendCallBacksDirector.RegisterFaultQuery", ex); throw new RuntimeException(ex); } } public void UnregisterFaultQuery(Guid FaultQueryId) { try { CallBackData callBack; synchronized (_callBacks) { callBack = _callBacks.get(ThreadLocalParamsContainer.getHttpSessionId()); } if (callBack != null) { synchronized (callBack) { callBack.UnregisterQuery(FaultQueryId); } } } catch (Exception ex) { log.error("Unexpected error in BackendCallBacksDirector.UnregisterFaultQuery", ex); throw new RuntimeException(ex); } } private static LogCompat log = LogFactoryCompat.getLog(BackendCallBacksDirector.class); }