SEO URL supporting l10n/i18n with //Rewrite

Splash Forums Rewrite Users SEO URL supporting l10n/i18n with //Rewrite

This topic contains 10 replies, has 3 voices, and was last updated by  Christian Kaltepoth 3 years, 3 months ago.

Viewing 11 posts - 1 through 11 (of 11 total)
  • Author
    Posts
  • #23573

    Stephan Rudolph
    Participant

    Hello Christian, hello Lincoln,
    we like to use your //Rewrite solution to provide a SEO URL that supports l10n/i18n.
    Let me describe in detail, what we like to achieve.

    Case: Request from outside without LOCALE, PATH and VIEW information (homepage) / to /pages/index.xhtmllocalehandler.localeselected becomes set by evaluation of the HTTP header element Accept-Language and the JSF configuration elements supported-locale as well as default-locale
    Case: Request from outside with LOCALE, VIEW or PATH and VIEW information (bookmark)/LOCALE/VIEW to /pages/VIEW/LOCALE/PATH.../VIEW to /pages/PATH.../VIEWlocalehandler.localeselected becomes set by LOCALE
    Case: Request from internal (navigation)/pages/VIEW to /LOCALE/VIEW/pages/PATH.../VIEW to /LOCALE/PATH.../VIEWLOCALE becomes set by localehandler.localeselected

    LOCALE stands for an element of the URL representing the locale (e.g. de-CH, en-US, ...)
    PATH stands for any path element of an URL (e.g. /company/location/)
    VIEW stands for the view (e.g. services.xhtml)

    There is also a session scoped managed bean, named localehandler. The property localehandler.localeselected represents the current locale of the website (f:view locale) and becomes set by:

    evaluation of the HTTP header element Accept-Language and the JSF configuration elements supported-locale as well as default-locale – if there is no other input (LOCALE element or user input)
    LOCALE element of the URL
    explicitly, by the user, via a select menu (deutsch, english, french, …)

    In order to implement the 1st case , we use the code:
    @Override public Configuration getConfiguration(final ServletContext context) { return ConfigurationBuilder.begin() .addRule(Join.path("/").to("/pages/index.xhtml")) ; }
    That works very well :-).
    In order to implement the 2nd case, we use the code, mentioned in “SEO URL supporting l10n/i18n with PrettyFaces”:
    @Override public Configuration getConfiguration(final ServletContext context) { return ConfigurationBuilder.begin() .addRule(Join.path("/").to("/pages/index.xhtml")) .addRule(Join.path("/{lang}/{path}") .where("lang").bindsTo(El.property("localeHandler.languageSelected")) .where("path").matches(".*") .to("/pages/{path}")); }
    This results in the exception:
    [#|2013-04-12T23:10:53.611+0200|SEVERE|glassfish3.1.2|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=78;_ThreadName=Thread-2;|WebModule[]PWC1270: Exception starting filter OCPsoft Rewrite Filter java.lang.InstantiationException ... Caused by: java.lang.NullPointerException ...
    Now, my question is: What code does we need in order to implement the 2nd and 3th case the right way?

    #23590

    Stephan Rudolph
    Participant
    package ch.rudolphag.config;
    
    import org.ocpsoft.rewrite.config.Configuration;
    import org.ocpsoft.rewrite.config.ConfigurationBuilder;
    import org.ocpsoft.rewrite.el.El;
    import org.ocpsoft.rewrite.servlet.config.HttpConfigurationProvider;
    import org.ocpsoft.rewrite.servlet.config.rule.Join;
    import javax.servlet.ServletContext;
    
    /**
     * Created by Stephan Rudolph rudolph ag.
     * Date: 24.03.13
     * Time: 12:45
     *
     * @author: Stephan Rudolph
     */
    public class RewriteConfigurationProvider extends HttpConfigurationProvider {
    
        @Override
        public int priority() {
            return 10;
        }
    
        @Override
        public Configuration getConfiguration(final ServletContext context) {
            return ConfigurationBuilder.begin()
                .addRule(Join.path("/").to("/pages/index.xhtml"))
                .addRule(Join.path("/{lang}/{path}")
                    .where("lang").bindsTo(El.property("localeHandler.languageSelected"))
                    .where("path").matches(".*")
                    .to("/pages/{path}"))
    
                ;
        }
    }
    #23591

    Ah, yes, I see your problem. This was a known issue that has been fixed and will be released in the 2.0.0 Release.

    You need to move the `.where()` clauses below the `to()` method in your last `Join` rule.

    I hope this helps!

    #23592

    It should look like this:

    @Override
        public Configuration getConfiguration(final ServletContext context) {
            return ConfigurationBuilder.begin()
                .addRule(Join.path("/").to("/pages/index.xhtml"))
                .addRule(Join.path("/{lang}/{path}")
                    .to("/pages/{path}")
                    .where("lang").bindsTo(El.property("localeHandler.languageSelected"))
                    .where("path").matches(".*")
                );
        }
    #23598

    Stephan Rudolph
    Participant

    After implementing the change the application comes up and running, but no navigation is possible. As soon as I try to navigate exceptions are thrown as follows:

    [#|2013-04-18T22:32:20.569+0200|WARNING|glassfish3.1.2|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=109;_ThreadName=Thread-2;|StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
    org.ocpsoft.rewrite.exception.RewriteException: El provider [org.ocpsoft.rewrite.faces.FacesExpressionLanguageProvider] could not inject property [localeHandler.languageSelected} with value [[Ljava.lang.String;@64fea8f0]
        ...
    Caused by: java.lang.IllegalArgumentException: FacesContext.getCurrentInstance() returned null. EL expressions can only be evaluated in the JSF lifecycle. You should use PhaseAction and PhaseBinding to perform an deferred operation instead.
    	at org.ocpsoft.rewrite.faces.FacesExpressionLanguageProvider.getFacesContext(FacesExpressionLanguageProvider.java:132)
    	at org.ocpsoft.rewrite.faces.FacesExpressionLanguageProvider.submitValue(FacesExpressionLanguageProvider.java:47)
    	at org.ocpsoft.rewrite.el.El$ElProperty.submit(El.java:345)
    	... 30 more
    |#]
    
    [#|2013-04-18T22:32:20.648+0200|WARNING|glassfish3.1.2|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=108;_ThreadName=Thread-2;|StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
    org.ocpsoft.rewrite.exception.RewriteException: El provider [org.ocpsoft.rewrite.cdi.CdiExpressionLanguageProvider] could not inject property [localeHandler.languageSelected} with value [[Ljava.lang.String;@5a4a0750]
        ...
    Caused by: javax.el.PropertyNotFoundException: ELResolver cannot handle a null base Object with identifier 'localeHandler'
    	at com.sun.el.lang.ELSupport.throwUnhandled(ELSupport.java:66)
    	at com.sun.el.parser.AstIdentifier.getValue(AstIdentifier.java:105)
    	at com.sun.el.parser.AstValue.getTarget(AstValue.java:149)
    	at com.sun.el.parser.AstValue.getType(AstValue.java:84)
    	at com.sun.el.ValueExpressionImpl.getType(ValueExpressionImpl.java:200)
    	at org.jboss.weld.el.WeldValueExpression.getType(WeldValueExpression.java:93)
    	at org.ocpsoft.rewrite.cdi.CdiExpressionLanguageProvider.getExpectedType(CdiExpressionLanguageProvider.java:107)
    	at org.ocpsoft.rewrite.cdi.CdiExpressionLanguageProvider.submitValue(CdiExpressionLanguageProvider.java:46)
    	at org.ocpsoft.rewrite.el.El$ElProperty.submit(El.java:345)
    	... 30 more
    |#]
    #23602

    Let me try to explain what these errors mean:

    `
    java.lang.IllegalArgumentException: FacesContext.getCurrentInstance() returned null. EL expressions can only be evaluated in the JSF lifecycle. You should use PhaseAction and PhaseBinding to perform an deferred operation instead.
    `

    This basically tells you that Rewrite tries to bind the parameter value to you bean property but isn’t able to access the FacesContext. This is caused by the fact the Rewrite performs the binding very early in request processing at a time where the JSF lifecycle hasn’t started yet. To fix this issue you have to wrap the EL binding in a `PhaseBinding` like this:

    `
    .where(“lang”).bindsTo(PhaseBinding.to(El.property(“localeHandler.languageSelected”)))
    `

    The other error:

    `
    javax.el.PropertyNotFoundException: ELResolver cannot handle a null base Object with identifier ‘localeHandler’
    `

    This one means that CDI isn’t able to find a bean named `localeHandler`. Is you bean named correctly? Is the LocaleHandler a bean managed by CDI or by JSF?

    #23606

    Stephan Rudolph
    Participant

    Hello Christian

    thank you very much for your hint regarding `PhaseBinding`.

    By implementing your proposal, we got rid of both, the exception:

    org.ocpsoft.rewrite.exception.RewriteException
    	...
    Caused by: java.lang.IllegalArgumentException
    	...

    as well as the exception:

    org.ocpsoft.rewrite.exception.RewriteException
    	...
    Caused by: javax.el.PropertyNotFoundException

    Now, our code looks like this:

    @Override
    public Configuration getConfiguration(final ServletContext context) {
        return ConfigurationBuilder.begin()
            // 1. Case: Request from outside without LOCALE, PATH and VIEW information (homepage)
            .addRule(Join.path("/")
                .to("/pages/index.xhtml"))
            // 2. Case: Request from outside with LOCALE, VIEW or PATH and VIEW information (bookmark)
            .addRule(Join.path("/{lang}/{path}")
                .to("/pages/{path}")
                .where("lang").bindsTo(PhaseBinding.to(El.property("localeHandler.languageSelected")))
                .where("path").matches(".*"))
            // 3. Case: Request from internal (navigation)
            // TBD
            ;
    }

    The statement that should handle the 2nd case (see above) does nothing. The shape of the URL remains the same than without applying the rewrite statement. Instead, applying the rewrite statement, resources (images, etc.) become not reachable because they become an `.xhtml` attached, resulting in an exception like this:

    com.sun.faces.context.FacesFileNotFoundException: /pages/solutions_weblogic_platform_lifetime_en.png.xhtml Not Found in ExternalContext as a Resource

    How has the rewrite statement for the 2nd case to be, in order to achieve the behavior as described above?

    What rewrite statement does we need to address the 3rd case as described above?

    Best regards Stephan

    #23608

    #2. I think you need to restrict the value of “lang” so that it does not match against your CSS resource. Try this:

    Disable your rewrite configuration and look at the path of the CSS file in the rendered output from one of your pages. You need to make sure that your rule does *not* match this type of address from

    /javax.faces.resource/*

    .

    Next, add a `where(“lang”).matches(“\w{2}(_\w{2})?”)` clause to your 2nd rule. You may need to tweak so that it matches your expected LANG values.

    #3. Navigation should be as simple as using normal JSF2 implicit navigation: Just return `/pages/path_to_file.xhtml?lang=en_US&faces-redirect=true`.

    Rewrite should determine that this address should be corrected, and send you to the right location automatically.

    ~Lincoln

    #23611

    BTW: You could also have a look at this:

    http://ocpsoft.org/support/topic/yet-another-i18n-approach/

    This post describes a working I18N setup that works very well and is IMHO very good.

    Hope this helps. 🙂

    #23644

    Stephan Rudolph
    Participant

    Now I’m a little bit confused.

    Currently, using rewrite, all resources become attached the extendion “.xhtml”

    So I have to exclude all resources from rewriting like:

    /javax.faces.resource/*.js
    /javax.faces.resource/*.css
    /javax.faces.resource/*.gif
    /javax.faces.resource/*.jpg
    /javax.faces.resource/*.png

    Please tell me, by what statement(s) does I have to extend my current code in the `getConfiguration` method to achieve that?

    Regarding the 3rd case (request from internal), outgoing from your hint “…it could be possible to implement something like this so that you won’t have to adjust much code.”, I thougt I don’t need to provide any parameter like:

    f:param name="languageSelected" value="#{localeHandler.languageSelected}"

    For example, if I have an internal request like this:

    h:link value="#{cnti18n['LABEL_LINK_INTERNAL.company.location']}"
    	outcome="/pages/company/location"
    h:link

    The URL in the location bar of the browser should be rewritten to look like:

    http://TLD/LANG/company/location

    Where TLD stands for the top level domain and LANG for the string provided by the session scoped managed bean value localeHandler.languageSelected.

    Is there something I misunderstood?

    Please tell by what statement(s) does I have to extend my current code in the `getConfiguration` method to become LANG after TLD inserted into my URL, shown in the location bar of the browser?

    #23649

    You don’t have to exclude the resources if you just tell Rewrite more about how your parameters look like. If you specify a regular expression for the parameters, resources requests will simply not match you rule and therefore won’t break.

    Something like this:

    `
    .addRule(Join.path(“/{lang}/{path}”)
    .to(“/pages/{path}”)
    .where(“lang”)
    .matches(“\w{2}(_\w{2})?”)
    .bindsTo(PhaseBinding.to(El.property(“localeHandler.languageSelected”)))
    .where(“path”).matches(“.*”))
    `

Viewing 11 posts - 1 through 11 (of 11 total)

You must be logged in to reply to this topic.

Comments are closed.