sitemesh / sitemesh3

SiteMesh 3: Official repository
https://sitemesh.github.io/sitemesh-website/
Apache License 2.0
480 stars 168 forks source link

Java based configuration not working #135

Closed fcruzrj closed 8 months ago

fcruzrj commented 1 year ago

Hello,

I tried the java-based configuration but it doesn't work.

I created a custom filter as in the example on the website and registered it in my Spring MVC application according to the code below.

JAKARTA EE 17 SITEMESH 3.2.0-M2 SPRING FRAMEWORK 6.0.12 APACHE TOMCAT 10.1.13 ECLIPSE 2023-09 (4.29.0)

//custom filter public class MySiteMeshFilter extends ConfigurableSiteMeshFilter { @Override protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) { builder.addDecoratorPath("/*", "/decorator.html") .addDecoratorPath("/admin/*", "/admin/decorator.html"); } }

`public class AppConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class[] {SpringConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
    // TODO Auto-generated method stub
    return null;
}

@Override
protected String[] getServletMappings() {
    return new String[] {"/"};
}

@Override
protected Filter[] getServletFilters() {
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
    characterEncodingFilter.setEncoding("UTF-8");
    return new Filter[]{ characterEncodingFilter, new CustomSitemeshFilter() }; //HERE
}

}`

If I register the custom filter using web.xml, works.

`

sitemesh
    <filter-class>com.myapp.filter.MySiteMeshFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>`

EDIT 1:

With Java based config I can set a breakpoint and debug my CustomSitemeshFilter class, but the decorator is not applied.

Thank you very much in advance.

codeconsole commented 1 year ago

Hello @fcruzrj

Thanks for reporting the issue. I am looking into it.

Why don't you use a FilterRegistrationBean to register the filter?

fcruzrj commented 1 year ago

Hello @codeconsole.

Thanks a lot. Your answer helped me configure Sitemesh in Spring Boot, which I didn't know was possible. The error I reported occurs in a Eclipse Maven project with Spring MVC.

codeconsole commented 1 year ago

Hi @fcruzrj, yeah, there is a full SiteMesh3 String Boot starter example app.

All you need is the starter dependency: org:sitemesh:spring-boot-starter-sitemesh:3.2.0-M2

The issue you were having with AbstractAnnotationConfigDispatcherServletInitializer is that it is trying to add the ConfigurableSiteMeshFilter a second time because it was automatically injected due to the @WebFilter annotation it has. The ConfigurableSiteMeshFilter must be configured prior to the application container attempting to add the @WebFilters. Using web.xml always overrides all Filter settings.

fcruzrj commented 1 year ago

Hi @codeconsole,

Thanks for the example!

I used web.xml because java based config didn't works. With java based only, I debug the filter but the generated html isn't decorated.

P.S.: Java based config in Spring Boot application worked!

codeconsole commented 1 year ago

@fcruzrj What I am saying is there was no need to use web.xml in the first place because SiteMesh configures automatically without Spring Boot if you have the sitemesh jar in the class path. All you need to do is add /WEB-INF/sitemesh3.xml to configure it or just add a meta tag to any html page and it will decorate it automatically using convention over configuration.

The default (non spring boot) configuration expects decorators to be in the /WEB-INF/decorators folder.

Here is a very simple example: https://github.com/codeconsole/spring6-filter

codeconsole commented 1 year ago

@fcruzrj - I've updated https://github.com/codeconsole/spring6-filter to resemble your configuration and show that you are ending up with 2 filters initialized.

The first one is because Sitemesh is initialized by tomcat automatically due sitemesh being in the classpath. ConfigurableSiteMeshFilter has a @WebFilter annotation which is detected in the scan.

The second initialization was due to your protected Filter[] getServletFilters() { method.

The reason why you were not seeing anything and thinking it was not working is because you did not have a sitemesh3.xml file that defined url mappings and/or did not define a decorator in any html page using the decorator meta tag.

I've added logging to show when SiteMesh is initialized and also to detect if more than 1 filter has been initialized.

I am closing this ticket due to https://github.com/sitemesh/sitemesh3/commit/f8556d3747fa63481b8fa79543ba3984bf1e7ed3 and https://github.com/sitemesh/sitemesh3/commit/eea55c3b2ada5f0a7bdc61cbd61f29a8f6d13e8c now providing this logging information.

Feel free to reach out if you are experiencing any other issues.

xkguq007 commented 9 months ago

Similar issue here.

Currently i used sitemesh 3.1, springframework 5, fully Java based configuration. (without any xml)

I created CustomFilter, which implements applyCustomConfiguration.

public class MeshFilter extends ConfigurableSiteMeshFilter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    log.info("Start URI checking");
    super.init(filterConfig);
  }

  @Override
  protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
    builder.setDecoratorPrefix("/WEB-INF/views/layout")
       .addDecoratorPath("/*", "main.jsp")
       .addDecoratorPath("/auth/*", "auth.jsp");
  }

  @Override
  public void destroy() {
    log.info("End URI checking");
    super.destroy();
  }
}

So, I enrolled

FilterRegistration.Dynamic filter =
        servletContext.addFilter("sitemesh", MeshFilter.class);
    filter.addMappingForUrlPatterns(null, false, "*");
    filter.setInitParameter("encoding", "utf-8");
    filter.addMappingForServletNames(null, false, "dispatcher");

