001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.console.configmanager;
019    
020    import java.io.ByteArrayInputStream;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.IOException;
024    import java.io.StringWriter;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.ArrayList;
028    import javax.enterprise.deploy.shared.factories.DeploymentFactoryManager;
029    import javax.enterprise.deploy.spi.DeploymentManager;
030    import javax.enterprise.deploy.spi.Target;
031    import javax.enterprise.deploy.spi.TargetModuleID;
032    import javax.enterprise.deploy.spi.status.ProgressObject;
033    import javax.portlet.ActionRequest;
034    import javax.portlet.ActionResponse;
035    import javax.portlet.PortletConfig;
036    import javax.portlet.PortletException;
037    import javax.portlet.PortletRequestDispatcher;
038    import javax.portlet.RenderRequest;
039    import javax.portlet.RenderResponse;
040    import javax.xml.parsers.DocumentBuilder;
041    
042    import org.apache.commons.fileupload.FileItem;
043    import org.apache.commons.fileupload.FileUploadException;
044    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
045    import org.apache.commons.fileupload.portlet.PortletFileUpload;
046    import org.apache.commons.logging.Log;
047    import org.apache.commons.logging.LogFactory;
048    import org.apache.geronimo.console.BasePortlet;
049    import org.apache.geronimo.deployment.plugin.jmx.JMXDeploymentManager;
050    import org.apache.geronimo.deployment.plugin.ConfigIDExtractor;
051    import org.apache.geronimo.common.DeploymentException;
052    import org.apache.geronimo.kernel.repository.Artifact;
053    import org.apache.geronimo.kernel.util.XmlUtil;
054    import org.apache.geronimo.upgrade.Upgrade1_0To1_1;
055    import org.w3c.dom.Document;
056    
057    /**
058     * $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
059     */
060    public class DeploymentPortlet extends BasePortlet {
061        private final static Log log = LogFactory.getLog(DeploymentPortlet.class);
062        
063        private static final String DEPLOY_VIEW          = "/WEB-INF/view/configmanager/deploy.jsp";
064        private static final String HELP_VIEW            = "/WEB-INF/view/configmanager/deployHelp.jsp";
065        private static final String MIGRATED_PLAN_PARM   = "migratedPlan";
066        private static final String ORIGINAL_PLAN_PARM   = "originalPlan";
067        private static final String FULL_STATUS_PARM     = "fullStatusMessage";
068        private static final String ABBR_STATUS_PARM     = "abbrStatusMessage";
069        private PortletRequestDispatcher deployView;
070        private PortletRequestDispatcher helpView;
071    
072        public void processAction(ActionRequest actionRequest,
073                                  ActionResponse actionResponse) throws PortletException, IOException {
074            if (!PortletFileUpload.isMultipartContent(actionRequest)) {
075                throw new PortletException("Expected file upload");
076            }
077    
078            File rootDir = new File(System.getProperty("java.io.tmpdir"));
079            PortletFileUpload uploader = new PortletFileUpload(new DiskFileItemFactory(10240, rootDir));
080            File moduleFile = null;
081            File planFile = null;
082            String startApp = null;
083            String redeploy = null;
084            try {
085                List items = uploader.parseRequest(actionRequest);
086                for (Iterator i = items.iterator(); i.hasNext();) {
087                    FileItem item = (FileItem) i.next();
088                    if (!item.isFormField()) {
089                        String fieldName = item.getFieldName();
090                        String name = item.getName().trim();
091                        File file;
092                        if (name.length() == 0) {
093                            file = null;
094                        } else {
095                            // Firefox sends basename, IE sends full path
096                            int index = name.lastIndexOf('\\');
097                            if (index != -1) {
098                                name = name.substring(index + 1);
099                            }
100                            file = new File(rootDir, name);
101                        }
102                        if ("module".equals(fieldName)) {
103                            moduleFile = file;
104                        } else if ("plan".equals(fieldName)) {
105                            planFile = file;
106                        }
107                        if (file != null) {
108                            try {
109                                item.write(file);
110                            } catch (Exception e) {
111                                throw new PortletException(e);
112                            }
113                        }
114                    } else {
115                        // retrieve 'startApp' form field value
116                        if ("startApp".equalsIgnoreCase(item.getFieldName())) {
117                            startApp = item.getString();
118                        } else if ("redeploy".equalsIgnoreCase(item.getFieldName())) {
119                            redeploy = item.getString();
120                        }
121                    }
122                }
123            } catch (FileUploadException e) {
124                throw new PortletException(e);
125            }
126            DeploymentFactoryManager dfm = DeploymentFactoryManager.getInstance();
127            FileInputStream fis = null;
128            try {
129                DeploymentManager mgr = dfm.getDeploymentManager("deployer:geronimo:inVM", null, null);
130                try {
131                    boolean isRedeploy = redeploy != null && !redeploy.equals("");
132                    if(mgr instanceof JMXDeploymentManager) {
133                        ((JMXDeploymentManager)mgr).setLogConfiguration(false, true);
134                    }
135                    Target[] all = mgr.getTargets();
136                    if (null == all) {
137                        throw new IllegalStateException("No target to distribute to");
138                    }
139    
140                    ProgressObject progress;
141                    if(isRedeploy) {
142                        TargetModuleID[] targets = identifyTargets(moduleFile, planFile, mgr.getAvailableModules(null, all));
143                        if(targets.length == 0) {
144                            throw new PortletException("Unable to identify modules to replace.  Please include a Geronimo deployment plan or use the command-line deployment tool.");
145                        }
146                        progress = mgr.redeploy(targets, moduleFile, planFile);
147                    } else {
148                        progress = mgr.distribute(new Target[] {all[0]}, moduleFile, planFile);
149                    }
150                    while(progress.getDeploymentStatus().isRunning()) {
151                        Thread.sleep(100);
152                    }
153                    
154                    String abbrStatusMessage;
155                    String fullStatusMessage = null;
156                    if(progress.getDeploymentStatus().isCompleted()) {
157                        abbrStatusMessage = "The application was successfully "+(isRedeploy ? "re" : "")+"deployed.<br/>";
158                        // start installed app/s
159                        if (!isRedeploy && startApp != null && !startApp.equals("")) {
160                            progress = mgr.start(progress.getResultTargetModuleIDs());
161                            while(progress.getDeploymentStatus().isRunning()) {
162                                Thread.sleep(100);
163                            }
164                            if (progress.getDeploymentStatus().isCompleted()) {
165                                abbrStatusMessage += "The application was successfully started";
166                            } else {
167                                abbrStatusMessage += "The application was not successfully started";
168                                fullStatusMessage = progress.getDeploymentStatus().getMessage();
169                            }
170                        }
171                    } else {
172                        fullStatusMessage = progress.getDeploymentStatus().getMessage();
173                        // for the abbreviated status message clip off everything
174                        // after the first line, which in most cases means the gnarly stacktrace 
175                        abbrStatusMessage = "Deployment failed:<br/>"
176                                          + fullStatusMessage.substring(0, fullStatusMessage.indexOf('\n'));
177                        // try to provide an upgraded version of the plan
178                        try {
179                            if (planFile != null && planFile.exists()) {
180                                byte[] plan = new byte[(int) planFile.length()];
181                                fis = new FileInputStream(planFile);
182                                fis.read(plan);
183                                DocumentBuilder documentBuilder = XmlUtil.newDocumentBuilderFactory().newDocumentBuilder();
184                                Document doc = documentBuilder.parse(new ByteArrayInputStream(plan));
185                                // v1.1 switched from configId to moduleId
186                                String configId = doc.getDocumentElement().getAttribute("configId");
187                                if (configId != null && !("".equals(configId))) {
188                                    StringWriter sw = new StringWriter();
189                                    new Upgrade1_0To1_1().upgrade(new ByteArrayInputStream(plan), sw);
190                                    // have to store the original and upgraded plans in the session
191                                    // because the buffer size for render parameters is sometimes not
192                                    // big enough
193                                    actionRequest.getPortletSession().setAttribute(MIGRATED_PLAN_PARM, sw.getBuffer());
194                                    actionRequest.getPortletSession().setAttribute(ORIGINAL_PLAN_PARM, new String(plan));
195                                }
196                            }
197                        } catch (Exception e) {
198                            // cannot provide a migrated plan in this case, most likely
199                            // because the deployment plan would not parse. a valid
200                            // status message has already been provided in this case
201                        }
202                    }
203                    // have to store the status messages in the portlet session
204                    // because the buffer size for render parameters is sometimes not big enough
205                    actionRequest.getPortletSession().setAttribute(FULL_STATUS_PARM, fullStatusMessage);
206                    actionRequest.getPortletSession().setAttribute(ABBR_STATUS_PARM, abbrStatusMessage);
207                } finally {
208                    mgr.release();
209                    if (fis!=null) fis.close();
210                    if(moduleFile != null && moduleFile.exists()) {
211                        if(!moduleFile.delete()) {
212                            log.debug("Unable to delete temporary file "+moduleFile);
213                            moduleFile.deleteOnExit();
214                        }
215                    }
216                    if(planFile != null && planFile.exists()) {
217                        if(!planFile.delete()) {
218                            log.debug("Unable to delete temporary file "+planFile);
219                            planFile.deleteOnExit();
220                        }
221                    }
222                }
223            } catch (Exception e) {
224                throw new PortletException(e);
225            }
226        }
227    
228        private TargetModuleID[] identifyTargets(File module, File plan, TargetModuleID[] allModules) throws PortletException {
229            String moduleId = null;
230            List modules = new ArrayList();
231            try {
232                if(plan != null) {
233                    moduleId = ConfigIDExtractor.extractModuleIdFromPlan(plan);
234                } else if(module != null) {
235                    moduleId = ConfigIDExtractor.extractModuleIdFromArchive(module);
236                    if(moduleId == null) {
237                        int pos = module.getName().lastIndexOf('.');
238                        moduleId = pos > -1 ? module.getName().substring(0, pos) : module.getName();
239                    }
240                }
241                if(moduleId != null) {
242                    modules.addAll(ConfigIDExtractor.identifyTargetModuleIDs(allModules, moduleId, true));
243                } else {
244                    String name = module != null ? module.getName() : plan.getName();
245                    int pos = name.lastIndexOf('.');
246                    if(pos > -1) {
247                        name = name.substring(0, pos);
248                    }
249                    modules.addAll(ConfigIDExtractor.identifyTargetModuleIDs(allModules, Artifact.DEFAULT_GROUP_ID+"/"+name+"//", true));
250                }
251            } catch (IOException e) {
252                throw new PortletException("Unable to read input files: "+e.getMessage());
253            } catch (DeploymentException e) {
254                throw new PortletException(e.getMessage(), e);
255            }
256            return (TargetModuleID[]) modules.toArray(new TargetModuleID[modules.size()]);
257        }
258    
259        protected void doView(RenderRequest renderRequest,
260                              RenderResponse renderResponse) throws PortletException, IOException {
261            // The deployment plans and messages from the deployers sometime exceeds
262            // the buffer size for render attributes. To avoid the buffer
263            // overrun the render attributes are temporarily stored in the portlet
264            // session during the processAction phase and then copied into render
265            // attributes here so the JSP has easier access to them. This seems
266            // to only be an issue on tomcat.
267            copyRenderAttribute(renderRequest, FULL_STATUS_PARM);
268            copyRenderAttribute(renderRequest, ABBR_STATUS_PARM);
269            copyRenderAttribute(renderRequest, MIGRATED_PLAN_PARM);
270            copyRenderAttribute(renderRequest, ORIGINAL_PLAN_PARM);
271            deployView.include(renderRequest, renderResponse);
272        }
273        
274        private void copyRenderAttribute(RenderRequest renderRequest, String attr) {
275            Object value = renderRequest.getPortletSession().getAttribute(attr);
276            renderRequest.getPortletSession().removeAttribute(attr);
277            renderRequest.setAttribute(attr, value);
278        }
279    
280        protected void doHelp(RenderRequest renderRequest,
281                              RenderResponse renderResponse) throws PortletException, IOException {
282            helpView.include(renderRequest, renderResponse);
283        }
284    
285        public void init(PortletConfig portletConfig) throws PortletException {
286            super.init(portletConfig);
287            deployView = portletConfig.getPortletContext().getRequestDispatcher(DEPLOY_VIEW);
288            helpView = portletConfig.getPortletContext().getRequestDispatcher(HELP_VIEW);
289        }
290    
291        public void destroy() {
292            deployView = null;
293            helpView = null;
294            super.destroy();
295        }
296    }