001/*
002 * $Id: ServerAction.java 3972 2011-03-17 20:31:58Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.action;
022
023import java.awt.event.ActionEvent;
024import java.io.BufferedReader;
025import java.io.ByteArrayOutputStream;
026import java.io.IOException;
027import java.io.InputStreamReader;
028import java.io.PrintWriter;
029import java.net.HttpURLConnection;
030import java.net.MalformedURLException;
031import java.net.URL;
032import java.net.URLConnection;
033import java.net.UnknownHostException;
034import java.security.AccessControlException;
035import java.util.HashMap;
036import java.util.Iterator;
037import java.util.Map;
038import java.util.Set;
039import java.util.logging.Level;
040import java.util.logging.Logger;
041
042import javax.swing.AbstractAction;
043import javax.swing.Action;
044import javax.swing.Icon;
045
046//import org.jdesktop.swing.Application;
047
048
049/**
050 * An action which will invoke an http POST operation.
051 *
052 * @author Mark Davidson
053 */
054public class ServerAction extends AbstractAction {
055    // Server action support
056    private static final Logger LOG = Logger.getLogger(ServerAction.class
057            .getName());
058    private static final String PARAMS = "action-params";
059    private static final String HEADERS = "action-headers";
060    private static final String URL = "action-url";
061
062    private static final String URL_CACHE = "_URL-CACHE__";
063
064    public ServerAction() {
065        this("action");
066    }
067
068    public ServerAction(String name) {
069        super(name);
070    }
071
072    /**
073     * @param name display name of the action
074     * @param command the value of the action command key
075     */
076    public ServerAction(String name, String command) {
077        this(name, command, null);
078    }
079
080    public ServerAction(String name, Icon icon) {
081        super(name, icon);
082    }
083
084    /**
085     * @param name display name of the action
086     * @param command the value of the action command key
087     * @param icon icon to display
088     */
089    public ServerAction(String name, String command, Icon icon) {
090        super(name, icon);
091        putValue(Action.ACTION_COMMAND_KEY, command);
092    }
093
094    /**
095     * Set the url for the action.
096     * <p>
097     * @param url a string representation of the url
098     */
099    public void setURL(String url) {
100        putValue(URL, url);
101        putValue(URL_CACHE, null);
102    }
103
104    public String getURL() {
105        return (String)getValue(URL);
106    }
107
108    @SuppressWarnings("unchecked")
109    private Map<String, String> getParams() {
110        return (Map)getValue(PARAMS);
111    }
112
113    private void setParams(Map<String, String> params) {
114        putValue(PARAMS, params);
115    }
116
117    /**
118     * Adds a name value pair which represents a url parameter in an http
119     * POST request.
120     */
121    public void addParam(String name, String value) {
122        Map<String, String> params = getParams();
123        if (params == null) {
124            params = new HashMap<String, String>();
125            setParams(params);
126        }
127        params.put(name, value);
128    }
129
130    /**
131     * Return a parameter value corresponding to name or null if it doesn't exist.
132     */
133    public String getParamValue(String name) {
134        Map<String, String> params = getParams();
135        return params == null ? null : params.get(name);
136    }
137
138    /**
139     * Return a set of parameter names or null if there are no params
140     */
141    public Set<String> getParamNames() {
142        Map<String, String> params = getParams();
143        return params == null ? null : params.keySet();
144    }
145
146    @SuppressWarnings("unchecked")
147    private Map<String, String> getHeaders() {
148        return (Map)getValue(HEADERS);
149    }
150
151    private void setHeaders(Map<String, String> headers) {
152        putValue(HEADERS, headers);
153    }
154
155    /**
156     * Adds a name value pair which represents a url connection request property.
157     * For example, name could be "Content-Type" and the value could be
158     * "application/x-www-form-urlencoded"
159     */
160    public void addHeader(String name, String value) {
161        Map<String, String> map = getHeaders();
162        if (map == null) {
163            map = new HashMap<String, String>();
164            setHeaders(map);
165        }
166        map.put(name, value);
167    }
168
169    /**
170     * Return a header value corresponding to name or null if it doesn't exist.
171     */
172    public String getHeaderValue(String name) {
173        Map<String, String> headers = getHeaders();
174        return headers == null ? null : headers.get(name);
175    }
176
177    /**
178     * Return a set of parameter names or null if there are no params
179     */
180    public Set<String> getHeaderNames() {
181        Map<String, String> headers = getHeaders();
182        return headers == null ? null : headers.keySet();
183    }
184
185    /**
186     * Invokes the server operation when the action has been invoked.
187     */
188    @Override
189    public void actionPerformed(ActionEvent evt) {
190        URL execURL = (URL)getValue(URL_CACHE);
191        if (execURL == null && !"".equals(getURL())) {
192            try {
193                String url = getURL();
194                if (url.startsWith("http")) {
195                    execURL = new URL(url);
196                } else {
197                }
198                if (execURL == null) {
199                    // XXX TODO: send a message
200                    return;
201                } else {
202                    // Cache this value.
203                    putValue(URL_CACHE, execURL);
204                }
205
206            } catch (MalformedURLException ex) {
207                LOG.log(Level.WARNING, "something went wrong...", ex);
208            }
209        }
210
211        try {
212            URLConnection uc = execURL.openConnection();
213
214            // Get all the header name/value pairs ans set the request headers
215            Set<String> headerNames = getHeaderNames();
216            if (headerNames != null && !headerNames.isEmpty()) {
217                Iterator<String> iter = headerNames.iterator();
218                while (iter.hasNext()) {
219                    String name = (String)iter.next();
220                    uc.setRequestProperty(name, getHeaderValue(name));
221                }
222            }
223            uc.setUseCaches(false);
224            uc.setDoOutput(true);
225
226            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(512);
227            PrintWriter out = new PrintWriter(byteStream, true);
228            out.print(getPostData());
229            out.flush();
230
231            // POST requests must have a content-length.
232            String length = String.valueOf(byteStream.size());
233            uc.setRequestProperty("Content-length", length);
234
235            // Write POST data to real output stream.
236            byteStream.writeTo(uc.getOutputStream());
237
238            BufferedReader buf = null;
239            if (uc instanceof HttpURLConnection) {
240                HttpURLConnection huc = (HttpURLConnection)uc;
241                int code = huc.getResponseCode();
242                String message = huc.getResponseMessage();
243
244                // Handle the result.
245                if (code < 400) {
246                    // action succeeded send to status bar
247                    // XXX TODO: setStatusMessage(createMessage(code, message));
248                    // Format the response
249                    // TODO: This should load asychnonously
250                    buf = new BufferedReader(new InputStreamReader(uc.getInputStream()));
251
252                } else {
253                    // action has failed show dialog
254                    // XXX TODO: setStatusMessage(createMessage(code, message));
255                    buf = new BufferedReader(new InputStreamReader(huc.getErrorStream()));
256                }
257                String line;
258
259                StringBuffer buffer = new StringBuffer();
260                while ((line = buf.readLine()) != null) {
261            // RG: Fix for J2SE 5.0; Can't cascade append() calls because
262            // return type in StringBuffer and AbstractStringBuilder are different
263                    buffer.append(line);
264                    buffer.append('\n');
265                }
266            // JW: this used the Debug - maybe use finest level?    
267            LOG.finer("returned from connection\n" + buffer.toString());    
268            }
269        } catch (UnknownHostException ex) {
270            LOG.log(Level.WARNING, "UnknownHostException detected. Could it be a proxy issue?", ex);
271            
272        } catch (AccessControlException ex) {
273            LOG.log(Level.WARNING, "AccessControlException detected", ex);
274        } catch (IOException ex) {
275            LOG.log(Level.WARNING, "IOException detected", ex);
276        }
277    }
278
279    /**
280     * Retrieves a string which represents the parameter data for a server action.
281     * @return a string of name value pairs prefixed by a '?' and delimited by an '&'
282     */
283    private String getPostData() {
284        // Write the data into local buffer
285        StringBuffer postData = new StringBuffer();
286
287        // TODO: the action should be configured to retrieve the data.
288
289        // Get all the param name/value pairs and build the data string
290        Set<String> paramNames = getParamNames();
291        if (paramNames != null && !paramNames.isEmpty()) {
292            Iterator<String> iter = paramNames.iterator();
293        try {
294            while (iter.hasNext()) {
295                String name = iter.next();
296                postData.append('&').append(name).append('=');
297                postData.append(getParamValue(name));
298            }
299        }
300        catch (Exception ex) {  // RG: append(char) throws IOException in J2SE 5.0
301            /** @todo Log it */
302        }
303            // Replace the first & with a ?
304            postData.setCharAt(0, '?');
305        }
306        
307        LOG.finer("ServerAction: POST data: " + postData.toString());
308        return postData.toString();
309    }
310
311
312    /**
313     * Creates a human readable message from the server code and message result.
314     * @param code an http error code.
315     * @param msg server message
316     */
317    private String createMessage(int code, String msg) {
318        StringBuffer buffer = new StringBuffer("The action \"");
319        buffer.append(getValue(NAME));
320
321        if (code < 400) {
322            buffer.append("\" has succeeded ");
323        } else {
324            buffer.append("\" has failed\nPlease check the Java console for more details.\n");
325        }
326        // RG: Fix for J2SE 5.0; Can't cascade append() calls because
327        // return type in StringBuffer and AbstractStringBuilder are different
328        buffer.append("\nServer response:\nCode: ");
329        buffer.append(code);
330        buffer.append(" Message: ");
331        buffer.append(msg);
332
333        return buffer.toString();
334    }
335}