HarValidator.java
/*
* Copyright 2017 Uwe Plonus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sw4j.tool.har.io;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import org.sw4j.tool.har.model.Browser;
import org.sw4j.tool.har.model.Cookie;
import org.sw4j.tool.har.model.Creator;
import org.sw4j.tool.har.model.CreatorBrowser;
import org.sw4j.tool.har.model.Entry;
import org.sw4j.tool.har.model.Har;
import org.sw4j.tool.har.model.Header;
import org.sw4j.tool.har.model.Log;
import org.sw4j.tool.har.model.Page;
import org.sw4j.tool.har.model.PageTimings;
import org.sw4j.tool.har.model.QueryString;
import org.sw4j.tool.har.model.Request;
/**
* The {@code HarValidator} provides methods to validate the HAR model.
*
* @author Uwe Plonus <u.plonus@gmail.com>
*/
public final class HarValidator {
/** Hidden constructor for an utility class. */
private HarValidator() {
}
/**
* <p>
* Return all missing required attributes from the HAR model in a list.
* </p>
* <p>
* If the given parameter ({@code har}) is {@code null} then a list with only a single element is returned and the
* parent object is {@code ""} (an empty String) and the missing attribute is {@code "har"}.
* </p>
*
* @param har the model to check for missing attributes.
* @return a list with all missing required attributes. If no attributes are missing the an empty list will be
* returned.
*/
public static List<RequiredAttribute> getMissingAttributes(final Har har) {
List<RequiredAttribute> result = new LinkedList<>();
String parent = "";
if (har == null) {
result.add(new RequiredAttribute(parent, "har"));
} else {
result.addAll(getMissingLogAttributes(parent, har.getLog()));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the log object.
* </p>
*
* @param parent the parent of the log object to check.
* @param log the log object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingLogAttributes(final CharSequence parent, final Log log) {
List<RequiredAttribute> result = new LinkedList<>();
if (log == null) {
result.add(new RequiredAttribute(parent, "log"));
} else {
StringBuilder newParent = createNewParent(parent, "log");
if (log.getVersion() == null) {
result.add(new RequiredAttribute(newParent, "version"));
}
result.addAll(getMissingCreatorAttributes(newParent, log.getCreator()));
result.addAll(getMissingBrowserAttributes(newParent, log.getBrowser()));
result.addAll(getMissingPagesAttributes(newParent, log.getPages()));
result.addAll(getMissingEntriesAttributes(newParent, log.getEntries()));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the creator.
* </p>
*
* @param parent the parent of the creator object to check.
* @param creator the creator object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingCreatorAttributes(final CharSequence parent,
final Creator creator) {
List<RequiredAttribute> result = new LinkedList<>();
if (creator == null) {
result.add(new RequiredAttribute(parent, "creator"));
} else {
StringBuilder newParent = createNewParent(parent, "creator");
result.addAll(getMissingCreatorBrowserAttributes(newParent, creator));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the browser object.
* </p>
*
* @param parent the parent of the browser object to check.
* @param browser the browser object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingBrowserAttributes(final CharSequence parent,
final Browser browser) {
List<RequiredAttribute> result = new LinkedList<>();
if (browser != null) {
StringBuilder newParent = createNewParent(parent, "browser");
result.addAll(getMissingCreatorBrowserAttributes(newParent, browser));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the creator or browser object.
* </p>
*
* @param parent the parent object that represents the given creator or browser.
* @param creatorBrowser the creator or browser object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingCreatorBrowserAttributes(final CharSequence parent,
final CreatorBrowser creatorBrowser) {
List<RequiredAttribute> result = new LinkedList<>();
if (creatorBrowser.getName() == null) {
result.add(new RequiredAttribute(parent, "name"));
}
if (creatorBrowser.getVersion() == null) {
result.add(new RequiredAttribute(parent, "version"));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the list of pages.
* </p>
*
* @param parent the parent of the pages object to check.
* @param pages the list of pages to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingPagesAttributes(final CharSequence parent,
final List<Page> pages) {
List<RequiredAttribute> result = new LinkedList<>();
if (pages != null) {
for (int i = 0; i < pages.size(); i++) {
StringBuilder indexedPage = new StringBuilder("pages[").append(i).append(']');
StringBuilder newParent = createNewParent(parent, indexedPage);
result.addAll(getMissingPageAttributes(newParent, pages.get(i)));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the page object.
* </p>
*
* @param parent the parent of the page object to check.
* @param page the page object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingPageAttributes(final CharSequence parent, final Page page) {
List<RequiredAttribute> result = new LinkedList<>();
if (page != null) {
if (page.getStartedDateTime() == null) {
result.add(new RequiredAttribute(parent, "startedDateTime"));
}
if (page.getId() == null) {
result.add(new RequiredAttribute(parent, "id"));
}
if (page.getTitle() == null) {
result.add(new RequiredAttribute(parent, "title"));
}
result.addAll(getMissingPageTimingsAttributes(parent, page.getPageTimings()));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the page timings object.
* </p>
*
* @param parent the parent of the page timings object.
* @param pageTimings the page timings object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingPageTimingsAttributes(final CharSequence parent,
final PageTimings pageTimings) {
List<RequiredAttribute> result = new LinkedList<>();
if (pageTimings == null) {
result.add(new RequiredAttribute(parent, "pageTimings"));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the list of entries.
* </p>
*
* @param parent the parent of the entries object.
* @param entries the list of entries to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingEntriesAttributes(final CharSequence parent,
final List<Entry> entries) {
List<RequiredAttribute> result = new LinkedList<>();
if (entries == null) {
result.add(new RequiredAttribute(parent, "entries"));
} else {
for (int i = 0; i < entries.size(); i++) {
StringBuilder indexedPage = new StringBuilder("entries[").append(i).append(']');
StringBuilder newParent = createNewParent(parent, indexedPage);
result.addAll(getMissingEntryAttributes(newParent, entries.get(i)));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the entry object.
* </p>
*
* @param parent the parent of the entry object.
* @param entry the entry object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingEntryAttributes(final CharSequence parent, final Entry entry) {
List<RequiredAttribute> result = new LinkedList<>();
if (entry != null) {
if (entry.getStartedDateTime() == null) {
result.add(new RequiredAttribute(parent, "startedDateTime"));
}
if (entry.getTime() == null) {
result.add(new RequiredAttribute(parent, "time"));
}
result.addAll(getMissingRequestAttributes(parent, entry.getRequest()));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the request object.
* </p>
*
* @param parent the parent of the request object.
* @param request the request object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingRequestAttributes(final CharSequence parent,
final Request request) {
List<RequiredAttribute> result = new LinkedList<>();
if (request == null) {
result.add(new RequiredAttribute(parent, "request"));
} else {
StringBuilder newParent = createNewParent(parent, "request");
if (request.getMethod() == null) {
result.add(new RequiredAttribute(newParent, "method"));
}
if (request.getUrl() == null) {
result.add(new RequiredAttribute(newParent, "url"));
}
if (request.getHttpVersion() == null) {
result.add(new RequiredAttribute(newParent, "httpVersion"));
}
result.addAll(getMissingCookiesAttributes(newParent, request.getCookies()));
result.addAll(getMissingHeadersAttributes(newParent, request.getHeaders()));
result.addAll(getMissingQueryStringsAttributes(newParent, request.getQueryStrings()));
}
return result;
}
/**
* <p>
* Return all missing required attributes from the list of cookies.
* </p>
*
* @param parent the parent of the cookies object.
* @param cookies the list of cookies to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingCookiesAttributes(final CharSequence parent,
final List<Cookie> cookies) {
List<RequiredAttribute> result = new LinkedList<>();
if (cookies == null) {
result.add(new RequiredAttribute(parent, "cookies"));
} else {
for (int i = 0; i < cookies.size(); i++) {
StringBuilder indexedPage = new StringBuilder("cookies[").append(i).append(']');
StringBuilder newParent = createNewParent(parent, indexedPage);
result.addAll(getMissingCookieAttributes(newParent, cookies.get(i)));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the cookie object.
* </p>
*
* @param parent the parent of the cookie object.
* @param cookie the cookie object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingCookieAttributes(final CharSequence parent, final Cookie cookie) {
List<RequiredAttribute> result = new LinkedList<>();
if (cookie != null) {
if (cookie.getName() == null) {
result.add(new RequiredAttribute(parent, "name"));
}
if (cookie.getValue() == null) {
result.add(new RequiredAttribute(parent, "value"));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the list of headers.
* </p>
*
* @param parent the parent of the headers object.
* @param headers the list of headers to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingHeadersAttributes(final CharSequence parent,
final List<Header> headers) {
List<RequiredAttribute> result = new LinkedList<>();
if (headers == null) {
result.add(new RequiredAttribute(parent, "headers"));
} else {
for (int i = 0; i < headers.size(); i++) {
StringBuilder indexedPage = new StringBuilder("headers[").append(i).append(']');
StringBuilder newParent = createNewParent(parent, indexedPage);
result.addAll(getMissingHeaderAttributes(newParent, headers.get(i)));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the header object.
* </p>
*
* @param parent the parent of the header object.
* @param header the header object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingHeaderAttributes(final CharSequence parent, final Header header) {
List<RequiredAttribute> result = new LinkedList<>();
if (header != null) {
if (header.getName() == null) {
result.add(new RequiredAttribute(parent, "name"));
}
if (header.getValue() == null) {
result.add(new RequiredAttribute(parent, "value"));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the list of query strings.
* </p>
*
* @param parent the parent of the queryStrings object.
* @param queryStrings the list of query strings to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingQueryStringsAttributes(final CharSequence parent,
final List<QueryString> queryStrings) {
List<RequiredAttribute> result = new LinkedList<>();
if (queryStrings == null) {
result.add(new RequiredAttribute(parent, "queryString"));
} else {
for (int i = 0; i < queryStrings.size(); i++) {
StringBuilder indexedPage = new StringBuilder("queryString[").append(i).append(']');
StringBuilder newParent = createNewParent(parent, indexedPage);
result.addAll(getMissingQueryStringAttributes(newParent, queryStrings.get(i)));
}
}
return result;
}
/**
* <p>
* Return all missing required attributes from the query string object.
* </p>
*
* @param parent the parent of the query string object.
* @param queryString the query string object to check.
* @return a list containing all required but missing attributes.
*/
private static List<RequiredAttribute> getMissingQueryStringAttributes(final CharSequence parent,
final QueryString queryString) {
List<RequiredAttribute> result = new LinkedList<>();
if (queryString != null) {
if (queryString.getName() == null) {
result.add(new RequiredAttribute(parent, "name"));
}
if (queryString.getValue() == null) {
result.add(new RequiredAttribute(parent, "value"));
}
}
return result;
}
/**
* <p>
* Check for missing attributes from the HAR model and throw an exception when at least one attribute is missing.
* The exception contains the information for the first missing attribute.
* </p>
*
* @param har the model to check for missing attributes.
* @throws AttributeRequiredException if at least one required attribute in the model is missing.
*/
public static void checkRequiredAttributes(final Har har) throws AttributeRequiredException {
List<RequiredAttribute> missingAttributes = getMissingAttributes(har);
if (!missingAttributes.isEmpty()) {
RequiredAttribute firstEntry = missingAttributes.get(0);
throw new AttributeRequiredException(firstEntry.getParent(), firstEntry.getAttribute());
}
}
/**
* <p>
* Create a new parent from the old parent and the new child. The new parent is the child appended to the old parent
* separated by a dot ({@code .}). If the old parent is {@code null} or empty then no dot is prepended to the child.
* </p>
*
* @param parent the old parent to use.
* @param child the child to be appended.
* @return the new parent consisting of the old parent and the child.
*/
private static StringBuilder createNewParent(final CharSequence parent, final CharSequence child) {
StringBuilder newParent = new StringBuilder();
if (parent != null && !"".equals(parent)) {
newParent.append(parent).append('.');
}
newParent.append(child);
return newParent;
}
/**
* An immutable class to return required attributes from the model.
*/
public static class RequiredAttribute {
/**
* The parent of the required attribute.
*/
private final CharSequence parent;
/**
* The attribute.
*/
private final CharSequence attribute;
/**
* <p>
* Constructor of the immutable class.
* </p>
*
* @param parent the parent object of the attribute.
* @param attribute the attribute.
*/
public RequiredAttribute(final CharSequence parent, final CharSequence attribute) {
this.parent = parent;
this.attribute = attribute;
}
/**
* <p>
* Returns the parent of the attribute.
* </p>
*
* @return the parent.
*/
public String getParent() {
return parent.toString();
}
/**
* <p>
* Returns the attribute.
* </p>
*
* @return the attribute.
*/
public String getAttribute() {
return attribute.toString();
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RequiredAttribute that = (RequiredAttribute)o;
return Objects.equals(getParent(), that.getParent()) &&
Objects.equals(getAttribute(), that.getAttribute());
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return Objects.hash(getParent(), getAttribute());
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("RequiredAttribute{");
sb.append("parent='").append(parent).append('\'');
sb.append(", attribute='").append(attribute).append('\'');
sb.append('}');
return sb.toString();
}
}
}