1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.servlet.core;
20
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.concurrent.atomic.AtomicLongFieldUpdater;
26
27 import javax.servlet.MultipartConfigElement;
28 import javax.servlet.Servlet;
29 import javax.servlet.ServletException;
30 import javax.servlet.SingleThreadModel;
31 import javax.servlet.UnavailableException;
32
33 import io.undertow.server.handlers.form.FormEncodedDataDefinition;
34 import io.undertow.server.handlers.form.FormParserFactory;
35 import io.undertow.server.handlers.form.MultiPartParserDefinition;
36 import io.undertow.server.handlers.resource.ResourceChangeListener;
37 import io.undertow.server.handlers.resource.ResourceManager;
38 import io.undertow.servlet.UndertowServletLogger;
39 import io.undertow.servlet.UndertowServletMessages;
40 import io.undertow.servlet.api.DeploymentInfo;
41 import io.undertow.servlet.api.DeploymentManager;
42 import io.undertow.servlet.api.InstanceFactory;
43 import io.undertow.servlet.api.InstanceHandle;
44 import io.undertow.servlet.api.LifecycleInterceptor;
45 import io.undertow.servlet.api.ServletInfo;
46 import io.undertow.servlet.spec.ServletConfigImpl;
47 import io.undertow.servlet.spec.ServletContextImpl;
48
49 /**
50  * Manager for a servlets lifecycle.
51  *
52  * @author Stuart Douglas
53  */

