Archive

Archive for March, 2008

tagesschau.de

Monday, 31. March 2008 Andreas Höhmann Leave a comment

Durch Zufall lese ich gerade den Artikel “Serienmörder Fourniret provoziert Gericht” (http://www.tagesschau.de/ausland/fourniret6.html). Dort ist ein Bild des Mannes gezeigt, der Bildtext lautet “Michel Fourniret zeigte zum Prozessauftakt keine Reue.“. Im Archiv findet sich ein weiterer Artikel (http://www.tagesschau.de/ausland/meldung231524.html). Dort ist das Bild ebenfalls zu sehen aber ein kleinerer Ausschnitt und schwarzer Balken über den Augen. Das wäre ja nun nicht weiter interessant wenn die beiden Artikel nicht knapp 4 Jahre auseinanderliegen würden. Wie kann der Mann in 4 Jahren nicht altern, immer noch die gleiche Jeansjacke tragen und er wird immer noch von den gleichen Reportern belagert …

Bild1 http://www.tagesschau.de/ausland/fourniret6.html

Bild 2 http://www.tagesschau.de/ausland/meldung231524.html

Categories: Medien Tags:

JSF, Ajax and file download

Monday, 31. March 2008 Andreas Höhmann 2 comments

Today i show you a example which combines Ajax (Richfaces) and a “normal” Download in a JSF application.
The bean for the download-code is here:

public class DownloadBean {

  // actionListener="#{bean.execute}"
  public void execute (ActionEvent event) {
   download();
  }

  // action="#{bean.download}"
  public String download() {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    // XXX create temp. filecontent ... resultFile
    writeOutContent(getServletResponse(), new File(this.resultFile), "file.xml");
    facesContext.responseComplete();
    // dont use jsf-navigation-rules
    return null;
  }

  void writeOutContent(final HttpServletResponse res, final File content, final String theFilename) {
    if (content == null)
      return;
    try {
      res.setHeader("Pragma", "no-cache");
      res.setDateHeader("Expires", 0);
      res.setContentType("text/xml");
      res.setHeader("Content-disposition", "attachment; filename=" + theFilename);
      fastChannelCopy(Channels.newChannel(new FileInputStream(content)), Channels.newChannel(res.getOutputStream()));
    } catch (final IOException e) {
    }
  }

  void fastChannelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException {
    final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
    while (src.read(buffer) != -1) {
      buffer.flip();
      dest.write(buffer);
      buffer.compact();
    }
    buffer.flip();
    while (buffer.hasRemaining()){
      dest.write(buffer);
    }
  }
}

Now the facelets snipplets: Not Working – Javascript Error :-(

<h:form>
 <h:commandLink actionListener="#{command.execute}" value="Download"/>
 <h:commandLink action="#{command.download}" value="Download"/>
</h:form> 

Not Working – Download is shown in the current page – the ajax page will replaced with the download-content :-(


<a4j:form>
 <a4j:commandLink actionListener="#{command.execute}" value="Download"/>
 <a4j:commandLink action="#{command.download}" value="Download"/>
</a4j:form>

Working :-)

<a4j:form>
 <a4j:htmlCommandLink actionListener="#{command.execute}" value="Download"/>
 <a4j:htmlCommandLink action="#{command.download}" value="Download"/>
</a4j:form>
<h:form>
 <a4j:htmlCommandLink actionListener="#{command.execute}" value="Download"/>
 <a4j:htmlCommandLink action="#{command.download}" value="Download"/>
</h:form>

The a4j:htmlCommandLink was made for this szenario.

Notice: the fast channel copy routine was introduced by waffel.

JSF behind reverse proxy

Friday, 7. March 2008 Andreas Höhmann Leave a comment

The problem “try to run a jsf application behind a reverse proxy” it will not work if you change the context-path.

Imagine a local deployed jsf application “hello-world.war”. You can start browsing at http://localhost:8080/hello-word/. The browser handles the absolute paths for css and javascript-libs correctly and all forms will work. Remeber that all resource-urls converted to a “contex-absolut-url” by the jsf-framework.

If you open http://localhost:8080/hello-world/index.jsf the the client html will contains resources like this:

<link rel=’stylesheet’ type=‘text/css’
href
=’/hello-world/a4j_3_1_3.GAcss/tabPanel.xcss/DATB/eAGTWzQ.BgAD.AG8.jsf’>
<script type=‘text/javascript’ src=‘/hello-world/a4j_3_1_3.GAorg.ajax4jsf.javascript.PrototypeScript.jsf’>
<form id=“j_id14″ name=“j_id14″ method=“post” action=“/hello-world/index.jsf”>

(Note: I use richfaces 3.1.4 in the example)

The browser will append each absolute url (starting with ‘/’) to the host to make the request. And here is the problem if you want to use this jsf-application behind a reverse proxy. Then all absolute URLs pointing to a wrong resource and nothing will work. To try this you can install a local apache web server the following to the httpd.conf:

Listen 9090
ProxyRequests On
ProxyPreserveHost On
ProxyVia full
ProxyPass /external-path/hello-world http://localhost:8080/hello-world/

(This will start the apache at localhost:9090 to avoid port-conflicts)

If you open http://localhost:9090/external-path/hello-world/index.jsf the the client html contains the same absolute urls and the browser try to load these resource from the “wrong server”: http://localhost:9090/hello-world/. If you imagine a real world szenario then the reverse proxy will use a “real world domain” and the urls looks like this http://www.mycompany.de/external-path/hello-world/.

So whats the solution for this? I try my own ViewHandler to convert absolute to relative urls. Two things must be done:

1. Implement the ViewHandler

2. Register the ViewHandler

First the code … we extend javax.faces.application.ViewHandler and overwrite the two methods getActionURL and getResourceURL.

package de.ahoehma.jsf;

public class ReverseProxyViewHandler extends ViewHandler {

 @Override
 public String getActionURL(final FacesContext context, final String viewId) {
  return getRelativeURL(context, this.defaultHandler.getActionURL(context, viewId));
 }

 @Override
 public String getResourceURL(final FacesContext context, final String path) {
  return getRelativeURL(context, this.defaultHandler.getResourceURL(context, path));
 }

 /**
  * Transform the given URL to a relative URL <b>in the context of the current
  * faces request</b>. If the given URL is not absolute do nothing and return
  * the given url. The returned relative URL is "equal" to the original url but
  * will not start with a '/'. So the browser can request the "same" resource
  * but in a relative way and this is important behind reverse proxies!
  *
  * @param context
  * @param theURL
  * @return
  */
 private String getRelativeURL(final FacesContext context, final String theURL) {
  final HttpServletRequest request = ((HttpServletRequest) context.getExternalContext().getRequest());
  String result = theURL;
  if (theURL.startsWith("/")) {
   int subpath = StringUtils.countMatches(getPath(request), "/") - 1;
   String pathPrefix = "";
   if (subpath > 0) {
    while (subpath > 0) {
     pathPrefix += "/..";
     subpath--;
    }
    pathPrefix = StringUtils.removeStart(pathPrefix, "/");
   }
   result = pathPrefix + result;
  }
  return result;
 }

 /**
  * Get the url-path from the given request.
  *
  * @param request
  * @return clean path
  */
 private String getPath(final HttpServletRequest request) {
  try {
   // TODO handle more than two '/'
   return StringUtils.replace(new URI(request.getRequestURI()).getPath(), "//", "/");
  } catch (final URISyntaxException e) {
  // XXX URISyntaxException ignored
  return StringUtils.EMPTY;
  }
 }
}

The absolute2relative-algorithm prepends for each “/” in the current request-url a “../” to the resource/action-url … thats all :D

And last register the view-handler. If you use A4J you have to add this to your web.xml:

<context-param>
<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
<param-value>de.ahoehma.jsf.ReverseProxyViewHandler</param-value>
</context-param>

That’s all :D

Now if you open http://localhost:9090/external-path/hello-world/index.jsf the client html contains only relative urls:

<link rel=’stylesheet’ type=‘text/css’
href
=’../hello-world/a4j_3_1_3.GAcss/tabPanel.xcss/DATB/eAGTWzQ.BgAD.AG8.jsf’>
<script type=‘text/javascript’ src=‘../hello-world/a4j_3_1_3.GAorg.ajax4jsf.javascript.PrototypeScript.jsf’>
<form id=“j_id14″ name=“j_id14″ method=“post” action=“../hello-world/index.jsf”>