001package org.biopax.paxtools.client;
002
003/*
004 * #%L
005 * BioPAX Validator Client
006 * %%
007 * Copyright (C) 2008 - 2013 University of Toronto (baderlab.org) and Memorial Sloan-Kettering Cancer Center (cbio.mskcc.org)
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU Lesser General Public License as 
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Lesser Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Lesser Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
022 * #L%
023 */
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.apache.http.Header;
028import org.apache.http.HttpEntity;
029import org.apache.http.HttpResponse;
030import org.apache.http.client.ClientProtocolException;
031import org.apache.http.client.ResponseHandler;
032import org.apache.http.client.fluent.Executor;
033import org.apache.http.client.fluent.Request;
034import org.apache.http.entity.ContentType;
035import org.apache.http.entity.mime.MultipartEntityBuilder;
036import org.biopax.validator.jaxb.Behavior;
037import org.biopax.validator.jaxb.ValidatorResponse;
038
039import javax.xml.bind.JAXBContext;
040import javax.xml.bind.JAXBException;
041import javax.xml.bind.Unmarshaller;
042import javax.xml.transform.Source;
043import javax.xml.transform.stream.StreamSource;
044
045import java.io.BufferedReader;
046import java.io.File;
047import java.io.IOException;
048import java.io.OutputStream;
049import java.io.PrintWriter;
050import java.io.StringReader;
051import java.nio.charset.Charset;
052
053/**
054 * Simple (example) BioPAX Validator client 
055 * to upload and check BioPAX OWL files.
056 * 
057 * @author rodche
058 *
059 */
060public class BiopaxValidatorClient {
061        private static final Log log = LogFactory.getLog(BiopaxValidatorClient.class);
062        
063        /**
064         * Default BioPAX Validator's URL
065         */
066        public static final String 
067                DEFAULT_VALIDATOR_URL = "http://www.biopax.org/validator/check.html";
068        
069        /**
070         * The Java Option to set a BioPAX Validator URL
071         * (if set, overrides the default and URL provided by the Constructor arg.)
072         */
073        public static final String JVM_PROPERTY_URL = "biopax.validator.url";
074        
075        public static enum RetFormat {
076                HTML,// errors as HTML/Javascript 
077                XML, // errors as XML
078                OWL; // modified BioPAX only (when 'autofix' or 'normalize' is true)
079        }
080        
081        private String url;
082
083        
084    /**
085     * Main Constructor
086     * 
087     * It configures for the validator's URL
088     * (defined by DEFAULT_VALIDATOR_URL constant)
089     * and result format ().
090     * 
091     * @param url - validator's file-upload form address
092     */
093    public BiopaxValidatorClient(String url) {
094//              if(url == null || url.isEmpty())
095//                      this.url = System.getProperty(JVM_PROPERTY_URL, DEFAULT_VALIDATOR_URL);
096//              else 
097//                      this.url = url;
098//              
099                // 1) use the arg (if not empty/null) or the default URL
100        this.url = (url == null || url.isEmpty())
101                        ? DEFAULT_VALIDATOR_URL : url;
102        
103        // 2) override if the JVM option is set to another value
104        this.url = System.getProperty(JVM_PROPERTY_URL, this.url);
105                
106        // 3) get actual location (force through redirects, if any)     
107                try {
108                        this.url = location(this.url);
109//                      System.out.println("Location: " + this.url);
110                } catch (IOException e) {
111                        log.warn("Failed to resolve to actual web service " +
112                                "URL using: " + url + " (if there is a 301/302/307 HTTP redirect, " +
113                                        "then validation requests (using HTTP POST method) will probably fail...)", e);
114                }
115        }
116    
117    
118    /**
119     * Default Constructor
120     * 
121     * It configures for the default validator URL.
122     */
123    public BiopaxValidatorClient() {
124        this(null);
125        }
126       
127
128    /**
129     * Checks a BioPAX OWL file(s) or resource 
130     * using the online BioPAX Validator 
131     * and prints the results to the output stream.
132     * 
133     * @param autofix true/false (experimental)
134     * @param profile validation profile name
135     * @param retFormat xml, html, or owl (no errors, just modified owl, if autofix=true)
136         * @param filterBy filter validation issues by the error/warning level
137         * @param maxErrs errors threshold - max no. critical errors to collect before quitting
138         *                (warnings not counted; null/0/negative value means unlimited)
139     * @param biopaxUrl check the BioPAX at the URL
140     * @param biopaxFiles an array of BioPAX files to validate
141     * @param out validation report data output stream
142     * @throws IOException when there is an I/O error
143     */
144    public void validate(boolean autofix, String profile, RetFormat retFormat, Behavior filterBy,
145                Integer maxErrs, String biopaxUrl, File[] biopaxFiles, OutputStream out) throws IOException 
146    {
147        MultipartEntityBuilder meb = MultipartEntityBuilder.create();           
148        meb.setCharset(Charset.forName("UTF-8"));
149        
150        if(autofix)
151                meb.addTextBody("autofix", "true");
152//TODO add extra options (normalizer.fixDisplayName, normalizer.inferPropertyOrganism, normalizer.inferPropertyDataSource, normalizer.xmlBase)?
153        if(profile != null && !profile.isEmpty())
154                meb.addTextBody("profile", profile);
155        if(retFormat != null)
156                meb.addTextBody("retDesired", retFormat.toString().toLowerCase());
157        if(filterBy != null)
158                meb.addTextBody("filter", filterBy.toString());
159        if(maxErrs != null && maxErrs > 0)
160                meb.addTextBody("maxErrors", maxErrs.toString());
161        if(biopaxFiles != null && biopaxFiles.length > 0)
162                for (File f : biopaxFiles) //important: use MULTIPART_FORM_DATA content-type
163                        meb.addBinaryBody("file", f, ContentType.MULTIPART_FORM_DATA, f.getName());
164        else if(biopaxUrl != null) {
165                meb.addTextBody("url", biopaxUrl);
166        } else {
167                log.error("Nothing to do (no BioPAX data specified)!");
168                return;
169        }
170        
171        //execute the query and get results as string
172        HttpEntity httpEntity = meb.build();
173//      httpEntity.writeTo(System.err);
174        String content = Executor.newInstance()//Executor.newInstance(httpClient)
175                        .execute(Request.Post(url).body(httpEntity))
176                                .returnContent().asString();    
177
178        //save: append to the output stream (file)
179                BufferedReader res = new BufferedReader(new StringReader(content));
180                String line;
181                PrintWriter writer = new PrintWriter(out);
182                while((line = res.readLine()) != null) {
183                        writer.println(line);
184                }
185                writer.flush();
186                res.close();
187    }
188    
189    public void setUrl(String url) {
190                this.url = url;
191        }
192    
193    public String getUrl() {
194                return url;
195        }
196    
197    /**
198     * Converts a biopax-validator XML response to the java object.
199     *
200     * @param xml input XML data - validation report - to import
201     * @return validation report object
202     * @throws JAXBException when there is an JAXB unmarshalling error
203     */
204    public static ValidatorResponse unmarshal(String xml) throws JAXBException {
205        JAXBContext jaxbContext = JAXBContext.newInstance("org.biopax.validator.jaxb");
206                Unmarshaller un = jaxbContext.createUnmarshaller();
207                Source src = new StreamSource(new StringReader(xml));
208                ValidatorResponse resp = un.unmarshal(src, ValidatorResponse.class).getValue();
209                return resp;
210    }
211 
212    
213    private String location(final String url) throws IOException {
214        String location = url; //initially the same
215        // discover actual location, avoid going in circles:
216        int i=0;
217        for(String loc = url; loc != null && i<5; i++ ) 
218        {       
219                //do POST for location (Location header present if there's a 301/302/307 redirect on the way)
220                loc = Request.Post(loc).execute()
221                                .handleResponse(new ResponseHandler<String>() {
222                                                @Override
223                                                public String handleResponse(HttpResponse httpResponse)
224                                                                throws ClientProtocolException, IOException {
225                                                        Header header = httpResponse.getLastHeader("Location");
226//                                                      System.out.println("header=" + header);
227                                                        return (header != null) ? header.getValue().trim() : null;
228                                                }
229                        });
230                 
231                if(loc != null) {
232                        location = loc;
233                        log.info("BioPAX Validator location: " + loc);
234                }
235        }
236        
237        return location;
238    }
239    
240}