Modular Spring-based application - java

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.

Related

How to find the path of properties file dynamically from the code?

I have maven project in Java in which I have a property file (quartz.properties) under this directory:
/src/main/resources
Now I can use this property file in two ways from this class as shown below:
/**
* Create a StdSchedulerFactory that has been initialized via
* <code>{#link #initialize(Properties)}</code>.
*
* #see #initialize(Properties)
*/
public StdSchedulerFactory(Properties props) throws SchedulerException {
initialize(props);
}
/**
* Create a StdSchedulerFactory that has been initialized via
* <code>{#link #initialize(String)}</code>.
*
* #see #initialize(String)
*/
public StdSchedulerFactory(String fileName) throws SchedulerException {
initialize(fileName);
}
I am not sure how can I use StdSchedulerFactory class to provide the path of my quartz.properties file.
As of now I am providing hardcoded path like this but this is not the right way to provide the path since if anyone else is running this code in their desktop or laptop then it will not work. I will be running this application from my desktop and also I will be making a runnable jar as well so I want that my program should load my properties file dynamically without any hardcoded path.
public class TestingQuartz {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory(
"C:\\workspace\\tester_quartz\\quartzmain\\src\\main\\resources\\quartz.properties");
Scheduler scheduler = factory.getScheduler();
scheduler.start();
}
}
As your configuration file is in src/main/resources of a mavenized project, it will be embedded in the resulting artifact (jar, war...) you build with maven. Thus you should load the file "from the classpath" like this :
StdSchedulerFactory factory = new StdSchedulerFactory();
factory.initialize(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
Since it appears to be on your class path you could do this:
getClass().getClassLoader().getResource("quartz.properties").toExternalForm()

Liquibase execution order of changeset files when using includeAll with classpath*:

I am using liquibase (3.1.1) in a spring environment (3.2.x) and load the changesets via the inlcudeAll tag in a master file. There I use the "classpath*:/package/to/changesets" as path.
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<includeAll path="classpath*:/package/to/changesets"/>...
I use a naming strategy like "nnn_changesetname.xml" to keep ordering. But when I look into the changeset table this ordering via the filenames are not kept. Is this only working, if the changeset files are contained in a directory and not on the classpath?
Update
Hi, I found out that the below suggested solution is not enough. I think it lies in the implementation how liquibase resolves the includAll attribute. In my case it first resolves all "folders" and then looks into each folder for changeset xmls. This will break the ordering of the xml files in all classpath*:/changes locations, because there are now several "changes" folders in different locations. What I would suspect in such a case is a merge of all contents of this "virtual" classpath folders and loading of all resources in one enumeration. Or we could allow some resouce pattern in the inlcudeAll tag like resources="classpath*:/changes/*.xml" to directly select all needed files (tried it out with the path attribute, but did not work, because it checks for a folder)?
Update
I made a hack to check if the ordering in the returned enumeration is preserved with the anwser from below. To achive this I checked for the given package name and if it matches my pattern I added an additional "*.xml" to it. With this extension I get all changeset as needed.
#Override
public Enumeration<URL> getResources(String packageName)
throws IOException {
if(packageName.equals("classpath*:/plugin/liquibase/changes/")) {
packageName = packageName + "*.xml";
}
List<URL> resources = Collections.list(super.getResources(packageName));
Collections.sort(resources, new Comparator<URL>() {
#Override
public int compare(URL url1, URL url2) {
String path1 = FilenameUtils.getName(url1.getPath());
String path2 = FilenameUtils.getName(url2.getPath());
return String.CASE_INSENSITIVE_ORDER.compare(path1, path2);
}
});
logger.info("Found resources: {}", resources);
return Collections.enumeration(resources);
}};
In the log I can see now that the resources have the correct order. But when I look into the table DATABASECHANGELOCK it does not reflect the order I had in the enumeration. So it seems that this values get reodered somewhere else.
Update
Analyzed the code furhter and found out that the class liquibase.parser.core.xml.XMLChangeLogSAXHandler makes a reordering of the returned enumeration. So my changes will have no effect. I do not think that I can hack into this class as well.
You are right, Liquibase is relying on the underlying "list files" logic which orders files alphabetically through the file system but apparently does not through classpaths.
I created https://liquibase.jira.com/browse/CORE-1843 to track the fix.
For now, if you configure spring with a subclass of liquibase.integration.spring.SpringLiquibase that overrides getResources(String packageName) with a method that sorts the returned Enumeration that should resolve the problem for you.
So after some thinking and one night of sleep I came up with the following hack to guarantee order of the loaded changelog files via classpath pattern classpath*:/my/path/to/changelog/*.xml . The idea is to create the main changelog file on the fly via dom manipulation, when liquibase requests it.
It only works for the main changelog file. Following prerequisite:
The pattern can only be used for the main changelog file
I use an empty master changelog file as template
All other changelog files have to use the normal allowed loading mechanism
Works only in an Spring environment
First I had to extend/overwrite the liquibase.integration.spring.SpringLiquibase with my implementation.
public class MySpringLiquibase extends SpringLiquibase {
private static final Logger logger = LoggerFactory.getLogger(MySpringLiquibase.class);
private ApplicationContext context;
private String changeLogLocationPattern;
private List<String> changeLogLocations;
#Autowired
public void setContext(ApplicationContext context) {
this.context = context;
}
/**
* Location pattern to search for changelog files.
*
* #param changeLogLocationPattern
*/
public void setChangeLogLocationPattern(String changeLogLocationPattern) {
this.changeLogLocationPattern = changeLogLocationPattern;
}
#Override
public void afterPropertiesSet() throws LiquibaseException {
try {
changeLogLocations = new ArrayList<String>();
// retrieve all changelog resources for the pattern
List<Resource> changeLogResources = Arrays.asList(context.getResources(changeLogLocationPattern));
for (Resource changeLogResource : changeLogResources) {
// get only the classpath path of the resource
String changeLogLocation = changeLogResource.getURL().getPath();
changeLogLocation = "classpath:" + StringUtils.substringAfterLast(changeLogLocation, "!");
changeLogLocations.add(changeLogLocation);
}
// sort all found resources by string
Collections.sort(changeLogLocations, String.CASE_INSENSITIVE_ORDER);
} catch (IOException e) {
throw new LiquibaseException("Could not resolve changeLogLocationPattern", e);
}
super.afterPropertiesSet();
}
#Override
protected SpringResourceOpener createResourceOpener() {
final String mainChangeLog = getChangeLog();
return new SpringResourceOpener(getChangeLog()) {
#Override
public InputStream getResourceAsStream(String file)
throws IOException {
// check if main changelog file
if(mainChangeLog.equals(file)) {
// load master template and convert to dom object
Resource masterResource = getResourceLoader().getResource(file);
Document masterDocument = DomUtils.parse(masterResource, true);
// add all changelog locations as include elements
for (String changeLogLocation : changeLogLocations) {
Element inlcudeElement = masterDocument.createElement("include");
inlcudeElement.setAttribute("file", changeLogLocation);
masterDocument.getDocumentElement().appendChild(inlcudeElement);
}
if(logger.isDebugEnabled()) {
logger.debug("Master changeset: {}", DomUtils.toString(masterDocument));
}
// convert dom back to string and give it back as input resource
return new ByteArrayInputStream(DomUtils.toBytes(masterDocument));
} else {
return super.getResourceAsStream(file);
}
}
};
}
}
This class now needs to be used in the spring xml configuration.
<bean id="liquibase" class="liquibase.integration.spring.MySpringLiquibase"
p:changeLog="classpath:/plugin/liquibase/master.xml"
p:dataSource-ref="dataSource"
p:contexts="${liquibase.contexts:prod}"
p:ignoreClasspathPrefix="true"
p:changeLogLocationPattern="classpath*:/plugin/liquibase/changes/*.xml"/>
With this changes I have achieved that my main changelog files are ordered by their name.
Hope that helps others too.

