Inconsistency with ServletContextListener Usage in Spring Web Application - spring

I have a Spring Web application where I am implementing a ServletContextListener. I am setting a static JxBrowser Engine Object inside the contextInitialized method and setting it to null in the contextDestroyed method. This variable which was set in servlet context is being used in another class to create a browser object before using it to render html content in headless mode.
It works fine for some time and then it starts giving InvocationTargetException. As a temporary fix, I redeploy the application which again creates the engine object and stores it in Servlet Context.
I want to ask if setting something in ServletContext behaves inconsistently over time.
Here is the Sample code
#WebListener
public class SampleContextListener implements ServletContextListener {
public static final Logger LOGGER = LogManager.getRootLogger();
public static Engine engine = null;
public static ServletContext context;
public static String browserDirectory = "/tmp";
#Override
public void contextInitialized(ServletContextEvent event) {
engine = Engine.newInstance(EngineOptions.newBuilder(HARDWARE_ACCELERATED)
.licenseKey("")
.chromiumDir(Paths.get(browserDirectory))
.build());
LOGGER.info("Engine Initialized => " + engine);
context = event.getServletContext();
context.setAttribute("engine",engine);
}
//Run this before web application is destroyed
#Override
public void contextDestroyed(ServletContextEvent event) {
try {
context = null;
if(engine != null && !engine.isClosed()) {
engine.close();
LOGGER.info("Engine Closed => " + engine);
}
} catch (IOException e) {
LOGGER.info("Error While Deleting Browser Files: " + e.getMessage());
}
}
}
public class TestClass {
public static final Logger LOGGER = LogManager.getRootLogger();
public static final String TMP_DIRECTORY = "/tmp"
public static String getEngine() {
Engine engine = null;
Browser browser = null;
ServletContext servletContext = SampleContextListener.context;
engine = (Engine) servletContext.getAttribute("engine");
Browser browser = engine.newBrowser();
}
}

Related

Injecting spring bean (service layer class) into ResourceBundle

I created a class using ResourceBundle interface as shown below. This class is dependent on another class. The implementation class for ResourceBundle (QuestionnaireSource as shown below) always has null as dependencies. No matter if I use setter or constructor injection.
Could someone please help me with this issue. I am I missing some configuration here.
#Component
public class QuestionnaireSource extends ResourceBundle {
private final QuestionnaireCache questionnaireCache;
private static final Object lockObject = new Object();
#Override
protected Object handleGetObject(String key) {
// Gets an object for the given key from this resource bundle.
// Returns null if this resource bundle does not contain an object for the given key 0
Object value = null;
try {
value = getString(key, LocaleContextHolder.getLocale());
} catch (Exception ignored) {
}
return value;
}
public Questionnaire getString(String key, Locale locale) {
Locale l = safeLocale(locale);
return getResources(l).get(key);
}
private Locale safeLocale(Locale l) {
if (l.getLanguage().equalsIgnoreCase("DE")) {
return Locale.GERMAN;
} else {
return Locale.ENGLISH;
}
}
protected Map<String, Questionnaire> getResources(Locale locale) {
synchronized (lockObject) {
return questionnaireCache.getQuestionnaireCache().get(locale.getLanguage().toUpperCase());
}
}
#Override
public Enumeration<String> getKeys() {
return null;
}
public QuestionnaireSource(QuestionnaireCache questionnaireCache) {
super();
this.questionnaireCache = questionnaireCache;
}
}
Update:
I found that even simple dependency injection in resourceBundle is failing.
UPdate2:
The way I am using in the main class is as follows:
// ResourceBundle test here
System.out.println("Test here for resource bundle");
Locale locale = new Locale("de", "DE");
ResourceBundle bundle = ResourceBundle.getBundle("com.app.util.QuestionnaireSource", locale);
System.out.println(bundle.getString("some.test.string"));
Update3
I am writing a simple example to convey the scenario:
Some service class
#Service
public class SomeServiceTest {
public String testMethod(){
return "test here and complete";
}
}
Some example implementation of resource bundle
#Component
public class MyResourceBundle extends ResourceBundle {
private final SomeServiceTest someServiceTest;
#Autowired
public MyResourceBundle(SomeServiceTest someServiceTest) {
this.someServiceTest = someServiceTest;
}
#Override
protected Object handleGetObject(String key) {
if(key.equals("test"))
return "test";
return null;
}
#Override
public Enumeration<String> getKeys() {
return null;
}
}
Main.java
main(){
// ResourceBundle test here
System.out.println("Test here for resource bundle");
Locale locale = new Locale("de", "DE");
ResourceBundle bundle = ResourceBundle.getBundle("com.app.util.MyResourceBundle", locale);
System.out.println(bundle.getString("test"));
}
Update4:
I changed the annotation on classes as mentioned by on this post https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects
but still I have the null dependency injection for SomeServiceTest class. The changes are as shown below.
SomeServiceTest.java
#Service
public class SomeServiceTest {
public String testMethod(){
return "test here and complete";
}
}
MyResourceBundle.java
#Configurable
public class MyResourceBundle extends ResourceBundle {
#Autowired
private SomeServiceTest someServiceTest;
public MyResourceBundle() {
}
#Override
protected Object handleGetObject(String key) {
if(key.equals("test"))
return someServiceTest.testMethod();
return null;
}
#Override
public Enumeration<String> getKeys() {
return null;
}
}
still SomeServiceTest class is null.
Can you please post an example on how you are using this class? Is it you (your code) or spring who instanciate it (on startup)?
#Component only works for beans which Spring instanciate. If you want to inject stuff in classes you instanciate in you code you can annotate the class with #Configurable.
Please see https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects for some examples.
Make sure you have initialized the spring context
If you are using spring boot
You can get the application context after it starts and use it to get the bean you want
For example
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(YouApplication.class, args);
MyResourceBundle resConfig = run.getBean("myResourceBundle", MyResourceBundle .class);
resConfig.handleGetObject("test");
}
Unfortunately ResourceBundle.getBundle does not initialize the spring application context