54 public class ManagedServlet implements Lifecycle {
55
56     private final ServletInfo servletInfo;
57     private final ServletContextImpl servletContext;
58
59     private volatile boolean started = false;
60     private final InstanceStrategy instanceStrategy;
61     private volatile boolean permanentlyUnavailable = false;
62
63     private long maxRequestSize;
64     private FormParserFactory formParserFactory;
65     private MultipartConfigElement multipartConfig;
66
67     private static final AtomicLongFieldUpdater<ManagedServlet> unavailableUntilUpdater = AtomicLongFieldUpdater.newUpdater(ManagedServlet.class"unavailableUntil");
68
69     @SuppressWarnings("unused")
70     private volatile long unavailableUntil = 0;
71
72     public ManagedServlet(final ServletInfo servletInfo, final ServletContextImpl servletContext) {
73         this.servletInfo = servletInfo;
74         this.servletContext = servletContext;
75         if (SingleThreadModel.class.isAssignableFrom(servletInfo.getServletClass())) {
76             instanceStrategy = new SingleThreadModelPoolStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext);
77         } else {
78             instanceStrategy = new DefaultInstanceStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext);
79         }
80         setupMultipart(servletContext);
81     }
82
83     public void setupMultipart(ServletContextImpl servletContext) {
84         FormEncodedDataDefinition formDataParser = new FormEncodedDataDefinition()
85                 .setDefaultEncoding(servletContext.getDeployment().getDefaultRequestCharset().name());
86         MultipartConfigElement multipartConfig = servletInfo.getMultipartConfig();
87         if(multipartConfig == null) {
88             multipartConfig = servletContext.getDeployment().getDeploymentInfo().getDefaultMultipartConfig();
89         }
90         this.multipartConfig = multipartConfig;
91         if (multipartConfig != null) {
92             //todo: fileSizeThreshold
93             MultipartConfigElement config = multipartConfig;
94             if (config.getMaxRequestSize() != -1) {
95                 maxRequestSize = config.getMaxRequestSize();
96             } else {
97                 maxRequestSize = -1;
98             }
99             final Path tempDir;
100             if(config.getLocation() == null || config.getLocation().isEmpty()) {
101                 tempDir = servletContext.getDeployment().getDeploymentInfo().getTempPath();
102             } else {
103                 String location = config.getLocation();
104                 Path locFile = Paths.get(location);
105                 if(locFile.isAbsolute()) {
106                     tempDir = locFile;
107                 } else {
108                     final DeploymentInfo deploymentInfo = servletContext.getDeployment().getDeploymentInfo();
109                     tempDir = deploymentInfo.requireTempPath().resolve(location);
110                 }
111             }
112
113             MultiPartParserDefinition multiPartParserDefinition = new MultiPartParserDefinition(tempDir);
114             if(config.getMaxFileSize() > 0) {
115                 multiPartParserDefinition.setMaxIndividualFileSize(config.getMaxFileSize());
116             }
117             if (config.getFileSizeThreshold() > 0) {
118                 multiPartParserDefinition.setFileSizeThreshold(config.getFileSizeThreshold());
119             }
120             multiPartParserDefinition.setDefaultEncoding(servletContext.getDeployment().getDefaultRequestCharset().name());
121
122             formParserFactory = FormParserFactory.builder(false)
123                     .addParser(formDataParser)
124                     .addParser(multiPartParserDefinition)
125                     .build();
126
127         } else {
128             //no multipart config we don't allow multipart requests
129             formParserFactory = FormParserFactory.builder(false).addParser(formDataParser).build();
130             maxRequestSize = -1;
131         }
132     }
133
134
135     public synchronized void start() throws ServletException {
136
137     }
138
139     public void createServlet() throws ServletException {
140         if (permanentlyUnavailable) {
141             return;
142         }
143         try {
144             if (!started && servletInfo.getLoadOnStartup() != null && servletInfo.getLoadOnStartup() >= 0) {
145                 instanceStrategy.start();
146                 started = true;
147             }
148         } catch (UnavailableException e) {
149             if (e.isPermanent()) {
150                 permanentlyUnavailable = true;
151                 stop();
152             }
153         }
154     }
155
156     public synchronized void stop() {
157         if (started) {
158             instanceStrategy.stop();
159         }
160         started = false;
161     }
162
163     @Override
164     public boolean isStarted() {
165         return started;
166     }
167
168     public boolean isPermanentlyUnavailable() {
169         return permanentlyUnavailable;
170     }
171
172     public boolean isTemporarilyUnavailable() {
173         long until = unavailableUntil;
174         if (until != 0) {
175             if (System.currentTimeMillis() < until) {
176                 return true;
177             } else {
178                 unavailableUntilUpdater.compareAndSet(this, until, 0);
179             }
180         }
181         return false;
182     }
183
184     public void setPermanentlyUnavailable(final boolean permanentlyUnavailable) {
185         this.permanentlyUnavailable = permanentlyUnavailable;
186     }
187
188     public InstanceHandle<? extends Servlet> getServlet() throws ServletException {
189         if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) {
190             throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName());
191         }
192         if (!started) {
193             synchronized (this) {
194                 if (!started) {
195                     instanceStrategy.start();
196                     started = true;
197                 }
198             }
199         }
200         return instanceStrategy.getServlet();
201     }
202
203
204     public void forceInit() throws ServletException {
205         if (!started) {
206             if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) {
207                 throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName());
208             }
209             synchronized (this) {
210                 if (!started) {
211                     try {
212                         instanceStrategy.start();
213                     } catch (UnavailableException e) {
214                         handleUnavailableException(e);
215                     }
216                     started = true;
217                 }
218             }
219         }
220     }
221
222     public void handleUnavailableException(UnavailableException e) {
223         if (e.isPermanent()) {
224             UndertowServletLogger.REQUEST_LOGGER.stoppingServletDueToPermanentUnavailability(getServletInfo().getName(), e);
225             stop();
226             setPermanentlyUnavailable(true);
227         } else {
228             long until = System.currentTimeMillis() + e.getUnavailableSeconds() * 1000;
229             unavailableUntilUpdater.set(this, until);
230             UndertowServletLogger.REQUEST_LOGGER.stoppingServletUntilDueToTemporaryUnavailability(getServletInfo().getName(), new Date(until), e);
231         }
232     }
233
234     public ServletInfo getServletInfo() {
235         return servletInfo;
236     }
237
238     public long getMaxRequestSize() {
239         return maxRequestSize;
240     }
241
242     public FormParserFactory getFormParserFactory() {
243         return formParserFactory;
244     }
245
246     public MultipartConfigElement getMultipartConfig() {
247         return multipartConfig;
248     }
249
250     @Override
251     public String toString() {
252         return "ManagedServlet{" +
253                 "servletInfo=" + servletInfo +
254                 '}';
255     }
256
257     /**
258      * interface used to abstract the difference between single thread model servlets and normal servlets
259      */