How to render freemarker templates that have a symlink in their path?

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
}
}

Java webapp media directory

I'm working on a Java EE project that provide a servlet that store a lot of images (or common files) into glassfish server for specific users.
I'm wondering if there are standard directories to save the files into standard web programming.
For example, I have three users that want upload their files, where I can save them into server?
There are'nt any standard directories. I suggest you to create directory on a server to each user. For example: The user registers, some data goes to the database and also a directory for this user is created. Than this user can upload any file to his own directory.
P.S you can create directories anywhere on the server, then configure the path of directory in server's JNDI resources for look in your application.
You have to create PropertiesObjectFactory class to handle JNDI properties of java.util.Porperties (if you are using glassfish 2). Or yuo can write your custom ObjectFactory also. Glassfish 3 already have this function. It is set into: org.glassfish.resources.custom.factory.PropertiesFactory.
Create directory somewhere on the server. For e.g: /server/glassfish/users
Open glassfish admin console and navigate to: Resources -> JNDI -> Custom Resources, click "New". Provide a JNDI name, for e.g: jndi/users_directories, choose a resource type "java.util.Properties", specify Factory class: org.glassfish.resources.custom.factory.PropertiesFactory, then click "Add property", specify name for e.g: users.directories and in value column copy your directory path. In this case: /server/glassfish/users. Click OK and thats all.
Restart application server.
Make a look up in your application:
public Properties getProperties(String jndiName) {
Properties properties = null;
try {
InitialContext context = new InitialContext();
properties = (Properties) context.lookup(jndiName);
context.close();
} catch (NamingException e) {
LOGGER.error("Naming error occurred while initializing properties from JNDI.", e);
return null;
}
return properties;
}
When you call this method in your application provide a JNDI name you configured in your application server: jndi/users_directories. If you have mapped resources in deploymet descriptor you have to use: java:comp/env/jndi/users_directories.
If you whant to do the same using spring:
<jee:jndi-lookup id="usersDirectories"
jndi-name="jndi/users_directories"/>
Or if you are using glassfish 2, then create a custom PropertiesObjectFactory class:
public class PropertiesObjectFactory implements Serializable, ObjectFactory {
/**
* File property name.
*/
public static final String FILE_PROPERTY_NAME = "org.glassfish.resources.custom.factory.PropertiesFactory.fileName";
/**
* Implemented method from object factory interface.
*
* #param obj object
* #param name name
* #param nameCtx context name
* #param environment environment
* #return file properties
* #throws Exception if error occurs
*/
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Reference ref = (Reference) obj;
Enumeration<RefAddr> refAddrs = ref.getAll();
String fileName = null;
Properties fileProperties = new Properties();
Properties properties = new Properties();
while (refAddrs.hasMoreElements()) {
RefAddr addr = refAddrs.nextElement();
String type = addr.getType();
String value = (String) addr.getContent();
if (type.equalsIgnoreCase(FILE_PROPERTY_NAME)) {
fileName = value;
} else {
properties.put(type, value);
}
}
if (fileName != null) {
File file = new File(fileName);
if (!file.isAbsolute()) {
file = new File(System.getProperty("com.sun.aas.installRoot") + File.separator + fileName);
}
try {
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
if (fileName.toUpperCase().endsWith("XML")) {
fileProperties.loadFromXML(fis);
} else {
fileProperties.load(fis);
}
} catch (IOException ioe) {
throw new IOException("IO Exception during properties load : " + file.getAbsolutePath());
}
} else {
throw new FileNotFoundException("File not found : " + file.getAbsolutePath());
}
} catch (FileNotFoundException fnfe) {
throw new FileNotFoundException("File not found : " + file.getAbsolutePath());
}
}
fileProperties.putAll(properties);
return fileProperties;
}
}
Make a .jar file of this class and put it to server global library directory. Provide this factory class for your JNDI resources, restart the server and you are good to use the same look up, covered above.

