-
Notifications
You must be signed in to change notification settings - Fork 37.7k
/
FrameworkServlet.java
1067 lines (954 loc) · 42.5 KB
/
FrameworkServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SourceFilteringListener;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.WebUtils;
/**
* Base servlet for Spring's web framework. Provides integration with
* a Spring application context, in a JavaBean-based overall solution.
*
* <p>This class offers the following functionality:
* <ul>
* <li>Manages a {@link org.springframework.web.context.WebApplicationContext
* WebApplicationContext} instance per servlet. The servlet's configuration is determined
* by beans in the servlet's namespace.
* <li>Publishes events on request processing, whether or not a request is
* successfully handled.
* </ul>
*
* <p>Subclasses must implement {@link #doService} to handle requests. Because this extends
* {@link HttpServletBean} rather than HttpServlet directly, bean properties are
* automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()}
* for custom initialization.
*
* <p>Detects a "contextClass" parameter at the servlet init-param level,
* falling back to the default context class,
* {@link org.springframework.web.context.support.XmlWebApplicationContext
* XmlWebApplicationContext}, if not found. Note that, with the default
* {@code FrameworkServlet}, a custom context class needs to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext
* ConfigurableWebApplicationContext} SPI.
*
* <p>Accepts an optional "contextInitializerClasses" servlet init-param that
* specifies one or more {@link org.springframework.context.ApplicationContextInitializer
* ApplicationContextInitializer} classes. The managed web application context will be
* delegated to these initializers, allowing for additional programmatic configuration,
* e.g. adding property sources or activating profiles against the {@linkplain
* org.springframework.context.ConfigurableApplicationContext#getEnvironment() context's
* environment}. See also {@link org.springframework.web.context.ContextLoader} which
* supports a "contextInitializerClasses" context-param with identical semantics for
* the "root" web application context.
*
* <p>Passes a "contextConfigLocation" servlet init-param to the context instance,
* parsing it into potentially multiple file paths which can be separated by any
* number of commas and spaces, like "test-servlet.xml, myServlet.xml".
* If not explicitly specified, the context implementation is supposed to build a
* default location from the namespace of the servlet.
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files, at least when using Spring's
* default ApplicationContext implementation. This can be leveraged to
* deliberately override certain bean definitions via an extra XML file.
*
* <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a
* servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location
* with XmlWebApplicationContext). The namespace can also be set explicitly via
* the "namespace" servlet init-param.
*
* <p>As of Spring 3.1, {@code FrameworkServlet} may now be injected with a web
* application context, rather than creating its own internally. This is useful in Servlet
* 3.0+ environments, which support programmatic registration of servlet instances. See
* {@link #FrameworkServlet(WebApplicationContext)} Javadoc for details.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
* @author Rossen Stoyanchev
* @see #doService
* @see #setContextClass
* @see #setContextConfigLocation
* @see #setContextInitializerClasses
* @see #setNamespace
*/
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean {
/**
* Suffix for WebApplicationContext namespaces. If a servlet of this class is
* given the name "test" in a context, the namespace used by the servlet will
* resolve to "test-servlet".
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/**
* Default context class for FrameworkServlet.
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
/**
* Prefix for the ServletContext attribute for the WebApplicationContext.
* The completion is the servlet name.
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
/**
* Any number of these characters are considered delimiters between
* multiple values in a single init-param String value.
*/
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute;
/** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
/** WebApplicationContext id to assign */
private String contextId;
/** Namespace for this servlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation;
/** Should we publish the context as a ServletContext attribute? */
private boolean publishContext = true;
/** Should we publish a ServletRequestHandledEvent at the end of each request? */
private boolean publishEvents = true;
/** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
private boolean threadContextInheritable = false;
/** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */
private boolean dispatchOptionsRequest = false;
/** Should we dispatch an HTTP TRACE request to {@link #doService}? */
private boolean dispatchTraceRequest = false;
/** WebApplicationContext for this servlet */
private WebApplicationContext webApplicationContext;
/** Flag used to detect whether onRefresh has already been called */
private boolean refreshEventReceived = false;
/** Comma-delimited ApplicationContextInitializer classnames set through init param */
private String contextInitializerClasses;
/** Actual ApplicationContextInitializer instances to apply to the context */
private ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
/**
* Create a new {@code FrameworkServlet} that will create its own internal web
* application context based on defaults and values provided through servlet
* init-params. Typically used in Servlet 2.5 or earlier environments, where the only
* option for servlet registration is through {@code web.xml} which requires the use
* of a no-arg constructor.
* <p>Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation')
* will dictate which XML files will be loaded by the
* {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext}
* <p>Calling {@link #setContextClass} (init-param 'contextClass') overrides the
* default {@code XmlWebApplicationContext} and allows for specifying an alternative class,
* such as {@code AnnotationConfigWebApplicationContext}.
* <p>Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses')
* indicates which {@link ApplicationContextInitializer} classes should be used to
* further configure the internal application context prior to refresh().
* @see #FrameworkServlet(WebApplicationContext)
*/
public FrameworkServlet() {
}
/**
* Create a new {@code FrameworkServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>The given web application context may or may not yet be {@linkplain
* ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
* of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong>
* already been refreshed (the recommended approach), then the following will occur:
* <ul>
* <li>If the given context does not already have a {@linkplain
* ConfigurableApplicationContext#setParent parent}, the root application context
* will be set as the parent.</li>
* <li>If the given context has not already been assigned an {@linkplain
* ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #postProcessWebApplicationContext} will be called</li>
* <li>Any {@link ApplicationContextInitializer}s specified through the
* "contextInitializerClasses" init-param or through the {@link
* #setContextInitializers} property will be applied.</li>
* <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* @param webApplicationContext the context to use
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
/**
* Set the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public void setContextAttribute(String contextAttribute) {
this.contextAttribute = contextAttribute;
}
/**
* Return the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public String getContextAttribute() {
return this.contextAttribute;
}
/**
* Set a custom context class. This class must be of type
* {@link org.springframework.web.context.WebApplicationContext}.
* <p>When using the default FrameworkServlet implementation,
* the context class must also implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface.
* @see #createWebApplicationContext
*/
public void setContextClass(Class<?> contextClass) {
this.contextClass = contextClass;
}
/**
* Return the custom context class.
*/
public Class<?> getContextClass() {
return this.contextClass;
}
/**
* Specify a custom WebApplicationContext id,
* to be used as serialization id for the underlying BeanFactory.
*/
public void setContextId(String contextId) {
this.contextId = contextId;
}
/**
* Return the custom WebApplicationContext id, if any.
*/
public String getContextId() {
return this.contextId;
}
/**
* Set a custom namespace for this servlet,
* to be used for building a default context config location.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the namespace for this servlet, falling back to default scheme if
* no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
*/
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
/**
* Specify the set of fully-qualified {@link ApplicationContextInitializer} class
* names, per the optional "contextInitializerClasses" servlet init-param.
* @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
* @see #applyInitializers(ConfigurableApplicationContext)
*/
public void setContextInitializerClasses(String contextInitializerClasses) {
this.contextInitializerClasses = contextInitializerClasses;
}
/**
* Specify which {@link ApplicationContextInitializer} instances should be used
* to initialize the application context used by this {@code FrameworkServlet}.
* @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
* @see #applyInitializers(ConfigurableApplicationContext)
*/
public void setContextInitializers(ApplicationContextInitializer<ConfigurableApplicationContext>... contextInitializers) {
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : contextInitializers) {
this.contextInitializers.add(initializer);
}
}
/**
* Set the context config location explicitly, instead of relying on the default
* location built from the namespace. This location string can consist of
* multiple locations separated by any number of commas and spaces.
*/
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* Return the explicit context config location, if any.
*/
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
/**
* Set whether to publish this servlet's context as a ServletContext attribute,
* available to all objects in the web container. Default is "true".
* <p>This is especially handy during testing, although it is debatable whether
* it's good practice to let other application objects access the context this way.
*/
public void setPublishContext(boolean publishContext) {
this.publishContext = publishContext;
}
/**
* Set whether this servlet should publish a ServletRequestHandledEvent at the end
* of each request. Default is "true"; can be turned off for a slight performance
* improvement, provided that no ApplicationListeners rely on such events.
* @see org.springframework.web.context.support.ServletRequestHandledEvent
*/
public void setPublishEvents(boolean publishEvents) {
this.publishEvents = publishEvents;
}
/**
* Set whether to expose the LocaleContext and RequestAttributes as inheritable
* for child threads (using an {@link java.lang.InheritableThreadLocal}).
* <p>Default is "false", to avoid side effects on spawned background threads.
* Switch this to "true" to enable inheritance for custom child threads which
* are spawned during request processing and only used for this request
* (that is, ending after their initial task, without reuse of the thread).
* <p><b>WARNING:</b> Do not use inheritance for child threads if you are
* accessing a thread pool which is configured to potentially add new threads
* on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
* since this will expose the inherited context to such a pooled thread.
*/
public void setThreadContextInheritable(boolean threadContextInheritable) {
this.threadContextInheritable = threadContextInheritable;
}
/**
* Set whether this servlet should dispatch an HTTP OPTIONS request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. enumerating all standard HTTP request methods
* as a response to the OPTIONS request).
* <p>Turn this flag on if you prefer OPTIONS requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle an OPTIONS request.
* <p>Note that HttpServlet's default OPTIONS processing will be applied
* in any case if your controllers happen to not set the 'Allow' header
* (as required for an OPTIONS response).
*/
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
this.dispatchOptionsRequest = dispatchOptionsRequest;
}
/**
* Set whether this servlet should dispatch an HTTP TRACE request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. reflecting the message received back to the client).
* <p>Turn this flag on if you prefer TRACE requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle a TRACE request.
* <p>Note that HttpServlet's default TRACE processing will be applied
* in any case if your controllers happen to not generate a response
* of content type 'message/http' (as required for a TRACE response).
*/
public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
this.dispatchTraceRequest = dispatchTraceRequest;
}
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
/**
* Retrieve a {@code WebApplicationContext} from the {@code ServletContext}
* attribute with the {@link #setContextAttribute configured name}. The
* {@code WebApplicationContext} must have already been loaded and stored in the
* {@code ServletContext} before this servlet gets initialized (or invoked).
* <p>Subclasses may override this method to provide a different
* {@code WebApplicationContext} retrieval strategy.
* @return the WebApplicationContext for this servlet, or {@code null} if not found
* @see #getContextAttribute()
*/
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* <p>This implementation expects custom contexts to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* <p>Do not forget to register this servlet instance as application listener on the
* created context (for triggering its {@link #onRefresh callback}, and to call
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* @param parent the parent ApplicationContext to use, or {@code null} if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// the wac environment's #initPropertySources will be called in any case when
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* Delegates to #createWebApplicationContext(ApplicationContext).
* @param parent the parent WebApplicationContext to use, or {@code null} if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see #createWebApplicationContext(ApplicationContext)
*/
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
/**
* Delegate the WebApplicationContext before it is refreshed to any
* {@link ApplicationContextInitializer} instances specified by the
* "contextInitializerClasses" servlet init-param.
* <p>See also {@link #postProcessWebApplicationContext}, which is designed to allow
* subclasses (as opposed to end-users) to modify the application context, and is
* called immediately after this method.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #postProcessWebApplicationContext
* @see ConfigurableApplicationContext#refresh()
*/
@SuppressWarnings("unchecked")
protected void applyInitializers(ConfigurableApplicationContext wac) {
if (this.contextInitializerClasses != null) {
String[] initializerClassNames =
StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS);
for (String initializerClassName : initializerClassNames) {
ApplicationContextInitializer<ConfigurableApplicationContext> initializer;
try {
Class<?> initializerClass = ClassUtils.forName(initializerClassName, wac.getClassLoader());
initializer = BeanUtils.instantiateClass(initializerClass, ApplicationContextInitializer.class);
}
catch (Exception ex) {
throw new IllegalArgumentException(
String.format("Could not instantiate class [%s] specified via " +
"'contextInitializerClasses' init-param", initializerClassName), ex);
}
this.contextInitializers.add(initializer);
}
}
Collections.sort(this.contextInitializers, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
/**
* Post-process the given WebApplicationContext before it is refreshed
* and activated as context for this servlet.
* <p>The default implementation is empty. {@code refresh()} will
* be called automatically after this method returns.
* <p>Note that this method is designed to allow subclasses to modify the application
* context, while {@link #initWebApplicationContext} is designed to allow
* end-users to modify the context through the use of
* {@link ApplicationContextInitializer}s.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #initWebApplicationContext
* @see ConfigurableWebApplicationContext#refresh()
*/
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
/**
* Return the ServletContext attribute name for this servlet's WebApplicationContext.
* <p>The default implementation returns
* {@code SERVLET_CONTEXT_PREFIX + servlet name}.
* @see #SERVLET_CONTEXT_PREFIX
* @see #getServletName
*/
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
/**
* Return this servlet's WebApplicationContext.
*/
public final WebApplicationContext getWebApplicationContext() {
return this.webApplicationContext;
}
/**
* This method will be invoked after any bean properties have been set and
* the WebApplicationContext has been loaded. The default implementation is empty;
* subclasses may override this method to perform any initialization they require.
* @throws ServletException in case of an initialization exception
*/
protected void initFrameworkServlet() throws ServletException {
}
/**
* Refresh this servlet's application context, as well as the
* dependent state of the servlet.
* @see #getWebApplicationContext()
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
public void refresh() {
WebApplicationContext wac = getWebApplicationContext();
if (!(wac instanceof ConfigurableApplicationContext)) {
throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac);
}
((ConfigurableApplicationContext) wac).refresh();
}
/**
* Callback that receives refresh events from this servlet's WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current WebApplicationContext
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
/**
* Override the parent class implementation in order to intercept PATCH
* requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getMethod();
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
* with a {@code NoBodyResponse} that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate PUT requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate DELETE requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate OPTIONS requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard OPTIONS processing otherwise,
* and also if there is still no 'Allow' header set after dispatching.
* @see #doService
*/
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchOptionsRequest) {
processRequest(request, response);
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
super.doOptions(request, response);
String allowedMethods = response.getHeader("Allow");
allowedMethods += ", " + RequestMethod.PATCH.name();
response.setHeader("Allow", allowedMethods);
}
/**
* Delegate TRACE requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard TRACE processing otherwise.
* @see #doService
*/
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchTraceRequest) {
processRequest(request, response);
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
super.doTrace(request, response);
}
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
if (previousAttributes == null || (previousAttributes instanceof ServletRequestAttributes)) {
requestAttributes = new ServletRequestAttributes(request);
}
initContextHolders(request, localeContext, requestAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), getRequestBindingInterceptor(request));
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
} else {
if (asyncManager.isConcurrentHandlingStarted()) {
if (logger.isDebugEnabled()) {
logger.debug("Leaving response open for concurrent processing");
}
}
else {
this.logger.debug("Successfully completed request");
}
}
}
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
/**
* Build a LocaleContext for the given request, exposing the request's
* primary locale as current locale.
* @param request current HTTP request
* @return the corresponding LocaleContext
*/
protected LocaleContext buildLocaleContext(HttpServletRequest request) {
return new SimpleLocaleContext(request.getLocale());
}
private void initContextHolders(HttpServletRequest request,
LocaleContext localeContext, RequestAttributes attributes) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
if (attributes != null) {
RequestContextHolder.setRequestAttributes(attributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
private void resetContextHolders(HttpServletRequest request,
LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
}
private CallableProcessingInterceptor getRequestBindingInterceptor(final HttpServletRequest request) {