/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.template;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import com.rapidminer.Process;
import com.rapidminer.RepositoryProcessLocation;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.repository.MalformedRepositoryLocationException;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.repository.resource.ZipStreamResource;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.NonClosingZipInputStream;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.XMLException;
/**
* Templates for new processes. The template must be a .zip file renamed to .template containing
*
* <ul>
* <li>an {@code icon.png} of size 64x64</li>
* <li>a {@code template.properties} file</li>
* <li>one {@code .rmp} process file</li>
* <li>an arbitrarily number of {@code .ioo} and {@code .md} files that are used in the process</li>
* </ul>
* The {@code template.properties} file has to contain the keys
* <ul>
* <li>template.name (which defines the title of the template)</li>
* <li>template.short_description (which defines the description of the template)</li>
* </ul>
* The process and the data files are automatically added to the folder
* //Samples/Templates/[template.title] and the data files must be referred from the process with an
* absolute path accordingly. </br> </br> The template files can be added into
* .Rapidminer/templates/ or under template/ in the extension resources (
* {@code src/main/resources/com.rapidminer.resources.template/[fileName].template}). Template files
* in the resources have to be registered via {@link TemplateManager#registerTemplate(String)} while
* the files from the .Rapidminer folder are loaded automatically.
*
* @author Simon Fischer, Gisa Schaefer
*
*/
public class Template implements ZipStreamResource {
/** the repository location for templates */
private static final String TEMPLATES_PATH = "//Samples/Templates/";
/** the resources location for templates */
private static final String RESOURCES_LOCATION = "template/";
private static final String NO_DESCRIPTION = I18N.getGUILabel("template.no_description");
private static final String NO_TITLE = I18N.getGUILabel("template.no_title");
/** special template which is not loaded from a resource but simply creates a new, empty process */
public static final Template BLANK_PROCESS_TEMPLATE = new Template() {
@Override
public Process makeProcess() throws IOException, XMLException, MalformedRepositoryLocationException {
return new Process();
}
};
private String title = NO_TITLE;
private String shortDescription = NO_DESCRIPTION;
private String processName;
private List<String> demoData;
private Icon icon;
private final Path path;
private final String name;
/**
* Private constructor for special blank process template only.
*/
private Template() {
this.title = I18N.getGUILabel("getting_started.new.empty.title");
this.shortDescription = I18N.getGUILabel("getting_started.new.empty.description");
this.icon = SwingTools.createIcon("64/" + I18N.getGUILabel("getting_started.new.empty.icon"));
this.demoData = new LinkedList<>();
this.name = this.title;
this.processName = null;
this.path = null;
}
Template(String name) throws IOException, RepositoryException {
this.name = name;
this.path = null;
load();
}
Template(Path path) throws IOException, RepositoryException {
this.name = path.getFileName().toString().replaceAll("\\.template", "");
this.path = path;
load();
}
/**
* @return the {@link ZipInputStream} to load resources associated with this template
*/
@Override
public ZipInputStream getStream() throws IOException, RepositoryException {
return new ZipInputStream(getInputStream());
}
@Override
public String getTitle() {
return title;
}
@Override
public String getDescription() {
return shortDescription;
}
@Override
public String getStreamPath() {
return null;
}
/**
* @return the name of the template which is also the name of the zip file
*/
public String getName() {
return name;
}
/**
* @return a new process that contains the template process
* @throws XMLException
* @throws IOException
* @throws MalformedRepositoryLocationException
*/
public Process makeProcess() throws IOException, XMLException, MalformedRepositoryLocationException {
String processLocation = TEMPLATES_PATH + title + "/" + processName;
RepositoryProcessLocation repoLocation = new RepositoryProcessLocation(new RepositoryLocation(processLocation));
return new Process(repoLocation.getRawXML());
}
/**
* @return the name of the process behind this template
*/
public String getProcessName() {
return processName;
}
/**
* @return the icon to display
*/
public Icon getIcon() {
return icon;
}
/**
* @return the list of demo data names
*/
public List<String> getDemoData() {
return demoData;
}
/**
* Loads the content of the zip file.
*/
private void load() throws IOException, RepositoryException {
try (InputStream rawIn = getInputStream()) {
demoData = new LinkedList<>();
NonClosingZipInputStream zip = new NonClosingZipInputStream(rawIn);
try {
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
if (entry.isDirectory()) {
throw new RepositoryException("Template malformed. A template must not contain a directory.");
}
String entryName = entry.getName();
if ("template.properties".equals(entryName)) {
Properties props = new Properties();
props.load(zip);
title = props.getProperty("template.name", NO_TITLE);
shortDescription = props.getProperty("template.short_description", NO_DESCRIPTION);
} else if (entryName.endsWith(".rmp")) {
processName = entryName.split("\\.")[0];
} else if (entryName.endsWith(".ioo")) {
demoData.add(entryName.split("\\.")[0]);
} else if ("icon.png".equals(entryName)) {
icon = new ImageIcon(Tools.readInputStream(zip));
}
}
} finally {
zip.close(); // noop ; to avoid compile time warning about resource leak
zip.close2();
}
}
}
/**
* @return the {@link InputStream} to of this template
*/
private InputStream getInputStream() throws IOException, RepositoryException {
if (path != null) {
return Files.newInputStream(path);
} else {
return Tools.getResourceInputStream(RESOURCES_LOCATION + name + ".template");
}
}
}