How can I change the running directory of a program - by setting it in the exe shortcut?

I have an exe process that is running with a shortcut.
In the "Start in" property of the shortcut I set it to the folder where all app resources are. The process still looks for files at the location of the exe and not the location written in the shortcut.
I can also see it in Process Explorer - the "current directory" is the location of the exe.
Is there a way to change it?
(If I wasn't clear enough -
I want to put my app in a central network location and not in each user folder - but I want it to run - above each user folder by putting a shortcut in each user folder.)
BTW : Why don't I solve it with code writing? Because of third party jars I have in my exe (I am using exe4j to make an exe)
From exe4-j documentation.., it seems this can be configured in exe4j project.
Working directory
For some applications (especially GUI applications) you might want to change the working directory
to a specific directory relative to the executable, for example to read config files that are in a fixed
location. To do so, please select the Change working directory to: checkbox and enter a
directory relative to the executable in the adjacent text field. To change the current directory to the
same directory where the executable is located, please enter a single dot.
One alternative is to use a System Property. Just create a shortcut like this:
java -Dmyproperty="\\myserver\myfolder" -jar yourjar.jar
And get this property on your program:
System.getProperty("myproperty");
You can also set multiple System Properties.
I would start the java application via a cmd or bat file, then change to the work dir before you call javaw. If you don't do any thing special in your java application code all the paths in it will be relative to the place where you started java.
Jess
You can hack the classpath programatically which would allow you to specify a specific folder or series of folders to access the data.
import java.io.IOException;
import java.io.File;
import java.net.URLClassLoader;
import java.net.URL;
import java.lang.reflect.Method;
public class ClassPathHacker {
private static final Class[] parameters = new Class[]{URL.class};
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method
public static void addFile(File f) throws IOException {
addURL(f.toURI().toURL());
}//end method
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[]{u});
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
}//end class
with the property loader file of
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
public abstract class PropertyLoader
{
/**
* Looks up a resource named 'name' in the classpath. The resource must map
* to a file with .properties extention. The name is assumed to be absolute
* and can use either "/" or "." for package segment separation with an
* optional leading "/" and optional ".properties" suffix. Thus, the
* following names refer to the same resource:
* <pre>
* some.pkg.Resource
* some.pkg.Resource.properties
* some/pkg/Resource
* some/pkg/Resource.properties
* /some/pkg/Resource
* /some/pkg/Resource.properties
* </pre>
*
* #param name classpath resource name [may not be null]
* #param loader classloader through which to load the resource [null
* is equivalent to the application loader]
*
* #return resource converted to java.util.Properties [may be null if the
* resource was not found and THROW_ON_LOAD_FAILURE is false]
* #throws IllegalArgumentException if the resource was not found and
* THROW_ON_LOAD_FAILURE is true
*/
public static Properties loadProperties (String name, ClassLoader loader)
{
if (name == null)
throw new IllegalArgumentException ("null input: name");
if (name.startsWith ("/"))
name = name.substring (1);
if (name.endsWith (SUFFIX))
name = name.substring (0, name.length () - SUFFIX.length ());
Properties result = null;
InputStream in = null;
try
{
if (loader == null) loader = ClassLoader.getSystemClassLoader ();
if (LOAD_AS_RESOURCE_BUNDLE)
{
name = name.replace ('/', '.');
// Throws MissingResourceException on lookup failures:
final ResourceBundle rb = ResourceBundle.getBundle (name,
Locale.getDefault (), loader);
result = new Properties ();
for (Enumeration keys = rb.getKeys (); keys.hasMoreElements ();)
{
final String key = (String) keys.nextElement ();
final String value = rb.getString (key);
result.put (key, value);
}
}
else
{
name = name.replace ('.', '/');
if (! name.endsWith (SUFFIX))
name = name.concat (SUFFIX);
// Returns null on lookup failures:
in = loader.getResourceAsStream(name);
if (in != null)
{
result = new Properties ();
result.load (in); // Can throw IOException
}
}
}
catch (Exception e)
{
result = null;
}
finally
{
if (in != null) try { in.close (); } catch (Throwable ignore) {}
}
if (THROW_ON_LOAD_FAILURE && (result == null))
{
throw new IllegalArgumentException ("could not load [" + name + "]"+
" as " + (LOAD_AS_RESOURCE_BUNDLE
? "a resource bundle"
: "a classloader resource"));
}
return result;
}
/**
* A convenience overload of {#link #loadProperties(String, ClassLoader)}
* that uses the current thread's context classloader.
*/
public static Properties loadProperties (final String name)
{
return loadProperties (name,
Thread.currentThread ().getContextClassLoader ());
}
private static final boolean THROW_ON_LOAD_FAILURE = true;
private static final boolean LOAD_AS_RESOURCE_BUNDLE = false;
private static final String SUFFIX = ".properties";
} // End of class
then you can add a path as follows
try {
//First Load up the properties and populate the config
ClassPathHacker.addFile("/pathtomyapp");
} catch (IOException ex) {
ex.printStackTrace();
}
properties = PropertyLoader.loadProperties("myapp");
or you can also use getResourceBundle to get your resources, this is just one example of hacking the classpath to allow files to be available, you can always just add the classpath programatically and let the jar files you need to be available to reside there, so if you always ensure that the app network path is Q: you can add Q:\ to the classpath.

Categories

Resources