Problems handling multiple Service classes with same template class

61 views
Skip to first unread message

Kristian Vitsø

unread,
Jun 16, 2013, 10:24:29 AM6/16/13
to google-s...@googlegroups.com
This is partly related to https://groups.google.com/forum/?fromgroups#!topic/google-sitebricks/KfKWJM6SUPo because I have the need to serve mutiple @Ats with the same controller class

My use case:
I am developing a CMS, where a user can add a page and assign as slug to it. The slug can have 1 to n levels, and will be relative to the root

"/test/" -> //www.mycms.com/test/
"/test/test2/" -> //www.mycms.com/test/test2/
etc...

Ideally I would want to use multiple @At annotations in the same controller class, and with a catch-all wildcard that handles the request if nothing else matches, but from what I have understood from the other thread (please correct me if I am wrong) this is simply not possible. I would argue that it would still be logically possible to do this in a way that would produce the expected outcome (without knowing the internals of the Sitebricks framework) as long as the @At value is matched where the fixed values have prio over the wildcards, and parsing this level by level.

In the perfect world, @At("/:slug") would catch "//www.mycms.com/test/test2/" with "/test/test2" as the identified slug (as long as no other @At notations suits better). As/if this is not possible with the current version, I would like to request it as a future feature.

However, for now I have sort of settled with an ugly alternative, where I limit the number of levels a slug can have to a certain number (not decided exactly how many yet), and attempting to implement it like this:

PageService.class
public interface PageService {
    PageService setSlug(String slug);
    Reply<?> getReply();
    String getTitle();
    String getContent();
}

PageServiceImpl.class
@Show("/front/page.html")
public class PageServiceImpl implements PageService {

    @Inject
    private PageDao pageDao;

    protected String slug;
    private Page page;
    private Reply<?> reply;

    public PageServiceImpl() {
    }

    @Override
    public PageServiceImpl setSlug(String slug) {
        this.slug = slug;
        // Simplified logic (skipping error handling, response code management etc):
        this.page = this.pageDao.getBySlug(slug);
        this.reply = Reply.with(this).saying().ok()
                        .template(PageServiceImpl.class)
                        .as(Text.class)
                        .type("text/html");
        LOGGER.info(String.format("Page for slug '%s' found: '%s'",
                        this.slug, this.page != null));
        return this;
    }
    @Override
    public Reply<?> getReply() {
        return this.reply;
    }

    @Override
    public String getTitle() {
        return page.getDocument().getTitle();
    }

    @Override
    public String getContent() {
        return page.getDocument().getContent();
    }
}

PageLevelOneService.class
@At("/:slug/")
@Service
public class PageLevelOneService {

    @Inject
    private Injector injector;

    @Get
    public Reply<?> service(@Named("slug") String slug) {
        return injector.getInstance(PageService.class).setSlug(slug).getReply();
    }
}

PageLevelTwoService.class
@At("/:slug/")
@Service
public class PageLevelTwoService {

    @Inject
    private Injector injector;

    @Get
    public Reply<?> service(@Named("slug") String slug) {
        return injector.getInstance(PageService.class).setSlug(slug).getReply();
    }
}

/front/page.html
<html>
<body>
    <h1>${title}</h1>
    
    <div>${content}</div>
</body>
</html>

Creating a testpage with the slug "test" and trying to access it, produces the following output to the console:
Page for slug 'test' found: 'true'

But the template is not parsed correctly:

HTTP ERROR 500

Problem accessing /test/. Reason:

    [Error: unresolvable property or identifier: title]
[Near : {... title ....}]
             ^
[Line: 1, Column: 1]

