Announcement Announcement Module
Collapse
No announcement yet.
Facelets views on the classpath Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Facelets views on the classpath

    As has been discussed before, spring-faces doesn't seem to support relative views when the flow is defined in the classpath. In SWF-732 (which I don't seem to be able to link to, sorry), Keith mentions a workaround using a custom FlowResourceViewResolver. I haven't been able to locate any information on how to do this. I was wondering if anyone might have an example, or just some insight on where I might find more information on this? Thanks.

  • #2
    I've done it at my work.

    You have to extend com.sun.facelets.impl.ResourceResolver, and add a configuration to your web.xml. Here is a very simple implementation ...

    Code:
    public class ClassPathResourceResolver implements ResourceResolver {
    
    	public URL resolveUrl(String path) {
    	    return getClass().getResource(path);
    	}
    	
    }

    HTML Code:
    <context-param>
    	<param-name>facelets.RESOURCE_RESOLVER</param-name>
    	<param-value>foo.bar.ClassPathResourceResolver</param-value>
    </context-param>
    But for the Spring WebFlow, i would recommend to extend FaceletViewHandler.createFaceletFactory(Compiler c) and use it in faces-config.xml.

    Code:
    protected FaceletFactory createFaceletFactory(Compiler c) {
        return new DefaultFaceletFactory(c, new ClassPathResourceResolver(), 2);
    }

    Comment


    • #3
      Yes, I'm able to do that. However, I still have to define views "absolutely" in my flows - I can't define them relative to the flow - I was hoping there would be some way to do that.

      Comment


      • #4
        sorry i've not understood your first request

        Comment


        • #5
          Handled

          For anyone who might wonder, I was able to solve this issue for my situation with some custom resource classes...nothing extensive though...

          Extend UrlResource and implement ContextResource - this will allow Webflow to create the relative faces view id. It assumes "within context" means within a jar file...this may not work for views that are not within a jar file - I haven't tested it.
          Code:
          package foo.bar;
          
          import org.apache.commons.lang.UnhandledException;
          import org.springframework.core.io.ContextResource;
          import org.springframework.core.io.Resource;
          import org.springframework.core.io.UrlResource;
          import org.springframework.util.ResourceUtils;
          
          import java.io.IOException;
          import java.net.MalformedURLException;
          import java.net.URI;
          import java.net.URL;
          
          public class ContextUrlResource extends UrlResource implements ContextResource {
          
            public ContextUrlResource(URL url) {
              super(url);
            }
          
            public ContextUrlResource(URI uri) throws MalformedURLException {
              super(uri);
            }
          
            public ContextUrlResource(String path) throws MalformedURLException {
              super(path);
            }
          
            public String getPathWithinContext() {
              try {
                if (ResourceUtils.isJarURL(this.getURL())) {
                  String path = this.getURL().getPath();
                  int start = path.lastIndexOf("!");
                  if (start > 0) {
                    return path.substring(start + 2); // strip the leading "!/" from the path
                  }
                }
                return this.getURL().getPath();
              } catch (IOException e) {
                throw new UnhandledException("This should never happen.", e);
              }
            }
          
            @Override
            public Resource createRelative(String relativePath) throws MalformedURLException {
              try {
                return new ContextUrlResource(super.createRelative(relativePath).getURL());
              } catch (IOException e) {
                throw new UnhandledException("We should never get an IOException here.", e);
              }
            }
          }
          Create a ResourcePatternResolver that will actually use the ContextUrlResource - obviously if you were needing some other pattern resolver, extend that one.
          Code:
          package foo.bar;
          
          import org.springframework.web.context.support.ServletContextResourcePatternResolver;
          import org.springframework.core.io.ResourceLoader;
          import org.springframework.core.io.Resource;
          
          import javax.servlet.ServletContext;
          import java.net.URL;
          
          public class CustomServletContextResourcePatternResolver extends ServletContextResourcePatternResolver {
            public CustomServletContextResourcePatternResolver(ServletContext servletContext) {
              super(servletContext);
            }
          
            public CustomServletContextResourcePatternResolver(ResourceLoader resourceLoader) {
              super(resourceLoader);
            }
          
            @Override
            protected Resource convertClassLoaderURL(URL url) {
              return new ContextUrlResource(url);
            }
          }
          A WebApplicationContext that will use my new pattern resolver:
          Code:
          package foo.bar;
          
          import org.springframework.web.context.support.XmlWebApplicationContext;
          import org.springframework.core.io.support.ResourcePatternResolver;
          
          public class CustomXmlWebApplicationContext extends XmlWebApplicationContext {
            @Override
            protected ResourcePatternResolver getResourcePatternResolver() {
              return new CustomServletContextResourcePatternResolver(this);
            }
          }
          An finally, specifying the new application context in my web.xml:
          Code:
            <context-param>
              <param-name>contextClass</param-name>
              <param-value>foo.bar.CustomXmlWebApplicationContext</param-value>
            </context-param>
          This is how I solved my particular issue. Oh, and this requires Webflow 2.0.6 or higher in order to work.

          Comment


          • #6
            Good post!

            With Webflow 2.0.6 released we are starting to move the hole resources (flow-definition, pages and resource bundle) to the classpath.

            With this we have modularity in our gui.
            And we can have the webflow tests living in the module, where the flow beans reside.

            Comment


            • #7
              I would like to have flow+views in the same package, in classpath, and use relative view name in view-state, like this :

              /foo/bar/my-flow.xml
              /foo/bar/my-view.xhtml
              Code:
              <view-state id="foo" view="my-view" />
              Solution above didnt work for me with webflow 2.0.8, so here is my contribution

              I use a BeanPostProcessor to override all flowApplicationContext ResourceLoader in the FlowRegistry.

              Code:
              public class FlowRegistryClassPathPostProcessor implements BeanPostProcessor {
              
                  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                      return bean;
                  }
                  
                  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                      if(bean instanceof FlowDefinitionRegistry) {
                          alterRegistry((FlowDefinitionRegistry) bean);
                      }
                      return bean;
                  }
                  
                  protected void alterRegistry(FlowDefinitionRegistry registry) {
                      for(String flowId : registry.getFlowDefinitionIds()) {
                          ApplicationContext ctx = registry.getFlowDefinition(flowId).getApplicationContext();
                          overrideResourceLoader((GenericApplicationContext) ctx);
                      }
                  }
                  
                  protected void overrideResourceLoader(GenericApplicationContext ctx) {
                      ctx.setResourceLoader(new CustomFlowResourceLoader((ClassPathResource)ctx.getResource("")));
                  }
              }
              Code:
              class CustomFlowResourceLoader implements ResourceLoader {
                  
                  private ClassPathResource base;
                  
                  public CustomFlowResourceLoader(ClassPathResource base) {
                      this.base = base;
                  }
                  
                  public ClassLoader getClassLoader() {
                      return base.getClassLoader();
                  }
                  
                  public Resource getResource(String location) {
                      return new CustomClassPathContextResource(base.getPath()+location, base.getClassLoader());
                  }
              }
              Code:
              class CustomClassPathContextResource extends ClassPathResource implements ContextResource {
              
                  public CustomClassPathContextResource(String path, ClassLoader classLoader) {
                      super(path, classLoader);
                  }
              
                  public String getPathWithinContext() {
                      return getPath();
                  }
              
                  public Resource createRelative(String relativePath) {
                      String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
                      return new CustomClassPathContextResource(pathToUse, getClassLoader());
                  }
              }
              Sample use :
              Code:
              <bean class="foo.bar.FlowRegistryClassPathPostProcessor" />

              Comment


              • #8
                My 2.0.7 solution

                The solutions above didn't work a 100% for me, I needed to change a couple of things for SWF 2.0.7.

                I've written about it on my blog here:
                http://www.redcode.nl/blog/2009/12/s...ets-into-jars/

                If anybody finds bugs/improvements, let me know, drop a comment on the site.

                Comment

                Working...
                X