Skip to content

Commit

Permalink
Merge pull request #458 from bullhorn/f/exception-handling
Browse files Browse the repository at this point in the history
F/exception handling
  • Loading branch information
johnsully83 authored Mar 6, 2024
2 parents 42a9126 + 3ff69f4 commit c6f6a83
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 21 deletions.
24 changes: 13 additions & 11 deletions src/main/java/com/bullhornsdk/data/api/helper/RestApiSession.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bullhornsdk.data.api.helper;

import com.bullhornsdk.data.api.BullhornRestCredentials;
import com.bullhornsdk.data.exception.AuthorizationCodeException;
import com.bullhornsdk.data.exception.RestApiException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
Expand All @@ -18,21 +19,14 @@
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Wraps rest api session management.
*
* @author Yaniv Or-Shahar
* @author Magnus Fiore Palm
*
*/

@JsonIgnoreProperties({"sessionExpired"})
public class RestApiSession {

Expand Down Expand Up @@ -134,6 +128,8 @@ private void createSession() {
AccessTokenInfo accessTokenInfo = getAccessToken(authCode);
login(accessTokenInfo);
break;
} catch (AuthorizationCodeException e) {
throw e;
} catch (Exception e) {
if (tryNumber < SESSION_RETRY) {
String message = "Error creating REST session. Try number: " + tryNumber + " out of " + SESSION_RETRY + " trying again.";
Expand Down Expand Up @@ -168,7 +164,7 @@ private String getAuthorizationCode() throws RestApiException {

private String getAuthorizationCode(String url, Map<String, String> parameters, Boolean followRedirect) {
try {
ResponseEntity<Void> response = restTemplate.exchange(url, HttpMethod.POST, HttpEntity.EMPTY, Void.class, parameters);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, HttpEntity.EMPTY, String.class, parameters);

URI location = response.getHeaders().getLocation();

Expand All @@ -180,11 +176,17 @@ private String getAuthorizationCode(String url, Map<String, String> parameters,
return getAuthCode(location);
}

throw new RestApiException("Failed to get authorization code. Response had no Location header and code was: " + response.getStatusCodeValue());
throw new AuthorizationCodeException("Failed to get authorization code. Response had no Location header and code was: " + response.getStatusCodeValue(), response.getBody());
} catch (AuthorizationCodeException e) {
throw e;
} catch (HttpStatusCodeException e ) {
LOG.error("Failed to get authorization code.", e);

throw new AuthorizationCodeException("Failed to get authorization code.", e.getResponseBodyAsString(), e);
} catch (Exception e) {
LOG.error("Failed to get authorization code.", e);

throw new RestApiException("Failed to get authorization code.", e);
throw new AuthorizationCodeException("Failed to get authorization code.", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.bullhornsdk.data.exception;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

public class AuthorizationCodeException extends RestApiException {

private static final Pattern GET_DETAIL_ERROR = Pattern.compile(".*?<p class=\"error\">(.*?)</p>", Pattern.DOTALL);

public AuthorizationCodeException(String message, String responseBody) {
super(message, parseErrorMessageFromHtml(responseBody));
}

public AuthorizationCodeException(String message, String responseBody, Throwable cause) {
super(message, parseErrorMessageFromHtml(responseBody), cause);
}

public AuthorizationCodeException(String message, Throwable cause) {
super(message, cause);
}

private static String parseErrorMessageFromHtml(String responseBody) {
if (StringUtils.isBlank(responseBody)) {
return "";
}

Matcher matcher = GET_DETAIL_ERROR.matcher(responseBody);

if (matcher.find()) {
return matcher.group(1).trim();
}

return "";
}

}
29 changes: 19 additions & 10 deletions src/main/java/com/bullhornsdk/data/exception/RestApiException.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
/**
*
*/
package com.bullhornsdk.data.exception;

/**
* @author Yaniv Or-Shahar
*
*/

public class RestApiException extends RuntimeException {
/**
*
*/

private String detailMessage;

private static final long serialVersionUID = 1L;

public RestApiException() {
Expand All @@ -29,4 +23,19 @@ public RestApiException(String message, Throwable cause) {
super(message, cause);
}

public RestApiException(String message, String detailMessage) {
super(message);
this.detailMessage = detailMessage;
}


public RestApiException(String message, String detailMessage, Throwable cause) {
super(message, cause);
this.detailMessage = detailMessage;
}

public String getDetailMessage() {
return detailMessage;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.bullhornsdk.data.exception;

import org.junit.Assert;
import org.junit.jupiter.api.Test;

import com.bullhornsdk.data.BaseTest;

public class TestAuthorizationCodeException extends BaseTest {

@Test
public void testAccountLockedOut() {
RestApiException exception = new AuthorizationCodeException("", ACCOUNT_LOCKED_OUT_HTML);

Assert.assertEquals("Account is locked out.", exception.getDetailMessage());
}

@Test
public void testInvalidCredentials() {
RestApiException exception = new AuthorizationCodeException("", INVALID_CREDENTIALS_HTML);

Assert.assertEquals("Invalid credentials.", exception.getDetailMessage());
}

private static final String ACCOUNT_LOCKED_OUT_HTML = "\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"<!DOCTYPE html>\n" +
"<html>\n" +
" <head>\n" +
" <meta charset=\"utf-8\">\n" +
" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n" +
" <title>Bullhorn | Log in</title>\n" +
" <meta name=\"HandheldFriendly\" content=\"True\">\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\">\n" +
" <meta name=\"apple-touch-fullscreen\" content=\"yes\">\n" +
" <link href='//fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900' rel='stylesheet' type='text/css'>\n" +
" <link href=\"//cdn.rawgit.com/bullhorn/bullhorn-icons/development/fonts/Bullhorn-Glyphicons.css\" rel=\"stylesheet\" type=\"text/css\">\n" +
" <link rel=\"stylesheet\" href=\"/oauth/css/login.css\">\n" +
" <link rel='localizations' type='text/json' href='l10n/localizations.json' />\n" +
" <script type=\"text/javascript\" src=\"js/json2.js\"></script>\n" +
" <script type=\"text/javascript\" src=\"js/login.js\"></script>\n" +
" <script type=\"text/javascript\" src=\"js/l10n.js\"></script>\n" +
" <script type=\"text/javascript\">\n" +
" String.toLocaleString(\"l10n/localizations.json\");\n" +
" </script>\n" +
" </head>\n" +
" <body>\n" +
" <div class=\"login\">\n" +
" \n" +
" <div class=\"backheader\">\n" +
" <a href=\"null\" id=\"usernameUrlLink\">\n" +
" <i class=\"bhi-previous\"></i>\n" +
" <span id=\"usernameUrl\">Use a different account</span>\n" +
" </a>\n" +
" </div>\n" +
" \n" +
" <div class=\"container\">\n" +
" <div class=\"logo\"></div>\n" +
" <form method=\"POST\" name=\"loginForm\" class=\"content\">\n" +
" \t\n" +
" \t<input type=\"hidden\" name=\"username\" value=\"octopus.api.user.3\" >\n" +
" \t<div id=\"usernameText\"><span>Hello, octopus.api.user.3!</span></div>\n" +
" \n" +
" <div class=\"iconholder passwordholder\">\n" +
" <i class=\"bhi-lock\"></i>\n" +
" <input type=\"password\" name=\"password\" placeholder=\"Password\" id=\"pw\" onkeyup=\"checkLoginDisabled()\">\n" +
" </div>\n" +
"\n" +
" <div class=\"errors\">\n" +
" \n" +
" <p class=\"error\">\n" +
" Account is locked out.\n" +
" </p>\n" +
" \n" +
" </div>\n" +
"\n" +
" <input type=\"hidden\" name='action' value='Login'>\n" +
" <input id=\"btn\" type=\"submit\" value=\"Log in\" disabled>\n" +
" </form> \n" +
" </div> \n" +
" </div>\n" +
" <footer>\n" +
" <p>\n" +
" <a href=\"//www.bullhorn.com/privacy/\" target=\"_blank\">Privacy Policy</a>\n" +
" \n" +
" </p>\n" +
" </footer>\n" +
" <script type=\"text/javascript\">\n" +
" if(document.getElementById('un')!=undefined) {\n" +
" document.getElementById('un').placeholder = \"Username\".localize();\n" +
" }\n" +
" document.getElementById('pw').placeholder = \"Password\".localize();\n" +
" document.getElementById('btn').value = \"Log in\".localize();\n" +
" </script>\n" +
" </body>\n" +
"</html>\n";

private static final String INVALID_CREDENTIALS_HTML = "<!DOCTYPE html>\n" +
"<html>\n" +
"\n" +
"<head>\n" +
" <meta charset=\"utf-8\">\n" +
" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n" +
" <title>Bullhorn | Log in</title>\n" +
" <meta name=\"HandheldFriendly\" content=\"True\">\n" +
" <meta name=\"viewport\"\n" +
" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\">\n" +
" <meta name=\"apple-touch-fullscreen\" content=\"yes\">\n" +
" <link href='//fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900' rel='stylesheet' type='text/css'>\n" +
" <link href=\"//cdn.rawgit.com/bullhorn/bullhorn-icons/development/fonts/Bullhorn-Glyphicons.css\" rel=\"stylesheet\"\n" +
" type=\"text/css\">\n" +
" <link rel=\"stylesheet\" href=\"/oauth/css/login.css\">\n" +
" <link rel='localizations' type='text/json' href='l10n/localizations.json' />\n" +
" <script type=\"text/javascript\" src=\"js/json2.js\"></script>\n" +
" <script type=\"text/javascript\" src=\"js/login.js\"></script>\n" +
" <script type=\"text/javascript\" src=\"js/l10n.js\"></script>\n" +
" <script type=\"text/javascript\">\n" +
" String.toLocaleString(\"l10n/localizations.json\");\n" +
" </script>\n" +
"</head>\n" +
"\n" +
"<body>\n" +
" <div class=\"login\">\n" +
"\n" +
" <div class=\"backheader\">\n" +
" <a href=\"null\" id=\"usernameUrlLink\">\n" +
" <i class=\"bhi-previous\"></i>\n" +
" <span id=\"usernameUrl\">Use a different account</span>\n" +
" </a>\n" +
" </div>\n" +
"\n" +
" <div class=\"container\">\n" +
" <div class=\"logo\"></div>\n" +
" <form method=\"POST\" name=\"loginForm\" class=\"content\">\n" +
"\n" +
" <input type=\"hidden\" name=\"username\" value=\"octopus.api.user.3\" >\n" +
" <div id=\"usernameText\"><span>Hello, octopus.api.user.3!</span></div>\n" +
"\n" +
" <div class=\"iconholder passwordholder\">\n" +
" <i class=\"bhi-lock\"></i>\n" +
" <input type=\"password\" name=\"password\" placeholder=\"Password\" id=\"pw\" onkeyup=\"checkLoginDisabled()\">\n" +
" </div>\n" +
"\n" +
" <div class=\"errors\">\n" +
"\n" +
" <p class=\"error\">\n" +
" Invalid credentials.\n" +
" </p>\n" +
"\n" +
" </div>\n" +
"\n" +
" <input type=\"hidden\" name='action' value='Login'>\n" +
" <input id=\"btn\" type=\"submit\" value=\"Log in\" disabled>\n" +
" </form>\n" +
" </div>\n" +
" </div>\n" +
" <footer>\n" +
" <p>\n" +
" <a href=\"//www.bullhorn.com/privacy/\" target=\"_blank\">Privacy Policy</a>\n" +
"\n" +
" </p>\n" +
" </footer>\n" +
" <script type=\"text/javascript\">\n" +
" if(document.getElementById('un')!=undefined) {\n" +
" document.getElementById('un').placeholder = \"Username\".localize();\n" +
" }\n" +
" document.getElementById('pw').placeholder = \"Password\".localize();\n" +
" document.getElementById('btn').value = \"Log in\".localize();\n" +
" </script>\n" +
"</body>\n" +
"\n" +
"</html>";

}

0 comments on commit c6f6a83

Please sign in to comment.