/**
* Copyright (C) 2010 - 2016 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* • Apache License, version 2.0
* • Apache Software License, version 1.0
* • GNU Lesser General Public License, version 3
* • Mozilla Public License, versions 1.0, 1.1 and 2.0
* • Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* License version 2 and the aforementioned licenses.
*
* 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 General
* Public License for more details.
*/
package org.n52.wps.server.r.metadata;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import net.opengis.wps.x100.ProcessDescriptionType;
import org.n52.wps.server.ExceptionReport;
import org.n52.wps.server.r.R_Config;
import org.n52.wps.server.r.data.R_Resource;
import org.n52.wps.server.r.syntax.RAnnotation;
import org.n52.wps.server.r.syntax.RAnnotationException;
import org.n52.wps.server.r.syntax.RAnnotationType;
import org.n52.wps.server.r.syntax.RAttribute;
import org.n52.wps.server.r.syntax.RSeperator;
import org.n52.wps.server.r.syntax.ResourceAnnotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RAnnotationParser {
private static final String ANNOTATION_CHARACTER = "#";
private static final String COMMENTED_ANNOTATION_CHARACTER = "##";
private static Logger LOGGER = LoggerFactory.getLogger(RAnnotationParser.class);
private R_Config config;
public RAnnotationParser(R_Config config) {
this.config = config;
LOGGER.debug("New {}", this);
}
/**
*
* @param script
* @throws RAnnotationException
* if script is invalid
* @throws IOException
* @throws ExceptionReport
*/
public boolean validateScript(InputStream script, String identifier) throws RAnnotationException,
IOException,
ExceptionReport {
// TODO: improve this method to something more useful
// try to parse annotations:
List<RAnnotation> annotations = parseAnnotationsfromScript(script);
// try to create process description:
RProcessDescriptionCreator descriptionCreator = new RProcessDescriptionCreator(this.config);
// TODO: WPS.des and WPS.res should only occur once or not at all
try {
ProcessDescriptionType processType = descriptionCreator.createDescribeProcessType(annotations,
identifier,
new URL("http://some.valid.url/"),
new URL("http://some.valid.url/"));
boolean valid = processType.validate();
if (valid == false)
throw new ExceptionReport("Invalid R algorithm. The process description created from the script is not valid.",
ExceptionReport.NO_APPLICABLE_CODE);
return valid;
}
catch (ExceptionReport e) {
String message = "Invalid R algorithm. Script validation failed when executing process description creator.";
LOGGER.error(message, e);
throw e;
}
catch (RAnnotationException e) {
String message = "Invalid R algorithm. Script validation failed when executing process description creator.";
LOGGER.error(message, e);
throw e;
}
}
public List<RAnnotation> parseAnnotationsfromScript(InputStream inputScript) throws RAnnotationException {
LOGGER.debug("Starting to parse annotations from script " + inputScript);
try {
BufferedReader lineReader = new BufferedReader(new InputStreamReader(inputScript));
int lineCounter = 0;
boolean isCurrentlyParsingAnnotation = false;
StringBuilder annotationString = null;
RAnnotationType annotationType = null;
ArrayList<RAnnotation> annotations = new ArrayList<RAnnotation>();
while (lineReader.ready()) {
String line = lineReader.readLine();
lineCounter++;
if (line.trim().startsWith(ANNOTATION_CHARACTER)
&& !line.trim().startsWith(COMMENTED_ANNOTATION_CHARACTER)) {
line = line.split("#", 2)[1];
line = line.trim();
if (line.isEmpty())
continue;
LOGGER.debug("Parsing annotation line '{}'", line);
if ( !isCurrentlyParsingAnnotation)
// searches for startKey - expressions in a line
for (RAnnotationType anot : RAnnotationType.values()) {
String startKey = anot.getStartKey().getKey();
if (line.contains(startKey)) {
LOGGER.debug("Parsing annotation of type {}", startKey);
// start parsing an annotation, which might
// spread several lines
line = line.split(RSeperator.STARTKEY_SEPARATOR.getKey(), 2)[1];
annotationString = new StringBuilder();
annotationType = anot;
isCurrentlyParsingAnnotation = true;
break;
}
}
try {
if (isCurrentlyParsingAnnotation) {
String endKey = RSeperator.ANNOTATION_END.getKey();
if (line.contains(endKey)) {
line = line.split(endKey, 2)[0];
isCurrentlyParsingAnnotation = false;
// last line for multiline annotation
}
annotationString.append(line);
if ( !isCurrentlyParsingAnnotation) {
RAnnotation newAnnotation = null;
if (annotationType.equals(RAnnotationType.RESOURCE)) {
newAnnotation = createResourceAnnotation(annotationString.toString());
}
else {
HashMap<RAttribute, Object> attrHash = hashAttributes(annotationType,
annotationString.toString());
newAnnotation = new RAnnotation(annotationType, attrHash);
}
annotations.add(newAnnotation);
LOGGER.debug("Done parsing annotation {} > contains: ",
newAnnotation,
annotationString.toString());
}
}
}
catch (RAnnotationException e) {
LOGGER.error("Invalid R script with wrong annotation in Line {}: {}",
lineCounter,
e.getMessage());
}
}
}
LOGGER.debug("Finished parsing {} annotations from script {}:\n\t\t{}",
annotations.size(),
inputScript,
Arrays.deepToString(annotations.toArray()));
return annotations;
}
catch (Exception e) {
LOGGER.error("Error parsing annotations.", e);
throw new RAnnotationException("Error parsing annotations.", e);
}
}
private HashMap<RAttribute, Object> hashAttributes(RAnnotationType anotType, String attributeString) throws RAnnotationException {
HashMap<RAttribute, Object> attrHash = new HashMap<RAttribute, Object>();
StringTokenizer attrValueTokenizer = new StringTokenizer(attributeString,
RSeperator.ATTRIBUTE_SEPARATOR.getKey());
boolean iterableOrder = true;
// iterates over the attribute sequence of an Annotation
Iterator<RAttribute> attrKeyIterator = anotType.getAttributeSequence().iterator();
// Important for sequential order: start attribute contains no value,
// iteration starts from the second key
attrKeyIterator.next();
while (attrValueTokenizer.hasMoreElements()) {
String attrValue = attrValueTokenizer.nextToken();
if (attrValue.trim().startsWith("\"")) {
for (; attrValueTokenizer.hasMoreElements() && !attrValue.trim().endsWith("\"");) {
attrValue += RSeperator.ATTRIBUTE_SEPARATOR + attrValueTokenizer.nextToken();
}
attrValue = attrValue.substring(attrValue.indexOf("\"") + 1, attrValue.lastIndexOf("\""));
}
if (attrValue.contains(RSeperator.ATTRIBUTE_VALUE_SEPARATOR.getKey())) {
iterableOrder = false;
// in the following case, the annotation contains no sequential
// order and
// lacks an explicit attribute declaration --> Annotation cannot
// be interpreted
// e.g. value1, value2, attribute9 = value9, value4 --> parser
// error for "value4"
}
else if ( !iterableOrder) {
throw new RAnnotationException("Annotation contains no valid order: " + "\""
+ anotType.getStartKey().getKey() + " " + attributeString + "\"");
}
// Valid annotations:
// 1) Annotation with a sequential attribute order:
// wps.in: name,description,0,1;
// 2) Annotation with a partially sequential attribute order:
// wps.in: name,description, maxOccurs = 1;
// 3) Annotations without sequential order:
// wps.des: abstract = example process, title = Example1;
if (iterableOrder) {
attrHash.put(attrKeyIterator.next(), attrValue.trim());
}
else {
String[] keyValue = attrValue.split(RSeperator.ATTRIBUTE_VALUE_SEPARATOR.getKey());
RAttribute attribute = anotType.getAttribute(keyValue[0].trim());
String value = keyValue[1].trim();
if (value.startsWith("\"")) {
for (; attrValueTokenizer.hasMoreElements() && !value.trim().endsWith("\"");) {
value += RSeperator.ATTRIBUTE_SEPARATOR + attrValueTokenizer.nextToken();
}
value = value.substring(value.indexOf("\"") + 1, value.lastIndexOf("\""));
}
attrHash.put(attribute, value);
}
}
return attrHash;
}
private RAnnotation createResourceAnnotation(String attributeString) throws IOException, RAnnotationException {
List<R_Resource> resources = new ArrayList<R_Resource>();
StringTokenizer attrValueTokenizer = new StringTokenizer(attributeString,
RSeperator.ATTRIBUTE_SEPARATOR.getKey());
while (attrValueTokenizer.hasMoreElements()) {
String resourceValue = attrValueTokenizer.nextToken().trim();
R_Resource r_resource = new R_Resource(resourceValue);
resources.add(r_resource);
LOGGER.debug("Found new resource in annotation: {}", r_resource);
}
// add empty hasmap for now
HashMap<RAttribute, Object> attributeHash = new HashMap<RAttribute, Object>();
ResourceAnnotation resourceAnnotation = new ResourceAnnotation(attributeHash,
resources,
config.getResourceDirURL());
return resourceAnnotation;
}
}