Show a static HTML page while the Spring context initializes

Spring Boot 2.3.7, Embedded Jetty, JSP and WAR packaging. I want to show my some static HTML page while spring context initializes. It should be visible when application starts and before spring context refreshed. I tried to use this manual as example https://www.nurkiewicz.com/2015/09/displaying-progress-of-spring.html but this doesn't work.
I need to start embedded jetty directly when jetty is initialized. But spring boot starts embedded jetty only when context refreshed.
How should I do this?
I created a Jetty warpper warmup class:
public final class WarmupServer {
private final String contextPath;
private final String displayName;
private final DefaultApplicationArguments arguments;
private final String[] welcomeFiles;
private final Resource baseResource;
private Server server;
public WarmupServer(String contextPath,
String displayName,
Resource baseResource,
String[] welcomeFiles,
String... runArgs) {
this.contextPath = StringUtils.defaultIfBlank(contextPath, "/");
this.displayName = StringUtils.defaultIfBlank(displayName, "Warmup");
this.baseResource = ObjectUtils.defaultIfNull(baseResource, Resource.newClassPathResource("/static"));
this.welcomeFiles = ArrayUtils.isEmpty(welcomeFiles) ? new String[]{"html/warmup.html"} : welcomeFiles;
this.arguments = new DefaultApplicationArguments(ArrayUtils.nullToEmpty(runArgs));
}
public Server start() throws Exception {
if (server != null && server.isStarted()) {
throw new IllegalStateException("Server already started");
}
server = new Server();
server.setHandler(buildServletHandler());
final String configPath = parseArg(OPT_CONFIG);
if (StringUtils.isBlank(configPath)) {
throw new RuntimeException(OPT_CONFIG + " argument is not set");
}
final Config config = ConfigUtils.parseFile(new File(configPath), DEFAULT_CONFIG_FILE_NAME);
configureHttpConnector(config);
configureHttpsConnector(config);
server.start();
return server;
}
public void registerWarmupServerStopLifecycle(ConfigurableApplicationContext context) {
context.getBeanFactory()
.registerSingleton(WarmupStopLifecycle.class.getSimpleName(), new WarmupStopLifecycle(server));
}
private ServletContextHandler buildServletHandler() {
final ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);
handler.addServlet(DefaultServlet.class, "/");
handler.setDisplayName(displayName);
handler.setContextPath(contextPath);
handler.setWelcomeFiles(welcomeFiles);
handler.setBaseResource(baseResource);
return handler;
}
private void configureHttpConnector(Config config) {
final int httpPort = NumberUtils.toInt(parseArg(OPT_HTTP_PORT), config.getInt(OPT_HTTP_PORT));
final ServerConnector connector = new ServerConnector(server);
connector.setPort(httpPort);
server.addConnector(connector);
}
private void configureHttpsConnector(Config config) {
final int httpsPort = NumberUtils.toInt(
parseArg(OPT_HTTPS_PORT), config.getInt(OPT_HTTPS_PORT));
final String keyStorePath = StringUtils.defaultIfBlank(
parseArg(OPT_KEYSTORE_FILE), config.getString(OPT_KEYSTORE_FILE));
final boolean sslEnabled = StringUtils.isNotBlank(keyStorePath)
&& Files.isReadable(Paths.get(keyStorePath));
if (sslEnabled) {
final HttpConfiguration configuration = new HttpConfiguration();
configuration.setSecurePort(httpsPort);
configuration.setSecureScheme(HTTPS_SCHEME);
final ServerConnector httpsConnector = new HttpsConnector()
.createConnector(server, configuration, config.getConfig(JETTY_HTTPS_CONFIG), httpsPort);
server.addConnector(httpsConnector);
}
}
private String parseArg(String optionName) {
final List<String> values = arguments.getOptionValues(optionName);
return CollectionUtils.isEmpty(values) ? StringUtils.EMPTY : values.get(0);
}
public static WarmupServer start(String contextPath,
String displayName,
Resource baseResource,
String[] welcomeFiles,
String... runArgs) throws Exception {
final WarmupServer server = new WarmupServer(contextPath, displayName, baseResource, welcomeFiles, runArgs);
server.start();
return server;
}
}
This wrapper parses command line arguments and creates a Jetty handler and HTTP and (or) HTTPS connectors by using provided command line arguments.
And the simple Spring's Lifecycle implementation class:
#RequiredArgsConstructor
class WarmupStopLifecycle implements SmartLifecycle {
private static final Logger logger = LogManager.getFormatterLogger();
private final Server warmupServer;
private volatile boolean isRunning;
#Override
public void start() {
try {
warmupServer.stop();
isRunning = true;
} catch (Exception e) {
logger.error("Failed to stop warmup server", e);
throw new RuntimeException("Failed to stop warmup server", e);
}
}
#Override
public void stop() {
}
#Override
public boolean isRunning() {
return isRunning;
}
/**
* Returns phase of this lifecycle.
* A phase MUST be before the Spring web server starts.
* See {#code org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle} phase.
*/
#Override
public int getPhase() {
return Integer.MAX_VALUE - 2;
}
}
So usage of this:
#SpringBootApplication
public class SpringApplication {
public static void main(String[] args) throws Exception {
final WarmupServer warmupServer = WarmupServer.start(
"/my_context_path", "My Warmup server handler", args);
new SpringApplicationBuilder()
.sources(SpringApplication.class)
.initializers(warmupServer::registerWarmupServerStopLifecycle)
.run(args);
}
}
WarmupServer starts immediately after the application runs and will be stopped before starting the Spring's web server.

