/
FacesInitializer.java
292 lines (245 loc) · 13.6 KB
/
FacesInitializer.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
/*
* Copyright (c) 2021, 2022 Contributors to Eclipse Foundation.
* Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.faces.config;
import static com.sun.faces.RIConstants.ANNOTATED_CLASSES;
import static com.sun.faces.RIConstants.FACES_SERVLET_MAPPINGS;
import static com.sun.faces.RIConstants.FACES_SERVLET_REGISTRATION;
import static com.sun.faces.util.Util.isEmpty;
import static java.util.logging.Level.WARNING;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.sun.faces.util.FacesLogger;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.faces.annotation.FacesConfig;
import jakarta.faces.annotation.FacesConfig.ContextParam;
import jakarta.faces.application.ResourceDependencies;
import jakarta.faces.application.ResourceDependency;
import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.FacesBehavior;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.convert.FacesConverter;
import jakarta.faces.event.ListenerFor;
import jakarta.faces.event.ListenersFor;
import jakarta.faces.event.NamedEvent;
import jakarta.faces.event.PhaseListener;
import jakarta.faces.flow.FlowScoped;
import jakarta.faces.flow.builder.FlowBuilderParameter;
import jakarta.faces.flow.builder.FlowDefinition;
import jakarta.faces.lifecycle.ClientWindowScoped;
import jakarta.faces.model.DataModel;
import jakarta.faces.model.FacesDataModel;
import jakarta.faces.push.Push;
import jakarta.faces.render.FacesBehaviorRenderer;
import jakarta.faces.render.FacesRenderer;
import jakarta.faces.render.Renderer;
import jakarta.faces.validator.FacesValidator;
import jakarta.faces.validator.Validator;
import jakarta.faces.view.ViewScoped;
import jakarta.faces.webapp.FacesServlet;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.annotation.HandlesTypes;
import jakarta.websocket.server.ServerContainer;
/**
* Initializes Jakarta Faces if at least one of the following conditions is met:
*
* <ul>
* <li>The <code>Set</code> of classes passed to this initializer contains an user-defined Faces type, or</li>
* <li><code>FacesServlet</code> has been explicitly mapped ,or</li>
* <li><code>/WEB-INF/faces-config.xml</code> exists</li>
* </ul>
*
* If it is met, and the <code>FacesServlet</code> has not been explicitly mapped,
* then add mappings <em>*.xhtml</em>, <em>/faces</em>, <em>*.jsf</em>, and <em>*.faces</em> for the FacesServlet.
*/
@HandlesTypes({
// Jakarta Faces specific
ClientWindowScoped.class, Converter.class, DataModel.class, FacesBehavior.class, FacesBehaviorRenderer.class, FacesComponent.class, FacesConfig.class,
FacesConverter.class, FacesDataModel.class, FacesRenderer.class, FacesValidator.class, FlowBuilderParameter.class, FlowDefinition.class, FlowScoped.class,
ListenerFor.class, ListenersFor.class, NamedEvent.class, PhaseListener.class, Push.class, Renderer.class, ResourceDependencies.class, ResourceDependency.class,
UIComponent.class, Validator.class, ViewScoped.class,
})
public class FacesInitializer implements ServletContainerInitializer {
// NOTE: Logging should not be used with this class.
// NOTE: It can, we only need to ensure that the logger is only invoked when there's a (Init)FacesContext.
private static final Logger LOGGER = FacesLogger.CONFIG.getLogger();
public static final String FACES_PACKAGE_PREFIX = "jakarta.faces.";
public static final String MOJARRA_PACKAGE_PREFIX = "com.sun.faces.";
public static final String MOJARRA_TEST_PACKAGE_PREFIX = "com.sun.faces.test.";
private static final String FACES_CONFIG_RESOURCE_PATH = "/WEB-INF/faces-config.xml";
private static final String FACES_SERVLET_CLASS_NAME = FacesServlet.class.getName();
private static final String[] FACES_SERVLET_MAPPINGS_WITH_XHTML = { "/faces/*", "*.jsf", "*.faces", "*.xhtml" };
private static final String[] FACES_SERVLET_MAPPINGS_WITHOUT_XHTML = { "/faces/*", "*.jsf", "*.faces" };
// -------------------------------- Methods from ServletContainerInitializer
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
boolean appHasFacesContent = !isEmpty(classes) || isFacesConfigFilePresent(servletContext);
boolean appHasFacesServlet = isFacesServletRegistrationPresent(servletContext);
if (appHasFacesContent || appHasFacesServlet) {
addAnnotatedClasses(classes, servletContext);
InitFacesContext initFacesContext = new InitFacesContext(servletContext);
try {
if (appHasFacesContent) {
// Only look at mapping concerns if there is Faces content
handleMappingConcerns(servletContext, initFacesContext);
}
// Other concerns also handled if there is an existing Faces Servlet mapping
handleCdiConcerns(servletContext);
handleWebSocketConcerns(servletContext, initFacesContext);
// The Configure listener will do the bulk of initializing (configuring) Faces in a later phase.
servletContext.addListener(ConfigureListener.class);
}
finally {
initFacesContext.release();
}
} else {
// No Faces content, so if the other initializer added annotated classes they won't be needed
if (servletContext.getAttribute(ANNOTATED_CLASSES) != null) {
servletContext.removeAttribute(ANNOTATED_CLASSES);
}
}
}
public static void addAnnotatedClasses(Set<Class<?>> classes, ServletContext servletContext) {
if (isEmpty(classes)) {
// Nothing to add, just return
return;
}
@SuppressWarnings("unchecked")
Set<Class<?>> existingClasses = (Set<Class<?>>) servletContext.getAttribute(ANNOTATED_CLASSES);
if (isEmpty(existingClasses)) {
// No classes set before, so set the new classes as the initial set
servletContext.setAttribute(ANNOTATED_CLASSES, classes);
return;
}
// We have both existing and new classes, so create a new merged set.
Set<Class<?>> newAnnotatedClasses = new HashSet<Class<?>>();
newAnnotatedClasses.addAll(classes);
newAnnotatedClasses.addAll(existingClasses);
servletContext.setAttribute(ANNOTATED_CLASSES, newAnnotatedClasses);
}
// --------------------------------------------------------- Private Methods
private static boolean isFacesConfigFilePresent(ServletContext context) {
try {
return context.getResource(FACES_CONFIG_RESOURCE_PATH) != null;
}
catch (Exception ignore) {
return false;
}
}
private static boolean isFacesServletRegistrationPresent(ServletContext context) {
return getExistingFacesServletRegistration(context) != null;
}
private static ServletRegistration getExistingFacesServletRegistration(ServletContext servletContext) {
Map<String, ? extends ServletRegistration> existing = servletContext.getServletRegistrations();
for (ServletRegistration registration : existing.values()) {
if (FACES_SERVLET_CLASS_NAME.equals(registration.getClassName())) {
return registration;
}
}
return null;
}
private static void handleMappingConcerns(ServletContext servletContext, FacesContext facesContext) throws ServletException {
ServletRegistration existingFacesServletRegistration = getExistingFacesServletRegistration(servletContext);
if (existingFacesServletRegistration != null) {
// FacesServlet has already been defined, so we're not going to add additional mappings.
servletContext.setAttribute(FACES_SERVLET_REGISTRATION, existingFacesServletRegistration);
return;
}
ServletRegistration newFacesServletRegistration = servletContext.addServlet(FacesServlet.class.getSimpleName(), FACES_SERVLET_CLASS_NAME);
if (ContextParam.DISABLE_FACESSERVLET_TO_XHTML.isSet(facesContext)) {
newFacesServletRegistration.addMapping(FACES_SERVLET_MAPPINGS_WITHOUT_XHTML);
}
else {
newFacesServletRegistration.addMapping(FACES_SERVLET_MAPPINGS_WITH_XHTML);
}
servletContext.setAttribute(FACES_SERVLET_MAPPINGS, newFacesServletRegistration.getMappings());
servletContext.setAttribute(FACES_SERVLET_REGISTRATION, newFacesServletRegistration);
}
private static void handleCdiConcerns(ServletContext context) throws ServletException {
// If Weld is used as CDI impl then make sure it doesn't skip initialization when there's no BDA. See also #5232 and #5321
if (context.getAttribute("org.jboss.weld.environment.servlet.jakarta.enterprise.inject.spi.BeanManager") instanceof BeanManager) {
// Already initialized.
return;
}
ClassLoader cl = context.getClassLoader();
Class<?> weldInitializerClass;
try {
weldInitializerClass = cl.loadClass("org.jboss.weld.environment.servlet.EnhancedListener");
}
catch (ClassNotFoundException ignore) {
// Weld is actually not being used. That's OK for now, so just continue as usual.
return;
}
// Weld is being used so let's make sure it doesn't skip initialization when there's no BDA.
context.setInitParameter("org.jboss.weld.environment.servlet.archive.isolation", "false");
if (context.getAttribute("org.jboss.weld.environment.servlet.enhancedListenerUsed") == Boolean.TRUE) {
try {
LOGGER.log(WARNING, "Weld skipped initialization - forcing it to reinitialize");
ServletContainerInitializer weldInitializer = (ServletContainerInitializer) weldInitializerClass.getConstructor().newInstance();
weldInitializer.onStartup(null, context);
}
catch (Exception | LinkageError e) {
// Weld is being used but it failed for unclear reason while CDI should be enabled. That's not OK, so rethrow as ServletException.
throw new ServletException("Reinitializing Weld failed - giving up, please make sure your project contains at least one bean class with a bean defining annotation and retry", e);
}
}
}
private static void handleWebSocketConcerns(ServletContext context, FacesContext facesContext) throws ServletException {
if (context.getAttribute(ServerContainer.class.getName()) != null) {
// Already initialized
return;
}
if (!ContextParam.ENABLE_WEBSOCKET_ENDPOINT.isSet(facesContext)) {
// Register websocket endpoint is not enabled
return;
}
// Below work around is specific for Tyrus websocket impl.
// As Mojarra is to be designed as a container-provided JAR (not an user-provided JAR),
// the WebsocketEndpoint needs to be programmatically added in a ServletContextListener (in Mojarra's case the ConfigureListener),
// but at that point, servletContext.getAttribute(ServerContainer.class.getName()) returns null when Tyrus is used (Payara/GlassFish).
// Upon inspection it turns out that the TyrusServletContainerInitializer#onStartup() immediately returns when there are no user-provided endpoints
// and doesn't register the ServerContainer anymore, causing it to not be placed in ServletContext anymore.
// The below work around will thus manually take care of this.
ClassLoader cl = context.getClassLoader();
Class<?> tyrusInitializerClass;
try {
tyrusInitializerClass = cl.loadClass("org.glassfish.tyrus.servlet.TyrusServletContainerInitializer");
}
catch (ClassNotFoundException ignore) {
// Tyrus is actually not being used (all other impls known so far do not skip ServerContainer). That's OK for now, so just continue as usual.
return;
}
try {
ServletContainerInitializer tyrusInitializer = (ServletContainerInitializer) tyrusInitializerClass.getDeclaredConstructor().newInstance();
Class<?> tyrusConfigClass = cl.loadClass("org.glassfish.tyrus.server.TyrusServerConfiguration");
// Set of classes must be mutable and contain the TyrusServerConfiguration class.
Set<Class<?>> handledTypes = new HashSet<>();
handledTypes.add(tyrusConfigClass);
tyrusInitializer.onStartup(handledTypes, context);
}
catch (Exception e) {
// Tyrus is being used but it failed for unclear reason while websocket endpoint should be enabled. That's not OK, so rethrow as ServletException.
throw new ServletException(e);
}
}
}