260     interface InstanceStrategy {
261         void start() throws ServletException;
262
263         void stop();
264
265         InstanceHandle<? extends Servlet> getServlet() throws ServletException;
266     }
267
268
269     /**
270      * The default servlet pooling strategy that just uses a single instance for all requests
271      */

272     private static class DefaultInstanceStrategy implements InstanceStrategy {
273
274         private final InstanceFactory<? extends Servlet> factory;
275         private final ServletInfo servletInfo;
276         private final ServletContextImpl servletContext;
277         private volatile InstanceHandle<? extends Servlet> handle;
278         private volatile Servlet instance;
279         private ResourceChangeListener changeListener;
280         private final InstanceHandle<Servlet> instanceHandle = new InstanceHandle<Servlet>() {
281             @Override
282             public Servlet getInstance() {
283                 return instance;
284             }
285
286             @Override
287             public void release() {
288
289             }
290         };
291
292         DefaultInstanceStrategy(final InstanceFactory<? extends Servlet> factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) {
293             this.factory = factory;
294             this.servletInfo = servletInfo;
295             this.servletContext = servletContext;
296         }
297
298         public synchronized void start() throws ServletException {
299             try {
300                 handle = factory.createInstance();
301             } catch (Exception e) {
302                 throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e);
303             }
304             instance = handle.getInstance();
305             new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), servletInfo, instance, new ServletConfigImpl(servletInfo, servletContext)).proceed();
306
307             //if a servlet implements FileChangeCallback it will be notified of file change events
308             final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager();
309             if(instance instanceof ResourceChangeListener && resourceManager.isResourceChangeListenerSupported()) {
310                 resourceManager.registerResourceChangeListener(changeListener = (ResourceChangeListener) instance);
311             }
312         }
313
314         public synchronized void stop() {
315             if (handle != null) {
316                 final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager();
317                 if(changeListener != null) {
318                     resourceManager.removeResourceChangeListener(changeListener);
319                 }
320                 invokeDestroy();
321                 handle.release();
322             }
323         }
324
325         private void invokeDestroy() {
326             List<LifecycleInterceptor> interceptors = servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors();
327             try {
328                 new LifecyleInterceptorInvocation(interceptors, servletInfo, instance).proceed();
329             } catch (Exception e) {
330                 UndertowServletLogger.ROOT_LOGGER.failedToDestroy(servletInfo, e);
331             }
332         }
333
334         public InstanceHandle<? extends Servlet> getServlet() {
335             return instanceHandle;
336         }
337     }
338
339     /**
340      * pooling strategy for single thread model servlet
341      */

342     private static class SingleThreadModelPoolStrategy implements InstanceStrategy {
343
344
345         private final InstanceFactory<? extends Servlet> factory;
346         private final ServletInfo servletInfo;
347         private final ServletContextImpl servletContext;
348
349         private SingleThreadModelPoolStrategy(final InstanceFactory<? extends Servlet> factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) {
350             this.factory = factory;
351             this.servletInfo = servletInfo;
352             this.servletContext = servletContext;
353         }
354
355         @Override
356         public void start() throws ServletException {
357             if(servletInfo.getLoadOnStartup() != null) {
358                 //see UNDERTOW-734, make sure init method is called for load on startup
359                 getServlet().release();
360             }
361         }
362
363         @Override
364         public void stop() {
365
366         }
367
368         @Override
369         public InstanceHandle<? extends Servlet> getServlet() throws ServletException {
370             final InstanceHandle<? extends Servlet> instanceHandle;
371             final Servlet instance;
372             //TODO: pooling
373             try {
374                 instanceHandle = factory.createInstance();
375             } catch (Exception e) {
376                 throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e);
377             }
378             instance = instanceHandle.getInstance();
379             new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), servletInfo, instance, new ServletConfigImpl(servletInfo, servletContext)).proceed();
380
381             return new InstanceHandle<Servlet>() {
382                 @Override
383                 public Servlet getInstance() {
384                     return instance;
385                 }
386
387                 @Override
388                 public void release() {
389                     try {
390                         instance.destroy();
391                     } catch (Throwable t) {
392                         UndertowServletLogger.REQUEST_LOGGER.failedToDestroy(instance, t);
393                     }
394                     instanceHandle.release();
395                 }
396             };
397
398         }
399     }
400
401
402 }
403