/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.apidiff.serializer;
import com.emc.storageos.apidiff.ServiceCatalogDiff;
import com.emc.storageos.apidiff.model.ApiDescriptor;
import com.emc.storageos.apidiff.model.ApiDescriptorDiff;
import com.emc.storageos.apidiff.model.ApiIdentifier;
import com.emc.storageos.apidiff.Constants;
import com.emc.storageos.apidiff.util.Pair;
import com.emc.storageos.apidiff.model.ServiceCatalog;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringEscapeUtils;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Outputs API differences of all services to html file
*/
public class HtmlSerializer extends AbstractSerializer {
private final Map<String, StringBuilder> componentMap = new HashMap<String, StringBuilder>();
public HtmlSerializer(List<ServiceCatalogDiff> serviceCatalogDiffList, File folder) {
super(serviceCatalogDiffList, folder);
file = new File(folder.getAbsolutePath() + File.separator + "apidiff.html");
if (file.exists()) {
file.delete();
}
}
@Override
public void output() {
StringBuilder builder = new StringBuilder();
String title = "API Differences";
String subTitle = " Between ViPR " + diffList.get(0).getOldServiceCatalog().getVersion()
+ " and " + diffList.get(0).getNewServiceCatalog().getVersion();
builder.append(HtmlSerializerHelper.buildHeader(title + subTitle));
builder.append(HtmlSerializerHelper.buildBodyTitle(title, subTitle));
// Inserts api summary
builder.append(createSummary());
// Inserts component info
String components = createComponentChanges();
builder.append(createComponentList());
builder.append(components);
builder.append(HtmlSerializerHelper.buildTailer());
try {
FileUtils.write(file, builder.toString());
} catch (IOException ex) {
throw new IllegalStateException("Can't write result file: " + file.getAbsolutePath(), ex);
}
}
/**
* constructs summary of API changes
*
* @return format string of API changes summary
*/
public String createSummary() {
StringBuilder builder = new StringBuilder();
builder.append(HtmlSerializerHelper.buildDivHeader("summary"));
builder.append(HtmlSerializerHelper.buildContent("API", "summary", 2));
builder.append(HtmlSerializerHelper.buildTableHeader());
builder.append(HtmlSerializerHelper.buildTableHeaderRow(1,
new Pair<String, Integer>("Service Category", 25),
new Pair<String, Integer>("Added Number", 25),
new Pair<String, Integer>("Changed Number", 25),
new Pair<String, Integer>("Removed Number", 25)));
int addedAll = 0, removedAll = 0, changedAll = 0;
for (ServiceCatalogDiff serviceCatalogDiff : diffList) {
String serviceName = serviceCatalogDiff.getOldServiceCatalog().getServiceName();
int added = serviceCatalogDiff.getNewServiceCatalog().getApiMap().size();
int changed = serviceCatalogDiff.getApiChangedMap().size();
int removed = serviceCatalogDiff.getOldServiceCatalog().getApiMap().size();
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>(HtmlSerializerHelper.buildInPageLink(serviceName), 25),
new Pair<String, Integer>(Integer.toString(added), 25),
new Pair<String, Integer>(Integer.toString(changed), 25),
new Pair<String, Integer>(Integer.toString(removed), 25)));
addedAll += added;
changedAll += changed;
removedAll += removed;
}
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>("Total", 25),
new Pair<String, Integer>(Integer.toString(addedAll), 25),
new Pair<String, Integer>(Integer.toString(changedAll), 25),
new Pair<String, Integer>(Integer.toString(removedAll), 25)));
builder.append(HtmlSerializerHelper.buildTableTailer());
builder.append(HtmlSerializerHelper.buildDivTailer());
return builder.toString();
}
/**
* constructs component list which API are changed
*
* @return html format string of API changed component list
*/
public String createComponentList() {
StringBuilder builder = new StringBuilder();
builder.append(HtmlSerializerHelper.buildDivHeader("components"));
builder.append(HtmlSerializerHelper.buildContent("Component", "list", 2));
builder.append(HtmlSerializerHelper.buildListHeader());
SortedSet<String> sortedSet = new TreeSet<String>(componentMap.keySet());
for (String componentName : sortedSet) {
builder.append(HtmlSerializerHelper.buildListItem(HtmlSerializerHelper.buildInPageLink(componentName)));
}
builder.append(HtmlSerializerHelper.buildListTailer());
builder.append(HtmlSerializerHelper.buildDivTailer());
return builder.toString();
}
/**
* construct details of API changes by service and component name
*
* @return html format string of API change details
*/
private String createComponentChanges() {
StringBuilder builder = new StringBuilder();
for (ServiceCatalogDiff serviceCatalogDiff : diffList) {
builder.append(createApiChanges(serviceCatalogDiff));
}
return builder.toString();
}
/**
* constructs component list which API are changed
*
* @return format string of API changed component list
*/
public String createApiChanges(ServiceCatalogDiff serviceCatalogDiff) {
StringBuilder builder = new StringBuilder();
String serviceName = serviceCatalogDiff.getNewServiceCatalog().getServiceName();
builder.append(HtmlSerializerHelper.buildDivHeader(serviceName));
builder.append(HtmlSerializerHelper.buildContent("API", serviceName, 2));
addNormalRecords(serviceCatalogDiff.getNewServiceCatalog(), "Added APIs");
addComparisonRecords(serviceCatalogDiff, "Changed APIs");
addNormalRecords(serviceCatalogDiff.getOldServiceCatalog(), "Removed APIs");
SortedSet<String> sortedSet = new TreeSet<String>(componentMap.keySet());
for (String key : sortedSet) {
if (!key.startsWith(serviceName)) {
continue;
}
StringBuilder value = componentMap.get(key);
value.append(HtmlSerializerHelper.buildDivTailer());
builder.append(value);
}
builder.append(HtmlSerializerHelper.buildDivTailer());
return builder.toString();
}
/**
* Adds "added" or "removed" records to component map
*
* @param serviceCatalog
* The rest service which contains added/removed records
* @param title
* The tile of records table
*/
private void addNormalRecords(ServiceCatalog serviceCatalog, String title) {
Map<String, StringBuilder> changedMap = new HashMap<String, StringBuilder>();
for (Map.Entry<ApiIdentifier, ApiDescriptor> entry : serviceCatalog.getApiMap().entrySet()) {
String componentName = serviceCatalog.getServiceName()
+ Constants.NAME_STRING_SEPARATOR
+ entry.getKey().getPath().split(Constants.URL_PATH_SEPARATOR)[1];
StringBuilder componentValue = changedMap.get(componentName);
if (componentValue == null) {
componentValue = new StringBuilder();
if (componentMap.get(componentName) == null) {
componentValue.append(HtmlSerializerHelper.buildDivHeader(componentName));
componentValue.append(HtmlSerializerHelper.buildContent("Component", componentName, 3));
}
componentValue.append(HtmlSerializerHelper.buildTableHeader());
componentValue.append(HtmlSerializerHelper.buildTableHeaderRow(2,
new Pair<String, Integer>(title, 100)));
changedMap.put(componentName, componentValue);
}
componentValue.append(addNormalRecord(entry.getKey(), entry.getValue(),
serviceCatalog.getElementMap()));
}
for (StringBuilder builder : changedMap.values()) {
builder.append(HtmlSerializerHelper.buildTableTailer());
}
Iterator<Map.Entry<String, StringBuilder>> iterator = changedMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, StringBuilder> entry = iterator.next();
StringBuilder builder = componentMap.get(entry.getKey());
if (builder == null) {
builder = new StringBuilder();
componentMap.put(entry.getKey(), builder);
}
builder.append(entry.getValue().toString());
iterator.remove();
}
}
private String addNormalRecord(final ApiIdentifier apiIdentifier,
final ApiDescriptor apiResource,
final Map<String, String> elmentMap) {
// Constructs html content for added/removed apis
StringBuilder builder = new StringBuilder();
builder.append(HtmlSerializerHelper.buildTableHeader());
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>("URI", 15),
new Pair<String, Integer>(apiIdentifier.getHttpMethod() + " "
+ apiIdentifier.getPath(), 85)
));
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>("Parameter", 15),
new Pair<String, Integer>(apiResource.getParameters().toString(), 85)
));
String requestElement = elmentMap.get(apiResource.getRequestElement());
if (requestElement != null) {
requestElement = String.format("%s%s%s", Constants.CODE_PREFIX,
StringEscapeUtils.escapeHtml(requestElement), Constants.CODE_SUFFIX);
} else {
requestElement = "";
}
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>("Request Body", 15),
new Pair<String, Integer>(requestElement, 85)
));
String responseElement = elmentMap.get(apiResource.getResponseElement());
if (responseElement != null) {
responseElement = String.format("%s%s%s", Constants.CODE_PREFIX,
StringEscapeUtils.escapeHtml(responseElement), Constants.CODE_SUFFIX);
} else {
responseElement = "";
}
builder.append(HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>("Response Body", 15),
new Pair<String, Integer>(responseElement, 85)
));
builder.append(HtmlSerializerHelper.buildTableTailer());
return HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>(apiIdentifier.getPath(), 25),
new Pair<String, Integer>(builder.toString(), 75)
);
}
/**
* Adds "changed" records to component map
*
* @param serviceCatalogDiff
* The instance of ServiceCatalogDiff
* @param title
* The tile of records table
*/
private void addComparisonRecords(ServiceCatalogDiff serviceCatalogDiff, String title) {
Map<String, StringBuilder> changedMap = new HashMap<String, StringBuilder>();
for (Map.Entry<ApiIdentifier, ApiDescriptorDiff> entry : serviceCatalogDiff.getApiChangedMap().entrySet()) {
String componentName = serviceCatalogDiff.getNewServiceCatalog().getServiceName()
+ Constants.NAME_STRING_SEPARATOR
+ entry.getKey().getPath().split(Constants.URL_PATH_SEPARATOR)[1];
StringBuilder componentValue = changedMap.get(componentName);
if (componentValue == null) {
componentValue = new StringBuilder();
if (componentMap.get(componentName) == null) {
componentValue.append(HtmlSerializerHelper.buildDivHeader(componentName));
componentValue.append(HtmlSerializerHelper.buildContent("Component", componentName, 3));
}
componentValue.append(HtmlSerializerHelper.buildTableHeader());
componentValue.append(HtmlSerializerHelper.buildTableHeaderRow(2,
new Pair<String, Integer>(title, 100)));
changedMap.put(componentName, componentValue);
}
componentValue.append(addComparisonRecord(entry.getKey(), entry.getValue()));
}
for (StringBuilder builder : changedMap.values()) {
builder.append(HtmlSerializerHelper.buildTableTailer());
}
Iterator<Map.Entry<String, StringBuilder>> iterator = changedMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, StringBuilder> entry = iterator.next();
StringBuilder builder = componentMap.get(entry.getKey());
if (builder == null) {
builder = new StringBuilder();
componentMap.put(entry.getKey(), builder);
}
builder.append(entry.getValue().toString());
iterator.remove();
}
}
private String addComparisonRecord(final ApiIdentifier apiIdentifier,
final ApiDescriptorDiff apiDescriptorDiff) {
// Constructs html content for added/removed apis
StringBuilder builder = new StringBuilder();
builder.append(HtmlSerializerHelper.buildTableHeader());
builder.append(HtmlSerializerHelper.buildTableHeaderRow(1,
new Pair<String, Integer>("Item", 15),
new Pair<String, Integer>("Old", 40),
new Pair<String, Integer>("New", 45)
));
builder.append(HtmlSerializerHelper.buildTableRow(2,
new Pair<String, Integer>("URI", 15),
new Pair<String, Integer>(apiIdentifier.getHttpMethod() + " "
+ apiIdentifier.getPath(), 85)
));
builder.append(addChangedField("Parameter", apiDescriptorDiff.getParamDiff()));
builder.append(addChangedField("Request Body", apiDescriptorDiff.getRequestElementDiff()));
builder.append(addChangedField("Response Body", apiDescriptorDiff.getResponseElementDiff()));
builder.append(HtmlSerializerHelper.buildTableTailer());
return HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>(apiIdentifier.getPath(), 25),
new Pair<String, Integer>(builder.toString(), 75)
);
}
private String addChangedField(final String name, Pair<String, String> pair) {
if (name == null || pair == null) {
return "";
}
String left = Constants.CODE_PREFIX;
if (pair.getLeft() != null) {
left += StringEscapeUtils.escapeHtml(pair.getLeft());
}
left += Constants.CODE_SUFFIX;
String right = Constants.CODE_PREFIX;
if (pair.getRight() != null) {
right += StringEscapeUtils.escapeHtml(pair.getRight());
}
right += Constants.CODE_SUFFIX;
return HtmlSerializerHelper.buildTableRow(1,
new Pair<String, Integer>(name, 15),
new Pair<String, Integer>(left, 40),
new Pair<String, Integer>(right, 45)
);
}
}