View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.controllers.deploy;
12  
13  import com.google.common.base.Strings;
14  
15  import jakarta.servlet.http.HttpServletRequest;
16  import jakarta.servlet.http.HttpServletResponse;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.nio.file.Files;
21  import java.nio.file.Path;
22  import java.time.Duration;
23  
24  import org.apache.catalina.Context;
25  import org.apache.commons.io.FilenameUtils;
26  import org.apache.commons.io.file.PathUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  import org.springframework.beans.factory.annotation.Value;
30  import org.springframework.security.core.Authentication;
31  import org.springframework.security.core.context.SecurityContextHolder;
32  import org.springframework.stereotype.Controller;
33  import org.springframework.web.bind.annotation.GetMapping;
34  import org.springframework.web.bind.annotation.PostMapping;
35  import org.springframework.web.bind.annotation.RequestParam;
36  import org.springframework.web.multipart.MultipartFile;
37  import org.springframework.web.servlet.ModelAndView;
38  import org.springframework.web.servlet.view.InternalResourceView;
39  
40  import psiprobe.controllers.AbstractTomcatContainerController;
41  import psiprobe.controllers.jsp.DisplayJspController;
42  import psiprobe.model.jsp.Summary;
43  
44  /**
45   * Uploads and installs web application from a .WAR.
46   */
47  @Controller
48  public class UploadWarController extends AbstractTomcatContainerController {
49  
50    /** The Constant logger. */
51    private static final Logger logger = LoggerFactory.getLogger(UploadWarController.class);
52  
53    /** The Constant MAXSECONDS_WAITFOR_CONTEXT. */
54    private static final int MAXSECONDS_WAITFOR_CONTEXT = 10;
55  
56    @GetMapping("/adm/war.htm")
57    @Override
58    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
59        throws Exception {
60      return super.handleRequest(request, response);
61    }
62  
63    @Override
64    protected ModelAndView handleRequestInternal(HttpServletRequest request,
65        HttpServletResponse response) throws Exception {
66      return new ModelAndView(new InternalResourceView(getViewName()));
67    }
68  
69    @PostMapping("/adm/war.htm")
70    public ModelAndView handleUpload(@RequestParam("war") MultipartFile file,
71        @RequestParam(value = "context", required = false) String contextName,
72        @RequestParam(value = "update", required = false) String updateParam,
73        @RequestParam(value = "compile", required = false) String compileParam,
74        @RequestParam(value = "discard", required = false) String discardParam,
75        HttpServletRequest request) throws Exception {
76  
77      String errMsg = null;
78  
79      // If not multi-part content, exit
80      if (file == null || file.isEmpty()) {
81        errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.notWar.failure")
82            + " [null or empty]";
83        request.setAttribute("errorMessage", errMsg);
84        return new ModelAndView(new InternalResourceView(getViewName()));
85      }
86  
87      // Save the uploaded file to a temporary location
88      Path tmpPath = null;
89      try {
90        String fileName = file.getOriginalFilename();
91        if (!Strings.isNullOrEmpty(fileName)) {
92          fileName = FilenameUtils.getName(fileName);
93          tmpPath = Path.of(System.getProperty("java.io.tmpdir"), fileName);
94          file.transferTo(tmpPath);
95        } else {
96          errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.notWar.failure")
97              + " [null or empty]";
98          request.setAttribute("errorMessage", errMsg);
99          return new ModelAndView(new InternalResourceView(getViewName()));
100       }
101     } catch (IOException e) {
102       logger.error("Could not process file upload", e);
103       request.setAttribute("errorMessage", getMessageSourceAccessor()
104           .getMessage("probe.src.deploy.war.uploadfailure", new Object[] {e.getMessage()}));
105       // File is transferred so it will exist
106       Files.delete(tmpPath);
107       return new ModelAndView(new InternalResourceView(getViewName()));
108     }
109 
110     if (!tmpPath.getFileName().toString().endsWith(".war")) {
111       errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.notWar.failure") + " ["
112           + tmpPath.getFileName() + "]";
113       request.setAttribute("errorMessage", errMsg);
114       return new ModelAndView(new InternalResourceView(getViewName()));
115     }
116 
117     if (contextName == null || contextName.isEmpty()) {
118       String warFileName = tmpPath.getFileName().toString().replaceAll("\\.war$", "");
119       contextName = "/" + warFileName;
120     }
121 
122     try {
123       contextName = containerWrapper.getTomcatContainer().formatContextName(contextName);
124 
125       /*
126        * pass the name of the newly deployed context to the presentation layer using this name the
127        * presentation layer can render a url to view compilation details
128        */
129       String visibleContextName = contextName.isEmpty() ? "/" : contextName;
130       request.setAttribute("contextName", visibleContextName);
131 
132       // Checks if UPDATE option is selected
133       if ("yes".equals(updateParam)
134           && containerWrapper.getTomcatContainer().findContext(contextName) != null) {
135         if (contextName.matches("\\w*")) {
136           logger.debug("updating {}: removing the old copy", contextName);
137         }
138         containerWrapper.getTomcatContainer().remove(contextName);
139       }
140 
141       if (containerWrapper.getTomcatContainer().findContext(contextName) == null) {
142         // move the .war to tomcat application base dir
143         String destWarFilename =
144             containerWrapper.getTomcatContainer().formatContextFilename(contextName);
145         File destWar = Path.of(containerWrapper.getTomcatContainer().getAppBase().getPath(),
146             destWarFilename + ".war").toFile();
147 
148         Files.move(tmpPath, destWar.toPath());
149 
150         // let Tomcat know that the file is there
151         containerWrapper.getTomcatContainer().installWar(contextName);
152 
153         Path destContext =
154             Path.of(containerWrapper.getTomcatContainer().getAppBase().getPath(), destWarFilename);
155 
156         // Wait few seconds for creating context dir to avoid empty context
157         PathUtils.waitFor(destContext, Duration.ofSeconds(MAXSECONDS_WAITFOR_CONTEXT));
158 
159         Context ctx = containerWrapper.getTomcatContainer().findContext(contextName);
160         if (ctx == null) {
161           errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.notinstalled",
162               new Object[] {visibleContextName});
163         } else {
164           request.setAttribute("success", Boolean.TRUE);
165           // Logging action
166           Authentication auth = SecurityContextHolder.getContext().getAuthentication();
167           // get username logger
168           String name = auth.getName();
169           if (contextName.matches("\\w*")) {
170             logger.info(getMessageSourceAccessor().getMessage("probe.src.log.deploywar"), name,
171                 contextName);
172           }
173           // Checks if DISCARD "work" directory is selected
174           if ("yes".equals(discardParam)) {
175             containerWrapper.getTomcatContainer().discardWorkDir(ctx);
176             if (contextName.matches("\\w*")) {
177               logger.info(getMessageSourceAccessor().getMessage("probe.src.log.discardwork"), name,
178                   contextName);
179             }
180           }
181           // Checks if COMPILE option is selected
182           if ("yes".equals(compileParam)) {
183             Summary summary = new Summary();
184             summary.setName(ctx.getName());
185             containerWrapper.getTomcatContainer().listContextJsps(ctx, summary, true);
186             request.getSession(false).setAttribute(DisplayJspController.SUMMARY_ATTRIBUTE, summary);
187             request.setAttribute("compileSuccess", Boolean.TRUE);
188           }
189         }
190       } else {
191         errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.alreadyExists",
192             new Object[] {visibleContextName});
193       }
194     } catch (IOException e) {
195       errMsg = getMessageSourceAccessor().getMessage("probe.src.deploy.war.failure",
196           new Object[] {e.getMessage()});
197       logger.error("Tomcat threw an exception when trying to deploy", e);
198     } finally {
199       if (errMsg != null) {
200         request.setAttribute("errorMessage", errMsg);
201       }
202       // If war was not moved, delete it
203       if (Files.exists(tmpPath)) {
204         Files.delete(tmpPath);
205       }
206     }
207     return new ModelAndView(new InternalResourceView(getViewName()));
208   }
209 
210   @Value("/adm/deploy.htm")
211   @Override
212   public void setViewName(String viewName) {
213     super.setViewName(viewName);
214   }
215 
216 }