4

I have created a Java EE application that uses JSF. In my web directory, I have a file named index.xhtml. My goal is to serve different content on this webpage based upon the parent directory's name.

For example:

http://localhost:8080/myapp/1/index.xhtml would print You accessed through "1". http://localhost:8080/myapp/1234/index.xhtml would print You accessed through "1234".

I do not want to create a directory for every single possible number; it should be completely dynamic.

Additionally, I need my navigation rules to still be usable. So if I have a navigation rule such as this:

<navigation-rule>
    <display-name>*</display-name>
    <from-view-id>*</from-view-id>
    <navigation-case>
        <from-outcome>index</from-outcome>
        <to-view-id>/index.xhtml</to-view-id>
        <redirect />
    </navigation-case>
</navigation-rule>

Then if I am in the directory 1234, it will still redirect to the index.xhtml page within 1234.

Is this possible? How can I do this?

3
  • What you want is called url rewriting... many duplicates in stackoverflow about that. Please look for them Commented Jul 16, 2016 at 7:33
  • @Kukeltje I did not know the name for it. My apologies. I will look around and provide an answer if I find one. I feel that even though there may be duplicates, others (as myself) may not know that it is called URL rewriting, and it will be beneficial to provide an answer to this question for anyone else who thinks of it in the same way I did. I learned something new, thank you! Commented Jul 16, 2016 at 7:35
  • No need to apologize. You can mark it as a duplicate even if you think the other questions are close enough Commented Jul 16, 2016 at 7:37

1 Answer 1

4

In order to forward /[number]/index.xhtml to /index.xhtml whereby [number] is been stored as a request attribute, you need a servlet filter. The doFilter() implementation can look like this:

@WebFilter("/*")
public class YourFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        String[] paths = request.getRequestURI().substring(request.getContextPath().length()).split("/");

        if (paths.length == 3 && paths[2].equals("index.xhtml") && paths[1].matches("[0-9]{1,9}")) {
            request.setAttribute("directory", Integer.valueOf(paths[1]));
            request.getRequestDispatcher("/index.xhtml").forward(req, res);
        }
        else {
            chain.doFilter(req, res);
        }
    }

    // ...
}

It makes sure the number matches 1 to 9 latin digits and stores it as a request attribute identified by directory and finally forwards to /index.xhtml in context root. If nothing maches, it simply continues the request as if nothing special happened.

In the /index.xhtml you can access the number by #{directory}.

<p>You accessed through "#{directory}"</p>

Then, in order to make sure JSF navigation (and <h:form>!) keeps working, you need a custom view handler which overrides the getActionURL() to prepend the URL with the path represented by directory request attribute, if any. Here's a kickoff example:

public class YourViewHandler extends ViewHandlerWrapper {

    private ViewHandler wrapped;

    public YourViewHandler(ViewHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public String getActionURL(FacesContext context, String viewId) {
        String actionURL = super.getActionURL(context, viewId);

        if (actionURL.endsWith("/index.xhtml")) {
            Integer directory = (Integer) context.getExternalContext().getRequestMap().get("directory");

            if (directory != null) {
                actionURL = actionURL.substring(0, actionURL.length() - 11) + directory + "/index.xhtml";
            }
        }

        return actionURL;
    }

    @Override
    public ViewHandler getWrapped() {
        return wrapped;
    }

}

In order to get it to run, register in faces-config.xml as below.

<application>
    <view-handler>com.example.YourViewHandler</view-handler>
</application>

This is also pretty much how JSF targeted URL rewrite engines such as PrettyFaces work.

See also:

Sign up to request clarification or add additional context in comments.

10 Comments

This works perfectly!! Thanks so much!!! I learned a lot from it, too! You're my Java hero, BalusC!
Is there any way to get the directory request attribute from within a stateful session bean? I tried injecting the stateful bean into the Filter and setting the value there, however I ran into the issue that all clients share the same stateful bean. I tried JNDI lookup, and while that works for the Filter, a new bean is injected in the business logic tier, so I cannot access the previously set value, because it is a different bean and the value is null.
Not sure what exactly you're trying to achieve, but I think the following answers should help to get the basic concepts straight: stackoverflow.com/q/18369356 and stackoverflow.com/q/8887140. The solution at least boils down to either that you shouldn't be using an EJB at all here (perhaps a session scoped CDI bean), or that you should be passing it as stateless EJB method argument.
My main goal is to provide multi tenancy based on the requested URL. So, if the user accesses /1/index.xhtml, all the entities inserted, selected, etc. will be attached to the Tenant with ID 1. So, my plan was to pass the number from the filter into a stateful bean where I could later use it during CRUD operations. I came up with a solution that involves injecting HttpServletRequest into the constructor of a stateful bean and ensuring that no exceptions are thrown when injecting (in the case of there being no request) and then retrieving the attribute. It works but feels like the wrong way.
@Shirgill: Reproducing, understanding and solving other's problems is surely a great way to learn. Keep on!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.