It always logged register twice. It not only works, but also I want to replace Sitemesh Default Filter, not 2 filter initialized. If I use sitemesh3.xml and use default filter, it works. But java based config? not works.

How can I achieve this? ONLY IN FULLY JAVA CONFIG. (Even sitemesh3.xml too. because i set this config in my custom filter)

codeconsole commented 9 months ago

@xkguq007 it registers twice because you are using a different name than the java config. If you want to override the default configuration, you have to register your filter with the name configurableSiteMeshFilter

See https://github.com/sitemesh/sitemesh3/blob/382faab5e6977ae8202910e03ae991d4d04537cc/sitemesh/src/main/java/org/sitemesh/config/ConfigurableSiteMeshFilter.java#L137

xkguq007 commented 9 months ago

@codeconsole thanks for fast reply.

public class SpringStarter implements WebApplicationInitializer {

  @Override
  public void onStartup(ServletContext servletContext) {
    FilterRegistration reg = servletContext.getFilterRegistration("configurableSiteMeshFilter");
    FilterRegistration.Dynamic filter =
        servletContext.addFilter("configurableSiteMeshFilter", new MeshFilter());
    filter.addMappingForUrlPatterns(null, false, "*");
    filter.setInitParameter("encoding", "utf-8");
    filter.addMappingForServletNames(null, false, "dispatcher");

    // Root Context
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootContextConfiguration.class);
    servletContext.addListener(new ContextLoaderListener(rootContext));

    ...
  }
}

FilterRegistration.Dynamic filter always null which means already enrolled.

@Slf4j
@RequiredArgsConstructor
public class MeshFilter extends ConfigurableSiteMeshFilter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    log.info("Start URI checking");
    super.init(filterConfig);
  }

  @Override
  protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
    builder.setDecoratorPrefix("/WEB-INF/views/layout")
       .addDecoratorPath("/*", "main.jsp")
       .addDecoratorPath("/auth/*", "auth.jsp");
  }

  @Override
  public void destroy() {
    log.info("End URI checking");
    super.destroy();
  }
}

My Custom Filter does not have any annotation which indicates filter, component, or else. I set my filter on top of onStartup method. and, reg which define first line on method, is not null. reg is already set by org.sitemesh. any thought about this problem?

ps) fun thing is when i debugged in first line of configurablesitemeshfilter init method and first line of onStartup method, it breaks onStartup method first.

codeconsole commented 9 months ago

hello @xkguq007, I need some more time to look into this, but I will get it working for you.

codeconsole commented 9 months ago

@xkguq007 can you please provide me with a very minimal gradle example that does not work?

xkguq007 commented 9 months ago

https://github.com/xkguq007/spring5-filter I forked your test repository, https://github.com/codeconsole/spring6-filter. Just delete sitemesh3.xml and set version spring6 to spring5, jakarta to javax, sitemesh3.2 to 3.1.

In this code, you've been set your own filter and first line of onStartup, you check already that enrolled default filter or not. Run your code, you can check the log that already initialized default filter. This is exactly same as my condition.

I think my forked code's log must not hit the code logger.severe(String.format("*** '%s' this line. but still hit. So, Any decorator is not work. Because i removed sitemesh3.xml, and not register my custom filter.

In conclusion, the problem is "I didn't register any sitemesh filter before, except for my customs. It still has default filter" with condition, do not use any xml, including web.xml, sitemesh3.xml, etc.

codeconsole commented 8 months ago

@xkguq007 the issue is that you need to register your filter prior to @WebFilter being initialized by the embedded server.

https://stackoverflow.com/questions/18704226/disable-webfilter-embedded-in-dependency-jar

Are you using Spring Boot, if so, you can just create a FilterRegistrationBean to disable it? https://github.com/spring-projects/spring-boot/issues/2173

If neither of these solutions work for you, we can explore providing a release that does not have @WebFilter in it. The key is that you register your filter with the same name before the server attempts to initialize one automatically.

xkguq007 commented 8 months ago

@codeconsole Thank you for your help. I'm using Spring Framework not Spring Boot. I saw stackoverflow links, two answers which is in link is not proper, neither. One is for spring boot, the other(selected) is little hacky way I think. Not unregister, but makes unwanted filter useless.

New release version without @Webfilter will be the best option for me. Also I prefer registering filter explicitly.

codeconsole commented 8 months ago

Ok, we will look into repackaging. You can expect a new release in a week timeframe.

codeconsole commented 8 months ago

@xkguq007 you want to give the latest snapshot a try?

repositories {
    mavenCentral() 
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
dependencies {
    runtimeOnly 'org.sitemesh:sitemesh:3.2.1-SNAPSHOT'
}

I separated it here: https://github.com/sitemesh/sitemesh3/commit/7b01ebb46833f0531a1626428b155f6d0d498d56

xkguq007 commented 8 months ago

@codeconsole Thank you I'll give it a try it. I'll close this issue when this dependency works.

codeconsole commented 8 months ago

@xkguq007 I will do an official release after you confirm it working

codeconsole commented 8 months ago

@xkguq007 were you able to confirm this working?

xkguq007 commented 8 months ago

@codeconsole Sorry for late. Yeah, I checked it works and it's related source code changes. I will use this SNAPSHOT version until GA version released. Thanks for your help and I think this issue can be closed.

codeconsole commented 8 months ago

@xkguq007 3.2.1 and 3.1.1 have both just been released to maven central.

xkguq007 commented 7 months ago

@codeconsole I checked maven central that version has been released! Thanks for your help.