001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.deployment.plugin.remote;
021    
022    import java.io.BufferedInputStream;
023    import java.io.BufferedOutputStream;
024    import java.io.DataInputStream;
025    import java.io.DataOutputStream;
026    import java.io.File;
027    import java.io.FileInputStream;
028    import java.io.FileNotFoundException;
029    import java.io.IOException;
030    import java.net.URL;
031    import java.net.URLConnection;
032    import java.util.Iterator;
033    import java.util.LinkedList;
034    import java.util.List;
035    import java.util.Set;
036    
037    import org.apache.commons.logging.Log;
038    import org.apache.commons.logging.LogFactory;
039    import org.apache.geronimo.gbean.AbstractName;
040    import org.apache.geronimo.gbean.AbstractNameQuery;
041    import org.apache.geronimo.kernel.Kernel;
042    import org.apache.geronimo.crypto.encoders.Base64;
043    
044    /**
045     *
046     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
047     */
048    public class FileUploadServletClient implements FileUploadClient {
049        private static final Log log = LogFactory.getLog(FileUploadServletClient.class);
050    
051        /** Note:  The below versions should be kept in sync with those in FileUploadServlet.java **/
052        // Starting RemoteDeploy datastream versions
053        public static final int REMOTE_DEPLOY_REQUEST_VER_0 = 0;
054        public static final int REMOTE_DEPLOY_RESPONSE_VER_0 = 0;
055        // Current RemoteDeploy datastream versions
056        public static final int REMOTE_DEPLOY_REQUEST_VER = 0;
057        public static final int REMOTE_DEPLOY_RESPONSE_VER = 0;
058    
059        public URL getRemoteDeployUploadURL(Kernel kernel) {
060            AbstractName deployerName = getDeployerName(kernel);
061            String remoteDeployUpload;
062            try {
063                remoteDeployUpload = (String) kernel.getAttribute(deployerName, "remoteDeployUploadURL");
064                return new URL(remoteDeployUpload);
065            } catch (Exception e) {
066                throw new AssertionError(e);
067            }
068         }
069    
070        protected AbstractName getDeployerName(Kernel kernel) {
071            Set<AbstractName> deployerNames = kernel.listGBeans(new AbstractNameQuery("org.apache.geronimo.deployment.Deployer"));
072            if (1 != deployerNames.size()) {
073                throw new IllegalStateException("No Deployer GBean present in running Geronimo server. " +
074                     "This usually indicates a serious problem with the configuration of " +
075                     "your running Geronimo server.  If " +
076                     "the deployer is present but not started, the workaround is to run " +
077                     "a deploy command like 'start geronimo/geronimo-gbean-deployer/1.0/car'.  " +
078                     "If the deployer service is not present at all (it was undeployed) then " +
079                     "you need to either re-install Geronimo or get a deployment plan for the " +
080                     "runtime deployer and distribute it while the server is not running and " +
081                     "then start the server with a command like the above.  For help on this, " +
082                     "write to user@geronimo.apache.org and include the contents of your " +
083                     "var/config/config.xml file.");
084            }
085            return deployerNames.iterator().next();
086        }
087    
088        public void uploadFilesToServer(URL uploadURL,
089                String username,
090                String password,
091                File[] files,
092                FileUploadProgress progress) {
093            if(files == null) {
094                return;
095            }
096            
097            List valid = new LinkedList();
098            for(int i=0; i<files.length; i++) {
099                if(files[i] == null) {
100                    continue;
101                }
102                File file = files[i];
103                if(!file.exists() || !file.canRead()) {
104                    continue;
105                }
106                valid.add(new Integer(i));
107            }
108            
109            if(valid.size() > 0) {
110                progress.updateStatus("Uploading "+valid.size()+" file(s) to server");
111                if (log.isDebugEnabled()) {
112                    log.debug("Uploading "+valid.size()+" file(s) to server");
113                }
114                try {
115                    URLConnection con = connectToServer(uploadURL, username, password);
116                    writeRequest(con, files, valid, progress);
117                    readResponse(con, files, valid, progress);
118                } catch (Exception e) {
119                    progress.fail(e);
120                }
121            }
122        }
123    
124        protected void readResponse(URLConnection con, File[] files, List valid, FileUploadProgress progress)
125                throws IOException {
126            /* ---------------------
127             * RemoteDeploy Response
128             * ---------------------
129             *
130             * Note:  The below code has to match FileUploadServlet.java
131             *
132             * RemoteDeployer response stream format:
133             *   It returns a serialized stream containing:
134             *   0) an int, the version of this datastream format - REMOTE_DEPLOY_RESPONSE_VER
135             *   1) a UTF string, the status (should be "OK")
136             *   2) an int, the number of files received
137             *   3) for each file:
138             *     3.1) a UTF String, the path to the file as saved to the server's filesystem
139             *   x) new data would be added here
140             *
141             *   The file positions in the response will be the same as in the request.
142             *   That is, a name for upload file #2 will be in response position #2.
143             */
144            DataInputStream in = new DataInputStream(new BufferedInputStream(con.getInputStream()));
145            // 0) an int, the version of this datastream format - REMOTE_DEPLOY_RESPONSE_VER
146            int rspVer = in.readInt();
147            // whenever we update the stream version, the next line needs to
148            // be changed to just - (rspVer >= REMOTE_DEPLOY_RESPONSE_VER_0)
149            // but until then, be more restrictive so we can handle old servers
150            // that don't send a version as the first thing, but UTF instead...
151            if ((rspVer >= REMOTE_DEPLOY_RESPONSE_VER_0) && (rspVer <= REMOTE_DEPLOY_RESPONSE_VER)) {
152                // 1) a UTF string, the status (should be "OK")
153                String status = in.readUTF();
154                if(!status.equals("OK")) {
155                    progress.fail("Unable to upload files to server.  Server returned status="+status);
156                    log.error("Unable to upload files to server.  Server returned status="+status);
157                    return;
158                }
159                progress.updateStatus("File upload complete (Server status="+status+")");
160                if (log.isDebugEnabled()) {
161                    log.debug("File upload complete (Server status="+status+")");
162                }
163                // 2) an int, the number of files received
164                int count = in.readInt();
165                if(count != valid.size()) {
166                    progress.fail("Server only received "+count+" of "+valid.size()+" files");
167                    log.warn("Server only received "+count+" of "+valid.size()+" files");
168                }
169                // 3) for each file:
170                for (Iterator it = valid.iterator(); it.hasNext();) {
171                    Integer index = (Integer) it.next();
172                    // 3.1) a UTF String, the path to the file as saved to the server's filesystem
173                    String serverFileName = in.readUTF();
174                    if (serverFileName != null) {
175                        files[index.intValue()] = new File(serverFileName);
176                    } else {
177                        log.error("Received an invalid filename from the server");
178                        files[index.intValue()] = null;
179                    }
180                    if (log.isDebugEnabled()) {
181                        log.debug("Server created file="+serverFileName);
182                    }
183                }
184                // x) new data would be added here
185                if (rspVer > REMOTE_DEPLOY_RESPONSE_VER_0) {
186                    // additions in later datastream versions would be handled here
187    
188                    if (rspVer > REMOTE_DEPLOY_RESPONSE_VER) {
189                        // if the server is sending a newer version than we know about
190                        // just ignore it and warn the user about the mismatch
191                        log.warn("Received a newer server response ("+rspVer+") than expected ("+REMOTE_DEPLOY_RESPONSE_VER+").  Ignoring any additional server response data.");
192                    }
193                }
194            } else {
195                // should never happen, but handle it anyway
196                progress.fail("Received unknown server response version="+rspVer);
197                log.warn("Received unknown server response version="+rspVer);
198            }
199            in.close();
200            progress.updateStatus("File(s) transferred to server.  Resuming deployment operation.");
201        }
202    
203        protected void writeRequest(URLConnection con, File[] files, List valid, FileUploadProgress progress)
204                throws IOException, FileNotFoundException {
205            /* --------------------
206             * RemoteDeploy Request
207             * --------------------
208             *
209             * Note:  The below code has to match FileUploadServlet.java
210             *
211             * RemoteDeployer data stream format:
212             *   0) an int, the version of this datastream format - REMOTE_DEPLOY_REQUEST_VER
213             *   1) an int, the number of files being uploaded
214             *   2) for each file:
215             *     2.0) a UTF String, the filename of the file being uploaded
216             *     2.1) a long, the length of the file in bytes
217             *     2.2) byte[], byte count equal to the number above for the file
218             */
219            DataOutputStream out = new DataOutputStream(new BufferedOutputStream(con.getOutputStream()));
220            // 0) an int, the version of this datastream format - REMOTE_DEPLOY_REQUEST_VER
221            out.writeInt(REMOTE_DEPLOY_REQUEST_VER);
222            // 1) an int, the number of files being uploaded
223            out.writeInt(valid.size());
224            byte[] buf = new byte[1024];
225            int size;
226            long total, length, threshold, next;
227            // 2) for each file:
228            for (Iterator it = valid.iterator(); it.hasNext();) {
229                Integer index = (Integer) it.next();
230                File file = files[index.intValue()];
231                // 2.0) a UTF String, the filename of the file being uploaded
232                out.writeUTF(file.getName().trim());
233                // 2.1) a long, the length of the file in bytes
234                out.writeLong(length = file.length());
235                threshold = Math.max(length / 100, (long)10240);
236                next = threshold;
237                BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
238                if (log.isDebugEnabled()) {
239                    log.debug("Uploading "+file.getName());
240                }
241                total = 0;
242                // 2.2) raw bytes, equal to the number above for the file
243                while((size = in.read(buf)) > -1) {
244                    out.write(buf, 0, size);
245                    total += size;
246                    if(total > next) {
247                        progress.updateStatus("Uploading "+file.getName()+": "+(total/1024)+" KB");
248                        while(total > next) next += threshold;
249                    }
250                }
251                in.close();
252            }
253            out.flush();
254            out.close();
255        }
256    
257        protected URLConnection connectToServer(URL url, String username, String password) throws IOException {
258            URLConnection con = url.openConnection();
259            String auth = username + ":" + password;
260            byte[] data = auth.getBytes();
261            String s = new String(Base64.encode(data));
262            while(s.length() % 4 != 0) s += "=";
263            con.setRequestProperty("Authorization", "Basic "+s);
264            con.setDoInput(true);
265            con.setDoOutput(true);
266            con.connect();
267            return con;
268        }
269    
270    }