Tomcat 8 webapp, dynamically add PostResources - servlets

I have a webapp which uses a listener to dynamically add servlet instances.
Each servlet instance is defined by a user-defined configuration files, and encapsulates a distinct dataset. Each of these datasets may also include some user-defined static web files (HTML, JPG, CSS etc).
The static resources for each servlet instance are stored outside the webapp folder (to avoid deletion on redeployment), and each servlet instance has a separate folder hierarchy.
In the listener contextInitialized method, I am using
javax.servlet.ServletContext.addServlet
to add each of my servlet instances, and
javax.servlet.ServletRegistration.Dynamic.addMapping
to add the URL mapping for this servlet.
I would like to add other mappings for the static content in external folders.
In Tomcat 7, I extended org.apache.catalina.servlets.DefaultServlet
to change the relativePath to my new document root, but this class
does not work in Tomcat 8 0 - ClassNotFoundException (org.apache.naming.resources.FileDirContext).
Tomcat 8 has a new 'Resources' framework which should make this much more straightforward.
My question is - how can I add a PostResources element to my context dynamically (at web app startup, inside my listener), without editing the web.xml?
In my listener, I should be able to do something like this:
WebResourceRoot root = new StandardRoot(context);
root.createWebResourceSet(WebResourceRoot.ResourceSetType.POST,
"/my/url", "my/filesystem/path", null, "/");
but I cannot figure out how to get the required context (org.apache.catalina.Context), which is a completely different type to the similarly named javax.servlet.ServletContext provided by the ServletContextEvent in the listener contextInitialized method.
Any suggestions?
Thanks.

I think I have figured it out using the MBeanServer.
This seems a bit of a roundabout method though - is there no way of getting to the StandardRoot or StandardContext object from the ServletContext?
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.servlet.ServletContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.Container;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.WebResourceRoot;
...
private void addPostResources(ServletContext servletContext)
throws Exception /* just for debugging */ {
MBeanServer mbs = MBeanServerFactory.findMBeanServer(null).get(0);
ObjectName name = new ObjectName("Catalina","type","Server");
Server server = (Server)mbs.getAttribute(name, "managedResource");
Service service = server.findService("Catalina");
StandardEngine engine = service.getContainer();
Container hostContainer = engine.findChild(engine.getDefaultHost());
StandardContext standardContext = (StandardContext)hostContainer.findChild(servletContext.getContextPath());
WebResourceRoot root = standardContext.getResources();
root.createWebResourceSet(WebResourceRoot.ResourceSetType.POST,
"/my/url", "my/filesystem/path", null, "/");
}

Related

.NET Core - How do you reference one IHostedService from another?

I've got two hosted services in my web service: Domain and ComplianceService. ComplianceService needs to reference data and methods in Domain, like so:
public ComplianceService(ILogger<ComplianceService> logger, Domain domain, IRuleRepository ruleRepository)
{
// Initialize the service.
this.logger = logger;
this.domain = domain;
this.ruleRepository = ruleRepository;
}
My initialization in startup.cs looks like this:
// Add the dependencies.
serviceCollection.AddSingleton<RulesActivator>()
.AddSingleton<Accruals>()
.AddSingleton<IRuleRepository, ComplianceRuleRepository>()
.AddSingleton<IViolation, Violation>();
// Add the hosted services.
serviceCollection.AddHostedService<Domain>();
serviceCollection.AddHostedService<ComplianceService>();
When I try to run this, I get this message:
System.InvalidOperationException: 'Unable to resolve service for type 'ThetaRex.OpenBook.ServerDomain.Domain' while attempting to activate 'ThetaRex.OpenBook.WebApi.ComplianceService'.'
If I add Domain to the IoC, like this:
serviceCollection.AddSingleton<Domain>()
.AddSingleton<RulesActivator>()
.AddSingleton<Accruals>()
.AddSingleton<IRuleRepository, ComplianceRuleRepository>()
.AddSingleton<IViolation, Violation>();
It runs, but I get two copies of Domain. I can force it to work like this:
// Add the hosted services.
serviceCollection.AddHostedService(sp => sp.GetRequiredService<Domain>());
if (this.Configuration.GetValue<bool>("ComplianceEnabled"))
{
serviceCollection.AddHostedService<ComplianceService>();
}
But this seems really clumsy and counterintuitive. How come the AddHostedService can instantiate ComplianceService, but can't instantiate the dependent service?

