001/* 002 * Copyright (c) 2018 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2017-09-04 12:58:25 -0700 (Mon, 04 Sep 2017) $' 007 * '$Revision: 1406 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 */ 028package org.kepler.webview.server.app.ro; 029 030import java.io.File; 031import java.io.IOException; 032import java.net.URI; 033 034import org.apache.commons.httpclient.util.HttpURLConnection; 035import org.kepler.objectmanager.lsid.KeplerLSID; 036import org.kepler.provenance.manager.ProvenanceManager; 037import org.kepler.webview.server.WebViewConfiguration; 038import org.kepler.webview.server.WebViewServer; 039import org.kepler.webview.server.app.AbstractApp; 040 041import io.vertx.core.AsyncResult; 042import io.vertx.core.Future; 043import io.vertx.core.Handler; 044import io.vertx.core.MultiMap; 045import io.vertx.core.buffer.Buffer; 046import io.vertx.core.file.FileSystem; 047import io.vertx.core.file.OpenOptions; 048import io.vertx.core.http.HttpClientRequest; 049import io.vertx.core.json.JsonArray; 050import io.vertx.core.json.JsonObject; 051import io.vertx.core.streams.ReadStream; 052import io.vertx.ext.auth.User; 053import io.vertx.ext.web.client.WebClient; 054 055/** Create a research object on ROHUB. 056 * 057 * @author Daniel Crawl 058 */ 059public class CreateRO extends AbstractApp { 060 061 public CreateRO() { 062 _authStr = WebViewConfiguration.getROHubAuthToken(); 063 064 String roHubStr = WebViewConfiguration.getROHubURI(); 065 066 try { 067 _roHubURI = new URI(roHubStr); 068 } catch(Exception e) { 069 System.err.println("Syntax error parsing ROHUB URI: " + roHubStr); 070 } 071 072 _roHubPort = _roHubURI.getPort(); 073 if(_roHubPort == -1) { 074 _roHubPort = _roHubURI.getScheme().startsWith("https") ? 443 : 80; 075 } 076 077 } 078 079 @Override 080 public void exec(User user, JsonObject inputs, Handler<AsyncResult<JsonArray>> handler) 081 throws Exception { 082 083 if(_authStr == null) { 084 handler.handle(Future.failedFuture("Server not configured with ROHUB token.")); 085 } else if(_roHubURI == null) { 086 handler.handle(Future.failedFuture("Server configured with invalid ROHUB URI.")); 087 } else if(!inputs.containsKey("name")) { 088 handler.handle(Future.failedFuture("Must specify name.")); 089 } else if(!inputs.containsKey("title")) { 090 handler.handle(Future.failedFuture("Must specify title.")); 091 } else if(!inputs.containsKey("description")) { 092 handler.handle(Future.failedFuture("Must specify description.")); 093 } else if(inputs.containsKey("runs") && inputs.getJsonArray("runs").size() < 1) { 094 handler.handle(Future.failedFuture("Missing runs.")); 095 } else { 096 String name = inputs.getString("name"); 097 098 HttpClientRequest request = WebViewServer.vertx().createHttpClient() 099 .post(_roHubPort, _roHubURI.getHost(), _roHubURI.getPath(), response -> { 100 int status = response.statusCode(); 101 if(status != HttpURLConnection.HTTP_OK && 102 status != HttpURLConnection.HTTP_CREATED) { 103 handler.handle(Future.failedFuture("Unexpected response from ROHUB: " + status)); 104 } else { 105 /* 106 response.headers().forEach(a -> { 107 System.out.println(a.getKey() + " -> " + a.getValue()); 108 }); 109 */ 110 String location = response.headers().get("Location"); 111 if(location == null) { 112 handler.handle(Future.failedFuture("No research object URI in response.")); 113 } else { 114 115 URI roURI; 116 try { 117 roURI = new URI(location); 118 } catch(Exception e) { 119 handler.handle(Future.failedFuture(new Exception("Error parsing RO URI: " + location))); 120 return; 121 } 122 123 //System.out.println(location); 124 _annotateRO(roURI, inputs, annotateHandler -> { 125 if(annotateHandler.failed() || !inputs.containsKey("runs")) { 126 handler.handle(annotateHandler); 127 } else { 128 _addRunsToRO(roURI, inputs, handler); 129 } 130 }); 131 } 132 } 133 }); 134 135 MultiMap headers = request.headers(); 136 headers.set("Content-type", "text/plain") 137 .set("Accept", "text/turtle") 138 .set("slug", name) 139 .set("Authorization", "Bearer " + _authStr); 140 request.setFollowRedirects(true).end(); 141 } 142 } 143 144 private void _addRunToRO(URI roURI, JsonArray runs, int index, Handler<AsyncResult<JsonArray>> handler) { 145 // export the run as an ro 146 147 KeplerLSID runLSID = null; 148 try { 149 runLSID = new KeplerLSID(runs.getString(index)); 150 } catch(Exception e) { 151 handler.handle(Future.failedFuture("Invalid run LSID: " + runs.getString(index))); 152 return; 153 } 154 155 File roBundleFile = null; 156 boolean error = false; 157 try { 158 roBundleFile = File.createTempFile("webviewROBundle", ".zip"); 159 ProvenanceManager.exportROBundle(roBundleFile.getAbsolutePath(), runLSID); 160 } catch (IOException e) { 161 handler.handle(Future.failedFuture("Error creating temporary file: " + e.getMessage())); 162 error = true; 163 return; 164 } catch (Exception e) { 165 handler.handle(Future.failedFuture("Error creating robundle zip file: " + e.getMessage())); 166 error = true; 167 return; 168 } finally { 169 if(error && roBundleFile != null && !roBundleFile.delete()) { 170 System.err.println("WARNING: unable to delete " + roBundleFile.getAbsolutePath()); 171 } 172 } 173 174 final File roBundleFileFinal = roBundleFile; 175 176 FileSystem fs = WebViewServer.vertx().fileSystem(); 177 fs.open(roBundleFile.getAbsolutePath(), new OpenOptions(), fileRes -> { 178 if(fileRes.failed()) { 179 handler.handle(Future.failedFuture("Error reading robundle zip file: " + fileRes.cause().getMessage())); 180 if(!roBundleFileFinal.delete()) { 181 System.err.println("WARNING: unable to delete " + roBundleFileFinal.getAbsolutePath()); 182 } 183 return; 184 } else { 185 186 ReadStream<Buffer> stream = fileRes.result(); 187 188 // upload to rohub 189 WebClient.create(WebViewServer.vertx()) 190 .post(roURI.getHost(), roURI.getPath()) 191 .putHeader("Content-type", "application/octect-stream") 192 .putHeader("Accept", "text/turtle") 193 .putHeader("Slug", "ro_bundle" + index + ".zip") 194 .putHeader("Authorization", "Bearer " + _authStr) 195 .sendStream(stream, response -> { 196 197 // delete the bundle .zip 198 if(!roBundleFileFinal.delete()) { 199 System.err.println("WARNING: unable to delete " + roBundleFileFinal.getAbsolutePath()); 200 } 201 202 if(response.failed()) { 203 handler.handle(Future.failedFuture("Error adding run bundle to RO: " + response.cause().getMessage())); 204 } else { 205 206 // if more runs, process them 207 if(index + 1 < runs.size()) { 208 _addRunToRO(roURI, runs, index + 1, handler); 209 } else { 210 211 //int status = response.statusCode(); 212 //System.out.println("annotation status: " + status); 213 _stringResponse(handler, "location", roURI.toString()); 214 /* 215 response.bodyHandler(b -> { 216 System.out.println(b); 217 }); 218 */ 219 } 220 } 221 }); 222 223 } 224 }); 225 226 } 227 228 private void _addRunsToRO(URI roURI, JsonObject inputs, Handler<AsyncResult<JsonArray>> handler) { 229 JsonArray runs = inputs.getJsonArray("runs"); 230 _addRunToRO(roURI, runs, 0, handler); 231 } 232 233 private void _annotateRO(URI roURI, JsonObject inputs, Handler<AsyncResult<JsonArray>> handler) { 234 235 String title = inputs.getString("title"); 236 String description = inputs.getString("description"); 237 238 239 HttpClientRequest request = WebViewServer.vertx() 240 .createHttpClient() 241 .post(roURI.getHost(), roURI.getPath(), response -> { 242 //int status = response.statusCode(); 243 //System.out.println("annotation status: " + status); 244 _stringResponse(handler, "location", roURI.toString()); 245 /* 246 response.bodyHandler(b -> { 247 System.out.println(b); 248 }); 249 */ 250 }); 251 252 String body = "<?xml version=\"1.0\"?>\n" + 253 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:prov=\"http://www.w3.org/ns/prov#\" xmlns:dct=\"http://purl.org/dc/terms/\" xmlns:pav=\"http://purl.org/pav/\" xmlns:foaf=\"http://xmlns.com/foaf/0.1/\" xmlns:skos=\"http://www.w3.org/2004/02/skos/core#\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:odrs=\"http://schema.theodi.org/odrs#\" xmlns:roes=\"http://w3id.org/ro/earth-science#\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema#\" xmlns:roterms=\"http://purl.org/wf4ever/roterms#\">\n" + 254 " <rdf:Description rdf:about=\"" + roURI.toString() + "\">\n" + 255 " <dct:title>" + title + "</dct:title>\n" + 256 " <dct:description>" + description + "</dct:description>\n" + 257 " </rdf:Description>\n" + 258 "</rdf:RDF>\n"; 259 260 //System.out.println(body); 261 262 request.headers() 263 .set("Content-type", "application/rdf+xml") 264 .set("Content-length", String.valueOf(body.length())) 265 .set("Slug", "title.rdf") 266 .set("Authorization", "Bearer " + _authStr) 267 .set("Link", "<" + roURI.toString() + ">; rel=\"http://purl.org/ao/annotatesResource\""); 268 request.setFollowRedirects(true).write(body).end(); 269 270 //System.out.println(request.headers().get("Link")); 271 } 272 273 274 private String _authStr; 275 private URI _roHubURI; 276 private int _roHubPort; 277}