/***********************************************************************************************************************
*
* Copyright (C) 2010 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.nephele.visualization.swt;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public final class SWTFailurePatternsEditor implements JobFailurePatternTreeListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 400;
private final Shell shell;
private final SWTJobFailurePatternTree jobTree;
private final SWTFailureEventTable failureEventTable;
private final Set<String> jobSuggestions;
private final Map<String, JobFailurePattern> loadedPatterns;
SWTFailurePatternsEditor(final Shell parent, final Set<String> jobSuggestions, final Set<String> nameSuggestions,
final Map<String, JobFailurePattern> loadedPatterns) {
this.jobSuggestions = jobSuggestions;
this.loadedPatterns = loadedPatterns;
// Set size
this.shell = new Shell(parent);
this.shell.setSize(WIDTH, HEIGHT);
this.shell.setText("Manage Failure Patterns");
GridLayout gl = new GridLayout(1, false);
gl.horizontalSpacing = 0;
gl.verticalSpacing = 0;
gl.marginRight = 0;
gl.marginLeft = 0;
gl.marginBottom = 0;
gl.marginTop = 0;
gl.marginHeight = 0;
gl.marginWidth = 0;
this.shell.setLayout(gl);
final Composite mainComposite = new Composite(this.shell, SWT.NONE);
mainComposite.setLayout(new GridLayout(1, false));
GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.verticalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
mainComposite.setLayoutData(gridData);
final SashForm horizontalSash = new SashForm(mainComposite, SWT.HORIZONTAL);
horizontalSash.setLayoutData(new GridData(GridData.FILL_BOTH));
final Group jobGroup = new Group(horizontalSash, SWT.NONE);
jobGroup.setText("Job Failure Patterns");
jobGroup.setLayout(new FillLayout());
this.jobTree = new SWTJobFailurePatternTree(jobGroup, SWT.SINGLE | SWT.BORDER, this);
this.failureEventTable = new SWTFailureEventTable(horizontalSash, SWT.BORDER | SWT.SINGLE, nameSuggestions);
horizontalSash.setWeights(new int[] { 2, 8 });
final Composite buttonComposite = new Composite(this.shell, SWT.NONE);
buttonComposite.setLayout(new GridLayout(2, false));
gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
buttonComposite.setLayoutData(gridData);
final Label fillLabel = new Label(buttonComposite, SWT.NONE);
gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = false;
fillLabel.setLayoutData(gridData);
final Button closeButton = new Button(buttonComposite, SWT.PUSH);
closeButton.setText("Close");
gridData = new GridData();
gridData.horizontalAlignment = SWT.RIGHT;
closeButton.setLayoutData(gridData);
closeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
shell.dispose();
}
});
final Iterator<JobFailurePattern> it = this.loadedPatterns.values().iterator();
while (it.hasNext()) {
this.jobTree.addFailurePatternToTree(it.next());
}
// Initialize the tables
if (this.jobTree.getItemCount() > 0) {
final TreeItem ti = this.jobTree.getItem(0);
this.jobTree.setSelection(ti);
this.failureEventTable.showFailurePattern((JobFailurePattern) ti.getData());
} else {
this.failureEventTable.showFailurePattern(null);
}
}
public Map<String, JobFailurePattern> show() {
this.shell.open();
final Display display = this.shell.getDisplay();
while (!this.shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
return this.loadedPatterns;
}
@Override
public void addFailurePattern() {
final Set<String> takenNames = this.loadedPatterns.keySet();
final SWTNewFailurePatternDialog dialog = new SWTNewFailurePatternDialog(this.shell, this.jobSuggestions,
takenNames);
final String patternName = dialog.showDialog();
if (patternName == null) {
return;
}
final JobFailurePattern jobFailurePattern = new JobFailurePattern(patternName);
// Add to loaded patterns
this.loadedPatterns.put(jobFailurePattern.getName(), jobFailurePattern);
this.jobTree.addFailurePatternToTree(jobFailurePattern);
this.failureEventTable.showFailurePattern(jobFailurePattern);
}
@Override
public void removeFailurePattern(final TreeItem selectedItem) {
final JobFailurePattern failurePattern = (JobFailurePattern) selectedItem.getData();
if (failurePattern == null) {
return;
}
final MessageBox messageBox = new MessageBox(this.shell, SWT.ICON_QUESTION | SWT.YES | SWT.NO);
messageBox.setText("Confirm Removal");
messageBox
.setMessage("Do you really want to remove the job failure pattern '" + failurePattern.getName() + "'?");
if (messageBox.open() != SWT.YES) {
return;
}
selectedItem.dispose();
this.loadedPatterns.remove(failurePattern.getName());
if (this.jobTree.getItemCount() == 0) {
jobFailurePatternSelected(null);
} else {
final TreeItem ti = this.jobTree.getItem(0);
this.jobTree.setSelection(ti);
jobFailurePatternSelected(ti);
}
}
@Override
public void saveFailurePattern(final TreeItem selectedItem) {
final JobFailurePattern failurePattern = (JobFailurePattern) selectedItem.getData();
if (failurePattern == null) {
return;
}
final FileDialog fileDialog = new FileDialog(this.shell, SWT.SAVE);
fileDialog.setText("Save Failure Pattern");
final String[] filterExts = { "*.xml", "*.*" };
fileDialog.setFilterExtensions(filterExts);
final String selectedFile = fileDialog.open();
if (selectedFile == null) {
return;
}
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
try {
final DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
final Document doc = builder.newDocument();
// Construct the DOM tree
final Element root = doc.createElement("pattern");
doc.appendChild(root);
final Element name = doc.createElement("name");
root.appendChild(name);
name.appendChild(doc.createTextNode(failurePattern.getName()));
final Element failures = doc.createElement("failures");
root.appendChild(failures);
final Iterator<AbstractFailureEvent> it = failurePattern.iterator();
while (it.hasNext()) {
final AbstractFailureEvent event = it.next();
final Element failure = doc.createElement("failure");
failure.setAttribute("type", (event instanceof VertexFailureEvent) ? "task" : "instance");
final Element failureName = doc.createElement("name");
failureName.appendChild(doc.createTextNode(event.getName()));
failure.appendChild(failureName);
final Element interval = doc.createElement("interval");
interval.appendChild(doc.createTextNode(Integer.toString(event.getInterval())));
failure.appendChild(interval);
failures.appendChild(failure);
}
// Write the DOM tree to the chosen file
final DOMSource domSource = new DOMSource(doc);
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final FileOutputStream fos = new FileOutputStream(selectedFile);
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.transform(domSource, new StreamResult(fos));
} catch (Exception e) {
final MessageBox messageBox = new MessageBox(this.shell, SWT.ICON_ERROR);
messageBox.setText("Cannot Save Failure Pattern");
messageBox.setMessage(e.getMessage());
messageBox.open();
}
}
@Override
public void jobFailurePatternSelected(final TreeItem selectedItem) {
if (selectedItem == null) {
this.failureEventTable.showFailurePattern(null);
} else {
this.failureEventTable.showFailurePattern((JobFailurePattern) selectedItem.getData());
}
}
@Override
public void loadFailurePattern() {
final FileDialog fileDialog = new FileDialog(this.shell, SWT.OPEN);
fileDialog.setText("Load Failure Pattern");
final String[] filterExts = { "*.xml", "*.*" };
fileDialog.setFilterExtensions(filterExts);
final String selectedFile = fileDialog.open();
if (selectedFile == null) {
return;
}
final JobFailurePattern failurePattern = loadFailurePatternFromFile(selectedFile);
if (this.loadedPatterns.containsKey(failurePattern.getName())) {
final MessageBox messageBox = new MessageBox(this.shell, SWT.ICON_ERROR);
messageBox.setText("Cannot Load Failure Pattern");
messageBox.setMessage("There is already a failure pattern loaded with the name '"
+ failurePattern.getName() + "'. Please remove it first.");
messageBox.open();
return;
}
this.loadedPatterns.put(failurePattern.getName(), failurePattern);
this.jobTree.addFailurePatternToTree(failurePattern);
this.failureEventTable.showFailurePattern(failurePattern);
}
private JobFailurePattern loadFailurePatternFromFile(final String filename) {
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
// Ignore comments in the XML file
docBuilderFactory.setIgnoringComments(true);
docBuilderFactory.setNamespaceAware(true);
JobFailurePattern jobFailurePattern = null;
InputStream inputStream = null;
try {
inputStream = new FileInputStream(filename);
final DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
Document doc = null;
Element root = null;
doc = builder.parse(inputStream);
if (doc == null) {
throw new Exception("Document is null");
}
root = doc.getDocumentElement();
if (root == null) {
throw new Exception("Root element is null");
}
if (!"pattern".equals(root.getNodeName())) {
throw new Exception("Encountered unknown element " + root.getNodeName());
}
final NodeList patternChildren = root.getChildNodes();
for (int i = 0; i < patternChildren.getLength(); ++i) {
final Node patternChild = patternChildren.item(i);
if (patternChild instanceof org.w3c.dom.Text) {
continue;
}
if (patternChild instanceof Element) {
final Element patternElement = (Element) patternChild;
if ("name".equals(patternElement.getNodeName())) {
final String name = extractValueFromElement(patternElement);
if (jobFailurePattern != null) {
throw new Exception("Element name detected more than once in the file");
}
jobFailurePattern = new JobFailurePattern(name);
continue;
}
if ("failures".equals(patternElement.getNodeName())) {
if (jobFailurePattern == null) {
throw new Exception("Expected pattern name to be stored before the failure events");
}
final NodeList failuresChildren = patternElement.getChildNodes();
for (int j = 0; j < failuresChildren.getLength(); ++j) {
final Node failuresChild = failuresChildren.item(j);
if (failuresChild instanceof org.w3c.dom.Text) {
continue;
}
if (!(failuresChild instanceof Element)) {
throw new Exception("Expected type element as child of element 'failures'");
}
final Element failuresElement = (Element) failuresChild;
if (!"failure".equals(failuresElement.getNodeName())) {
throw new Exception("Expected element 'failure' as child of element 'failures'");
}
final String type = failuresElement.getAttribute("type");
if (type == null) {
throw new Exception("Element 'failure' lacks the attribute 'type'");
}
final boolean taskFailure = ("task".equals(type));
String name = null;
String interval = null;
final NodeList failureChildren = failuresElement.getChildNodes();
for (int k = 0; k < failureChildren.getLength(); ++k) {
final Node failureChild = failureChildren.item(k);
if (failureChild instanceof org.w3c.dom.Text) {
continue;
}
if (!(failureChild instanceof Element)) {
throw new Exception("Expected type element as child of element 'failure'");
}
final Element failureElement = (Element) failureChild;
if ("name".equals(failureElement.getNodeName())) {
name = extractValueFromElement(failureElement);
}
if ("interval".equals(failureElement.getNodeName())) {
interval = extractValueFromElement(failureElement);
}
}
if (name == null) {
throw new Exception("Could not find name for failure event " + j);
}
if (interval == null) {
throw new Exception("Could not find interval for failure event " + j);
}
int iv = 0;
try {
iv = Integer.parseInt(interval);
} catch (NumberFormatException e) {
throw new Exception("Interval " + interval + " for failure event " + j
+ " is not an integer number");
}
if (iv <= 0) {
throw new Exception("Interval for failure event " + j
+ " must be greather than zero, but is " + iv);
}
AbstractFailureEvent failureEvent = null;
if (taskFailure) {
failureEvent = new VertexFailureEvent(name, iv);
} else {
failureEvent = new InstanceFailureEvent(name, iv);
}
jobFailurePattern.addEvent(failureEvent);
}
continue;
}
throw new Exception("Uncountered unecpted element " + patternElement.getNodeName());
} else {
throw new Exception("Encountered unexpected child of type " + patternChild.getClass());
}
}
} catch (Exception e) {
final MessageBox messageBox = new MessageBox(this.shell, SWT.ICON_ERROR);
messageBox.setText("Cannot Load Failure Pattern");
messageBox.setMessage(e.getMessage());
messageBox.open();
return null;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
}
}
}
return jobFailurePattern;
}
private String extractValueFromElement(final Element element) throws Exception {
final NodeList children = element.getChildNodes();
if (children.getLength() != 1) {
throw new Exception("Element " + element.getNodeName() + " has an unexpected number of children");
}
final Node child = children.item(0);
if (!(child instanceof org.w3c.dom.Text)) {
throw new Exception("Expected child of element " + element.getNodeName() + " to be of type text");
}
org.w3c.dom.Text childText = (org.w3c.dom.Text) child;
return childText.getTextContent();
}
}