(see http://pastebin.com/H5KvDZmY for full response with stacktraces)

What am I doing wrong here, and is there a more elegant way I could solve my problem?

Thanks








Kristian Vitsø

unread,
Jun 16, 2013, 7:47:19 PM6/16/13
to google-s...@googlegroups.com
I managed to find the error myself, the reason was that I was adding .saying().ok() to the method chain.

However, after getting one step further, I ran into another problem. PageServiceImpl.class extends a class Design.class (which handles the sites overall design) and is annotated with @Decorated. The template for the design-class has a @Decorated -tag put in where I want to merge the sub-pages into the overall design. This works well with all my other controllers (which does not use the Reply-API, but are annotated with @Decorated, @At and @Show. It does not seem like this approach works when using the Reply-API, but I do not see why that (logically) would make a difference? Can someone confirm that this is not possible? Or if someone can tell me how I can achieve this with using the Reply-API (which I need to avoid code-duplication to solve my "multi-level URL-slug" -problem...?

On the note of having a controller/service that catches all unmatched requests, could it be a solution to allow one (and only one) class annotated with something like @CatchAll, that can take care of all unmatched requests (and dispatch it to other controllers/services via the Reply-API)?

Dhanji R. Prasanna

unread,
Jun 17, 2013, 12:03:51 AM6/17/13
to google-s...@googlegroups.com
Hi,

The reply API is a totally different API and should not be mixed with @Show-style classes. If you want to mix them, then declare another model class:

@Show
class Person {..}

Then your Page object becomes a pure controller:
@At("/")
@Service
class PersonService {

  @Get Reply<Person> get() { return Reply.with(new Person()).template(Person.class); }  // Notice this triggers the page render

}


As for your previous question, you should be able to specify static paths:

/test/one
/test/two

in @At declarations in the same controller as @At("/:something/:something_else") The static paths will take precedence.

One issue we have in path matching is that it is greedy, so if you match on @At("/:slug") it will match everything.
So if you want both in the same controller, one option is to create a top level matcher:

@At("/:slug") @Service
class Slug {

  @Get @At("/") ...

  @Get @At("/:lower_slug") ...
}

With sub levels inside. Let me know if this works. If not there may be a bug and we can fix it.


--
You received this message because you are subscribed to the Google Groups "Google Sitebricks" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-sitebri...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Kristian Vitsø

unread,
Jun 17, 2013, 3:36:09 AM6/17/13
to google-s...@googlegroups.com
Thanks for the reply, I will try the suggestion with sub-level matchers inside the top level matcher when I get home and let you know how it works!

Regarding using the Reply API, here is the extended version of your snippet that fits our project:

Design.class
Show(".../design.html)
class Design {..}

design.html
<html>
  ...
  @Decorated
  <div />
  ...
<html>

Person.class
@Decorated
@Show(".../person.html")
class Person extends Design {..}

PersonService.class (can this implement an interface without this causing issues with Sitebricks?)
@At("/")
@Service
class PersonService {

  @Get Reply<Person> get() { return Reply.with(new Person()).template(Person.class); }  // Notice this triggers the page render

}

I do not seem to get this to work (while it works if I do not use the Reply API). Is there a known reason why the person.html template will not be rendered inside the design-template?

Kristian Vitsø

unread,
Jun 18, 2013, 8:10:41 PM6/18/13
to google-s...@googlegroups.com
1) I cannot get the @Decorated annotation to work for me in conjunction with the Reply-API. Can someone confirm this, or indicate whether this is a bug or not?

2) I just discovered today that adding @At("/:path") will also prevent static files (from my resources directory, served by the container ie. Jetty/Tomcat) from being delivered (everything goes through the service class). Is this as expected? And does this mean that there is simply no way to serve static files through the servlet container AND have a service/controller class that handles /(*) at the same time?

Kristian Vitsø

unread,
Jun 20, 2013, 10:49:09 AM6/20/13
to google-s...@googlegroups.com
For other developer's reference, regarding my issue #2 here, I came up with the following solution that seem to work fine:

web.xml
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>StaticResourceFilter</filter-name>
        <filter-class>com.digitalmarketingassoc.web.front.servlet.filter.StaticResourceFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>StaticResourceFilter</filter-name>
        <url-pattern>/resources/*</url-pattern>
    </filter-mapping>

<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

StaticResourceFilter.class
    public class StaticResourceFilter implements Filter {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(StaticResourceFilter.class);
    
        private static final String RESOURCE_PATH = "/resources/";
        @Override
        public void init(final FilterConfig filterConfig) throws ServletException {
            LOGGER.info("StaticResourceFilter initialized");
        }
    
        @Override
        public void doFilter(final ServletRequest request, final ServletResponse response,
                             final FilterChain chain) throws IOException, ServletException {
    
            String path = ((HttpServletRequest) request).getRequestURI();
            if (path.toLowerCase().startsWith(RESOURCE_PATH)) {
                request.getRequestDispatcher(path).forward(request, response);
            } else {
                chain.doFilter(request, response);
            }
        }
    
        @Override
        public void destroy() {
            LOGGER.info("StaticResourceFilter destroyed");
        }
    }

I would still like to request a feature in Sitebricks that could handle this, eg. with an annotation like @AtExclude("/resources/*") though
Reply all
Reply to author
Forward
0 new messages