blob: dff04b4aa10e1f9960d44c657d45cc82f952fa02 [file] [log] [blame]
package org.apache.taverna.scufl2.api.common;
/*
* 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.
*/
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDF;
import org.apache.commons.rdf.simple.SimpleRDF;
import org.apache.taverna.scufl2.api.annotation.Annotation;
import org.apache.taverna.scufl2.api.annotation.Revision;
import org.apache.taverna.scufl2.api.common.Visitor.VisitorWithPath;
import org.apache.taverna.scufl2.api.container.WorkflowBundle;
import org.apache.taverna.scufl2.api.core.BlockingControlLink;
import org.apache.taverna.scufl2.api.core.DataLink;
import org.apache.taverna.scufl2.api.core.Workflow;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyNode;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
import org.apache.taverna.scufl2.api.port.InputPort;
import org.apache.taverna.scufl2.api.port.OutputPort;
import org.apache.taverna.scufl2.api.port.Port;
import org.apache.taverna.scufl2.api.port.ProcessorPort;
import org.apache.taverna.scufl2.api.profiles.ProcessorPortBinding;
/**
* Utility methods for dealing with URIs.
*/
public class URITools {
private static final String MERGE_POSITION = "mergePosition";
private static final String TO = "to";
private static final String FROM = "from";
private static final String DATALINK = "datalink";
private static final URI DOT = URI.create(".");
private RDF rdf = new SimpleRDF();
public IRI asIRI(URI uri) {
// TODO: Handle internationalization?
return rdf.createIRI(uri.toString());
}
public URI asURI(IRI iri) {
// TODO: Handle internationalization?
return URI.create(iri.getIRIString());
}
public URI relativePath(URI base, URI uri) {
URI root = base.resolve("/");
if (!root.equals(uri.resolve("/")))
// Different protocol/host/auth
return uri;
base = base.normalize();
uri = uri.normalize();
if (base.resolve("#").equals(uri.resolve("#")))
// Same path, easy
return base.relativize(uri);
if (base.isAbsolute()) {
// Ignore hostname and protocol
base = root.relativize(base).resolve(".");
uri = root.relativize(uri);
}
// Pretend they start from /
base = root.resolve(base).resolve(".");
uri = root.resolve(uri);
URI candidate = base.relativize(uri);
URI relation = DOT;
while (candidate.getPath().startsWith("/")
&& !(base.getPath().isEmpty() || base.getPath().equals("/"))) {
base = base.resolve("../");
relation = relation.resolve("../");
candidate = base.relativize(uri);
}
// Add the ../.. again
URI resolved = relation.resolve(candidate);
return resolved;
}
public URI relativeUriForBean(WorkflowBean bean, WorkflowBean relativeToBean) {
URI rootUri = uriForBean(relativeToBean);
URI beanUri = uriForBean(bean);
return relativePath(rootUri, beanUri);
}
public WorkflowBean resolveUri(URI uri, WorkflowBundle wfBundle) {
// Check if it's a workflow URI
for (Workflow wf : wfBundle.getWorkflows())
if (wf.getIdentifier().equals(uri))
return wf;
String rel = Workflow.WORKFLOW_ROOT.relativize(uri).toASCIIString();
if (rel.matches("[0-9a-f-]+/"))
return null;
/*
* Naive, super-inefficient reverse-lookup - we could have even returned
* early!
*/
final Map<URI, WorkflowBean> uriToBean = new HashMap<>();
wfBundle.accept(new VisitorWithPath() {
@Override
public boolean visit() {
WorkflowBean node = getCurrentNode();
URI uri;
try {
uri = uriForBean(node);
} catch (IllegalStateException ex) {
return false;
}
WorkflowBean existing = uriToBean.put(uri, node);
if (existing != null)
/*
* Check if we should keep the existing object instead,
* because the inserted object is "lesser worth" (for
* instance we try to insert a Revision when a
* WorkflowBundle already exists,
*/
if (node instanceof Revision
&& !(existing instanceof Revision))
uriToBean.put(uri, existing);
return true;
}
});
if (!uri.isAbsolute()) {
// Make absolute, but remove / first
uri = URI.create("/").relativize(uri);
uri = uriForBean(wfBundle).resolve(uri);
}
return uriToBean.get(uri);
}
public URI uriForBean(WorkflowBean bean) {
if (bean == null)
throw new NullPointerException("Bean can't be null");
if (bean instanceof Root) {
Root root = (Root) bean;
if (root.getGlobalBaseURI() == null) {
if (!(root instanceof WorkflowBundle))
throw new IllegalArgumentException(
"sameBaseAs is null for bean " + bean);
((WorkflowBundle) root).newRevision();
}
return root.getGlobalBaseURI();
}
if (bean instanceof Child) {
@SuppressWarnings("rawtypes")
Child child = (Child) bean;
WorkflowBean parent = child.getParent();
if (parent == null)
throw new IllegalStateException("Bean does not have a parent: "
+ child);
URI parentUri = uriForBean(parent);
if (!parentUri.getPath().endsWith("/"))
parentUri = parentUri.resolve(parentUri.getPath() + "/");
String relation;
if (child instanceof InputPort)
relation = "in/";
else if (child instanceof OutputPort)
relation = "out/";
else if (child instanceof IterationStrategyStack)
relation = "iterationstrategy/";
else
// TODO: Get relation by container annotations
relation = child.getClass().getSimpleName() + "/"; // Stupid fallback
URI relationURI = parentUri.resolve(relation.toLowerCase());
if (parent instanceof List) {
@SuppressWarnings("rawtypes")
int index = ((List) parent).indexOf(child);
return parentUri.resolve(index + "/");
}
if (bean instanceof Named) {
Named named = (Named) bean;
String name = validFilename(named.getName());
if (!(bean instanceof Port || bean instanceof Annotation))
name = name + "/";
return relationURI.resolve(name);
} else if (bean instanceof DataLink) {
DataLink dataLink = (DataLink) bean;
Workflow wf = dataLink.getParent();
URI wfUri = uriForBean(wf);
URI receivesFrom = relativePath(wfUri,
uriForBean(dataLink.getReceivesFrom()));
URI sendsTo = relativePath(wfUri,
uriForBean(dataLink.getSendsTo()));
String dataLinkUri = MessageFormat.format(
"{0}?{1}={2}&{3}={4}", DATALINK, FROM, receivesFrom,
TO, sendsTo);
if (dataLink.getMergePosition() != null)
dataLinkUri += MessageFormat.format("&{0}={1}",
MERGE_POSITION, dataLink.getMergePosition());
return wfUri.resolve(dataLinkUri);
} else if (bean instanceof BlockingControlLink) {
BlockingControlLink runAfterCondition = (BlockingControlLink) bean;
Workflow wf = runAfterCondition.getParent();
URI wfUri = uriForBean(wf);
URI start = relativePath(wfUri,
uriForBean(runAfterCondition.getBlock()));
URI after = relativePath(wfUri,
uriForBean(runAfterCondition.getUntilFinished()));
String conditionUri = MessageFormat.format(
"{0}?{1}={2}&{3}={4}", "control", "block", start,
"untilFinished", after);
return wfUri.resolve(conditionUri);
} else if (bean instanceof IterationStrategyStack) {
return relationURI;
} else if (bean instanceof IterationStrategyNode) {
IterationStrategyNode iterationStrategyNode = (IterationStrategyNode) bean;
parent = iterationStrategyNode.getParent();
@SuppressWarnings("unchecked")
List<IterationStrategyNode> parentList = (List<IterationStrategyNode>) parent;
int index = parentList.indexOf(iterationStrategyNode);
return parentUri.resolve(index + "/");
} else if (bean instanceof ProcessorPortBinding) {
// Named after the processor port, extract in/blah part.
ProcessorPortBinding<?, ?> processorPortBinding = (ProcessorPortBinding<?, ?>) bean;
ProcessorPort procPort = processorPortBinding
.getBoundProcessorPort();
if (procPort == null)
throw new IllegalStateException(
"ProcessorPortBinding has no bound processor port: "
+ bean);
URI procPortUri = relativeUriForBean(procPort,
processorPortBinding.getParent().getBoundProcessor());
return parentUri.resolve(procPortUri);
} else
throw new IllegalStateException(
"Can't create URIs for child of unrecogized type " + bean.getClass());
}
if (bean instanceof Revision) {
Revision revision = (Revision) bean;
return revision.getIdentifier();
}
throw new IllegalArgumentException("Unsupported type "
+ bean.getClass() + " for bean " + bean);
}
public String validFilename(String name) {
if (name == null)
throw new NullPointerException();
// Make a relative URI
URI uri;
try {
uri = new URI(null, null, name, null, null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid name " + name);
}
String ascii = uri.toASCIIString();
// And escape / and \
String escaped = ascii.replace("/", "%2f");
// escaped = escaped.replace("\\", "%5c");
escaped = escaped.replace(":", "%3a");
escaped = escaped.replace("=", "%3d");
return escaped;
}
}