package org.ovirt.engine.ui.frontend.communication; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.queries.VdcQueryParametersBase; import org.ovirt.engine.core.common.queries.VdcQueryReturnValue; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.ui.frontend.gwtservices.GenericApiGWTServiceAsync; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.XsrfToken; import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; import com.google.inject.Inject; /** * This class is an implementation of the {@code CommunicationProvider} using the GWT-RPC mechanism. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class GWTRPCCommunicationProvider implements CommunicationProvider { /** * Callback interface when retrieving the GWT RPC service. We need the callback in order to retrieve the * XSRF token if it is not available yet. */ private interface ServiceCallback { /** * The callback method with the service. * @param service The GWT RPC service that contains the appropriate token. */ void serviceFound(GenericApiGWTServiceAsync service); /** * The failure callback in case we are unable to retrieve the approriate token. * @param exception The exception thrown. */ void onFailure(Throwable exception); } /** * GWT RPC service. */ private final GenericApiGWTServiceAsync service; /** * GWT XSRF service. */ private final XsrfTokenServiceAsync xsrfService; /** * The XSRF request builder. */ private final XsrfRpcRequestBuilder xsrfRequestBuilder; /** * Get the GWT RPC service. * @param callback The callback to use when determining the service. */ private void getService(final ServiceCallback callback) { if (xsrfRequestBuilder.getXsrfToken() != null) { callback.serviceFound(service); } else { xsrfService.getNewXsrfToken(new AsyncCallback<XsrfToken>() { @Override public void onSuccess(XsrfToken token) { xsrfRequestBuilder.setXsrfToken(token); callback.serviceFound(service); } @Override public void onFailure(Throwable caught) { callback.onFailure(caught); } }); } } /** * Constructor. * @param asyncService GWT RPC service. * @param xsrfTokenService The GWT XSRF token service, used to retrieve a new XSRF token. */ @Inject public GWTRPCCommunicationProvider(final GenericApiGWTServiceAsync asyncService, final XsrfTokenServiceAsync xsrfTokenService, XsrfRpcRequestBuilder xsrfRequestBuilder) { this.service = asyncService; this.xsrfService = xsrfTokenService; this.xsrfRequestBuilder = xsrfRequestBuilder; } /** * Transmit a single operation, with the expectation of a single result object. * @param operation The operation to execute. */ void transmitOperation(final VdcOperation<?, ?> operation) { // Figure out if this is an action or a query. if (operation.isAction()) { // Action runAction(operation); } else { // Query if (operation.isPublic()) { runPublicQuery(operation); } else { runQuery(operation); } } } /** * Run a query that does not require the user to be logged in. * @param operation The operation to run. */ private void runPublicQuery(final VdcOperation<?, ?> operation) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.runPublicQuery((VdcQueryType) operation.getOperation(), (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { @Override public void onFailure(final Throwable exception) { operation.getCallback().onFailure(operation, exception); } @Override public void onSuccess(final VdcQueryReturnValue result) { operation.getCallback().onSuccess(operation, result); } }); } @Override public void onFailure(Throwable exception) { operation.getCallback().onFailure(operation, exception); } }); } /** * Run a query that requires the user to be logged in. * @param operation The operation to run. */ private void runQuery(final VdcOperation<?, ?> operation) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.runQuery((VdcQueryType) operation.getOperation(), (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { @Override public void onFailure(final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); operation.getCallback().onFailure(operation, exception); } @Override public void onSuccess(final VdcQueryReturnValue result) { operation.getCallback().onSuccess(operation, result); } }); } @Override public void onFailure(Throwable exception) { operation.getCallback().onFailure(operation, exception); } }); } /** * Run an action on the {@code GenericApiGWTServiceAsync} service. * @param operation The operation to run. */ private void runAction(final VdcOperation<?, ?> operation) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.runAction((VdcActionType) operation.getOperation(), (VdcActionParametersBase) operation.getParameter(), new AsyncCallback<VdcReturnValueBase>() { @Override public void onFailure(final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); operation.getCallback().onFailure(operation, exception); } @Override public void onSuccess(final VdcReturnValueBase result) { operation.getCallback().onSuccess(operation, result); } }); } @Override public void onFailure(Throwable exception) { operation.getCallback().onFailure(operation, exception); } }); } @Override public void transmitOperationList(final List<VdcOperation<?, ?>> operations) { // Operations can be either actions or queries. Both require different handling so lets // Split them out into two lists so we can process them independently. List<VdcOperation<?, ?>> queriesList = new ArrayList<>(); Map<VdcActionType, List<VdcOperation<?, ?>>> actionsMap = new HashMap<>(); for (VdcOperation<?, ?> operation: operations) { if (operation.isAction()) { List<VdcOperation<?, ?>> actionsList = actionsMap.get(operation.getOperation()); if (actionsList == null) { actionsList = new ArrayList<>(); actionsMap.put((VdcActionType) operation.getOperation(), actionsList); } actionsList.add(operation); } else { queriesList.add(operation); } } if (!actionsMap.isEmpty()) { // We have some actions, call method to send actions. transmitMultipleActions(actionsMap); } if (!queriesList.isEmpty()) { // We have some queries, call method to send queries. transmitMultipleQueries(queriesList); } } /** * Call the back-end with either RunMultipleQueries or RunQuery based on the size of the queriesList. * When the query(ies) complete call the appropriate callback(s). * @param queriesList The list of queries. */ private void transmitMultipleQueries(final List<VdcOperation<?, ?>> queriesList) { if (queriesList.size() > 1 || (queriesList.size() == 1 && queriesList.get(0).getCallback() instanceof VdcOperationCallbackList)) { final List<VdcQueryType> queryTypes = new ArrayList<>(); final List<VdcQueryParametersBase> parameters = new ArrayList<>(); for (VdcOperation<?, ?> operation: new ArrayList<>(queriesList)) { if (operation.isPublic()) { queriesList.remove(operation); runPublicQuery(operation); } else { queryTypes.add((VdcQueryType) operation.getOperation()); parameters.add((VdcQueryParametersBase) operation.getParameter()); } } getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.runMultipleQueries((ArrayList<VdcQueryType>) queryTypes, (ArrayList<VdcQueryParametersBase>) parameters, new AsyncCallback<ArrayList<VdcQueryReturnValue>>() { @Override public void onFailure(final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); handleMultipleQueriesFailure(queriesList, exception); } @Override public void onSuccess(final ArrayList<VdcQueryReturnValue> result) { Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(queriesList); for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { List<VdcQueryReturnValue> queryResult = (List<VdcQueryReturnValue>) getOperationResult( callbackEntry.getValue(), queriesList, result); if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { ((VdcOperationCallbackList) callbackEntry.getKey()) .onSuccess(callbackEntry.getValue(), queryResult); } else { ((VdcOperationCallback) callbackEntry.getKey()) .onSuccess(callbackEntry.getValue().get(0), queryResult.get(0)); } } } }); } @Override public void onFailure(Throwable exception) { handleMultipleQueriesFailure(queriesList, exception); } }); } else if (queriesList.size() == 1) { transmitOperation(queriesList.get(0)); } } /** * Multiple queries failure handler. * @param queriesList The queries list. * @param exception The exception causing the failure. */ private void handleMultipleQueriesFailure(final List<VdcOperation<?, ?>> queriesList, final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(queriesList); for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); } else { ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), exception); } } } /** * Call the back-end using either RunAction or RunMultiple actions based on the fact that the map being * passed is keyed on the {@code VdcActionType} this allows us to determine if there are one or more actions * pending per action type. * When the action completes call back the appropriate callback methods with the appropriate results. * @param actions A {@code Map} of {@code VdcActionType}s with a list of operations associated with that * type */ private void transmitMultipleActions(final Map<VdcActionType, List<VdcOperation<?, ?>>> actions) { for (final Map.Entry<VdcActionType, List<VdcOperation<?, ?>>> actionEntry: actions.entrySet()) { List<VdcActionParametersBase> parameters = new ArrayList<>(); final List<VdcOperation<?, ?>> allActionOperations = actionEntry.getValue(); boolean runOnlyIfAllValidationPass = false; for (VdcOperation<?, ?> operation: allActionOperations) { runOnlyIfAllValidationPass = operation.isRunOnlyIfAllValidationPass(); parameters.add((VdcActionParametersBase) operation.getParameter()); } if (parameters.size() > 1 || (allActionOperations.size() == 1 && allActionOperations.get(0).getCallback() instanceof VdcOperationCallbackList)) { List<VdcOperation<?, ?>> waitForResultList = getWaitForResultList(actionEntry.getValue()); if (!waitForResultList.isEmpty()) { runMultipleActions(actionEntry.getKey(), waitForResultList, parameters, allActionOperations, true, runOnlyIfAllValidationPass); } if (waitForResultList.size() != actionEntry.getValue().size()) { List<VdcOperation<?, ?>> immediateReturnList = actionEntry.getValue(); immediateReturnList.removeAll(waitForResultList); //Don't care if it succeeds or not. runMultipleActions(actionEntry.getKey(), immediateReturnList, parameters, allActionOperations, false, runOnlyIfAllValidationPass); } } else if (actionEntry.getValue().size() == 1) { transmitOperation(actionEntry.getValue().get(0)); } } } private List<VdcOperation<?, ?>> getWaitForResultList(List<VdcOperation<?, ?>> originalList) { List<VdcOperation<?, ?>> result = new ArrayList<>(); for (VdcOperation<?, ?> operation: originalList) { if (!operation.isFromList()) { result.add(operation); } } return result; } private void runMultipleActions(final VdcActionType actionType, final List<VdcOperation<?, ?>> operations, final List<VdcActionParametersBase> parameters, final List<VdcOperation<?, ?>> allActionOperations, final boolean waitForResults, final boolean runOnlyIfAllValidationPass) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.runMultipleActions(actionType, (ArrayList<VdcActionParametersBase>) parameters, runOnlyIfAllValidationPass, waitForResults, new AsyncCallback<ArrayList<VdcReturnValueBase>>() { @Override public void onFailure(final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); handleRunMultipleActionFailure(operations, exception); } @Override public void onSuccess(final ArrayList<VdcReturnValueBase> result) { Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(operations); for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { List<VdcReturnValueBase> actionResult = (List<VdcReturnValueBase>) getOperationResult(callbackEntry.getValue(), allActionOperations, result); if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { ((VdcOperationCallbackList) callbackEntry.getKey()) .onSuccess(callbackEntry.getValue(), actionResult); } else { ((VdcOperationCallback) callbackEntry.getKey()) .onSuccess(callbackEntry.getValue().get(0), actionResult.get(0)); } } } }); } @Override public void onFailure(Throwable exception) { handleRunMultipleActionFailure(operations, exception); } }); } private void handleRunMultipleActionFailure(final List<VdcOperation<?, ?>> operations, final Throwable exception) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(operations); for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); } else { ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), exception); } } } /** * Map operations by callback, so we can properly call a single callback for all related operations. * @param operationList The list of operations to determine the map for. * @return A Map of operations keyed by the callback. */ private Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> getCallbackMap( final List<VdcOperation<?, ?>> operationList) { Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = new HashMap<>(); for (VdcOperation<?, ?> operation: operationList) { List<VdcOperation<?, ?>> operationsByCallback = callbackMap.get(operation.getCallback()); if (operationsByCallback == null) { operationsByCallback = new ArrayList<>(); callbackMap.put(operation.getCallback(), operationsByCallback); } operationsByCallback.add(operation); } return callbackMap; } /** * Build a results list that maps 1 to 1 with the operationList. * allOperations and allResults have the same index, so we look up the index of the operations we want in all * operations, then retrieve the result that matches that. * @param operationList The list of operations we want to compare to. * @param allOperations The list of all operations that we want to get indexes from. * @param allResults The list of results that map 1 to 1 with allOperations to get the results from. * @return A {@code List} of return values that maps 1 to 1 with operationList. */ List<?> getOperationResult(final List<VdcOperation<?, ?>> operationList, final List<VdcOperation<?, ?>> allOperations, final List<?> allResults) { List result = new ArrayList(); for (VdcOperation<?, ?> operation: operationList) { int index = allOperations.indexOf(operation); if (index > -1 && index < allResults.size()) { result.add(allResults.get(index)); } } return result; } /** * Log the user out. * @param callback The callback to call when the operation is complete. */ @Override public void logout(final UserCallback callback) { //Remove the rpc token when logging out. xsrfRequestBuilder.setXsrfToken(null); VdcReturnValueBase retVal = new VdcReturnValueBase(); retVal.setSucceeded(true); callback.onSuccess(retVal); } @Override public void storeInHttpSession(final String key, final String value, final StorageCallback callback) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.storeInHttpSession(key, value, new AsyncCallback<Void>() { @Override public void onSuccess(final Void result) { callback.onSuccess(null); } @Override public void onFailure(final Throwable caught) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); callback.onFailure(caught); } }); } @Override public void onFailure(Throwable caught) { xsrfRequestBuilder.setXsrfToken(null); callback.onFailure(caught); } }); } @Override public void retrieveFromHttpSession(final String key, final StorageCallback callback) { getService(new ServiceCallback() { @Override public void serviceFound(GenericApiGWTServiceAsync service) { service.retrieveFromHttpSession(key, new AsyncCallback<String>() { @Override public void onSuccess(final String result) { callback.onSuccess(result); } @Override public void onFailure(final Throwable caught) { //Clear out the token, and let the retry mechanism try again. xsrfRequestBuilder.setXsrfToken(null); callback.onFailure(caught); } }); } @Override public void onFailure(Throwable exception) { xsrfRequestBuilder.setXsrfToken(null); callback.onFailure(exception); } }); } }