Unable to access static resources in JAR file using embedded Jetty

I basically have the same issue as this question:
Setting Jetty resourcebase to static file embedded in the same jar file
where I am using embedded Jetty, and I want to access some static HTML files in the same JAR file.
Here is how the Jetty server is set up:
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
String res = ApiServer.class.getClassLoader().getResource("res").toExternalForm();
context.setResourceBase(res);
jettyServer = new Server(port);
jettyServer.setHandler(context);
ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
// Tells the Jersey Servlet which REST service/class to load.
String classes = new StringJoiner(",")
.add(MyClass1.class.getCanonicalName())
.add(MyClass2.class.getCanonicalName())
.toString();
jerseyServlet.setInitParameter(ServerProperties.PROVIDER_CLASSNAMES, classes);
The folder structure of the JAR is like this:
root
| src (Java classes in here)
| res
| index.html
However it just doesn't work. I have tried to access the URL in various ways:
http://localhost:12345/res/index.html
or
http://localhost:12345/index.html
but neither works.
What am I doing wrong?
The code you have ...
String res = ApiServer.class.getClassLoader().getResource("res").toExternalForm();
context.setResourceBase(res);
Does not work for me, as you cannot use a classloader to obtain a directory reference, only file references. The call ClassLoader.getResource("res") always returns null.
This needs to be fixed first.
Next, your declaration of Jersey is exceedingly greedy.
ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
This means that Servlet (ServletContainer.class) is handling 100% of all requests, even requests for static content.
It is impossible for that Servlet, based on your url-pattern, to "not handle" static requests and let Jetty serve those static requests.
Relax this url-pattern, to say /api/* and then you'll be one step closer.
The final thing you need is a DefaultServlet (the component in the Servlet spec, and Jetty that serves static files).
So you'll wind up with the following code ...
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
URL url = ApiServer.class.getClassLoader().getResource("res/index.html");
if (url == null)
throw new FileNotFoundException("Whoops, can't find static resources folder");
URI webroot = url.toURI().resolve("./");
context.setBaseResource(Resource.newResource(webroot));
ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/api/*");
jerseyServlet.setInitOrder(0);
// Tells the Jersey Servlet which REST service/class to load.
String classes = new StringJoiner(",")
.add(MyClass1.class.getCanonicalName())
.add(MyClass2.class.getCanonicalName())
.toString();
jerseyServlet.setInitParameter(ServerProperties.PROVIDER_CLASSNAMES, classes);
// always named "default", always last, always on url-pattern "/"
ServletHolder defaultServ = new ServletHolder("default", DefaultServlet.class);
defaultServ.setInitParameter("dirAllowed","true");
context.addServlet(defaultServ,"/");
jettyServer = new Server(port);
jettyServer.setHandler(context);

XUnit Net Core Web API Integration Test: "The ConnectionString property has not been initialized."

Just trying to build an Integration Test project for a NET Core Web API.
So I've followed a few examples, including this one (https://dotnetcorecentral.com/blog/asp-net-core-web-api-integration-testing-with-xunit/) and naturally, I run into issues. When I run the simple GET test I get an exception:
"System.InvalidOperationException : The ConnectionString property has not been initialized."
Any help would be appreciated.
For server = new TestServer(new WebHostBuilder().UseStartup<Startup>());, you need to manually configure the appsettings.json path like
var server = new TestServer(WebHost.CreateDefaultBuilder()
.UseContentRoot(#"D:\Edward\SourceCode\AspNetCore\Tests\IntegrationTestMVC")
// This is the path for project which needs to be test
.UseStartup<Startup>()
);
For a convenience way, I would suggest you try Basic tests with the default WebApplicationFactory.
The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (*.sln) and appends the TEntryPoint assembly name to the solution directory. The app root directory (the content root path) is used to discover views and content files.
Reference:How the test infrastructure infers the app content root path
I had to override CreateHostBuilder in my derived WebApplicationFactory in order to add the configuration for the connection string (since it was read from user secrets).
public class CustomApplicationFactory : WebApplicationFactory<Sedab.MemberAuth.Startup>
{
protected override IHostBuilder CreateHostBuilder()
{
var initialData = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("ConnectionStrings:DefaultConnection", "test")
};
return base.CreateHostBuilder().ConfigureHostConfiguration(config => config.AddInMemoryCollection(initialData));
}
}

How to do coldfusion mapping in application level

I have below directory structure -
rootFolder
---CFC (contains all cfc file)
---SERVICES (contains all service file)
application.cfc
I have created one service named (userService.cfc) which have the below script
import services.userService;
component accessors="true" alias="services.userService"
{
remote userService function init()
{
return This;
}
remote any function getUser()
{
var userObj = new cfc.sessionUser();
return userObj;
}
}
If i call this service from inside the application, this is working fine
Again if i am trying to call it from outside the application, need to change this statement as below and again it is working fine.
import rootFolderName.services.userService;
component accessors="true" alias="rootFolderName.services.userService"
{
remote userServicefunction init()
{
return This;
}
remote any function getUser()
{
var userObj = new rootFolderName.cfc.sessionUser();
return userObj;
}
}
But If i put this code on another rootFolder suppose on "rootFolderName1" name i have to changed all the place where i used the rootFolderName. I got one solution by CFADMIN folder mapping on server level. but i want it on application level.
Can we configure it on Application.cfc? I have used Mappings also but that is not working.
Actually i have two separate application one flex application which is trying to access the second application services remotely. second application have cfc and sevices.
Please help on this.
I would review Ben Nadel's post on the use of ExpandPath() in writing app level CF mappings
http://www.bennadel.com/blog/2519-ExpandPath-Works-With-ColdFusion-s-Per-Application-Mappings.htm

Accessing services directory when setting up Zend AMF in Codeigniter

I followed the instructions in this tutorial to set up Zend AMF as a way of passing data from my flash app to my PHP app:
http://codeigniter.com/forums/viewthread/180414/
So I have the directory structure and everything as described there. This is my gateway controller:
class Gateway extends CI_Controller
{
function __construct()
{
parent::__construct();
$this->load->library('zend');
//root_folder + application + controllers + amf + services
define('SERVICES_FOLDER', APPPATH.'controllers/amf/services/');
}
public function index()
{
$server = new Zend_Amf_Server();
$server->setProduction(false);
//$server->addFunction('testservice');
$server->addDirectory(SERVICES_FOLDER);
echo $server->handle();
}
}
And the APPPATH is /application/ so the path defined by SERVIES_FOLDER is "/application/controllers/amf/services" which is where my file 'testservice.php' sits.
When I try and connect to that service in flash:
var gateway:String = "http://mysite.com/amf/gateway";
con.connect(gateway);
con.call("Testservice.getMessage", new Responder(onResult, onFault));
It calls the onFault method and displays the error:
Plugin by name 'Testservice' was not found in the registry;
Which makes me think that the addDirectory() line in Gateway.php was the problem somehow. Interestingly, I also cannot access the testservice function through a URL, ie by going to mysite.com/amf/services/testservice.
Any thoughts on what might be going on here?
Figured it out, sort of.
Instead of using the addDirectory method which I was having no luck with, I used the setClass method and created another class within the gateway.php file that has the functions, and now I can connect to those functions from flash.
I had an issue with this when using parent::__construct() in my service controllers. Once I removed that, the problem went away.

Resources