Embeded Tomcat with webapp directory within the classpath/jar - java

So I have a Java application that use to be packaged as a war and then deployed to Tomcat, but now I have it setup so it all runs straight from a jar file using embedded Jetty as follows:
class JettyServer extends ServerTrait {
val server = new Server()
val connector = new ServerConnector(server)
connector.setPort(BigSenseServer.config.options("httpPort").toInt)
server.setConnectors(Array(connector))
val context = new ServletContextHandler()
context.setContextPath(BigSenseServer.webRoot)
context.addServlet(new MasterServlet().getClass, "/*")
context.addEventListener(new InitLoggingListener())
context.addEventListener(new DBUpdateListener())
val fileContext = new WebAppContext()
fileContext.setContextPath(BigSenseServer.contentRoot)
fileContext.setResourceBase(BigSenseServer.getClass.getResource("/io/bigsense/web").toExternalForm)
val handlers = new HandlerCollection()
handlers.setHandlers(Array( fileContext, context, new DefaultHandler()))
server.setHandler(handlers)
override def startServer() {
server.start
server.join
}
override def stopServer() {
server.stop
}
}
The webroot is /bigsense/api and the contentRoot is /static. In this configuration, static files like CSS and Javascript are served from the /io/bigsense/web package (kept in src/main/resources in SBT). In my original, the context-root was /bigsense and the servlet was mapped to api/*, so all the static content could be served directly from /bigsense/{js,css,whatever}.
I couldn't figure out how to get Jetty to do the same thing, but the current setup listed above works fine and I adjusted all my templates to get that static path from the same config object (which has a backend in a property file).
I want to create an embedded Tomcat implementation as well and I've read several guides, but they all seem to want a real webapp base directory. I can't find any examples which either just map a servlet directory without a webapp base or take the webapp base from the classpath (in a jar) instead of a real physical directory. I've tried things similar to the following:
EDIT Got the servlet working with the following. Now I just need the ServletContextListneres and a way to server files from the jar:
class TomcatServer extends ServerTrait {
val tomcat = new Tomcat()
tomcat.setPort(BigSenseServer.config.options("httpPort").toInt)
val tmp = new File(System.getProperty("java.io.tmpdir"))
val ctx = tomcat.addContext(BigSenseServer.webRoot,tmp.getAbsolutePath)
Tomcat.addServlet(ctx,"bigsense",new MasterServlet())
ctx.addServletMapping("/*","bigsense")
override def startServer() = {
tomcat.start()
tomcat.getServer().await()
}
override def stopServer() = tomcat.stop
}
So I have a similar setup to Jetty for the main servlet. I can't find any functions on the context object for adding in the two ServletContextListner objects I have. I also need to be able to serve my static context from the jar on contentRoot (/static).

Related

Vertx Hazelcast Cluster Error "There is no discovery strategy factory to create ..." when using AWS Join Strategy

I am trying to setup a simple vertx cluster in aws.(you can find all the code here)
In the pom file I already add all the depedency I need, including vertx-hazelcast and hazelcast plugin for aws.
As for The Main Class it is quite simple and all the hazelcast setup is prepared in the main class.
public class Main {
public static void main(String[] args) {
Logger log = Logger.getLogger(Main.class.getSimpleName());
Config hazelcastConfig = new Config();
hazelcastConfig.getNetworkConfig().getInterfaces().
setEnabled(true).addInterface("10.0.*.*");
JoinConfig joinConfig = hazelcastConfig.getNetworkConfig().getJoin();
joinConfig.getMulticastConfig().setEnabled(false);
joinConfig.getAwsConfig()
.setEnabled(true)
.setProperty("region", "ap-southeast-1")
.setProperty("tag-key", "aws:cloudformation:stack-name")
.setProperty("tag-value", "EC2ContainerService-test-cluster");
hazelcastConfig.getNetworkConfig().setJoin(joinConfig);
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
String hostAddress = getAddress();
VertxOptions options = new VertxOptions()
.setClusterManager(mgr)
.setHAEnabled(true);
EventBusOptions ebOptions = new EventBusOptions()
.setClustered(true)
.setHost(hostAddress);
options.setEventBusOptions(ebOptions);
Vertx.clusteredVertx(options, handler->{
if(handler.succeeded()){
DeploymentOptions containerOption = new DeploymentOptions().setHa(false);
handler.result().deployVerticle(Verticle.class,containerOption,deployHandler->{
if(handler.succeeded()){
log.info("Verticle Deployed");
}else{
log.severe("Verticle Deployment Failed");
}
});
} else{
log.severe(handler.cause().getMessage());
}
});
}
}
I always run into this error whenever I run the fat jar.
Caused by: com.hazelcast.config.properties.ValidationException: There is no discovery strategy factory to create 'D
iscoveryStrategyConfig{properties={tag-value=EC2ContainerService-test-cluster, region=ap-southeast-1, tag-key=aws:c
loudformation:stack-name}, className='com.hazelcast.aws.AwsDiscoveryStrategy', discoveryStrategyFactory=null}' Is i
t a typo in a strategy classname? Perhaps you forgot to include implementation on a classpath?
Is there anything missing in my code ?
The error message usually means that you don't have hazelcast-aws.jar plugin in your classpath.
I see that you have it in your Maven dependencies, but please make sure that:
You use the compatible hazelcast-aws version with your hazelcast version (you can find the compatibility information here).
Your JAR really includes classes from the com.hazelcast.aws package.
Your JAR includes the file resources/META-INF/services/com.hazelcast.spi.discovery.DiscoveryStrategyFactory.
Your resources/META-INF/services/com.hazelcast.spi.discovery.DiscoveryStrategyFactory does contain the entry com.hazelcast.aws.AwsDiscoveryStrategyFactory
If anything of above is not correct, then you need to revisit how your fat JAR is built. One of the solutions of having it all in place is to have a dependency to hazelcast-all (not hazelcast). This will release you from all these issues.

Infinite scan for fonts in Apache FOP on CentOS

I use Apache Batik to convert SVG into PDF in one of the projects. The project is Spring application running in Tomcat 7. Everything works OK on development machine which runs under Ubuntu with Tomcat being started using $CATALINA_HOME/bin/startup.sh. But when I try to run the app on production server with CentOS 6 and Tomcat started using service tomcat7 start command the app falls into infinite loop on convertation. I've tried to debug the problem and found this piece of code:
/**
* Creates the {#link FontInfo} instance for the given configuration.
* #param cfg the configuration
* #param useComplexScriptFeatures true if complex script features enabled
* #return the font collection
* #throws FOPException if an error occurs while setting up the fonts
*/
public static FontInfo createFontInfo(Configuration cfg, boolean useComplexScriptFeatures)
throws FOPException {
FontInfo fontInfo = new FontInfo();
final boolean strict = false;
if (cfg != null) {
URI thisUri = new File(".").getAbsoluteFile().toURI();
InternalResourceResolver resourceResolver
= ResourceResolverFactory.createDefaultInternalResourceResolver(thisUri);
//TODO The following could be optimized by retaining the FontManager somewhere
FontManager fontManager = new FontManager(resourceResolver, FontDetectorFactory.createDefault(),
FontCacheManagerFactory.createDefault());
//TODO Make use of fontBaseURL, font substitution and referencing configuration
//Requires a change to the expected configuration layout
DefaultFontConfig.DefaultFontConfigParser parser
= new DefaultFontConfig.DefaultFontConfigParser();
DefaultFontConfig fontInfoConfig = parser.parse(cfg, strict);
DefaultFontConfigurator fontInfoConfigurator
= new DefaultFontConfigurator(fontManager, null, strict);
List<EmbedFontInfo> fontInfoList = fontInfoConfigurator.configure(fontInfoConfig);
fontManager.saveCache();
FontSetup.setup(fontInfo, fontInfoList, resourceResolver, useComplexScriptFeatures);
} else {
FontSetup.setup(fontInfo, useComplexScriptFeatures);
}
return fontInfo;
}
in PDFDocumentGraphics2DConfigurator class. When I'm running the app on developer machine the line URI thisUri = new File(".").getAbsoluteFile().toURI(); results with thisUri being assigned with ~/tomcat/bin/. folder. When the app is running on production machine it is assigned with /. value. I think that this is the main problem because the value of thisUri is the folder in which FOP starts fonts search and on the production machine this is the root of file system and recursive search on the whole FS structure is very slow. I tried to add fop.xconf file to the WEB-INF directory with fonts configuration but it didn't affected the behavior of FOP. And I can't start Tomcat on production server the same way as I start on the dev machine.
Has anyone ideas on how to configure the base directory for font scan of FOR? Or am I doing something wrong?
I've found the workaround for the problem. I'm not sure if its wright or wrong to do stuff like that but it works. The workaround is based on the fact that File.getAbsolutFile() by default returns the directory resolved against the directory defined by user.dir option. So I need some way to pass this option on Tomcat service start. The only way I've found to do this is to add -Duser.dir=/%CATALINA_HOME/ to the CATALINA_OPTS variable defined in %CATALINA_HOME/bin/setenv.sh file. After that the font scan process took normal amount of time and my app started to work fine.

