001    /*
002     * $Id: ServerAction.java 3197 2009-01-21 17:54:30Z 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     */
021    package org.jdesktop.swingx.action;
022    
023    import java.awt.event.ActionEvent;
024    import java.io.BufferedReader;
025    import java.io.ByteArrayOutputStream;
026    import java.io.IOException;
027    import java.io.InputStreamReader;
028    import java.io.PrintWriter;
029    import java.net.HttpURLConnection;
030    import java.net.MalformedURLException;
031    import java.net.URL;
032    import java.net.URLConnection;
033    import java.net.UnknownHostException;
034    import java.security.AccessControlException;
035    import java.util.HashMap;
036    import java.util.Iterator;
037    import java.util.Map;
038    import java.util.Set;
039    import java.util.logging.Level;
040    import java.util.logging.Logger;
041    
042    import javax.swing.AbstractAction;
043    import javax.swing.Action;
044    import 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     */
054    public 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        public void actionPerformed(ActionEvent evt) {
189            URL execURL = (URL)getValue(URL_CACHE);
190            if (execURL == null && !"".equals(getURL())) {
191                try {
192                    String url = getURL();
193                    if (url.startsWith("http")) {
194                        execURL = new URL(url);
195                    } else {
196                    }
197                    if (execURL == null) {
198                        // XXX TODO: send a message
199                        return;
200                    } else {
201                        // Cache this value.
202                        putValue(URL_CACHE, execURL);
203                    }
204    
205                } catch (MalformedURLException ex) {
206                    LOG.log(Level.WARNING, "something went wrong...", ex);
207                }
208            }
209    
210            try {
211                URLConnection uc = execURL.openConnection();
212    
213                // Get all the header name/value pairs ans set the request headers
214                Set<String> headerNames = getHeaderNames();
215                if (headerNames != null && !headerNames.isEmpty()) {
216                    Iterator<String> iter = headerNames.iterator();
217                    while (iter.hasNext()) {
218                        String name = (String)iter.next();
219                        uc.setRequestProperty(name, getHeaderValue(name));
220                    }
221                }
222                uc.setUseCaches(false);
223                uc.setDoOutput(true);
224    
225                ByteArrayOutputStream byteStream = new ByteArrayOutputStream(512);
226                PrintWriter out = new PrintWriter(byteStream, true);
227                out.print(getPostData());
228                out.flush();
229    
230                // POST requests must have a content-length.
231                String length = String.valueOf(byteStream.size());
232                uc.setRequestProperty("Content-length", length);
233    
234                // Write POST data to real output stream.
235                byteStream.writeTo(uc.getOutputStream());
236    
237                BufferedReader buf = null;
238                if (uc instanceof HttpURLConnection) {
239                    HttpURLConnection huc = (HttpURLConnection)uc;
240                    int code = huc.getResponseCode();
241                    String message = huc.getResponseMessage();
242    
243                    // Handle the result.
244                    if (code < 400) {
245                        // action succeeded send to status bar
246                        // XXX TODO: setStatusMessage(createMessage(code, message));
247                        // Format the response
248                        // TODO: This should load asychnonously
249                        buf = new BufferedReader(new InputStreamReader(uc.getInputStream()));
250    
251                    } else {
252                        // action has failed show dialog
253                        // XXX TODO: setStatusMessage(createMessage(code, message));
254                        buf = new BufferedReader(new InputStreamReader(huc.getErrorStream()));
255                    }
256                    String line;
257    
258                    StringBuffer buffer = new StringBuffer();
259                    while ((line = buf.readLine()) != null) {
260                // RG: Fix for J2SE 5.0; Can't cascade append() calls because
261                // return type in StringBuffer and AbstractStringBuilder are different
262                        buffer.append(line);
263                        buffer.append('\n');
264                    }
265                // JW: this used the Debug - maybe use finest level?    
266                LOG.finer("returned from connection\n" + buffer.toString());    
267                }
268            } catch (UnknownHostException ex) {
269                LOG.log(Level.WARNING, "UnknownHostException detected. Could it be a proxy issue?", ex);
270                
271            } catch (AccessControlException ex) {
272                LOG.log(Level.WARNING, "AccessControlException detected", ex);
273            } catch (IOException ex) {
274                LOG.log(Level.WARNING, "IOException detected", ex);
275            }
276        }
277    
278        /**
279         * Retrieves a string which represents the parameter data for a server action.
280         * @return a string of name value pairs prefixed by a '?' and delimited by an '&'
281         */
282        private String getPostData() {
283            // Write the data into local buffer
284            StringBuffer postData = new StringBuffer();
285    
286            // TODO: the action should be configured to retrieve the data.
287    
288            // Get all the param name/value pairs and build the data string
289            Set<String> paramNames = getParamNames();
290            if (paramNames != null && !paramNames.isEmpty()) {
291                Iterator<String> iter = paramNames.iterator();
292            try {
293                while (iter.hasNext()) {
294                    String name = iter.next();
295                    postData.append('&').append(name).append('=');
296                    postData.append(getParamValue(name));
297                }
298            }
299            catch (Exception ex) {  // RG: append(char) throws IOException in J2SE 5.0
300                /** @todo Log it */
301            }
302                // Replace the first & with a ?
303                postData.setCharAt(0, '?');
304            }
305            
306            LOG.finer("ServerAction: POST data: " + postData.toString());
307            return postData.toString();
308        }
309    
310    
311        /**
312         * Creates a human readable message from the server code and message result.
313         * @param code an http error code.
314         * @param msg server message
315         */
316        private String createMessage(int code, String msg) {
317            StringBuffer buffer = new StringBuffer("The action \"");
318            buffer.append(getValue(NAME));
319    
320            if (code < 400) {
321                buffer.append("\" has succeeded ");
322            } else {
323                buffer.append("\" has failed\nPlease check the Java console for more details.\n");
324            }
325            // RG: Fix for J2SE 5.0; Can't cascade append() calls because
326            // return type in StringBuffer and AbstractStringBuilder are different
327            buffer.append("\nServer response:\nCode: ");
328            buffer.append(code);
329            buffer.append(" Message: ");
330            buffer.append(msg);
331    
332            return buffer.toString();
333        }
334    }