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}