Jxbtrowser retrieve platform specific artefact at runtime

I'm writing an intelij plugin and would like to download the platform specific artefact at runtime.
I've loaded the platform specific jar into a class loader but the ChromiumExtractor cannot access the nested resources when prefixed with "/". So I can access the resource as "chromium-mac.zip" but the library cannot.
I've tried to unzip the nested zipped chromium artefact into the correct directory but this does not leading to a working solution. So now I've been trying to piece together the way the library extracts the artefact but it's rather tedious as the code is obfuscated.
Does the jxbrowser plugin have some support for retrieving the artefact at runtime. Could such support be added (jxbtrowser devs use SO for support questions etc, this is a message to them :D ) ?
Approach taken :
// inside intelij plugin . The plugin has the jxbrowser-6.6.jar
// and license.jar loaded into the classloader. the platform specific
// artefact will be retrieved manual).
val cl = URLClassLoader(arrayOf(URL("file://.../jxbrowser-mac-6.6.jar")), Browser::class.java.classLoader)
val backup = Thread.currentThread().contextClassLoader
try {
Thread.currentThread().contextClassLoader = cl
// can access like this
Thread.currentThread().contextClassLoader.getResource("chromium-mac.zip")
val ce = ChromiumExtractor.create()
// cannot access as resource is retrieved "/chromium-mac.zip" ?
ce.extract(BrowserPreferences.getChromiumDir())
browser = Browser()
} finally {
Thread.currentThread().contextClassLoader = backup
}
The following does the trick, The resource jar had to be in the same class loader as the client jar (as well as the license). It would be nice if JxBrowser added a helper for this that is capable of performing the download and initialising chromium, perhaps taking just a path for a persistent storage directory.
private fun initializeJxBrowser(): Browser {
if(ChromiumExtractor.create().shouldExtract(BrowserPreferences.getChromiumDir())) {
val cl = URLClassLoader(arrayOf(
URL("file:.../license.jar"),
URL("file:.../jxbrowser-mac-6.6.jar"),
URL("file:../jxbrowser-6.6.jar")
))
cl.loadClass("com.teamdev.jxbrowser.chromium.BrowserContext")
.getMethod("defaultContext")
.invoke(null)
}
return Browser()
}

