RelativePathsInJSF
|
for details.
During working on EmForge, initially all pages was placed on one level - and I do not meet problems with linking to resources and pages itself - since all pages are placed on one level - it was easy to use relative paths...
But, in some moment we decided to use 'Search-Engine Friendly URLs' - and now, same page may be placed on different levels... and we meet problem with linking to resources...
Actually, it appears in followed cases:
<base href="#{emForgeContext.applicationPath}" />
EmForgeContext bean received applicationPath from configuration. I did not liked this way because:
)
:8080/EmForge and http://akakunin-nb
:8080/EmForge in my case). And even if we accessed site by one host-name - all links will be referenced by host, specified in application path
After discussing with Walter Mourao about this problem he pointed me another solution: to use FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() for converting relatives path to absolujte on the web-server
So, this way
<script src="#{emForgeContext.path}/resources/scripts/prototype.js" type="text/javascript"></script>
there #{emForgeContext.path} returns this context path works perfectly...
And for pure html task I had to do so... But - to add this #{emForgeContext.path} into all h:outputLinks and and h:graphicImages - it is too much. Hopefully, using of JSF give us better way to solve this problem
We are using MyFaces-1.1.5 in our project - so, I simple get, sources of renderer, responsible for links and implemented own:
First - two utility methods to easily get context path and to determine - is link relative or not:
public static String getPath() {
return FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath();
}
/** Determines - is specified path - relative path or not?
*
* @param i_href
* @return
*/
public static boolean isRelativePath(String i_href) {
if (StringUtils.isEmpty(i_href)) {
return false;
}
if (i_href.contains("://")) {
// it is absolute path with protocol specified
return false;
}
if (i_href.charAt(0) == '/') {
// absolute path inside the site
return false;
}
return true;
}
Second: implementing own LinkRenderer - here I simple inherited from org.apache.myfaces.renderkit.html.HtmlLinkRenderer and overrided method renderOutputLinkStart, implemented in HtmlLinkRendererBase:
/** Extension for standard Link Renreder to correctly handle relative paths
*
* @author akakunin
*
*/
public class HtmlLinkRenderer extends
org.apache.myfaces.renderkit.html.HtmlLinkRenderer {
/**Rendred Output Link
* This code is mostly copied from MyFaces renderer, but - we extensinf it for checking href
* If href is relative path - we adds context path to get absolute path on the server.
* It helps easily build web-gui with pages, placed on different levels to refer to some static context
*/
@Override
protected void renderOutputLinkStart(FacesContext facesContext, UIOutput output) throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
//calculate href
String href = org.apache.myfaces.shared_impl.renderkit.RendererUtils.getStringValue(facesContext, output);
if (Helper.isRelativePath(href)) {
href = Helper.getPath() + "/" + href;
}
if (getChildCount(output) > 0) {
StringBuffer hrefBuf = new StringBuffer(href);
addChildParametersToHref(output, hrefBuf,
(href.indexOf('?') == -1), //first url parameter?
writer.getCharacterEncoding());
href = hrefBuf.toString();
}
href = facesContext.getExternalContext().encodeResourceURL(href); //TODO: or encodeActionURL ?
String clientId = output.getClientId(facesContext);
//write anchor
writer.startElement(HTML.ANCHOR_ELEM, output);
writer.writeAttribute(HTML.ID_ATTR, clientId, null);
writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
writer.writeURIAttribute(HTML.HREF_ATTR, href, null);
HtmlRendererUtils.renderHTMLAttributes(writer, output, org.apache.myfaces.shared_impl.renderkit.html.HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
writer.flush();
}
Actually it is simple copy of base implementation with simple checking for relative links and converting them to absolute (if it is required)
Next - we need to say JSF to use our rendered instead of standard - for doing it add into your faces-config.xml
<render-kit>
<render-kit-id>HTML_BASIC</render-kit-id>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>javax.faces.Link</renderer-type>
<renderer-class>ru.emdev.EmForge.web.renderer.HtmlLinkRenderer</renderer-class>
</renderer>
</render-kit>
So, now, all JSF-components like h:outputLink and based on it will use our renderer - and links will be processed automatically - so, you do not need to case about it in your html-code.
String src = facesContext.getApplication().getViewHandler().getResourceURL(facesContext, url);
So, seems better solution - to implement own getResourceURL that will convert links for all resources
Since EmForge is using Facelets and Ajax4Jsf we did followed steps:
Implemented own ViewHandler - based on FaceletViewHandler and override only one method:
/** Our implementation of View Handler to correctly process resource links
*
*/
public class ViewHandlerImpl extends FaceletViewHandler {
public ViewHandlerImpl(ViewHandler arg0) {
super(arg0);
}
/** We overriding this method to check - is specified path is relative path -
* and add context path to make it absolute
*/
@Override
public String getResourceURL(FacesContext i_context, String i_url) {
String url = i_url;
if (Helper.isRelativePath(url)) {
url = Helper.getPath() + "/" + url;
return url;
}
return super.getResourceURL(i_context, url);
}
}
Here is one issue: initially, I called return super.getResourceURL(i_context, url); for all url's, but somebody inside it added contextPath once again... I did not realize who did it - so, decided to do not pass url to super-class if it was already processed by our handler
Now, we should say to ajax4jsf to use our view-handler instead of facelets based (in web.xml):
<context-param>
<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
<param-value>ru.emdev.EmForge.web.ViewHandlerImpl</param-value>
</context-param>
If ajax4jsf is not used - we need to specify it in faces-config.xml:
<view-handler>
ru.emdev.EmForge.web.ViewHandlerImpl
</view-handler>
OK, that is all!
But, at least we should not care about it in all jsf-based tags like h:outputLink and h:graphicImage - that anyway makes our life simpler.
I do not sure this solution 100% working (I did not meet problems with it for now - but who knows) - and I'm not sure it is best solution - but it is that I found after some time working on this problem - any links to some problems and better solutions are only welcomed!
| Last Modified by akakunin 1 week ago |