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?

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
Mitch Talmadge
  • 4,638
  • 3
  • 26
  • 44
  • What you want is called url rewriting... many duplicates in stackoverflow about that. Please look for them – Kukeltje Jul 16 '16 at 07: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! – Mitch Talmadge Jul 16 '16 at 07:35
  • No need to apologize. You can mark it as a duplicate even if you think the other questions are close enough – Kukeltje Jul 16 '16 at 07:37

1 Answers1

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:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • This works perfectly!! Thanks so much!!! I learned a lot from it, too! You're my Java hero, BalusC! – Mitch Talmadge Jul 16 '16 at 11:51
  • 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. – Mitch Talmadge Jul 17 '16 at 20:05
  • Not sure what exactly you're trying to achieve, but I think the following answers should help to get the basic concepts straight: http://stackoverflow.com/q/18369356 and http://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. – BalusC Jul 18 '16 at 12:32
  • 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. – Mitch Talmadge Jul 18 '16 at 12:51
  • @BalusC: The paths array in your code above contain strings [an empty string, then the digit, and finally index.xhtml]. What is this empty string? – Farhan stands with Palestine Jul 18 '16 at 13:42
  • @Shirgill: The string before the first `/`. If you don't like this, you could also do `substring(1)` beforehand. – BalusC Jul 18 '16 at 13:43
  • @BalusC: If the url is like this one - [ http://localhost:8080/glassfish-ee/1/index.xhtml ], you mean before the /1/index.xhtml – Farhan stands with Palestine Jul 18 '16 at 13:46
  • @Shirgill: Yes, the string to split is `/1/index.xhtml` and not `1/index.xhtml`. – BalusC Jul 18 '16 at 13:47
  • @BalusC: You're insanely talented. It's so captivating. I find myself now addicted to reading your answers and then replicating them on a daily basis. – Farhan stands with Palestine Jul 18 '16 at 13:48
  • 1
    @Shirgill: Reproducing, understanding and solving other's problems is surely a great way to learn. Keep on! – BalusC Jul 18 '16 at 14:27