Java tutorial
/* A Java-based HTTP stub server Copyright (C) 2012 Alexander Zagniotov, Isa Goksu and Eric Mrak This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package by.stub.yaml.stubs; import by.stub.annotations.CoberturaIgnore; import by.stub.annotations.VisibleForTesting; import by.stub.common.Common; import by.stub.utils.CollectionUtils; import by.stub.utils.ConsoleUtils; import by.stub.utils.FileUtils; import by.stub.utils.HandlerUtils; import by.stub.utils.ObjectUtils; import by.stub.utils.StringUtils; import by.stub.yaml.YamlProperties; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier; import org.json.JSONException; import org.skyscreamer.jsonassert.JSONCompare; import org.skyscreamer.jsonassert.JSONCompareMode; import org.xml.sax.SAXException; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import static by.stub.utils.StringUtils.isSet; import static by.stub.utils.StringUtils.isWithinSquareBrackets; import static by.stub.yaml.stubs.StubAuthorizationTypes.BASIC; import static by.stub.yaml.stubs.StubAuthorizationTypes.BEARER; import static by.stub.yaml.stubs.StubAuthorizationTypes.CUSTOM; /** * @author Alexander Zagniotov * @since 6/14/12, 1:09 AM */ public class StubRequest { public static final String HTTP_HEADER_AUTHORIZATION = "authorization"; private final String url; private final String post; private final File file; private final byte[] fileBytes; private final List<String> method; private final Map<String, String> headers; private final Map<String, String> query; private final Map<String, String> regexGroups; public StubRequest(final String url, final String post, final File file, final List<String> method, final Map<String, String> headers, final Map<String, String> query) { this.url = url; this.post = post; this.file = file; this.fileBytes = ObjectUtils.isNull(file) ? new byte[] {} : getFileBytes(); this.method = ObjectUtils.isNull(method) ? new ArrayList<String>() : method; this.headers = ObjectUtils.isNull(headers) ? new LinkedHashMap<String, String>() : headers; this.query = ObjectUtils.isNull(query) ? new LinkedHashMap<String, String>() : query; this.regexGroups = new TreeMap<>(); } public final ArrayList<String> getMethod() { final ArrayList<String> uppercase = new ArrayList<>(method.size()); for (final String string : method) { uppercase.add(StringUtils.toUpper(string)); } return uppercase; } public void addMethod(final String newMethod) { if (isSet(newMethod)) { method.add(newMethod); } } public String getUrl() { if (getQuery().isEmpty()) { return url; } final String queryString = CollectionUtils.constructQueryString(query); return String.format("%s?%s", url, queryString); } private byte[] getFileBytes() { try { return FileUtils.fileToBytes(file); } catch (Exception e) { return new byte[] {}; } } public String getPostBody() { if (fileBytes.length == 0) { return FileUtils.enforceSystemLineSeparator(post); } final String utf8FileContent = StringUtils.newStringUtf8(fileBytes); return FileUtils.enforceSystemLineSeparator(utf8FileContent); } //Used by reflection when populating stubby admin page with stubbed information public String getPost() { return post; } public final Map<String, String> getHeaders() { final Map<String, String> headersCopy = new LinkedHashMap<>(headers); final Set<Map.Entry<String, String>> entrySet = headersCopy.entrySet(); this.headers.clear(); for (final Map.Entry<String, String> entry : entrySet) { this.headers.put(StringUtils.toLower(entry.getKey()), entry.getValue()); } return headers; } public Map<String, String> getQuery() { return query; } public byte[] getFile() { return fileBytes; } // Just a shallow copy that protects collection from modification, the points themselves are not copied public Map<String, String> getRegexGroups() { return new TreeMap<>(regexGroups); } public File getRawFile() { return file; } public boolean hasHeaders() { return !getHeaders().isEmpty(); } public boolean hasQuery() { return !getQuery().isEmpty(); } public boolean hasPostBody() { return isSet(getPostBody()); } public boolean isSecured() { return getHeaders().containsKey(BASIC.asYamlProp()) || getHeaders().containsKey(BEARER.asYamlProp()) || getHeaders().containsKey(CUSTOM.asYamlProp()); } @VisibleForTesting StubAuthorizationTypes getStubbedAuthorizationTypeHeader() { if (getHeaders().containsKey(BASIC.asYamlProp())) { return BASIC; } else if (getHeaders().containsKey(BEARER.asYamlProp())) { return BEARER; } else { return CUSTOM; } } String getStubbedAuthorizationHeaderValue(final StubAuthorizationTypes stubbedAuthorizationHeaderType) { return getHeaders().get(stubbedAuthorizationHeaderType.asYamlProp()); } public String getRawAuthorizationHttpHeader() { return getHeaders().get(HTTP_HEADER_AUTHORIZATION); } public static StubRequest newStubRequest() { return new StubRequest(null, null, null, null, null, null); } public static StubRequest newStubRequest(final String url, final String post) { return new StubRequest(url, post, null, null, null, null); } public static StubRequest createFromHttpServletRequest(final HttpServletRequest request) throws IOException { final StubRequest assertionRequest = StubRequest.newStubRequest(request.getPathInfo(), HandlerUtils.extractPostRequestBody(request, "stubs")); assertionRequest.addMethod(request.getMethod()); final Enumeration<String> headerNamesEnumeration = request.getHeaderNames(); final List<String> headerNames = ObjectUtils.isNotNull(headerNamesEnumeration) ? Collections.list(request.getHeaderNames()) : new LinkedList<String>(); for (final String headerName : headerNames) { final String headerValue = request.getHeader(headerName); assertionRequest.getHeaders().put(StringUtils.toLower(headerName), headerValue); } assertionRequest.getQuery().putAll(CollectionUtils.constructParamMap(request.getQueryString())); ConsoleUtils.logAssertingRequest(assertionRequest); return assertionRequest; } @Override public boolean equals(final Object o) { if (this == o) { return true; } else if (o instanceof StubRequest) { final StubRequest dataStoreRequest = (StubRequest) o; return urlsMatch(dataStoreRequest.url, this.url) && arraysIntersect(dataStoreRequest.getMethod(), this.getMethod()) && postBodiesMatch(dataStoreRequest.isPostStubbed(), dataStoreRequest.getPostBody(), this.getPostBody()) && headersMatch(dataStoreRequest.getHeaders(), this.getHeaders()) && queriesMatch(dataStoreRequest.getQuery(), this.getQuery()); } return false; } @VisibleForTesting boolean isPostStubbed() { return isSet(this.getPostBody()) && (getMethod().contains("POST") || getMethod().contains("PUT")); } private boolean urlsMatch(final String dataStoreUrl, final String thisAssertingUrl) { return stringsMatch(dataStoreUrl, thisAssertingUrl, YamlProperties.URL); } private boolean postBodiesMatch(final boolean isDataStorePostStubbed, final String dataStorePostBody, final String thisAssertingPostBody) { if (isDataStorePostStubbed) { final String assertingContentType = this.getHeaders().get("content-type"); final boolean isAssertingValueSet = isSet(thisAssertingPostBody); if (!isAssertingValueSet) { return false; } else if (isSet(assertingContentType) && assertingContentType.contains(Common.HEADER_APPLICATION_JSON)) { try { return JSONCompare .compareJSON(dataStorePostBody, thisAssertingPostBody, JSONCompareMode.NON_EXTENSIBLE) .passed(); } catch (JSONException e) { return false; } } else if (isSet(assertingContentType) && assertingContentType.contains(Common.HEADER_APPLICATION_XML)) { try { final Diff diff = new Diff(dataStorePostBody, thisAssertingPostBody); diff.overrideElementQualifier(new ElementNameAndAttributeQualifier()); return (diff.similar() || diff.identical()); } catch (SAXException | IOException e) { return false; } } else { return stringsMatch(dataStorePostBody, thisAssertingPostBody, YamlProperties.POST); } } else { return true; } } private boolean queriesMatch(final Map<String, String> dataStoreQuery, final Map<String, String> thisAssertingQuery) { return mapsMatch(dataStoreQuery, thisAssertingQuery, YamlProperties.QUERY); } private boolean headersMatch(final Map<String, String> dataStoreHeaders, final Map<String, String> thisAssertingHeaders) { final Map<String, String> dataStoreHeadersCopy = new HashMap<>(dataStoreHeaders); for (StubAuthorizationTypes authorizationType : StubAuthorizationTypes.values()) { // auth header is dealt with in StubbedDataManager after request is matched dataStoreHeadersCopy.remove(authorizationType.asYamlProp()); } return mapsMatch(dataStoreHeadersCopy, thisAssertingHeaders, YamlProperties.HEADERS); } @VisibleForTesting boolean mapsMatch(final Map<String, String> dataStoreMap, final Map<String, String> thisAssertingMap, final String mapName) { if (dataStoreMap.isEmpty()) { return true; } else if (thisAssertingMap.isEmpty()) { return false; } final Map<String, String> dataStoreMapCopy = new HashMap<>(dataStoreMap); final Map<String, String> assertingMapCopy = new HashMap<>(thisAssertingMap); for (Map.Entry<String, String> dataStoreParam : dataStoreMapCopy.entrySet()) { final boolean containsRequiredParam = assertingMapCopy.containsKey(dataStoreParam.getKey()); if (!containsRequiredParam) { return false; } else { final String assertedQueryValue = assertingMapCopy.get(dataStoreParam.getKey()); final String templateTokenName = String.format("%s.%s", mapName, dataStoreParam.getKey()); if (!stringsMatch(dataStoreParam.getValue(), assertedQueryValue, templateTokenName)) { return false; } } } return true; } @VisibleForTesting boolean stringsMatch(final String dataStoreValue, final String thisAssertingValue, final String templateTokenName) { final boolean isDataStoreValueSet = isSet(dataStoreValue); final boolean isAssertingValueSet = isSet(thisAssertingValue); if (!isDataStoreValueSet) { return true; } else if (!isAssertingValueSet) { return false; } else if (isWithinSquareBrackets(dataStoreValue)) { return dataStoreValue.equals(thisAssertingValue); } else { return regexMatch(dataStoreValue, thisAssertingValue, templateTokenName); } } private boolean regexMatch(final String dataStoreValue, final String thisAssertingValue, final String templateTokenName) { try { // Pattern.MULTILINE changes the behavior of '^' and '$' characters, // it does not mean that newline feeds and carriage return will be matched by default // You need to make sure that you regex pattern covers both \r (carriage return) and \n (linefeed). // It is achievable by using symbol '\s+' which covers both \r (carriage return) and \n (linefeed). final Matcher matcher = Pattern.compile(dataStoreValue, Pattern.MULTILINE).matcher(thisAssertingValue); final boolean isMatch = matcher.matches(); if (isMatch) { // group(0) holds the full regex match regexGroups.put(StringUtils.buildToken(templateTokenName, 0), matcher.group(0)); //Matcher.groupCount() returns the number of explicitly defined capturing groups in the pattern regardless // of whether the capturing groups actually participated in the match. It does not include matcher.group(0) final int groupCount = matcher.groupCount(); if (groupCount > 0) { for (int idx = 1; idx <= groupCount; idx++) { regexGroups.put(StringUtils.buildToken(templateTokenName, idx), matcher.group(idx)); } } } return isMatch; } catch (PatternSyntaxException e) { return dataStoreValue.equals(thisAssertingValue); } } @VisibleForTesting boolean arraysIntersect(final ArrayList<String> dataStoreArray, final ArrayList<String> thisAssertingArray) { if (dataStoreArray.isEmpty()) { return true; } else if (!thisAssertingArray.isEmpty()) { for (final String entry : thisAssertingArray) { if (dataStoreArray.contains(entry)) { return true; } } } return false; } @Override @CoberturaIgnore public int hashCode() { int result = (ObjectUtils.isNotNull(url) ? url.hashCode() : 0); result = 31 * result + method.hashCode(); result = 31 * result + (ObjectUtils.isNotNull(post) ? post.hashCode() : 0); result = 31 * result + (ObjectUtils.isNotNull(fileBytes) && fileBytes.length != 0 ? Arrays.hashCode(fileBytes) : 0); result = 31 * result + headers.hashCode(); result = 31 * result + query.hashCode(); return result; } @Override @CoberturaIgnore public final String toString() { final StringBuffer sb = new StringBuffer(); sb.append("StubRequest"); sb.append("{url=").append(url); sb.append(", method=").append(method); if (!ObjectUtils.isNull(post)) { sb.append(", post=").append(post); } sb.append(", query=").append(query); sb.append(", headers=").append(getHeaders()); sb.append('}'); return sb.toString(); } }