How can i use #Retryable(label="...") for logging or monitoring purposes?

I have simple retryable method annotated with
#Retryable(label = "myLabel")
According to documentation, it should be
A unique label for statistics reporting
Is this label accessible inside RetryListener? How can i use it?
The label is available in the RetryContext.NAME attribute in the context.
#Component
class Foo {
private static final Logger log = LoggerFactory.getLogger(Foo.class);
#Retryable(label = "myLabel")
public void retriable() {
log.info("Here with label: " + RetrySynchronizationManager.getContext().getAttribute(RetryContext.NAME));
throw new RuntimeException("test");
}
#Recover
public void recover(Exception e) {
log.info("Recovered");
}
}
The context is available in the listener methods.
There's a new feature in 1.3.
1.3 is not released yet but there is a snapshot 1.3.0.BUILD-SNAPSHOT in the spring snapshots repo https://repo.spring.io/snapshot.
This also gives you access to the method invocation.
#Component
class MyRetryListener extends MethodInvocationRetryListenerSupport {
private static final Logger log = LoggerFactory.getLogger(MyRetryListener.class);
#Override
protected <T, E extends Throwable> boolean doOpen(RetryContext context,
MethodInvocationRetryCallback<T, E> callback) {
log.info("Invocation of method: " + callback.getInvocation().getMethod().toGenericString()
+ " with label: " + callback.getLabel());
return super.doOpen(context, callback);
}
}
#Component
class Foo {
private static final Logger log = LoggerFactory.getLogger(Foo.class);
#Retryable(label = "myLabel")
public void retriable() {
log.info("Here");
throw new RuntimeException("test");
}
#Recover
public void recover(Exception e) {
log.info("Recovered");
}
}

Vaadin OSGI application URL access issue

