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}