001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.util.graph; 020 021import com.google.common.base.Preconditions; 022import com.google.common.base.Strings; 023import org.apache.oozie.client.WorkflowJob; 024import org.xml.sax.InputSource; 025import org.xml.sax.SAXException; 026import org.xml.sax.XMLReader; 027 028import javax.xml.XMLConstants; 029import javax.xml.parsers.ParserConfigurationException; 030import javax.xml.parsers.SAXParser; 031import javax.xml.parsers.SAXParserFactory; 032import java.io.IOException; 033import java.io.OutputStream; 034import java.io.StringReader; 035 036/** 037 * Class to generate and plot runtime workflow DAG. 038 * <p> 039 * Since it delegates to {@link WorkflowGraphHandler} and a {@link GraphRenderer}, it is the single entry point when changing graph 040 * generation behavior. 041 */ 042public class GraphGenerator { 043 public static final String SAX_FEATURE_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities"; 044 public static final String SAX_FEATURE_EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities"; 045 public static final String SAX_FEATURE_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; 046 private final GraphRenderer graphRenderer; 047 private final String xml; 048 private final WorkflowJob job; 049 private final boolean showKill; 050 051 /** 052 * Constructor Inversion of Control-style for better testability. 053 * @param xml The workflow definition XML 054 * @param job Current status of the job 055 * @param showKill Flag to whether show 'kill' node 056 * @param graphRenderer the renderer 057 */ 058 public GraphGenerator(final String xml, final WorkflowJob job, final boolean showKill, final GraphRenderer graphRenderer) { 059 Preconditions.checkArgument(!Strings.isNullOrEmpty(xml), "xml can't be empty"); 060 Preconditions.checkArgument(job != null, "WorkflowJob can't be null"); 061 062 this.xml = xml; 063 this.job = job; 064 this.showKill = showKill; 065 this.graphRenderer = graphRenderer; 066 } 067 068 /** 069 * Stream the generated PNG, DOT or SVG stream to the caller. Note that closing the {@link OutputStream} is the responsibility 070 * of the caller. 071 * 072 * @param out the {@link OutputStream} to use on streaming 073 * @param outputFormat The output format to apply when rendering 074 * @throws ParserConfigurationException if the parser is not configured properly 075 * @throws SAXException in case of XML error 076 * @throws IOException in case of any other IO related issues 077 */ 078 public void write(final OutputStream out, final OutputFormat outputFormat) 079 throws ParserConfigurationException, SAXException, IOException { 080 final XMLReader xmlReader = newXmlReader(); 081 xmlReader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 082 083 xmlReader.setContentHandler( 084 new WorkflowGraphHandler(out, outputFormat, job, showKill, graphRenderer)); 085 086 try (final StringReader stringReader = new StringReader(xml)) { 087 xmlReader.parse(new InputSource(stringReader)); 088 } 089 } 090 091 private XMLReader newXmlReader() throws ParserConfigurationException, SAXException { 092 final SAXParserFactory spf = SAXParserFactory.newInstance(); 093 spf.setFeature(SAX_FEATURE_EXTERNAL_GENERAL_ENTITIES, false); 094 spf.setFeature(SAX_FEATURE_EXTERNAL_PARAMETER_ENTITIES, false); 095 spf.setFeature(SAX_FEATURE_DISALLOW_DOCTYPE_DECL, true); 096 spf.setNamespaceAware(true); 097 098 final SAXParser saxParser = spf.newSAXParser(); 099 final XMLReader xmlReader = saxParser.getXMLReader(); 100 xmlReader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 101 102 return xmlReader; 103 } 104}