diff --git a/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmRestApi.java b/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmRestApi.java new file mode 100644 index 0000000000000000000000000000000000000000..484c39a77ed7eb1d6803bee65736b914df0e84ba --- /dev/null +++ b/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmRestApi.java @@ -0,0 +1,675 @@ +package gov.anl.aps.dm.api; + +import gov.anl.aps.dm.common.constants.DmHttpHeader; +import gov.anl.aps.dm.common.constants.DmProperty; +import gov.anl.aps.dm.common.constants.DmRole; +import gov.anl.aps.dm.common.constants.DmServiceProtocol; +import gov.anl.aps.dm.common.exceptions.AuthorizationError; +import gov.anl.aps.dm.common.exceptions.CommunicationError; +import gov.anl.aps.dm.common.exceptions.ConfigurationError; +import gov.anl.aps.dm.common.exceptions.InvalidArgument; +import gov.anl.aps.dm.common.exceptions.InvalidSession; +import gov.anl.aps.dm.common.exceptions.DmException; +import gov.anl.aps.dm.common.exceptions.DmExceptionFactory; +import gov.anl.aps.dm.common.utilities.NoServerVerificationSSLSocketFactory; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import org.apache.log4j.Logger; + +/** + * DM REST Web Service API class. + * + * This class serves as superclass for all DM web service interface classes. It + * handles basic communication with web service (establishing sessions, sending + * requests, receiving responses, generating exceptions, etc.). + */ +public class DmRestApi { + + /** + * Relative path for login requests. + */ + public static final String LOGIN_REQUEST_URL = "/login"; + + private static final String DefaultSessionId = "defaultSession"; + + private static final boolean httpsInitialized = initializeHttpsConnection(); + private static final Logger logger = Logger.getLogger(DmRestApi.class.getName()); + + private static boolean initializeHttpsConnection() { + HttpsURLConnection.setDefaultSSLSocketFactory(new NoServerVerificationSSLSocketFactory()); + return true; + } + + private URL serviceUrl; + private DmSession session = new DmSession(); + + /** + * Constructor. + * + * Initializes web service URL from system properties. + * + * @throws ConfigurationError if web service URL property is malformed or + * null + */ + public DmRestApi() throws ConfigurationError { + configureFromProperties(); + } + + /** + * Constructor. + * + * @param webServiceUrl web service URL + * @throws ConfigurationError if web service URL is malformed or null + */ + public DmRestApi(String webServiceUrl) throws ConfigurationError { + configureFromString(webServiceUrl); + } + + /** + * Configure web service URL from Java VM properties. + * + * @throws ConfigurationError if web service URL property is malformed or + * null + */ + public final void configureFromProperties() throws ConfigurationError { + String webServiceUrl = System.getProperty(DmProperty.WEB_SERVICE_URL_PROPERTY_NAME); + configureFromString(webServiceUrl); + } + + /** + * Configure web service URL from string. + * + * @param webServiceUrl web service URL + * @throws ConfigurationError if web service URL property is malformed or + * null + */ + public final void configureFromString(String webServiceUrl) throws ConfigurationError { + if (webServiceUrl == null) { + throw new ConfigurationError("DM web service url is not specified."); + } + try { + serviceUrl = new URL(webServiceUrl); + } catch (MalformedURLException ex) { + throw new ConfigurationError("Malformed DM web service url: " + webServiceUrl); + } + + DmServiceProtocol protocol = DmServiceProtocol.fromString(serviceUrl.getProtocol()); + if (protocol == null) { + throw new ConfigurationError("Unsupported service protocol specified in " + webServiceUrl); + } + } + + public URL getServiceUrl() { + return serviceUrl; + } + + public DmSession getSession() { + return session; + } + + public void setSession(DmSession session) { + this.session = session; + } + + /** + * Check HTTP response for exceptions. + * + * @param connection HTTP connection + * @throws DmException when DM error is detected + */ + public static void checkHttpResponseForDmException(HttpURLConnection connection) throws DmException { + String exceptionType = connection.getHeaderField(DmHttpHeader.DM_EXCEPTION_TYPE_HEADER); + if (exceptionType != null) { + String statusMessage = connection.getHeaderField(DmHttpHeader.DM_STATUS_MESSAGE_HEADER); + String statusCode = connection.getHeaderField(DmHttpHeader.DM_STATUS_CODE_HEADER); + int code = Integer.parseInt(statusCode); + DmExceptionFactory.throwDmException(exceptionType, code, statusMessage); + } + } + + /** + * Convert HTTP error for exceptions. + * + * @param httpError HTTP error + * @param connection HTTP connection + * @return generated DM exception + */ + public static DmException convertHttpErrorToDmException(Exception httpError, HttpURLConnection connection) { + String exceptionType = connection.getHeaderField(DmHttpHeader.DM_EXCEPTION_TYPE_HEADER); + if (exceptionType != null) { + String statusMessage = connection.getHeaderField(DmHttpHeader.DM_STATUS_MESSAGE_HEADER); + String statusCode = connection.getHeaderField(DmHttpHeader.DM_STATUS_CODE_HEADER); + int code = Integer.parseInt(statusCode); + return DmExceptionFactory.generateDmException(exceptionType, code, statusMessage); + } else { + return new DmException(httpError); + } + } + + /** + * Get full request URL. + * + * @param requestUrl relative request path, e.g. /object + * @return full request URL string, e.g. http://localhost:17524/dm/object + */ + public String getFullRequestUrl(String requestUrl) { + String url = serviceUrl + requestUrl; + return url; + } + + /** + * Verify session cookie. + * + * @return session cookie + * @throws InvalidSession if session cookie is expired or null + */ + public String verifySessionCookie() throws InvalidSession { + return session.verifyCookie(); + } + + /* + * Get all response headers in a single string. + * + * @param connection HTTP connection + * @return string containing response headers + */ + private static String getResponseHeaders(HttpURLConnection connection) { + String headerString = ""; + Map<String, List<String>> headerMap = connection.getHeaderFields(); + for (String key : headerMap.keySet()) { + List<String> values = headerMap.get(key); + headerString += key + ": " + values + "\n"; + } + return headerString; + } + + /** + * Prepare post data. + * + * @param data key/value data map + * @return string suitable for HTTP post request + * @throws InvalidArgument in case of invalid input data + */ + public static String preparePostData(Map<String, String> data) throws InvalidArgument { + try { + String postData = ""; + String separator = ""; + for (String key : data.keySet()) { + postData += separator + key + "=" + URLEncoder.encode(data.get(key), "UTF8"); + separator = "&"; + } + return postData; + } catch (UnsupportedEncodingException ex) { + logger.error("Invalid argument: " + ex); + throw new InvalidArgument(ex); + } + } + + /** + * Update session cookie from connection's HTTP headers. + * + * @param connection HTTP connection + */ + private void updateSessionCookie(HttpURLConnection connection) { + String cookie = connection.getHeaderField(DmHttpHeader.DM_SET_COOKIE_HEADER); + if (cookie != null) { + session.setCookie(cookie); + logger.debug("Updated session cookie: " + cookie); + } + String sessionRole = connection.getHeaderField(DmHttpHeader.DM_SESSION_ROLE_HEADER); + if (sessionRole != null) { + session.setRole(DmRole.fromString(sessionRole)); + logger.debug("Updated session role: " + sessionRole); + } + } + + /** + * Send post data. + * + * @param data key/value data map + * @param connection HTTP connection + * @throws InvalidArgument in case there is a problem with post data + * @throws DmException in case of any other error + */ + private static void sendPostData(Map<String, String> data, HttpURLConnection connection) throws InvalidArgument, DmException { + String postData = preparePostData(data); + try (DataOutputStream dos = new DataOutputStream(connection.getOutputStream())) { + dos.writeBytes(postData); + dos.flush(); + } catch (IOException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Read HTTP response. + * + * @param connection HTTP connection + * @return HTTP response as a string + * @throws DmException in case of any errors + */ + private static String readHttpResponse(HttpURLConnection connection) throws DmException { + try { + BufferedReader br = new BufferedReader(new InputStreamReader( + (connection.getInputStream()))); + StringBuilder sb = new StringBuilder(); + String output; + while ((output = br.readLine()) != null) { + sb.append(output); + sb.append('\n'); + } + return sb.toString(); + } catch (IOException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Set session cookie in the request headers. + * + * @param connection HTTP connection + * @param sessionCookie session cookie (may be null) + */ + private static void setCookieRequestHeader(HttpURLConnection connection, String sessionCookie) { + if (sessionCookie != null) { + connection.setRequestProperty("Cookie", sessionCookie); + logger.debug("Setting session cookie to: " + sessionCookie); + } + } + + /** + * Set common POST request headers plus session cookie. + * + * @param connection HTTP connection + * @param sessionCookie session cookie (may be null) + * @throws DmException in case of any errors + */ + private static void setPostRequestHeaders(HttpURLConnection connection, String sessionCookie) throws DmException { + setPostRequestHeaders(connection); + setCookieRequestHeader(connection, sessionCookie); + } + + /** + * Set common POST request headers. + * + * @param connection HTTP connection + * @throws DmException in case of any errors + */ + private static void setPostRequestHeaders(HttpURLConnection connection) throws DmException { + try { + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + } catch (ProtocolException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Set common GET request headers plus session cookie. + * + * @param connection HTTP connection + * @param sessionCookie session cookie (may be null) + * @throws DmException in case of any errors + */ + private static void setGetRequestHeaders(HttpURLConnection connection, String sessionCookie) throws DmException { + setGetRequestHeaders(connection); + setCookieRequestHeader(connection, sessionCookie); + } + + /** + * Set common GET request headers. + * + * @param connection HTTP connection + * @throws DmException in case of any errors + */ + private static void setGetRequestHeaders(HttpURLConnection connection) throws DmException { + try { + connection.setRequestMethod("GET"); + } catch (ProtocolException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Set common PUT request headers plus session cookie. + * + * @param connection HTTP connection + * @param sessionCookie session cookie (may be null) + * @throws DmException in case of any errors + */ + private static void setPutRequestHeaders(HttpURLConnection connection, String sessionCookie) throws DmException { + setPutRequestHeaders(connection); + setCookieRequestHeader(connection, sessionCookie); + } + + /** + * Set common PUT request headers. + * + * @param connection HTTP connection + * @throws DmException in case of any errors + */ + private static void setPutRequestHeaders(HttpURLConnection connection) throws DmException { + try { + connection.setDoOutput(true); + connection.setRequestMethod("PUT"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + } catch (ProtocolException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Set common DELETE request headers plus session cookie. + * + * @param connection HTTP connection + * @param sessionCookie session cookie (may be null) + * @throws DmException in case of any errors + */ + private static void setDeleteRequestHeaders(HttpURLConnection connection, String sessionCookie) throws DmException { + setDeleteRequestHeaders(connection); + setCookieRequestHeader(connection, sessionCookie); + } + + /** + * Set common DELETE request headers. + * + * @param connection HTTP connection + * @throws DmException in case of any errors + */ + private static void setDeleteRequestHeaders(HttpURLConnection connection) throws DmException { + try { + connection.setRequestMethod("DELETE"); + } catch (ProtocolException ex) { + logger.error(ex); + throw new DmException(ex); + } + } + + /** + * Login with a given username and password, and with specified session id. + * + * @param username username + * @param password password + * @param sessionId session id, can be null + * @throws AuthorizationError in case of incorrect username or password + * @throws CommunicationError in case service cannot be contacted + * @throws DmException in case of any other errors + */ + public void login(String username, String password, String sessionId) throws DmException { + HttpURLConnection connection = null; + try { + String urlString = getFullRequestUrl(LOGIN_REQUEST_URL); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + setPostRequestHeaders(connection); + HashMap<String, String> loginData = new HashMap<>(); + loginData.put("username", username); + loginData.put("password", password); + + logger.debug("Establishing session for user: " + username); + logger.debug("Service URL: " + serviceUrl); + sendPostData(loginData, connection); + checkHttpResponseForDmException(connection); + session.setUsername(username); + session.setId(sessionId); + updateSessionCookie(connection); + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (DmException ex) { + logger.error(ex); + throw ex; + } catch (IOException ex) { + logger.error(ex); + throw new DmException(ex); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + } + + /** + * Login with a given username and password. + * + * @param username username + * @param password password + * @throws AuthorizationError in case of incorrect username or password + * @throws CommunicationError in case service cannot be contacted + * @throws DmException in case of any other errors + */ + public void login(String username, String password) throws DmException { + login(username, password, DefaultSessionId); + } + + /** + * Invoke GET request. + * + * @param requestUrl relative request path, e.g. /object + * @return service response string + * @throws DmException in case of any errors + */ + public String invokeSessionGetRequest(String requestUrl) throws DmException { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + logger.debug("Invoking session get request for URL: " + requestUrl); + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + setGetRequestHeaders(connection, sessionCookie); + updateSessionCookie(connection); + checkHttpResponseForDmException(connection); + logger.debug("Response message:\n" + connection.getResponseMessage()); + return readHttpResponse(connection); + } catch (DmException ex) { + throw ex; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (IOException ex) { + DmException dmException = convertHttpErrorToDmException(ex, connection); + logger.error(ex.getMessage()); + throw dmException; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Invoke GET request. + * + * @param requestUrl relative request path, e.g. /object + * @return service response string + * @throws DmException in case of any errors + */ + public String invokeGetRequest(String requestUrl) throws DmException { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + logger.debug("Invoking get request for URL: " + requestUrl); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + updateSessionCookie(connection); + checkHttpResponseForDmException(connection); + logger.debug("Response message:\n" + connection.getResponseMessage()); + return readHttpResponse(connection); + } catch (DmException ex) { + throw ex; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (IOException ex) { + DmException dmException = convertHttpErrorToDmException(ex, connection); + logger.error(ex.getMessage()); + throw dmException; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Invoke POST request. + * + * @param requestUrl relative request path, e.g. /object + * @param data request data + * @return service response string + * @throws DmException in case of any errors + */ + public String invokeSessionPostRequest(String requestUrl, Map<String, String> data) throws DmException { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + logger.debug("Invoking session post request for URL: " + requestUrl); + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + setPostRequestHeaders(connection, sessionCookie); + sendPostData(data, connection); + updateSessionCookie(connection); + checkHttpResponseForDmException(connection); + logger.debug("Response message:\n" + connection.getResponseMessage()); + return readHttpResponse(connection); + } catch (DmException ex) { + throw ex; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (IOException ex) { + DmException dmException = convertHttpErrorToDmException(ex, connection); + logger.error(ex.getMessage()); + throw dmException; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Invoke PUT request. + * + * @param requestUrl relative request path, e.g. /object + * @param data request data + * @return service response string + * @throws DmException in case of any errors + */ + public String invokeSessionPutRequest(String requestUrl, Map<String, String> data) throws DmException { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + logger.debug("Invoking session put request for URL: " + requestUrl); + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + setPutRequestHeaders(connection, sessionCookie); + sendPostData(data, connection); + updateSessionCookie(connection); + checkHttpResponseForDmException(connection); + logger.debug("Response message:\n" + connection.getResponseMessage()); + return readHttpResponse(connection); + } catch (DmException ex) { + throw ex; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (IOException ex) { + DmException dmException = convertHttpErrorToDmException(ex, connection); + logger.error(ex.getMessage()); + throw dmException; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Invoke DELETE request. + * + * @param requestUrl relative request path, e.g. /dm/object + * @return service response string + * @throws DmException in case of any errors + */ + public String invokeSessionDeleteRequest(String requestUrl) throws DmException { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + logger.debug("Invoking session post request for URL: " + requestUrl); + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + setDeleteRequestHeaders(connection, sessionCookie); + updateSessionCookie(connection); + checkHttpResponseForDmException(connection); + logger.debug("Response message:\n" + connection.getResponseMessage()); + return readHttpResponse(connection); + } catch (DmException ex) { + throw ex; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + logger.error(errorMsg); + throw new CommunicationError(errorMsg, ex); + } catch (IOException ex) { + DmException dmException = convertHttpErrorToDmException(ex, connection); + logger.error(ex.getMessage()); + throw dmException; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + + /* + * Main method, used for simple testing. + * + * @param args main arguments + */ + public static void main(String[] args) { + try { + DmRestApi client = new DmRestApi("http://zagreb.svdev.net:10232/dm"); + //client.login("sveseli", "sveseli"); + HashMap<String, String> data = new HashMap<>(); + //data.put("parentDirectory", "/"); + String drawing = client.invokeGetRequest("/pdmLink/drawings/D14100201-113160.asm"); + System.out.println("Drawing: \n" + drawing); + } catch (DmException ex) { + System.out.println("Sorry: " + ex); + } + } +} diff --git a/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmSession.java b/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmSession.java new file mode 100644 index 0000000000000000000000000000000000000000..41e294838c872d7d6239960aba805cf926cd3b73 --- /dev/null +++ b/src/java/DmWebPortal/src/java/gov/anl/aps/dm/api/DmSession.java @@ -0,0 +1,100 @@ +package gov.anl.aps.dm.api; + +import gov.anl.aps.dm.common.constants.DmRole; +import gov.anl.aps.dm.common.exceptions.InvalidSession; +import java.io.Serializable; +import java.net.HttpCookie; + +/** + * DM session class, used for keeping all session-related information (session + * id, username, role, etc.). + */ +public class DmSession implements Serializable { + + private static final long serialVersionUID = 1L; + + private String id = null; + private String username = null; + private String cookie = null; + private DmRole role = null; + + public DmSession() { + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCookie() { + return cookie; + } + + public void setCookie(String cookie) { + this.cookie = cookie; + } + + public String verifyCookie() throws InvalidSession { + if (cookie == null) { + throw new InvalidSession("Valid session has not been established."); + } else { + HttpCookie httpCookie = HttpCookie.parse(cookie).get(0); + if (httpCookie.hasExpired()) { + throw new InvalidSession("Session id " + id + " has expired."); + } + } + return cookie; + } + + public DmRole getRole() { + return role; + } + + public void setRole(DmRole role) { + this.role = role; + } + + public boolean isAdminRole() { + if (role != null) { + return role.equals(DmRole.ADMIN); + } + return false; + } + + public boolean isUserRole() { + if (role != null) { + return role.equals(DmRole.USER); + } + return false; + } + + @Override + public String toString() { + String result = "{ "; + String delimiter = ""; + if (username != null) { + result += "username :" + username; + delimiter = "; "; + } + if (id != null) { + result += delimiter + "id : " + id; + delimiter = "; "; + } + if (cookie != null) { + result += delimiter + "cookie : " + cookie; + } + result += " }"; + return result; + } +}