001/*
002 * $Id: Submitter.java,v 1.8 2011/03/24 16:06:34 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030package edu.wisc.ssec.mcidasv.supportform;
031
032import java.io.ByteArrayInputStream;
033import java.io.File;
034import java.io.InputStream;
035import java.util.ArrayList;
036import java.util.List;
037
038import org.apache.commons.httpclient.Header;
039import org.apache.commons.httpclient.HttpClient;
040import org.apache.commons.httpclient.methods.PostMethod;
041import org.apache.commons.httpclient.methods.multipart.FilePart;
042import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
043import org.apache.commons.httpclient.methods.multipart.Part;
044import org.apache.commons.httpclient.methods.multipart.PartSource;
045import org.apache.commons.httpclient.methods.multipart.StringPart;
046
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050import ucar.unidata.util.IOUtil;
051import ucar.unidata.util.WrapperException;
052
053import edu.wisc.ssec.mcidasv.util.BackgroundTask;
054
055public class Submitter extends BackgroundTask<String> {
056
057    private static final Logger logger = LoggerFactory.getLogger(Submitter.class);
058
059    /** We'll follow up to this many redirects for {@code requestUrl}. */
060    private static final int POST_ATTEMPTS = 5;
061
062    /** Used to gather user input and system information. */
063    private final SupportForm form;
064
065    /** URL that we'll attempt to {@code POST} our requests at.*/
066    private final String requestUrl = "https://www.ssec.wisc.edu/mcidas/misc/mc-v/supportreq/support.php";
067
068    /** Keeps track of the most recent redirect for {@code requestUrl}. */
069    private String validFormUrl = requestUrl;
070
071    /** Number of redirects we've tried since starting. */
072    private int tryCount = 0;
073
074    /** Handy reference to the status code (and more) of our {@code POST}. */
075    private PostMethod method = null;
076
077    public Submitter(final SupportForm form) {
078        this.form = form;
079    }
080
081    /** 
082     * Creates a file attachment that's based upon a real file.
083     * 
084     * @param id The parameter ID. Usually something like 
085     * {@literal "form_data[att_two]"}.
086     * @param file Path to the file that's going to be attached.
087     * 
088     * @return {@code POST}-able file attachment using the name and contents of
089     * {@code file}.
090     */
091    private static FilePart buildRealFilePart(final String id, final String file) {
092        return new FilePart(id, new PartSource() {
093            public InputStream createInputStream() {
094                try {
095                    return IOUtil.getInputStream(file);
096                } catch (Exception e) {
097                    throw new WrapperException("Reading file: "+file, e);
098                }
099            }
100            public String getFileName() {
101                return new File(file).getName();
102            }
103            public long getLength() {
104                return new File(file).length();
105            }
106        });
107    }
108
109    /**
110     * Creates a file attachment that isn't based upon an actual file. Useful 
111     * for something like the {@literal "extra"} attachment where you collect
112     * a bunch of data but don't want to deal with creating a temporary file.
113     * 
114     * @param id Parameter ID. Typically something like 
115     * {@literal "form_data[att_extra]"}.
116     * @param file Fake name of the file. Can be whatever you like.
117     * @param data The actual data to place inside the attachment.
118     * 
119     * @return {@code POST}-able file attachment using a spoofed filename!
120     */
121    private static FilePart buildFakeFilePart(final String id, final String file, final byte[] data) {
122        return new FilePart(id, new PartSource() {
123            public InputStream createInputStream() {
124                return new ByteArrayInputStream(data);
125            }
126            public String getFileName() {
127                return file;
128            }
129            public long getLength() {
130                return data.length;
131            }
132        });
133    }
134
135    /**
136     * Attempts to {@code POST} to {@code url} using the information from 
137     * {@code form}.
138     * 
139     * @param url URL that'll accept the {@code POST}. Typically 
140     * {@link #requestUrl}.
141     * @param form The {@link SupportForm} that contains the data to use in the
142     * support request.
143     * 
144     * @return Big honkin' object that contains the support request.
145     */
146    private static PostMethod buildPostMethod(String url, SupportForm form) {
147        PostMethod method = new PostMethod(url);
148
149        List<Part> parts = new ArrayList<Part>();
150        parts.add(new StringPart("form_data[fromName]", form.getUser()));
151        parts.add(new StringPart("form_data[email]", form.getEmail()));
152        parts.add(new StringPart("form_data[organization]", form.getOrganization()));
153        parts.add(new StringPart("form_data[subject]", form.getSubject()));
154        parts.add(new StringPart("form_data[description]", form.getDescription()));
155        parts.add(new StringPart("form_data[submit]", ""));
156        parts.add(new StringPart("form_data[p_version]", "p_version=ignored"));
157        parts.add(new StringPart("form_data[opsys]", "opsys=ignored"));
158        parts.add(new StringPart("form_data[hardware]", "hardware=ignored"));
159        parts.add(new StringPart("form_data[cc_user]", Boolean.toString(form.getSendCopy())));
160
161        // attach the files the user has explicitly attached.
162        if (form.hasAttachmentOne()) {
163            parts.add(buildRealFilePart("form_data[att_two]", form.getAttachmentOne()));
164        }
165        if (form.hasAttachmentTwo()) {
166            parts.add(buildRealFilePart("form_data[att_three]", form.getAttachmentTwo()));
167        }
168        // if the user wants, attach an XML bundle of the state
169        if (form.canBundleState() && form.getSendBundle()) {
170            parts.add(buildFakeFilePart("form_data[att_state]", form.getBundledStateName(), form.getBundledState()));
171        }
172
173        // attach system properties
174        parts.add(buildFakeFilePart("form_data[att_extra]", form.getExtraStateName(), form.getExtraState()));
175
176        if (form.canSendLog()) {
177            parts.add(buildRealFilePart("form_data[att_log]", form.getLogPath()));
178        }
179
180        Part[] arr = parts.toArray(new Part[0]);
181        MultipartRequestEntity mpr = new MultipartRequestEntity(arr, method.getParams());
182        method.setRequestEntity(mpr);
183        return method;
184    }
185
186    protected String compute() {
187        // logic ripped from the IDV's HttpFormEntry#doPost(List, String)
188        try {
189            while ((tryCount++ < POST_ATTEMPTS) && !isCancelled()) {
190                method = buildPostMethod(validFormUrl, form);
191                HttpClient client = new HttpClient();
192                client.executeMethod(method);
193                if (method.getStatusCode() >= 300 && method.getStatusCode() <= 399) {
194                    Header location = method.getResponseHeader("location");
195                    if (location == null) {
196                        return "Error: No 'location' given on the redirect";
197                    }
198                    validFormUrl = location.getValue();
199                    if (method.getStatusCode() == 301) {
200                        logger.warn("form post has been permanently moved to: {}", validFormUrl);
201                    }
202                    continue;
203                }
204                break;
205            }
206            return IOUtil.readContents(method.getResponseBodyAsStream());
207        } catch (Exception e) {
208            throw new WrapperException("doing post", e);
209        }
210    }
211
212    @Override protected void onCompletion(String result, Throwable exception, boolean cancelled) {
213        logger.trace("result={} exception={} cancelled={}", new Object[] { result, exception, cancelled });
214        if (cancelled) {
215            return;
216        }
217
218        if (exception == null) {
219            form.showSuccess();
220        } else {
221            form.showFailure(exception.getMessage());
222        }
223    }
224
225}