I'm trying to get a effective model for a project and currently under the Maven Core API 3.0.3 (or 3.0.4) there's this method called build() which is neat BUT..
it requires too much like ModelNormalizer, ProfileInjector etc for it not to throw null pointer exception while building. There's so much things that need initialization before I can build the effective model and all the information I have is a filepath to the pom.xml
Anyone has work this out?
Here is sample code:
DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
ModelBuildingRequest req = new DefaultModelBuildingRequest();
req.setProcessPlugins(false);
req.setPomFile(file);
req.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
req.setModelResolver(new MyLocalModelResolver());
Model model = modelBuilder.build(req).getEffectiveModel();
Maybe this library can help: https://github.com/jenkinsci/lib-jenkins-maven-embedder
Or at least as a sample.
hth
Olivier
Yes it works for me. I have to set all data with default values. Very annoying...
I have created a MavenModelBuilder builder class for that
/**
* Default MavenModelBuilder.
*/
public class MavenModelBuilder extends DefaultModelBuilder {
/**
* Constructor
*/
public MavenModelBuilder() {
super();
ModelProcessor modelProcessor = new DefaultModelProcessor()
.setModelLocator(new DefaultModelLocator())
.setModelReader(new DefaultModelReader());
ModelInterpolator modelInterpolator = new StringSearchModelInterpolator()
.setPathTranslator(new DefaultPathTranslator())
.setUrlNormalizer(new DefaultUrlNormalizer());
setProfileSelector(new DefaultProfileSelector());
setModelProcessor(modelProcessor);
setModelValidator(new DefaultModelValidator());
setSuperPomProvider(new DefaultSuperPomProvider().setModelProcessor(modelProcessor));
setModelNormalizer(new DefaultModelNormalizer());
setInheritanceAssembler(new DefaultInheritanceAssembler());
setModelInterpolator(modelInterpolator);
setModelUrlNormalizer(new DefaultModelUrlNormalizer().setUrlNormalizer(new DefaultUrlNormalizer()));
setModelPathTranslator(new DefaultModelPathTranslator().setPathTranslator(new DefaultPathTranslator()));
setPluginManagementInjector(new DefaultPluginManagementInjector());
setLifecycleBindingsInjector(new DefaultLifecycleBindingsInjector());
setDependencyManagementInjector(new DefaultDependencyManagementInjector());
setReportConfigurationExpander(new DefaultReportConfigurationExpander());
setReportingConverter(new DefaultReportingConverter());
setPluginConfigurationExpander(new DefaultPluginConfigurationExpander());
setDependencyManagementImporter(new DefaultDependencyManagementImporter());
setProfileInjector(new DefaultProfileInjector());
}
}
And i call it like this
/**
* Parse the xml file to get the model of the xml file.
*
* #param path of the project
* #throws IllegalArgumentException : exception
*/
public static Model parseFile(String path) throws IllegalArgumentException {
DefaultModelBuilder builder = new MavenModelBuilder();
ModelBuildingRequest req = new DefaultModelBuildingRequest();
req.setProcessPlugins(false);
req.setPomFile(new File(path));
req.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
Model model = null;
try {
model = builder.build(req).getEffectiveModel();
} catch (ModelBuildingException e) {
e.printStackTrace();
}
return model;
}
Related
I am attempting to run the axl demo shown at 'https://developer.cisco.com/docs/axl/#!javajax-ws-quickstart'
I am a newbie to axl, eclipse, and java, and just trying to get my toes wet.
I have followed the instructions listed and the project only shows 1 error at line:
GetPhoneRes getPhoneResponse = axlPort.getPhone(axlParams);
The error type says AXL Error.
There are no other errors showing in the compiler.
Can anyone give me any ideas of what the problem could be, or how to chase it?
package com.cisco.axl.demo;
/**
* demo to pull basic phone info
*/
import javax.xml.ws.BindingProvider;
import com.cisco.axlapiservice.AXLAPIService;
import com.cisco.axlapiservice.AXLPort;
import com.cisco.axl.api._10.*;
/**
*
** #author t01136
** Performs Getphone using AXL API
** Service Consumers were generated by the java ?? wsimport command:
** wsimport -keep -b schema/current/AXLSOAP.xsd -Xnocompile -s src -d bin -verbose schema/current/AXLAPI.wsd
* and since AXL uses HTTPS, you will have to install the UC applications
* certificate into you keystore in order to run this sample app.
* You can run the program by CD'ing to the bin folder within this project
* C:\Users\t01136.POS\eclipse-workspace\axl-demo\bin
* and running the following command
* java -cp . com.cisco.axl.demo.Demo
*/
public class Demo {
/**
* UC app host.
*/
protected static String ucHost = null;
/**
* OS admin.
*/
protected static String ucAdmin = null;
/**
* OS admin password.
*/
protected static String ucPswd = null;
/**
* phoneName used in request.
*/
protected static String phoneName = null;
/**
* Run the demo
*
* #param args not used
*/
public static void main(String[] args) {
// Verify JVM has a console
if (System.console() == null) {
System.err.println("The Cisco AXL Sample App requires a console.");
System.exit(1);
} else {
Demo.informUser("%nWelcome to the Cisco AXL Sample APP .%n");
}
Demo demo = new Demo();
demo.getPhoneInfo();
}
/**
* get information about phone
*/
public void getPhoneInfo() {
// Ask for the UC application to upgrade
// Demo.informuser("%nWhat UC server would you like to access?%n");
ucHost = promptUser(" Host: ");
ucAdmin = promptUser(" OS Admin Account: ");
ucPswd = promptUser(" OS Admin Password: ");
// Ask for the phone name
Demo.informUser("%nEnter the name of the phone you want to retrieve information about.%n");
phoneName = promptUser(" Phone Name: ");
// Make the getPhoneRequest
getPhone();
}
//private String promptUser(String string) {
// // TODO Auto-generated method stub
// return null;
// }
/**
* Makes the getPhone request and displays some of the fields that are returned.
*/
private void getPhone() {
// Instantiate the wsimport generated AXL API Service client --
// see the wsimport comments in the class javadocs above
AXLAPIService axlService = new AXLAPIService();
AXLPort axlPort = axlService.getAXLPort();
// Set the URL, user, and password on the JAX-WS client
String validatorUrl = "https://"
+ Demo.ucHost
+ ":8443/axl/";
((BindingProvider) axlPort).getRequestContext().put(
BindingProvider.ENDPOINT_ADDRESS_PROPERTY, validatorUrl);
((BindingProvider) axlPort).getRequestContext().put(
BindingProvider.USERNAME_PROPERTY, Demo.ucAdmin);
((BindingProvider) axlPort).getRequestContext().put(
BindingProvider.PASSWORD_PROPERTY, Demo.ucPswd);
// Create a GetPhoneReq object to represent the getPhone request and set the name of the device
//to name entered by user
GetPhoneReq axlParams = new GetPhoneReq();
axlParams.setName(phoneName);
//Make a call to the AXL Service and pass the getPhone request
GetPhoneRes getPhoneResponse = axlPort.getPhone(axlParams);
//display information returned in the response to the user
Demo.informUser("Product=" + getPhoneResponse.getReturn().getPhone().getProduct() + "%n"
+ getPhoneResponse.getReturn().getPhone().getLoadInformation().getValue() + "%n");
}
// -------------------- Some I/O Helper Methods ------------------------
/**
* Provide the user some instructions.
*/
protected static void informUser(String info) {
System.console().format(info);
}
/**
* Ask the user a question
*/
protected static String promptUser(String question) {
String answer = null;
while (answer==null || answer.isEmpty() ) {
answer = System.console().readLine(question);
}
return answer.trim();
}
}
Actually I have also noticed
That contained the following code:
package com.cisco.axlapiservice;
import javax.xml.ws.WebFault;
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.1.6 in JDK 6
* Generated source version: 2.1
*
*/
#WebFault(name = "axlError", targetNamespace = "http://www.cisco.com/AXL/API/10.5")
public class AXLError
extends Exception
{
/**
* Java type that goes as soapenv:Fault detail element.
*
*/
private com.cisco.axl.api._10.AXLError faultInfo;
/**
*
* #param message
* #param faultInfo
*/
public AXLError(String message, com.cisco.axl.api._10.AXLError faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
/**
*
* #param message
* #param faultInfo
* #param cause
*/
public AXLError(String message, com.cisco.axl.api._10.AXLError faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
/**
*
* #return
* returns fault bean: com.cisco.axl.api._10.AXLError
*/
public com.cisco.axl.api._10.AXLError getFaultInfo() {
return faultInfo;
}
}
and when I look at the contents of 'getFaultInfo():AXLError' I see 'The serializable class AXLError does not declare a static final serialVersionUID field of type long', with 4 quick-fixes available.
But since all of that was part of the download from callmanager, I wouldnt think there would be an error in it.
Perhaps this will give someone a clue.
thanks
I'm getting the following error when I try to deserialize a previously serialized file:
Exception in thread "main" java.lang.ClassCastException:
com.ssgg.bioinfo.effect.Sample$Zygosity cannot be cast to
com.ssgg.bioinfo.effect.Sample$.Zygosity
at com.ssgg.ZygosityTest.deserializeZygosityToAvroStructure(ZygosityTest.java:45)
at com.ssgg.ZygosityTest.main(ZygosityTest.java:30)
In order to reproduce the error, the main class is as follows:
public class ZygosityTest {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String filepath = "/home/XXXX/zygosity.avro";
/* Populate Zygosity*/
com.ssgg.bioinfo.effect.Sample$.Zygosity zygosity = com.ssgg.bioinfo.effect.Sample$.Zygosity.HET;
/* Create file serialized */
createZygositySerialized(zygosity, filepath);
/* Deserializae file */
com.ssgg.bioinfo.effect.Sample$.Zygosity avroZygosityOutput = deserializeZygosityToAvroStructure(filepath);
}
private static com.ssgg.bioinfo.effect.Sample$.Zygosity deserializeZygosityToAvroStructure(String filepath)
throws IOException {
com.ssgg.bioinfo.effect.Sample$.Zygosity zygosity = null;
File myFile = new File(filepath);
DatumReader<com.ssgg.bioinfo.effect.Sample$.Zygosity> reader = new SpecificDatumReader<com.ssgg.bioinfo.effect.Sample$.Zygosity>(
com.ssgg.bioinfo.effect.Sample$.Zygosity.class);
DataFileReader<com.ssgg.bioinfo.effect.Sample$.Zygosity> dataFileReader = new DataFileReader<com.ssgg.bioinfo.effect.Sample$.Zygosity>(
myFile, reader);
while (dataFileReader.hasNext()) {
zygosity = dataFileReader.next(zygosity);
}
dataFileReader.close();
return zygosity;
}
private static void createZygositySerialized(com.ssgg.bioinfo.effect.Sample$.Zygosity zygosity, String filepath)
throws IOException {
DatumWriter<com.ssgg.bioinfo.effect.Sample$.Zygosity> datumWriter = new SpecificDatumWriter<com.ssgg.bioinfo.effect.Sample$.Zygosity>(
com.ssgg.bioinfo.effect.Sample$.Zygosity.class);
DataFileWriter<com.ssgg.bioinfo.effect.Sample$.Zygosity> fileWriter = new DataFileWriter<com.ssgg.bioinfo.effect.Sample$.Zygosity>(
datumWriter);
Schema schema = com.ssgg.bioinfo.effect.Sample$.Zygosity.getClassSchema();
fileWriter.create(schema, new File(filepath));
fileWriter.append(zygosity);
fileWriter.close();
}
}
The avro generated enum for Zygosity is as follows:
/**
* Autogenerated by Avro
*
* DO NOT EDIT DIRECTLY
*/
package com.ssgg.bioinfo.effect.Sample$;
#SuppressWarnings("all")
#org.apache.avro.specific.AvroGenerated
public enum Zygosity {
HOM_REF, HET, HOM_VAR, HEMI, UNK ;
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"enum\",\"name\":\"Zygosity\",\"namespace\":\"com.ssgg.bioinfo.effect.Sample$\",\"symbols\":[\"HOM_REF\",\"HET\",\"HOM_VAR\",\"HEMI\",\"UNK\"]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
}
I'm a newbie in Avro, can somebody plese help me to find the problem?
In my project I try to serialize and deserialize a bigger structure, but I have problems with the enums, so I isolated a smaller problem here.
If you need more info I can post it.
Thanks.
I believe the main issue here is that $ has a special meaning in Java classes, and less important is that package names are typically lowercased.
So, you should at least edit the namespaces to remove the $
I'd like to allow users to add/refresh/update/remove modules in the main project without the need of restart or redeploy. Users will be able to code their own modules and add them in the main project.
Technicaly, a module will be a JAR which may be "hot-started" and may contain :
spring controllers
services, ejbs...
resources (jsps, css, images, javascripts...)
So, when the user adds a module, the application have to register controllers, services, ejbs and map resources as intend. When he removes, the application unloads them.
Easy to say. Actually seems a lot more difficult to do.
Currently, I did it using Servlet 3.0 and web-fragment.xml. The main issue is that I have to redeploy everytime I update a module. I need to avoid that.
I read some docs about OSGi but I don't understand how I can link it with my project neither how It can load/unload on demand.
Can someone lead me to a solution or an idea?
What I use :
Glassfish 3.1.2
Spring MVC 3.1.3
Spring Security 3.1.3
Thanks.
EDIT:
I can now say that it is possible. Here's the way I will do :
Add module :
Upload the module.jar
Handle the file, expand in a module folder
Close Spring application context
Load JAR in a custom classloader where parent is WebappClassLoader
Copy resources in the main project (maybe it will be possible to find alternative, I hope but currently, this should work)
Refresh Spring application context
Remove module :
Close Spring application context
Unbind custom classloader and let it go to GC
Remove resources
Remove files from the module folder + jar if kept
Refresh Spring application context
For each, Spring have to scan another folder than
domains/domain1/project/WEB-INF/classes
domains/domain1/project/WEB-INF/lib
domains/domain1/lib/classes
And that's actually my current issue.
Technicaly, I found PathMatchingResourcePatternResolver and ClassPathScanningCandidateComponentProvider was involved. Now I need to tell them to scan specific folder/classes.
For the rest, I already did some tests and it should work as intended.
One point which will not be possible : ejbs in the jar.
I'll post some sources when I'd have done something usable.
Ok, I did it but I have really too much sources to post it here. I will explain step by step how I did but won't post the classloading part which is simple for an average skilled developper.
One thing is currently not supported by my code is the context config scan.
First, the explanation below depends on your needs and also your application server. I use Glassfish 3.1.2 and I did not find how to configure a custom classpath :
classpath prefix/suffix not supported anymore
-classpath parameter on the domain's java-config did not work
CLASSPATH environment did not work either
So the only available paths in classpath for GF3 are : WEB-INF/classes, WEB-INF/lib... If you find a way to do it on your application server, you can skip the first 4 steps.
I know this is possible with Tomcat.
Step 1 : Create a custom namespace handler
Create a custom NamespaceHandlerSupport with its XSD, spring.handlers and spring.schemas. This namespace handler will contain a redefinition of <context:component-scan/>.
/**
* Redefine {#code component-scan} to scan the module folder in addition to classpath
* #author Ludovic Guillaume
*/
public class ModuleContextNamespaceHandler extends NamespaceHandlerSupport {
#Override
public void init() {
registerBeanDefinitionParser("component-scan", new ModuleComponentScanBeanDefinitionParser());
}
}
The XSD contains only component-scan element which is a perfect copy of the Spring's one.
spring.handlers
http\://www.yourwebsite.com/schema/context=com.yourpackage.module.spring.context.config.ModuleContextNamespaceHandler
spring.schemas
http\://www.yourwebsite.com/schema/context/module-context.xsd=com/yourpackage/module/xsd/module-context.xsd
N.B.: I didn't override the Spring default namespace handler due to some issues like the name of the project which need to have a letter greater than 'S'. I wanted to avoid that so I made my own namespace.
Step 2 : Create the parser
This will be initialized by the namespace handler created above.
/**
* Parser for the {#code <module-context:component-scan/>} element.
* #author Ludovic Guillaume
*/
public class ModuleComponentScanBeanDefinitionParser extends ComponentScanBeanDefinitionParser {
#Override
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
return new ModuleBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
}
}
Step 3 : Create the scanner
Here's the custom scanner which uses the same code as ClassPathBeanDefinitionScanner. The only code changed is String packageSearchPath = "file:" + ModuleManager.getExpandedModulesFolder() + "/**/*.class";.
ModuleManager.getExpandedModulesFolder() contains an absolute url. e.g.: C:/<project>/modules/.
/**
* Custom scanner that detects bean candidates on the classpath (through {#link ClassPathBeanDefinitionScanner} and on the module folder.
* #author Ludovic Guillaume
*/
public class ModuleBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
private ResourcePatternResolver resourcePatternResolver;
private MetadataReaderFactory metadataReaderFactory;
/**
* #see {#link ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner(BeanDefinitionRegistry, boolean)}
* #param registry
* #param useDefaultFilters
*/
public ModuleBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
try {
// get parent class variable
resourcePatternResolver = (ResourcePatternResolver)getResourceLoader();
// not defined as protected and no getter... so reflection to get it
Field field = ClassPathScanningCandidateComponentProvider.class.getDeclaredField("metadataReaderFactory");
field.setAccessible(true);
metadataReaderFactory = (MetadataReaderFactory)field.get(this);
field.setAccessible(false);
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Scan the class path for candidate components.<br/>
* Include the expanded modules folder {#link ModuleManager#getExpandedModulesFolder()}.
* #param basePackage the package to check for annotated classes
* #return a corresponding Set of autodetected bean definitions
*/
#Override
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(super.findCandidateComponents(basePackage));
logger.debug("Scanning for candidates in module path");
try {
String packageSearchPath = "file:" + ModuleManager.getExpandedModulesFolder() + "/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
}
Step 4 : Create a custom resource caching implementation
This will allow Spring to resolve your module classes out of the classpath.
public class ModuleCachingMetadataReaderFactory extends CachingMetadataReaderFactory {
private Log logger = LogFactory.getLog(ModuleCachingMetadataReaderFactory.class);
#Override
public MetadataReader getMetadataReader(String className) throws IOException {
List<Module> modules = ModuleManager.getStartedModules();
logger.debug("Checking if " + className + " is contained in loaded modules");
for (Module module : modules) {
if (className.startsWith(module.getPackageName())) {
String resourcePath = module.getExpandedJarFolder().getAbsolutePath() + "/" + ClassUtils.convertClassNameToResourcePath(className) + ".class";
File file = new File(resourcePath);
if (file.exists()) {
logger.debug("Yes it is, returning MetadataReader of this class");
return getMetadataReader(getResourceLoader().getResource("file:" + resourcePath));
}
}
}
return super.getMetadataReader(className);
}
}
And define it in the bean configuration :
<bean id="customCachingMetadataReaderFactory" class="com.yourpackage.module.spring.core.type.classreading.ModuleCachingMetadataReaderFactory"/>
<bean name="org.springframework.context.annotation.internalConfigurationAnnotationProcessor"
class="org.springframework.context.annotation.ConfigurationClassPostProcessor">
<property name="metadataReaderFactory" ref="customCachingMetadataReaderFactory"/>
</bean>
Step 5 : Create a custom root classloader, module classloader and module manager
This is the part I won't post classes. All classloaders extend URLClassLoader.
Root classloader
I did mine as singleton so it can :
initialize itself
destroy
loadClass (modules classes, parent classes, self classes)
The most important part is loadClass which will allow context to load your modules classes after using setCurrentClassLoader(XmlWebApplicationContext) (see bottom of the next step). Concretly, this method will scan the children classloader (which I personaly store in my module manager) and if not found, it will scan parent/self classes.
Module classloader
This classloader simply adds the module.jar and the .jar it contains as url.
Module manager
This class can load/start/stop/unload your modules. I did like this :
load : store a Module class which represent the module.jar (contains id, name, description, file...)
start : expand the jar, create module classloader and assign it to the Module class
stop : remove the expanded jar, dispose classloader
unload : dispose Module class
Step 6 : Define a class which will help to do context refreshs
I named this class WebApplicationUtils. It contains a reference to the dispatcher servlet (see step 7). As you will see, refreshContext call methods on AppClassLoader which is actually my root classloader.
/**
* Refresh {#link DispatcherServlet}
* #return true if refreshed, false if not
* #throws RuntimeException
*/
private static boolean refreshDispatcherServlet() throws RuntimeException {
if (dispatcherServlet != null) {
dispatcherServlet.refresh();
return true;
}
return false;
}
/**
* Refresh the given {#link XmlWebApplicationContext}.<br>
* Call {#link Module#onStarted()} after context refreshed.<br>
* Unload started modules on {#link RuntimeException}.
* #param context Application context
* #param startedModules Started modules
* #throws RuntimeException
*/
public static void refreshContext(XmlWebApplicationContext context, Module[] startedModules) throws RuntimeException {
try {
logger.debug("Closing web application context");
context.stop();
context.close();
AppClassLoader.destroyInstance();
setCurrentClassLoader(context);
logger.debug("Refreshing web application context");
context.refresh();
setCurrentClassLoader(context);
AppClassLoader.setThreadsToNewClassLoader();
refreshDispatcherServlet();
if (startedModules != null) {
for (Module module : startedModules) {
module.onStarted();
}
}
}
catch (RuntimeException e) {
for (Module module : startedModules) {
try {
ModuleManager.stopModule(module.getId());
}
catch (IOException e2) {
e.printStackTrace();
}
}
throw e;
}
}
/**
* Set the current classloader to the {#link XmlWebApplicationContext} and {#link Thread#currentThread()}.
* #param context ApplicationContext
*/
public static void setCurrentClassLoader(XmlWebApplicationContext context) {
context.setClassLoader(AppClassLoader.getInstance());
Thread.currentThread().setContextClassLoader(AppClassLoader.getInstance());
}
Step 7 : Define a custom context loader listener
/**
* Initialize/destroy ModuleManager on context init/destroy
* #see {#link ContextLoaderListener}
* #author Ludovic Guillaume
*/
public class ModuleContextLoaderListener extends ContextLoaderListener {
public ModuleContextLoaderListener() {
super();
}
#Override
public void contextInitialized(ServletContextEvent event) {
// initialize ModuleManager, which will scan the given folder
// TODO: param in web.xml
ModuleManager.init(event.getServletContext().getRealPath("WEB-INF"), "/dev/temp/modules");
super.contextInitialized(event);
}
#Override
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
XmlWebApplicationContext context = (XmlWebApplicationContext)super.createWebApplicationContext(sc);
// set the current classloader
WebApplicationUtils.setCurrentClassLoader(context);
return context;
}
#Override
public void contextDestroyed(ServletContextEvent event) {
super.contextDestroyed(event);
// destroy ModuleManager, dispose every module classloaders
ModuleManager.destroy();
}
}
web.xml
<listener>
<listener-class>com.yourpackage.module.spring.context.ModuleContextLoaderListener</listener-class>
</listener>
Step 8 : Define a custom dispatcher servlet
/**
* Only used to keep the {#link DispatcherServlet} easily accessible by {#link WebApplicationUtils}.
* #author Ludovic Guillaume
*/
public class ModuleDispatcherServlet extends DispatcherServlet {
private static final long serialVersionUID = 1L;
public ModuleDispatcherServlet() {
WebApplicationUtils.setDispatcherServlet(this);
}
}
web.xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>com.yourpackage.module.spring.web.servlet.ModuleDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Step 9 : Define a custom Jstl view
This part is 'optional' but it brings some clarity and cleanness in the controller implementation.
/**
* Used to handle module {#link ModelAndView}.<br/><br/>
* <b>Usage:</b><br/>{#code new ModuleAndView("module:MODULE_NAME.jar:LOCATION");}<br/><br/>
* <b>Example:</b><br/>{#code new ModuleAndView("module:test-module.jar:views/testModule");}
* #see JstlView
* #author Ludovic Guillaume
*/
public class ModuleJstlView extends JstlView {
#Override
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {
String beanName = getBeanName();
// checks if it starts
if (beanName.startsWith("module:")) {
String[] values = beanName.split(":");
String location = String.format("/%s%s/WEB-INF/%s", ModuleManager.CONTEXT_ROOT_MODULES_FOLDER, values[1], values[2]);
setUrl(getUrl().replaceAll(beanName, location));
}
return super.prepareForRendering(request, response);
}
}
Define it in the bean config :
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="com.yourpackage.module.spring.web.servlet.view.ModuleJstlView"
p:prefix="/WEB-INF/"
p:suffix=".jsp"/>
Final step
Now you just need to create a module, interface it with ModuleManager and add resources in the WEB-INF/ folder.
After that you can call load/start/stop/unload. I personaly refresh the context after each operation except for load.
The code is probably optimizable (ModuleManager as singleton e.g.) and there's maybe a better alternative (though I did not find it).
My next goal is to scan a module context config which shouldn't be so difficult.
I am trying to use FreeMarker to render some templates that come from a CMS path that happens to include a symbolic link (under Linux). Our CMS code handles the path to the template so, for example, this path:
/var/cms/live/display/main.html
really points to:
/var/cms/trunk/127/display/main.html
/var/cms/live is the base-directory while /display/main.html is the path.
In my case, live is a symbolic link -- in this case to trunk/127. FYI: the trunk is our SVN branch. When our CMS system downloads a new release of CMS files as (for example) trunk-128.zip, it unpacks it into trunk/128 and then changes the symlink (atomically) to trunk/128. Great.
The problem is that FreeMarker seems to have cached the trunk/127 path. It doesn't recognize that the file /var/cms/live/display/main.html has been updated and if the trunk/127 tree is removed, it generates a 500 error.
500 Unable to load template: /display/main.html
How can I get FreeMarker to cache the proper path?
The problem turned out to be with FreeMarker's FileTemplateLoader class. It does a baseDir.getCanonicalFile(...) call on the base-directory passed into the constructor. When our application booted, the base directory /var/cms/live gets resolved into the real path /var/cms/trunk/127/ by getCanonicalFile(...) so any future changes to the symlink are ignored.
It does this in the constructor, so we were forced to create our own LocalFileTemplateLoader which is listed below.
It is just a basic spring loaded implementation of TemplateLoader. Then when we are building our FreeMarker Configuration we set the template loader:
Configuration config = new Configuration();
LocalTemplateLoader loader = new LocalTemplateLoader();
// this is designed for spring
loader.setBaseDir("/var/cms/live");
config.setTemplateLoader(loader);
...
Here is our LocalFileTemplateLoader code. Full class on pastebin:
public class LocalFileTemplateLoader implements TemplateLoader {
public File baseDir;
#Override
public Object findTemplateSource(String name) {
File source = new File(baseDir, name);
if (source.isFile()) {
return source;
} else {
return null;
}
}
#Override
public long getLastModified(Object templateSource) {
if (templateSource instanceof File) {
return new Long(((File) templateSource).lastModified());
} else {
throw new IllegalArgumentException("templateSource is an unknown type: " + templateSource.getClass());
}
}
#Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
if (templateSource instanceof File) {
return new InputStreamReader(new FileInputStream((File) templateSource), encoding);
} else {
throw new IllegalArgumentException("templateSource is an unknown type: " + templateSource.getClass());
}
}
#Override
public void closeTemplateSource(Object templateSource) {
// noop
}
#Required
public void setBaseDir(File baseDir) {
this.baseDir = baseDir;
// it may not exist yet because CMS is going to download and create it
}
}
A videoconferencing project I was working on used JMF to capture video and audio, and transmit it to another endpoint. An issue was that my team didn't want the user of the product to have to install JMF.
I thought it might be worthwhile to share our solution to this problem. It works. It works well. My question to you is: does anyone have a better way to do it?
Environment: Windows, XP and above
Download JMF for Windows
Install it on your machine
Locate the following dlls in the system32 folder after jmf installs:
jmacm.dll
jmam.dll
jmcvid.dll
jmdaud.dll
jmdaudc.dll
jmddraw.dll
jmfjawt.dll
jmg723.dll
jmgdi.dll
jmgsm.dll
jmh261.dll
jmh263enc.dll
jmjpeg.dll
jmmci.dll
jmmpa.dll
jmmpegv.dll
jmutil.dll
jmvcm.dll
jmvfw.dll
jmvh263.dll
jsound.dll
Copy the dlls into a temporary folder
Locate the jmf.properties file (Do a search on your computer for it)
Download the JMF source code
In the source code, find the following files:
JMFinit.java
JMRPropertiesGen.java
Registry.java
RegistryGen.java
Create a package; I'll call it JMFNoInstall
Add the files listed in step 6
Add a class called Main to this package as such:
package JMFNoInstall;
// add your imports and whatnot here
public class Main()
{
public Main()
{
JMFinit.main(null);
JMFPropertiesGen.main(null);
Registry.main(null);
RegistryGen.main(new String[] {
new File(".").getAbsolutePath(),
"registrylib"
});
}
}
The jmf.properties file needs to go in the same folder as the class that has your main method or the same folder as the JAR archive that contains the main method.
The dlls need to go into the win32 folder. You can have your program check to see if they are in the win32 folder. If they are not, you can have it copy them over from some location. The jmf.properties file gets updated whenever the the Main class listed above runs. You only need to run this once, the first time the program is ever run, or if the user would like to add new capture devices. Lastly, just make sure the jmf.jar file and jmfcom.jar that comes along with the Windows JMF download is included in the classpath. You're good to go at this point. All the functionality of JMF without actually having to install it.
There really isn't a lot of work involved with this, and you can incorporate it into your custom installer quite easily.
Has anyone found a better way to do this though? There are a few pitfalls of doing it this way.
EDIT: I thought it might be worthwhile to share some of the code that I created. Of course youll need to modify it to handle what you. It prob wont compile, but the stuff that is missing should be easy enough to recreate. But thought it might be a good starting point to help people. The detectCaptureDevices function is probably what will help most people. Ill update this class as I go.
import GUI.Window;
import GlobalUtilities.OS;
import GlobalUtilities.ProgressBar;
import GlobalUtilities.FileUtilities;
import java.io.File;
import java.util.ArrayList;
import java.util.Vector;
import javax.swing.text.Utilities;
/**
* This class providex easy access to the most needed info about JMF. You can test
* a JMF install (Windows only currently) and also get info about the captrue
* devices hooked up to JMF.
* #author dvargo
*/
public class JMFRunner
{
/**
* Show the status of operations
*/
final ProgressBar theBar = new ProgressBar();
/**
* Location where the dll's JMF relies on need to be placed
*/
final String windowsDllFolder = "C:\\WINDOWS\\system32\\";
final String linuxDllFolder = "/usr/lib/";
/**
* Dll's that JMF uses
*/
final String[] windowsDllList = new String[]{
"jmacm.dll",
"jmam.dll",
"jmcvid.dll",
"jmdaud.dll",
"jmdaudc.dll",
"jmddraw.dll",
"jmfjawt.dll",
"jmg723.dll",
"jmgdi.dll",
"jmgsm.dll",
"jmh261.dll",
"jmh263enc.dll",
"jmjpeg.dll",
"jmmci.dll",
"jmmpa.dll",
"jmmpegv.dll",
"jmutil.dll",
"jmvcm.dll",
"jmvfw.dll",
"jmvh263.dll",
"jsound.dll"};
String[] linuxDllList = new String[]{
"libjmcvid.so",
"libjmdaud.so",
"libjmfjawt.so",
"libjmg723.so",
"libjmgsm.so",
"libjmh261.so",
"libjmh263enc.so",
"libjmjpeg.so",
"libjmmpa.so",
"libjmmpegv.so",
"libjmmpx.so",
"libjmutil.so",
"libjmv4l.so",
"libjmxlib.so"
};
String [] dlls= null;
String dir = null;
/**
* List of the video capture devices found by JMF
*/
Vector videoDevices = null;
/**
* List of the audio capture devices found by JMF
*/
Vector audioDevices = null;
public JMFRunner()
{
if(OS.isWindows())
{
dlls = windowsDllList;
dir = windowsDllFolder;
}
else if(OS.isLinux())
{
dlls = linuxDllList;
dir = linuxDllFolder;
}
else
{
Window.getLogger().severe("Operating system does not support JMF");
}
}
/**
* Adds new capture devices
*/
public void detectCaptureDecives()
{
Thread theTread = new Thread(theBar);
theTread.start();
theBar.repaint();
JMFInit.main(new String[] {""});
JMFPropertiesGen.main(new String[] {""});
Registry.main(new String[] {""});
RegistryGen.main(new String[] {"-d",
new File(".").getAbsolutePath(),
"registrylib"
});
theBar.setMessage("");
theBar.stop();
}
/**
* Verifies that all the dll's that JMF needs are in their correct spot
* #return True if all dlls are in their correct spot, false otherwise
*/
public boolean detectDlls()
{
boolean retVal = true;
String currFile;
for(String currDll : dlls)
{
currFile = dir + currDll;
if(! new File(currFile).exists())
{
Window.getLogger().severe("Can not find dll " + currFile + " for JMF");
retVal = false;
}
}
return retVal;
}
//Doesnt work quite yet
public boolean installLibraryFiles()
{
boolean retVal = true;
String currFile;
for(String currDll : dlls)
{
currFile = dir + currDll;
File newDll = new File(currFile);
//see if this dll is already there
if(!newDll.exists())
{
//its not there so lets copy it
try
{
FileUtilities.copy(newDll,FileUtilities.getResourceFile("/JMFManager/Resources/"+currDll,currDll));
}
catch(Exception e)
{
retVal = false;
}
}
}
return retVal;
}
/**
* Returns the location of the jmf.properties file that STix is using
* #return THe locaiton of the JMF properties
*/
public String getJMFPropertiesFileLocation()
{
return Registry.getJMFPropertiesFileLocation();
}
/**
* Returns a list of the audio devices found by JMF
* #return Returns an Arraylist containing info about the audio capture devices
*/
public ArrayList getAudioDevices()
{
DeviceFinder df = new DeviceFinder();
audioDevices = df.getSoundCaptureDevices();
return new ArrayList(audioDevices);
}
/**
* Returns a list of the video decives deteced by JMF
* #return returns an arraylist with info of the video capture devices
*/
public ArrayList getVideoDevices()
{
DeviceFinder df = new DeviceFinder();
videoDevices = df.getVideoCaptureDevices();
return new ArrayList(videoDevices);
}
public static void main(String [] args)
{
JMFRunner x = new JMFRunner();
//x.detectCaptureDecives();
x.installLibraryFiles();
System.out.println(x.detectDlls());
System.out.println(x.getJMFPropertiesFileLocation());
System.out.println(x.getAudioDevices());
System.out.println(x.getVideoDevices());
}
}
DeviceFinder.java
import java.util.Vector;
import javax.media.*;
import javax.media.format.*;
/**
* this class gets information about capture devices (mics and cameras)
*/
public class DeviceFinder {
Vector videoDevices = new Vector();
Vector audioDevices = new Vector();
/**
* Constructor
* Creates a new DeviceFinder
*/
public DeviceFinder()
{
/*retrieve ALL video and audio devices*/
videoDevices = CaptureDeviceManager.getDeviceList(new VideoFormat(null));
audioDevices = CaptureDeviceManager.getDeviceList(new AudioFormat(null));
}
/**
* purpose: Get information on all Video capture devices on the system
* #return java.util.Vector a vector of attributes
*/
public Vector getVideoCaptureDevices()
{
return videoDevices;
}
/**
* purpose: Get information on all audio capture devices on the system
* #return java.util.Vector a vector of attributes
*/
public Vector getSoundCaptureDevices()
{
return audioDevices;
}
/**
* retrieve the first video capture device
*/
public CaptureDeviceInfo getPrimaryVideoCaptureDevice()
{
return (CaptureDeviceInfo)videoDevices.get(0);
}
/*retrieve the first audio capture device*/
public CaptureDeviceInfo getPrimaryAudioCaptureDevice()
{
return (CaptureDeviceInfo)audioDevices.get(0);
}
/**
* get the first video device name
* #return String the name of the video device
*/
public String getVideoCaptureDeviceName()
{
return ((CaptureDeviceInfo)videoDevices.get(0)).getName();
}
/**
* get the first audio device name
* #return String the name of the audio device
*/
public String getAudioCaptureDeviceName()
{
return ((CaptureDeviceInfo)audioDevices.get(0)).getName();
}
/**
* get the first video device media locator
* #return MediaLocator
*/
public MediaLocator getVideoMediaLocator()
{
return ((CaptureDeviceInfo)videoDevices.get(0)).getLocator();
}
/**
* get the first audio device media locator
* #return MediaLocator
*/
public MediaLocator getAudioMediaLocator()
{
return ((CaptureDeviceInfo)audioDevices.get(0)).getLocator();
}
/**
* get the video device media locator at index idx
* #param idx index of the media locator (0 is the first/default,
* as ordered by
* the JMFRegistry)
* #return MediaLocator
*/
public MediaLocator getVideoMediaLocator(int idx)
{
if(idx >= videoDevices.size())
{
return null;
}
return ((CaptureDeviceInfo)videoDevices.get(idx)).getLocator();
}
/**
* get the audio device media locator at index idx
* #param idx index of the audio device (as ordered by the JMFRegistry)
* #return MediaLocator
*/
public MediaLocator getAudioMediaLocator(int idx)
{
return ((CaptureDeviceInfo)audioDevices.get(idx)).getLocator();
}
/**
*
* #param args
*/
public static void main(String[] args)
{
DeviceFinder df = new DeviceFinder();
//DEBUG:
System.out.println(df.getVideoMediaLocator());
System.out.println(df.getAudioMediaLocator());
}
}
I don't think there is a better way. Unless the DLLs are explicitly loaded by path name, you would just need to make sure they are in the system path, so if they lived right next to the JVM executables it should also work. Windows does implicitly include the directory the program was started from in the system path, so that is another potential location.
Installers are a double edged sword, they make it easy to add new functionality and remove it later, but they also make it harder to deploy solutions that use the product.
One of the nice things about Java in general is that you don't have to install it for it to work. Essentially, once you perform the install of the JRE on one system, you can bundle it up and use it on another system as a zip file. Java doesn't need to explicitly register the DLLs because it loads them dynamically as needed.