/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.ambari.server.api.services.serializers; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.ambari.server.api.services.Result; import org.apache.ambari.server.api.services.ResultStatus; import org.apache.ambari.server.api.util.TreeNode; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.utils.Closeables; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; /** * CSV serializer used to generate a CSV-formatted document from a result. */ public class CsvSerializer implements ResultSerializer { /** * Property name for the CsvSerializer-specific column map where the value of this property * contains a map of resource property names to header descriptive names. * <p/> * If not specified, no header record will be serialized. */ public static final String PROPERTY_COLUMN_MAP = "csv_column_map"; /** * Property name for the CsvSerializer-specific column order where the value of this property * contains a list of resource property names in the order to export. * <p/> * If not specified, the order will be taken from key order in the csv_column_map property (if * available) or from the "natural" order of the properties in the resource. */ public static final String PROPERTY_COLUMN_ORDER = "csv_column_order"; /** * Serialize the result into a CSV-formatted text document. * <p/> * It is expected that the result set is a collection of flat resources - no sub-resources will be * included in the output. The root of the tree structure may have a column map (csv_column_map) * and a column order (csv_column_order) property set to indicate the header record and ordering * of the columns. * <p/> * The csv_column_map is a map of resource property names to header descriptive names. If not * specified, a header record will not be serialized. * <p/> * The csv_column_order is a list of resource property names declaring the order of the columns. * If not specified, the order will be taken from the key order of csv_column_map or the "natural" * ordering of the resource property names, both may be unpredictable. * * @param result internal result * @return a String containing the CSV-formatted document */ @Override public Object serialize(Result result) { if (result.getStatus().isErrorState()) { return serializeError(result.getStatus()); } else { CSVPrinter csvPrinter = null; try { // A StringBuffer to store the CSV-formatted document while building it. It may be // necessary to use file-based storage if the data set is expected to be really large. StringBuffer buffer = new StringBuffer(); TreeNode<Resource> root = result.getResultTree(); if (root != null) { csvPrinter = new CSVPrinter(buffer, CSVFormat.DEFAULT); // TODO: recursively handle tree structure, for now only handle single level of detail if ("true".equalsIgnoreCase(root.getStringProperty("isCollection"))) { List<String> fieldNameOrder = processHeader(csvPrinter, root); Collection<TreeNode<Resource>> children = root.getChildren(); if (children != null) { // Iterate over the child nodes of the collection an add each as a new record in the // CSV document. for (TreeNode<Resource> child : children) { processRecord(csvPrinter, child, fieldNameOrder); } } } } return buffer.toString(); } catch (IOException e) { //todo: exception handling. Create ResultStatus 500 and call serializeError throw new RuntimeException("Unable to serialize to csv: " + e, e); } finally { Closeables.closeSilently(csvPrinter); } } } @Override public Object serializeError(ResultStatus error) { CSVPrinter csvPrinter = null; try { StringBuffer buffer = new StringBuffer(); csvPrinter = new CSVPrinter(buffer, CSVFormat.DEFAULT); csvPrinter.printRecord(Arrays.asList("status", "message")); csvPrinter.printRecord(Arrays.asList(error.getStatus().getStatus(), error.getMessage())); return buffer.toString(); } catch (IOException e) { //todo: exception handling. Create ResultStatus 500 and call serializeError throw new RuntimeException("Unable to serialize to csv: " + e, e); } finally { Closeables.closeSilently(csvPrinter); } } /** * Generate a CSV record by processing the resource embedded in the specified node. The order of * the fields are to be set as specified. * * @param csvPrinter the CSVPrinter used to create the record * @param node the relevant node in the collection * @param fieldNameOrder a list of field names indicating order * @throws IOException if an error occurs creating the CSV record */ private void processRecord(CSVPrinter csvPrinter, TreeNode<Resource> node, List<String> fieldNameOrder) throws IOException { if (node != null) { Resource recordResource = node.getObject(); if (recordResource != null) { List<Object> values = new ArrayList<>(); if (fieldNameOrder != null) { for (String fieldName : fieldNameOrder) { values.add(recordResource.getPropertyValue(fieldName)); } } else { Map<String, Map<String, Object>> properties = recordResource.getPropertiesMap(); if (properties != null) { for (Map.Entry<String, Map<String, Object>> outer : properties.entrySet()) { Map<String, Object> innerProperties = outer.getValue(); if (innerProperties != null) { for (Map.Entry<String, Object> inner : innerProperties.entrySet()) { values.add(inner.getValue()); } } } } } if (!values.isEmpty()) { csvPrinter.printRecord(values); } } } } /** * Optionally generate the CSV header record and establish the field order by processing the * csv_column_map and csv_column_order node properties. * * @param csvPrinter the CSVPrinter used to create the record * @param node a node containing header and ordering information * @return a list indicating the field order for the CSV records * @throws IOException if an error occurs creating the CSV header */ private List<String> processHeader(CSVPrinter csvPrinter, TreeNode<Resource> node) throws IOException { Map<String, String> header; List<String> fieldNameOrder; Object object; // Get the explicitly set header property for the current tree node. This may be null if no // header needs to be written out. The header map is expected to be a map of field names to // descriptive header values. object = node.getProperty(PROPERTY_COLUMN_MAP); if (object instanceof Map) { header = (Map<String, String>) object; } else { header = null; } // Determine the field name order. If explicitly set, use it, else grab it from the header map // (if available). object = node.getProperty(PROPERTY_COLUMN_ORDER); if (object instanceof List) { // Use the explicitly set ordering fieldNameOrder = (List<String>) object; } else if (header != null) { // Use the ordering specified by the map. fieldNameOrder = new ArrayList<>(header.keySet()); } else { fieldNameOrder = null; } if (header != null) { // build the header record List<String> headerNames = new ArrayList<>(); for (String fieldName : fieldNameOrder) { headerNames.add(header.get(fieldName)); } // write out the header... csvPrinter.printRecord(headerNames); } return fieldNameOrder; } }