two versions of solr on tomcat

I have got installed two versions of solr on Tomcat 6, 1.3 and 4.7 each of them are accessible but in the tomcat configuration's tab Java -Dsolr.solr.home=C:\Solr\solr where this path is the path of 1.3 However, I have the 4.7 on E:\new-solr.
When I try to create new core it created well but it disappeared after restarting Tomcat. I belive that the missing of correct Solr home is the reason. So, is there a way to set multiple solr home in Java properties of Tomcat?
Edit: When I run Tomcat with -Dsolr.solr.home=C:\Solr\solr I have got errors about missing cores in Solr 4.7 version where those
cores works fine in Solr 1.3.
SolrCore Initialization Failures archive: org.apache.solr.common.SolrException:org.apache.solr.common.SolrException:
Could not load config file c:\solr\solr\archive\solrconfig.xml
Looks like you are passing in the value of solr home using JAVA_OPTS. You need to edit server.xml and add the appropriate Solr home to Context. The following example is from SolrTomcat page on the Solr wiki.
<Context docBase="/opt/solr/example/solr/solr.war" debug="0" crossContext="true">
<Environment name="solr/home" type="java.lang.String" value="/opt/solr/example/solr" override="true"/>
</Context>
Solr's SolrResourceLoader#locateSolrHome first tries JNDI and then system properties to find its settings. As -D system properties are shared between all applications in a single Tomcat instance, those cannot be used to configure multiple Solr instances.
If for some reason one cannot use JNDI (like <Environment name="solr/home" ...> in XML files), or a single instance with multiple cores, then one could wrap the Solr WAR into one's own application and use a servlet context listener to (temporarily) change the system properties while starting.
This surely is a hack, and relies on Tomcat not starting applications in parallel, and on Solr only reading the system properties on startup. I've tested this to work nicely, but still it's probably only suitable for testing purposes.
Again, this first of all needs one to wrap Solr into one's own application. Next, create a folder /some/config with a sub folder for each instance, matching its context name. In each sub folder create a custom.properties file:
# This file /some/config/[servlet-context-name]/custom.properties is used
# if Tomcat is started with:
# -Dcustom.config.dir=/some/config
# ...and then (temporarily) overwrites any system properties specified below:
solr.solr.home=/some/other/folder/conf
solr.data.dir=/some/other/folder/data
To merge system properties based on some key-values in a properties file:
/**
* Tries to find the given file and merges its properties into the existing
* system properties.
*
* #param configFile
* full path of a property file
* #return {#code true} if the file was found and merged; {#code false}
* otherwise
*/
private boolean mergeSystemProperties(final String configFile) {
try (final FileInputStream is = new FileInputStream(configFile)) {
final Properties custom = new Properties();
custom.load(is);
for (final Map.Entry<Object, Object> prop : custom.entrySet()) {
LOG.info("Setting {}={}", prop.getKey(), prop.getValue());
System.setProperty((String)prop.getKey(), (String)prop.getValue());
}
return true;
} catch (final FileNotFoundException e) {
LOG.info("Could not find custom properties: {}", configFile);
} catch (final IOException e) {
LOG.error("Failed to read custom properties: " + configFile, e);
}
return false;
}
This can be used in a listener:
public class TestConfigContextListener implements ServletContextListener {
private static final Logger LOG = ...
private static final String PROP_DIR = "custom.config.dir";
private static final String FILE_NAME = "custom.properties";
#Override
public void contextInitialized(final ServletContextEvent event) {
final String configDir = System.getProperty(PROP_DIR);
if (configDir == null) {
LOG.info("No value for -D{}; not reading custom config", PROP_DIR);
} else {
LOG.info("Custom config dir: -D{}={}", PROP_DIR, configDir);
final ServletContext context = event.getServletContext();
// Either "" for the root, or "/some-path" otherwise
final String contextPath = context.getContextPath();
if (!contextPath.isEmpty()) {
if (mergeSystemProperties(configDir + File.separator
+ contextPath.substring(1, contextPath.length())
+ File.separator + FILE_NAME)) {
// We found the configuration in a subfolder matching the
// specific contextPath; done.
return;
}
}
// Root context, or no configuration in a subfolder matching the
// specific contextPath; try to load from configDir itself:
mergeSystemProperties(configDir + File.separator + FILE_NAME);
}
}
...
}
...with in web.xml:
<!--
Tries to read a property file to set/override system properties just before
Solr is initialized, sort of allowing to run multiple instances with
different settings, IF THEY ARE NOT STARTED SIMULTANEOUSLY.
-->
<listener>
<listener-class>net.example.TestConfigContextListener</listener-class>
</listener>
Final notes: though one could use <env-entry> in web.xml for "fake" JNDI entries, using ServletContext#setInitParameter to change those in the context listener has no effect. Also, the JNDI context java:comp/env is read-only hence cannot be changed from code. So one really needs to fall back to (temporarily) setting the system properties.

