package org.ovirt.engine.ui.frontend.server.gwt; import java.io.IOException; import java.util.ArrayList; import java.util.UUID; import java.util.regex.Pattern; import javax.ejb.EJB; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; 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.constants.SessionConstants; import org.ovirt.engine.core.common.interfaces.BackendLocal; 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.core.utils.CorrelationIdTracker; import org.ovirt.engine.ui.frontend.gwtservices.GenericApiGWTService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gwt.user.client.rpc.SerializationException; public class GenericApiGWTServiceImpl extends OvirtXsrfProtectedServiceServlet implements GenericApiGWTService { private static final long serialVersionUID = 7395780289048030855L; private static final Logger log = LoggerFactory.getLogger(GenericApiGWTServiceImpl.class); private static final String UI_PREFIX = "UI_"; //$NON-NLS-1$ private static final String CORRELATION_ID_HEADER = "Correlation-Id"; //$NON-NLS-1$ private static final Pattern INVALID_CORRELATION_ID_CHARACTERS_RE = Pattern.compile("[^0-9a-zA-Z_-]+"); //$NON-NLS-1$ private BackendLocal backend; @EJB(beanInterface = BackendLocal.class, mappedName = "java:global/engine/bll/Backend!org.ovirt.engine.core.common.interfaces.BackendLocal") public void setBackend(BackendLocal backend) { this.backend = backend; } public BackendLocal getBackend() { return backend; } private static String filterCorrelationIdCharacters(String correlationId) { if (StringUtils.isNotEmpty(correlationId)) { correlationId = INVALID_CORRELATION_ID_CHARACTERS_RE.matcher(correlationId).replaceAll(""); if (StringUtils.isNotEmpty(correlationId)) { return correlationId.substring(0, Math.min(correlationId.length(), 36)); } } return null; } private static String getCorrelationId(HttpServletRequest request) { String correlationId = filterCorrelationIdCharacters(request.getHeader(CORRELATION_ID_HEADER)); if (StringUtils.isEmpty(correlationId)) { correlationId = UUID.randomUUID().toString(); } return correlationId; } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String correlationId = getCorrelationId(request); CorrelationIdTracker.setCorrelationId(correlationId); response.addHeader(CORRELATION_ID_HEADER, correlationId); super.service(request, response); } @Override public VdcQueryReturnValue runQuery(VdcQueryType search, VdcQueryParametersBase searchParameters) { log.debug("Server: RunQuery invoked!"); //$NON-NLS-1$ debugQuery(search, searchParameters); searchParameters.setSessionId(getEngineSessionId()); if (searchParameters.getCorrelationId() == null) { searchParameters.setCorrelationId(CorrelationIdTracker.getCorrelationId()); } return getBackend().runQuery(search, searchParameters); } @Override public VdcQueryReturnValue runPublicQuery(VdcQueryType queryType, VdcQueryParametersBase params) { log.debug("Server: runPublicQuery invoked! '{}'", queryType); //$NON-NLS-1$ if (params.getCorrelationId() == null) { params.setCorrelationId(CorrelationIdTracker.getCorrelationId()); } debugQuery(queryType, params); return getBackend().runPublicQuery(queryType, params); } @Override public ArrayList<VdcQueryReturnValue> runMultipleQueries( ArrayList<VdcQueryType> queryTypeList, ArrayList<VdcQueryParametersBase> queryParamsList) { int size = queryTypeList == null ? 0 : queryTypeList.size(); log.debug("Server: RunMultipleQuery invoked! [amount of queries: {}]", size); //$NON-NLS-1$ ArrayList<VdcQueryReturnValue> ret = new ArrayList<>(); if (queryTypeList != null && queryParamsList != null && queryTypeList.size() == queryParamsList.size()) { String correlationId = CorrelationIdTracker.getCorrelationId(); for (int i = 0; i < queryTypeList.size(); i++) { if (queryParamsList.get(i).getCorrelationId() == null) { queryParamsList.get(i).setCorrelationId(correlationId); } debugQuery(queryTypeList.get(i), queryParamsList.get(i)); ret.add(runQuery(queryTypeList.get(i), queryParamsList.get(i))); } } else { log.error( "Wrong multi query usage: the query types and parameters must not be null " //$NON-NLS-1$ + "or be equally long. Types length '{}' vs params length '{}'", //$NON-NLS-1$ queryTypeList == null ? 0 : queryTypeList.size(), queryParamsList == null ? 0 : queryParamsList.size() ); } for (VdcQueryReturnValue vqrv : ret) { log.debug("VdcQueryReturnValue '{}'", vqrv); //$NON-NLS-1$ } log.debug("Server: RunMultipleQuery result [amount of queries: {}]", ret.size()); //$NON-NLS-1$ return ret; } @Override public ArrayList<VdcReturnValueBase> runMultipleActions(VdcActionType actionType, ArrayList<VdcActionParametersBase> multipleParams, boolean isRunOnlyIfAllValidationPass) { return runMultipleActions(actionType, multipleParams, isRunOnlyIfAllValidationPass, false); } @Override public ArrayList<VdcReturnValueBase> runMultipleActions(VdcActionType actionType, ArrayList<VdcActionParametersBase> multipleParams, boolean isRunOnlyIfAllValidationPass, boolean isWaitForResult) { log.debug("Server: RunMultipleAction invoked! [amount of actions: {}]", multipleParams.size()); //$NON-NLS-1$ String correlationId = CorrelationIdTracker.getCorrelationId(); for (VdcActionParametersBase params : multipleParams) { params.setSessionId(getEngineSessionId()); if (params.getCorrelationId() == null) { params.setCorrelationId(correlationId); } } ArrayList<VdcReturnValueBase> returnValues = getBackend().runMultipleActions(actionType, multipleParams, isRunOnlyIfAllValidationPass, isWaitForResult); return returnValues; } @Override public VdcReturnValueBase runAction(VdcActionType actionType, VdcActionParametersBase params) { log.debug("Server: RunAction invoked!"); //$NON-NLS-1$ debugAction(actionType, params); params.setSessionId(getEngineSessionId()); if (params.getCorrelationId() == null) { params.setCorrelationId(CorrelationIdTracker.getCorrelationId()); } return getBackend().runAction(actionType, params); } private String getEngineSessionId() { return (String) getSession().getAttribute(SessionConstants.HTTP_SESSION_ENGINE_SESSION_ID_KEY); } private HttpSession getSession() { HttpServletRequest request = this.getThreadLocalRequest(); HttpSession session = request.getSession(); log.debug("IP '{}', Session ID '{}'", request.getRemoteAddr(), session.getId()); //$NON-NLS-1$ return session; } @Override public void storeInHttpSession(String key, String value) { HttpServletRequest request = this.getThreadLocalRequest(); HttpSession session = request.getSession(); session.setAttribute(UI_PREFIX + key, value); } @Override public String retrieveFromHttpSession(String key) { HttpServletRequest request = this.getThreadLocalRequest(); HttpSession session = request.getSession(); Object value = session.getAttribute(UI_PREFIX + key); String result = null; if (value instanceof String) { result = (String) value; } else if (value != null) { log.error("Retrieving non string value from session"); //$NON-NLS-1$ } return result; } @Override protected void doUnexpectedFailure(Throwable error) { // If the user is using a version of the application different to what // the server expects the names of the RPC serialization policy files // will not match, and in that case GWT just sends the exception to the // log, which is not very user or admin friendly, so we replace that // with a more friendly message: if (error instanceof SerializationException) { error = new SerializationException( "Can't find the serialization policy file. " + //$NON-NLS-1$ "This probably means that the user has an old " + //$NON-NLS-1$ "version of the application loaded in the " + //$NON-NLS-1$ "browser. To solve the issue the user needs " + //$NON-NLS-1$ "to close the browser and open it again, so " + //$NON-NLS-1$ "that the application is reloaded.", //$NON-NLS-1$ error ); } // Now that we replaced the message let GWT do what it uses to do: super.doUnexpectedFailure(error); } private void debugQuery(VdcQueryType queryType, VdcQueryParametersBase parameters) { log.debug("Query type '{}', Parameters '{}'", queryType, parameters); //$NON-NLS-1$ } private void debugAction(VdcActionType actionType, VdcActionParametersBase params) { log.debug("Action type '{}', Parameters '{}'", actionType, params); //$NON-NLS-1$ } }