/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.apidiff.serializer; import com.emc.storageos.apidiff.Constants; import com.emc.storageos.apidiff.Main; import com.emc.storageos.apidiff.ServiceCatalogDiff; import com.emc.storageos.apidiff.model.ApiChangeEnum; 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.util.Pair; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringEscapeUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * Outputs API differences of all services to html file */ public class HtmlSerializerMultiPages extends AbstractSerializer { class ComponentView { final List<String> added = new ArrayList<String>(); final List<String> changed = new ArrayList<String>(); final List<String> removed = new ArrayList<String>(); } private final Map<String, ComponentView> componentMap = new TreeMap<String, ComponentView>(); public HtmlSerializerMultiPages(final List<ServiceCatalogDiff> diffList, File folder) { super(diffList, folder); file = new File(folder.getAbsolutePath() + File.separator + "apidiff-docs"); if (!file.exists()) { file.mkdir(); } } @Override public void output() { String oldVersion = null, newVersion = null; for (ServiceCatalogDiff serviceCatalogDiff : diffList) { buildComponentList(serviceCatalogDiff); if (oldVersion == null) { oldVersion = serviceCatalogDiff.getOldServiceCatalog().getVersion(); } if (newVersion == null) { newVersion = serviceCatalogDiff.getNewServiceCatalog().getVersion(); } } // Create component pages outputComponents(); // Creates index file outputIndex(oldVersion, newVersion); } private String[] getParts(String path) { String[] services = path.split(Constants.URL_PATH_SEPARATOR); String[] parts = new String[2]; parts[1] = services[1]; if (services.length == 2) { parts[2] = services[2]; } else { parts[2] = ""; } return parts; } private void buildComponentList(final ServiceCatalogDiff serviceCatalogDiff) { for (Map.Entry<ApiIdentifier, ApiDescriptor> entry : serviceCatalogDiff.getNewServiceCatalog().getApiMap().entrySet()) { getComponentView(entry.getKey().getPath()).added.add( addNormalRecord(entry.getKey(), entry.getValue(), serviceCatalogDiff.getNewServiceCatalog().getElementMap())); } for (Map.Entry<ApiIdentifier, ApiDescriptorDiff> entry : serviceCatalogDiff.getApiChangedMap().entrySet()) { getComponentView(entry.getKey().getPath()).changed.add(addComparisonRecord(entry.getKey(), entry.getValue())); } for (Map.Entry<ApiIdentifier, ApiDescriptor> entry : serviceCatalogDiff.getOldServiceCatalog().getApiMap().entrySet()) { getComponentView(entry.getKey().getPath()).removed.add( addNormalRecord(entry.getKey(), entry.getValue(), serviceCatalogDiff.getOldServiceCatalog().getElementMap())); } } /** * constructs summary of API changes */ private void outputIndex(final String oldVersion, final String newVersion) { StringBuilder builder = new StringBuilder(); String title = "API Differences"; String subTitle = " Between ViPR " + oldVersion + " and " + newVersion; builder.append(HtmlSerializerHelper.buildHeader(title + subTitle)); builder.append(HtmlSerializerHelper.buildBodyTitle(title, subTitle)); 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 (Map.Entry<String, ComponentView> entry : componentMap.entrySet()) { String componentName = entry.getKey(); int added = entry.getValue().added.size(); int changed = entry.getValue().changed.size(); int removed = entry.getValue().removed.size(); String linkPage = componentName.replaceAll(" ", "") + "_diff.html"; builder.append(HtmlSerializerHelper.buildTableRow(1, new Pair<String, Integer>( HtmlSerializerHelper.buildLink(linkPage, componentName), 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()); builder.append(HtmlSerializerHelper.buildTailer()); outputToFile(file.getAbsolutePath() + File.separator + "index.html", builder.toString()); } private void outputComponents() { for (Map.Entry<String, ComponentView> entry : componentMap.entrySet()) { StringBuilder builder = new StringBuilder(); builder.append(HtmlSerializerHelper.buildHeader(entry.getKey())); builder.append(HtmlSerializerHelper.buildBodyHeader()); builder.append(HtmlSerializerHelper.buildBodyTitle(entry.getKey(), null)); if (!entry.getValue().added.isEmpty()) { Collections.sort(entry.getValue().added); // Added APIs builder.append(HtmlSerializerHelper.buildDivHeader(ApiChangeEnum.Added.toString())); builder.append(HtmlSerializerHelper.buildContent(ApiChangeEnum.Added.toString(), "REST API", 2)); builder.append(HtmlSerializerHelper.buildTableHeader()); for (String added : entry.getValue().added) { builder.append(added); } builder.append(HtmlSerializerHelper.buildTableTailer()); builder.append(HtmlSerializerHelper.buildDivTailer()); } if (!entry.getValue().changed.isEmpty()) { Collections.sort(entry.getValue().changed); // Changed APIs builder.append(HtmlSerializerHelper.buildSideLine()); builder.append(HtmlSerializerHelper.buildDivHeader(ApiChangeEnum.Changed.toString())); builder.append(HtmlSerializerHelper.buildContent(ApiChangeEnum.Changed.toString(), "REST API", 2)); builder.append(HtmlSerializerHelper.buildTableHeader()); for (String changed : entry.getValue().changed) { builder.append(changed); } builder.append(HtmlSerializerHelper.buildTableTailer()); builder.append(HtmlSerializerHelper.buildDivTailer()); } if (!entry.getValue().removed.isEmpty()) { // Removed APIs builder.append(HtmlSerializerHelper.buildSideLine()); builder.append(HtmlSerializerHelper.buildDivHeader(ApiChangeEnum.Removed.toString())); builder.append(HtmlSerializerHelper.buildContent(ApiChangeEnum.Removed.toString(), "REST API", 2)); builder.append(HtmlSerializerHelper.buildTableHeader()); for (String removed : entry.getValue().removed) { builder.append(removed); } builder.append(HtmlSerializerHelper.buildTableTailer()); builder.append(HtmlSerializerHelper.buildDivTailer()); } // add tailer builder.append(HtmlSerializerHelper.buildTailer()); String fileName = file.getAbsolutePath() + File.separator + entry.getKey().replaceAll(" ", "") + "_diff.html"; outputToFile(fileName, builder.toString()); } } private String addNormalRecord(final ApiIdentifier apiIdentifier, final ApiDescriptor apiResource, final Map<String, String> elementMap) { // 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 = elementMap.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 = elementMap.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>(getSubServiceName(apiIdentifier.getPath()), 15), new Pair<String, Integer>(builder.toString(), 85) ); } 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>(getSubServiceName(apiIdentifier.getPath()), 15), new Pair<String, Integer>(builder.toString(), 85) ); } 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) ); } private ComponentView getComponentView(String path) { String serviceName = ""; String componentName = ""; String[] parts = path.split(Constants.URL_PATH_SEPARATOR); serviceName = parts[1]; if (parts.length > 2) { componentName = parts[2].startsWith("{") ? "" : parts[2]; } String key = null; for (Map.Entry<String, List<Pair<String, String>>> entry : Main.serviceNamingMap.entrySet()) { for (Pair<String, String> item : entry.getValue()) { String tmp = serviceName + "." + componentName; if (item.getLeft().equals(tmp) || item.getLeft().equals(serviceName)) { key = entry.getKey(); break; } } if (key != null) { break; } } if (key == null) { throw new RuntimeException("Please check api PATH: /" + serviceName + "/" + componentName); } ComponentView componentView = componentMap.get(key); if (componentView == null) { componentView = new ComponentView(); componentMap.put(key, componentView); } return componentView; } private String getSubServiceName(String path) { String serviceName = ""; String componentName = ""; String[] parts = path.split(Constants.URL_PATH_SEPARATOR); serviceName = parts[1]; if (parts.length > 2) { componentName = parts[2].startsWith("{") ? "" : parts[2]; } for (Map.Entry<String, List<Pair<String, String>>> entry : Main.serviceNamingMap.entrySet()) { for (Pair<String, String> item : entry.getValue()) { String tmp = serviceName + "." + componentName; if (item.getLeft().equals(tmp) || item.getLeft().equals(serviceName)) { return item.getRight(); } } } throw new RuntimeException("Please check api PATH: /" + serviceName + "/" + componentName); } private static void outputToFile(final String fileName, final String content) { try { File file = new File(fileName); FileUtils.write(file, content); } catch (IOException ex) { throw new IllegalStateException("Can't write result file: " + fileName, ex); } } }