WSIT: JKS relative filepath

When creating a web service server using Netbeans, Maven, Metro and Tomcat, how can I use relative filepaths in the wsit configuration?
For example, I have this line inside the wsit file:
<sc:KeyStore wspp:visibility="private" location="SERVER_KeyStore.jks" type="JKS" storepass="*****" alias="*****"/>
where should I put the jks file so it matches that location?
Finally, I found the answer.
when providing the
keystore/trustore name and location in
the wsit-*.xml files, please note that
they'll be loaded as resources
scanning the META-INF directory in
your package (WEB-INF/classes/META-INF
when using war packages on JBoss
Application Server 5).
from JBossWS - Stack Metro User Guide
In my case that means adding a META-INF folder to my resources folder and add <include>**/*.jks</include> to the pom file.
I saw some number of questions for wsit security configuration, most of them deal with externalizing SSL configuration, rather than hardcoding into wsdl file. Just because there may be development and production environment, and all in all hardcoded configuration is bad anyway. I spend several days with this issue and found only some (often monstrous) hints here in stackoverflow and various other forums. But the solution turned to be not so complex indeed. I just leave it here for someone (it matches also original question, because it will allow having jks anywhere, also having external config file as well).
Say, you have wsit policy in your wsdl files like this:
<wsp1:Policy wsu:Id="MyBinding_IWebServicePolicy">
<wsp1:ExactlyOne>
<wsp1:All>
<sc:KeyStore wspp:visibility="private" type="JKS" storepass="pass" alias="some-alias" keypass="pass" location="keystore.jks"/>
<sc:TrustStore wspp:visibility="private" type="JKS" peeralias="other-alias" storepass="pass" location="truststore.jks"/>
</wsp1:All>
</wsp1:ExactlyOne>
</wsp1:Policy>
You need to use CallbackHandler instead.
Adjusted policy:
<wsp1:Policy wsu:Id="MyBinding_IWebServicePolicy">
<wsp1:ExactlyOne>
<wsp1:All>
<sc:KeyStore wspp:visibility="private" callbackHandler="com.my.KeyStoreHandler"/>
<sc:TrustStore wspp:visibility="private" callbackHandler="com.my.TrustStoreHandler"/>
</wsp1:All>
</wsp1:ExactlyOne>
</wsp1:Policy>
And handler might look like this (I use scala, but you may translate this to java easily):
import javax.security.auth.callback.{ CallbackHandler => ICallbackHandler, Callback }
import com.sun.xml.wss.impl.callback.{ KeyStoreCallback, PrivateKeyCallback }
import java.security.{ PrivateKey, KeyStore }
import java.io.FileInputStream
abstract class CallbackHandler extends ICallbackHandler {
def conf: Config // getting external configuration
def handle(callbacks: Array[Callback]): Unit = callbacks foreach {
// loads the keystore
case cb: KeyStoreCallback =>
val ks = KeyStore.getInstance(conf.getString("type"))
val is = new FileInputStream(conf.getString("file"))
try ks.load(is, conf.getString("store-password").toCharArray) finally is.close()
cb.setKeystore(ks)
// loads private key
case cb: PrivateKeyCallback =>
cb.setAlias(conf.getString("alias"))
cb.setKey(cb.getKeystore.getKey(conf.getString("alias"), conf.getString("key-password").toCharArray).asInstanceOf[PrivateKey])
// other things
case cb => // I didn't need anything else, but just in case
}
}
class TrustStoreHandler extends CallbackHandler {
lazy val conf = getMyTrustStoreConfig
}
class KeyStoreHandler extends CallbackHandler {
lazy val conf = getMyKeyStoreConfig
}
In java just use if (cb isinstanceof Class) instead of case cb: Class =>, the other code is practically java without semicolons.

Categories