I have created a Vaadin OSGI bundle using Vaadin 7 and Equinox ServletBridge (Tomcat 8.0). I am able to successfully deploy the bundle in bridge container and status in osgi environment is coming as ACTIVE but I am not able to access my Vaadin application. Not able to understand what URL has to be registered in HTTP Servlet registration.
Default URL for application in Tomcat is working fine (http://localhost:8080/VaadinOsgiApp/servlet/com.example.vaadinosgiapp.VaadinosgiappUI$Servlet)
but in OSGI it's not working.
Any other specific URL to be mapped or to be registered?
PFB respective class code
/** ACTIVATOR CLASS */
public class Activator implements BundleActivator {
private ServiceTracker httpServiceTracker;
public void start(BundleContext context) throws Exception {
httpServiceTracker = new HttpServiceTracker(context);
httpServiceTracker.open();
}
public void stop(BundleContext context) throws Exception {
httpServiceTracker.close();
httpServiceTracker = null;
}
private class HttpServiceTracker extends ServiceTracker {
public HttpServiceTracker(BundleContext context) {
super(context, HttpService.class.getName(), null);
}
public Object addingService(ServiceReference reference) {
HttpService httpService = (HttpService) context.getService(reference);
try {
httpService.registerResources("/*", "/*", null); //$NON-NLS-1$ //$NON-NLS-2$
httpService.registerServlet("/*", new VaadinosgiappUI.Servlet(), null, null); //$NON-NLS-1$
} catch (Exception e) {
e.printStackTrace();
}
return httpService;
}
public void removedService(ServiceReference reference, Object service) {
HttpService httpService = (HttpService) service;
httpService.unregister("/*"); //$NON-NLS-1$
httpService.unregister("/*"); //$NON-NLS-1$
super.removedService(reference, service);
}
}
}
/** VAADIN CLASS **/
#SuppressWarnings("serial")
#Theme("vaadinosgiapp")
public class VaadinosgiappUI extends UI {
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = VaadinosgiappUI.class)
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
Button button = new Button("Click Me");
button.addClickListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
layout.addComponent(new Label("Thank you for clicking"));
}
});
layout.addComponent(button);
}
}

Accessing HttpSession inside an annotated #WebSocket class on Embedded Jetty 9

How can I access a HttpSession object inside an annotated #WebSocket class in Jetty 9?
I found how to do it using #ServerEndpoint annotation, like here: HttpSession from #ServerEndpoint
Using the #WebSocket annotation, like in the class bellow, how can I do it?
#WebSocket
public class AuctionWebSocket {
// NEED TO ACCESS HttpSession OBJECT INSIDE THESE METHODS:
#OnWebSocketConnect
public void onConnect(Session session) {
System.out.println("onConnect...");
}
#OnWebSocketMessage
public void onMessage(String message) {
System.out.println("Message: " + message);
}
#OnWebSocketClose
public void onClose(int statusCode, String reason) {
System.out.println("onClose...");
}
#OnWebSocketError
public void onError(Throwable t) {
System.out.println("onError...");
}
}
Inside the method onConnect(Session session), I tried to call session.getUpgradeRequest().getSession() which always returns null.
For sake of information, here is how I start embedded Jetty 9:
public class Main {
public static void main(String[] args) throws Exception {
String webPort = System.getenv("PORT");
if (webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
Server server = new Server(Integer.parseInt(webPort));
ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
WebAppContext wac = new WebAppContext();
String webappDirLocation = "./src/main/webapp/";
wac.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*/classes/.*");
wac.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
wac.setBaseResource(new ResourceCollection(new String[]{webappDirLocation, "./target"}));
wac.setResourceAlias("/WEB-INF/classes/", "/classes/");
wac.setContextPath("/");
wac.setParentLoaderPriority(true);
/*
* WebSocket handler.
*/
WebSocketHandler wsh = new WebSocketHandler() {
#Override
public void configure(WebSocketServletFactory wssf) {
wssf.register(AuctionWebSocket.class);
}
};
ContextHandler wsc = new ContextHandler();
wsc.setContextPath("/auction-notifications");
wsc.setHandler(wsh);
ContextHandlerCollection chc = new ContextHandlerCollection();
chc.setHandlers(new Handler[]{wac, wsc});
server.setHandler(chc);
server.start();
server.join();
}
}
Let me know if you need more information.
Any help will be appreciated.
Thanks in advance.
You'll want to use the WebSocketCreator concepts.
First you set the WebSocketCreator of your choice in the WebSocketServletFactory that you configure in your WebSocketServlet
public class MySessionSocketServlet extends WebSocketServlet
{
#Override
public void configure(WebSocketServletFactory factory)
{
factory.getPolicy().setIdleTimeout(30000);
factory.setCreator(new MySessionSocketCreator());
}
}
Next, you'll want to grab the HttpSession during the upgrade and pass it into the WebSocket object that you are creating.
public class MySessionSocketCreator implements WebSocketCreator
{
#Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
HttpSession httpSession = req.getSession();
return new MySessionSocket(httpSession);
}
}
Finally, just keep track of that HttpSession in your own WebSocket.
#WebSocket
public class MySessionSocket
{
private HttpSession httpSession;
private Session wsSession;
public MySessionSocket(HttpSession httpSession)
{
this.httpSession = httpSession;
}
#OnWebSocketConnect
public void onOpen(Session wsSession)
{
this.wsSession = wsSession;
}
}
Of note: the HttpSession can expire and be scavenged and cleaned up while a WebSocket is active. Also, the HttpSession contents at this point are not guaranteed to be kept in sync with changes from other web actions (this mostly depends on what Session storage / caching technology you use on the server side)
And one more note: resist the urge to store / track the ServletUpgradeRequest object in your Socket instance, as this object is recycled and cleaned up aggressively by Jetty proper.

Resources