Quick-n-Dirty: A simple isRecycled() method

Update: Please read the second article – there you will find a better version.

You will find that the domino java API does not have a suitable method to test if a domino object was already recycled or not. And because of the internal caching of some method calls it is not reliable to check for example for a specific property with a try/catch block.

But domino objects have a private property isdeleted which is transient and gives us the required information. With Java Reflection it is possible to access this property:

package ch.hasselba.domino;

import java.lang.reflect.Field;

import lotus.domino.Base;
import lotus.domino.local.NotesBase;

/**
 * DominoUtil a library containing usefull Tools / Methods for domino
 * 
 * @author Sven Hasselbach
 * @category Domino
 * @category Tools
 * @version 1.2
 */
public class DominoUtil {

    private static Field isDeleted;    

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.local.NotesBase
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(NotesBase obj) {

        if( isDeleted == null )
            initIsDeleted();

        try {
            return isDeleted.getBoolean(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.Base
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(Base obj) {
        return isRecycled( (NotesBase) obj );
    }

    /**
     * inits Reflection of isDeleted field
     */
    private static void initIsDeleted(){
        try {
            isDeleted = lotus.domino.local.NotesBase.class
                    .getDeclaredField("isdeleted");
            isDeleted.setAccessible(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Here is a short demonstration of the usage in an agent (it works in XPages / SSJS too):

import lotus.domino.*;
import ch.hasselba.domino.DominoUtil;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

      try {
          Session session = getSession();
          AgentContext agentContext = session.getAgentContext();
          Database db = session.getCurrentDatabase();

          View v1 =  db.getView( "AllDocuments" );
          View v2 =  db.getView( "AllDocuments" );

          System.out.println( "Recycled: " + 
             DominoUtil.isRecycled( v1 ) );

          v2.recycle();

          System.out.println( "Recycled: " + 
            DominoUtil.isRecycled( v1 ) );

      } catch(Exception e) {
          e.printStackTrace();
       }
   }
}
Veröffentlicht unter Java, ServerSide JavaScript | Verschlagwortet mit , , , | 7 Kommentare

Teamstudio Unplugged: SSJS & Error Messages

Today I had to fight with a mysterious error message in a XPage application which is running on Teamstudio Unplugged:

SyntaxError: missing ; before statement

This IS the message. No more information. No stack trace. No library name. Nothing!

It was a hard piece of work to find the needle in the haystack: I used the synchronized keyword in a SSJS library. Hope in future Teamstudio will provide better error informations…

Veröffentlicht unter Errorhandling, Mobile, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Ein Kommentar

XPages: Capture Signatures with the jQuery-Plugin ‚jSignature‘

In one of my current projects it is one of the goals that the members of the field staff have the possibility to sign a report directly on their iPad. After some research I found the very cool jQuery plugin jSignature, which easily allows to add a signature capture field to a XPage.

The plugin is very easy to use: Just add a <div> to your XPage and initialize the plugin with the .jSignature() constructor and that’s it! The API is very simple, but provides everything needed: The captured signatures can be stored in different formats like PNG or SVG or as native Vector. Additionally they can be encoded as BASE64 or BASE30. The data can restored as easy as they can be saved: Just one API call and it’s done.

To save the signatures to a Notes document, the resulting data can be copied to a hidden input field. In the provied example above I additionally added the format of the signature. For a better handling of the generated data I decided to store the data in the SVG format, because this allows to save it directly in standard notes text field (without having problems because of the 32 K limit). This works well and the data can be displayed in standard browsers without any problems. Only in XPiNC this will not work, because the SVG format is not supported. PDFs doesn’t support SVG too, that’s why I created a converted agent using the Apache Batik framework.

I will add a demo database asap. A description of parameters to customize the plugin can be found here.

P.S. The compressed Version is not running on teamstudio Unplugged. You have to use the uncompressed Version. The example uses the x$ function of Mark Roden.

 XPage example

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.resources>
        <xp:script src="/x$.js" clientSide="true"></xp:script>
        <xp:script src="/jSignatureHandler.js" clientSide="true" />
        <xp:styleSheet href="/jSignature.css"></xp:styleSheet>
    </xp:this.resources>
    <xp:this.data>
        <xp:dominoDocument var="documentSig" 
           formName="frmSignature" />
    </xp:this.data>

    <script src="js/jquery-1.8.1.min.js" />
    <script src="js/jSignature/jSignature.min.js" />

    <xp:div id="jSignature" style="width:400.0px"></xp:div>
    <xp:scriptBlock id="scriptBlockSignature">
        <xp:this.value><![CDATA[
        $(document).ready(function() { 
            sigHandler.init( "#{id:jSignature}", 
               "#{id:inputHiddenSignatureData}", 
               "#{id:inputHiddenSignatureFormat}" );
        }
        )]]></xp:this.value>
    </xp:scriptBlock>

    <xp:button id="button1" value="Reset">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script><![CDATA[
                sigHandler.reset();
            ]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:button id="button2" value="Save">
        <xp:eventHandler event="onclick" submit="true"
           refreshMode="complete">
            <xp:this.script><![CDATA[ 
                return sigHandler.save();
            ]]></xp:this.script>
            <xp:this.action>
                <xp:saveDocument var="documentSig" />
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

    <xp:inputHidden id="inputHiddenSignatureData" 
       value="#{documentSignature.SignatureData}" />
    <xp:inputHidden id="inputHiddenSignatureFormat"
       value="#{documentSignature.SignatureFormat}" />

</xp:view>

CSJS library „jSignatureHandler.js“

/**
 * signatureHandler
 * JS Object for handling the signature data
 * Requires jSignature jQuery plugin & special 
 * function "x$" from Mark Roden
 * 
 * @author Sven Hasselbach
 * @category JavaScript
 * @category jQuery
 * @category UI
 * @version 1.0
 */
var signatureHandler = function() {
    _module = "signatureHandler";

    _idJSignature: null; // DOM id of JSignatue DIV
    _idItemData: null; // DOM id of INPUT for signature data
    _idItemFormat: null; // DOM id of INPUT for signature format
    _objDOMJSignature: null; // handle to DOM object
    _objDOMItemData: null; // handle to DOM object
    _objDOMItemFormat: null; // handle to DOM object
    _maxSize = 32000; // max characters to store (32K limit!)
    _format = "svg"; // format used

    /**
     * set DOM id of JSignature DIV
     * @param String id
     */
    this.setJSignatureId = function( id ){
        this._idJSignature = id;
    }
    /**
     * get DOM id of JSignature DIV
     * @return String id
     */
    this.getJSignatureId = function(){
        return this._idJSignature;
    }
    /**
     * set DOM id of data item
     * @param String id
     */
    this.setItemDataId = function( id ){
        this._idItemData = id;
    } 
    /**
     * get DOM id of data item
     * @return String id
     */
    this.getItemDataId = function(){
        return this._idItemData;
    }
    /**
     * set DOM id of format item
     * @param String id
     */
    this.setItemFormatId = function( id ){
        this._idItemFormat = id;
    } 
    /**
     * get DOM id of format item
     * @return String id
     */
    this.getItemFormatId = function(){
        return this._idItemFormat;
    }

    /**
     * get handle to DOM object of JSignature DIV
     * @return Object
     */
    this.getJSignatureDOMObj = function(){
        return this._objDOMJSignature;
    }
    /**
     * set handle to DOM object of JSignature DIV
     * @param Object
     */
    this.setJSignatureDOMObj = function( obj ){
        this._objDOMSignature = obj;
    }
    /**
     * get handle to DOM object of data item
     * @return Object
     */
    this.getItemDataDOMObj = function(){
        return this._objDOMItemData;
    }
    /**
     * set handle to DOM object of data item
     * @param Object
     */
    this.setItemDataDOMObj = function( obj ){
        this._objDOMItemData = obj;
    }
    /**
     * get handle to DOM object of format item
     * @return Object
     */
    this.getItemFormatDOMObj = function(){
        return this._objDOMItemFormat;
    }
    /**
     * set handle to DOM object of format item
     * @param Object
     */
    this.setItemFormatDOMObj = function( obj ){
        this._objDOMItemFormat = obj;
    }

    /**
     * initialize object
     * 
     * @param String id of jSignature DIV
     * @param String id of data item INPUT
     * @param String id of format item INPUT
     * 
     */
    this.init = function( idJSig, idItemData, idItemFormat ){
        try{

            // init jSignature
            this._idJSignature = idJSig;
            this._objDOMSignature = x$( this._idJSignature ) ;
            this._objDOMSignature.jSignature();
            // init data item
            this._idItemData = idItemData;
            this._objDOMItemData = x$( this._idItemData );

            // init format item
            this._idItemFormat = idItemFormat;
            this._objDOMItemFormat = x$( this._idItemFormat );

            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }

    /**
     * reset jSignature
     */
    this.reset = function(){
        try{
            this._objDOMSignature.jSignature("reset");
            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
    /**
     * saves the data from jSignature
     * 
     */
    this.save = function(){
        try{
            var datapair =  this._objDOMSignature.jSignature( "getData", _format );
            var format = "data:" + datapair[0];
            var data = datapair[1];
            // check max size!
            if( data.length >  _maxSize){
                alert( "The size of the signature is too large. Please retry!" );
                return false;
            }

            this._objDOMItemData.val( data );

            this._objDOMItemFormat.val( format )
            return true;

        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
}

// init JS instance
sigHandler = new signatureHandler();

EDIT:
Ursus created a demo database. You can find it here.

Veröffentlicht unter Java, Java Script, Mobile, Web, XPages | Verschlagwortet mit , , , , , , , , , | 10 Kommentare

Quick-n-Dirty: Development Helper for Unplugged

I started today testing/evaluating Teamstudio Unplugged for a customer project. The first thing I missed during developement was a button to sync the current page directly to see my changes on the fly. That’s why I created this small custom control:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <table border="1" width="100%" cellpadding="2">
      <tr>
         <td colspan="3" align="middle">
            <b>Unplugged Development Helper</b>
         </td>
     </tr>
     <tr>
        <td align="middle">
           <input type="button"
              onclick="window.location='/unpws.unp/'" 
              name="Workspace" value="Workspace" />
         </td>
         <td>&#160;</td>
         <td align="middle">
            <input type="button"
              onclick="$.get('/unpws.unp/ajaxreplicate.xsp', function(data){ location.reload(); });"
              name="Sync" value="Sync!" />
         </td>
      </tr>
   </table>   
</xp:view>

The Workspace Button brings you to the workspace, the Sync button starts the replication and reloads the current page.

Veröffentlicht unter HTML, Java Script, Mobile | Verschlagwortet mit , , , , | Schreib einen Kommentar

XPages: High Performance Applications

During the last months I worked on a high performance XPages application used by a lot of end users.  To get a better data throughput, I decided to use a reverse proxy for load balancing, caching of ressources, SSL connections etc.

For static resources I am using some „special“ domains: This means that the browser is allowed to do more HTTP requests at once to the same NSF. If you have for example some images in your database which are reachable from outside via http://www.example.com/mydb.nsf/image.gif, this can be changed to http://static.example.com/mydb.nsf/image.gif (Here you can find a list of best practices).

I solved the problem of multiple execution during the JSF lifecycle  by checking if the request has already actual data (details can be found here – German only), but there was still a problem: Everytime a XPage wants some data to display, a query is sent to the domino server. This is nice if your application requires data in real-time, but not for a normal application – it kills the user experience.

This is why I searched a way which easily allows to implement memory cached database queries for domino databases.  The main idea is that the database query is no longer send directly to the backend . Instead, the request is made against a JSON wrapper, and the request to this wrapper (an agent residing in the same NSF) is done via a proxy. This allows a full control of the requested data.

The browser sends the HTTP request to the XPage and receives the response. The XPage queries the MemCache for data; if the data is not in the cache, the proxy queries the data storage (the domino database) and caches the result. The XPage has to parse the JSON data only, and this boosts the performance in my test environment for about 250-300%.

By „stealing“ the session cookie, the database query to the backend database will be done in the context of the user; the security for domino databases is not influenced.

In my solution, I am using Apache 2.2 as reverse proxy. The following modules are additionally enabled for this solution:

  • cache_module
  • deflate_module
  • expires_module
  • headers_module
  • mem_cache_module
  • proxy_module
  • proxy_http_module
  • rewrite_module
  • setenvif_module

The virtual host configuration looks like this:

<VirtualHost *:8080>
 ServerName localhost

 # Enable reverseproxy
 ProxyRequests Off
 ProxyPreserveHost On
 <Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
 </Proxy>

 # Proxy config for Domino server
 # 
 ProxyPass / http://localhost:80/
 ProxyPassReverse / http://localhost:80/

  # prevent max-age calculation from Last-Modified
  # prevent If-Modified-Since requests
  # reduces the number of requests that hit the server
 <LocationMatch "/.*$">
  Header unset Last-Modified
  Header unset ETag
  Header unset HTTP_CACHE_CONTROL
 </LocationMatch>

 # MemCache Config
 # 
 CacheEnable mem /
 CacheEnable mem http://

 # Cache-Size 80 MB
 MCacheSize 81920
 MCacheMaxObjectCount 8192

 # Min Obj. Size 1 Byte
 MCacheMinObjectSize 1

 # Max Obj. Size 1 MB
 MCacheMaxObjectSize 1000000

 # cache for 60 seconds by default
 CacheDefaultExpire 60

 # FORCE caching for all documents (without Cache-Control: no-cache)
 CacheIgnoreNoLastMod On

 # force caching for all requests
 # ignore client side Cache-Control header
 CacheIgnoreCacheControl On
 # don't add Set-Cookie header to cache
 CacheIgnoreHeaders Set-Cookie

 # Add expires headers for images, css & js files
 # reduces the number of requests that hit the server
 ExpiresActive On
 ExpiresByType domino/json A600

</VirtualHost>

As you can see, the proxy runs on port 8080, and I have added a special content type „domino/json„. This makes it easier to identify the relevant data.

This is the XPage the user accesses:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
  xmlns:xc="http://www.ibm.com/xsp/custom">

    <xp:pager layout="Previous Group Next" partialRefresh="true"
        id="pager1" for="repeat1">
    </xp:pager>

    <xp:inputText id="inputSearch" value="#{sessionScope.searchFor}" />

    <xp:button value="Label" id="button1">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete">
        </xp:eventHandler>
    </xp:button>

    <xp:repeat id="repeat1" rows="30" var="rowData">
        <xp:this.value><![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );

            var sessionId = null;
            try{
                 sessionId = cookie.get("DomAuthSessId").getValue();
            }catch(e){}

            var url = "http://localhost:8080/Data.nsf/DoSearch?OpenAgent";
            url += "&sessionId=" + sessionId;

            if( sessionScope.get("searchFor") !== null ){
                if( sessionScope.get("searchFor") !== "" )
                    url += "&search="; 
                    url += java.net.URLEncoder.encode(sessionScope.get("searchFor"),"UTF-8");
            }

            var data = ch.hasselba.xpages.util.URLReader.read( url, sessionId );
            var parsed = null;
            try{
                 parsed = fromJson(data).data;
            }catch(e){}
            parsed
            }]]>
        </xp:this.value>
        <xp:text escape="true" id="computedField1" value="#{javascript:rowData.LastName}">
        </xp:text>
        &#160;
        <xp:text escape="true" id="computedField2" value="#{javascript:rowData.FirstName}">
        </xp:text>
        <xp:br />
    </xp:repeat>

</xp:view>

The Java class used is really simple, I know there are better ways to do a Http request, but this is a proof of concept.

package ch.hasselba.xpages.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * URLReader
 * performs a HTTP request
 * 
 * @author Sven Hasselbach
 * @category URL
 * @category Proxy
 * @version 0.2
 */
public class URLReader {

    // Constants
    private final static String PROPERTY_COOKIE_NAME = "Cookie";
    private final static String PROPERTY_DOMAUTHSESSID_VALUE = "DomAuthSessId=";

    /**
     * reads data from a given URL
     * 
     * @param pURL URL to load data from
     * @param pSessionId session data for doing a request in the current user context
     * @return String containg the result of the http request
     * @author Sven Hasselbach
     * @category URL
     * @category Proxy
     * @version 0.2
     */
    public static String read( final String pURL, final String pSessionId ){
        String data = null;

        try{
            // init the URL connection
            URL url = new URL( pURL );
            URLConnection uc = url.openConnection();

            // "steal" the original user session cookie
            if( !("".equals(pSessionId)))
                    uc.setRequestProperty ( PROPERTY_COOKIE_NAME ,
                       PROPERTY_DOMAUTHSESSID_VALUE + pSessionId);

            // do the HTTP request
            BufferedReader in = new BufferedReader( 
               new InputStreamReader( uc.getInputStream() ));

            // process the data returned 
            StringBuffer strBuf = new StringBuffer();
            String tmpStr = "";
            while((tmpStr = in.readLine()) != null ){
                strBuf.append( tmpStr );
            }
            data = strBuf.toString();

        }catch(Exception e){
            e.printStackTrace();
        }

        return data;
    }
}

And here comes the JSON handler, a simple Lotus Script agent:

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim isFirst As Boolean
    Dim contextDoc As NotesDocument
    Dim hlp
    Set contextDoc = session.Documentcontext
    Set db = session.Currentdatabase

    ' get the search string from the URL or use the default search
    hlp = Split( contextDoc.QUERY_STRING_DECODED(0), "search=" )
    If UBound( hlp ) = 0 Then
        Set dc = db.Ftsearch("[FirstNAME] CONTAINS AARON", 0)
    Else
        Set dc = db.Ftsearch(hlp(1), 0)
    End If

    ' create the JSON output    
    isFirst = true
    Set doc = dc.Getfirstdocument()

    ' special content type domino/json
    Print |Content-type: domino/json|
    Print
    Print |{"data":[|
    While Not doc Is Nothing
        If Not isFirst Then Print ","
        Print |{"LastName":"| & doc.LastName(0) & _
        |","FirstName":"| & doc.FirstName(0) & |"}|
        isFirst = False
        Set doc = dc.Getnextdocument( doc )
    Wend

    Print |]}|

End Sub

In the next days I will provide a sample database and add more details. A database with 300k test datasets will be added too.

Veröffentlicht unter Java, Java Script, Lotus Script, Performance, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , , | 8 Kommentare

XPages: Lost in Translation

The localization feature is really nice and helps a lot, but you also can have some trouble using it. The first problem is that the language codes which are used in XPages are different from the language codes in java.util.Locale.

This SSJS code for example will not work:

var locale = new java.util.Locale( "fr_BE" );
context.setLocale( locale );
context.reloadPage();

It will not work because the java.util.Locale object returns „fr_be„, but the PageDetails are set to „fr_BE„, and that’s why the setting the language does not work.

<xp:text escape="true" id="computedFieldLocale">
   <xp:this.value>
      <![CDATA[#{javascript:
         var locale = new java.util.Locale( "fr_BE" );
         locale.getLanguage()}]]>
      </xp:this.value>
</xp:text>

[This SSJS Code returns „fr_be“]

[In PageDetails „fr_BE“ is used]

But if you set the locale string manually with the setLocaleString() method of the context object, it works as designed.

The second problem I ran into is the aggressive transformation of all texts on a XPage. I copied a simple CSS Style definition from another database, but after copy/pasting it I was unable to see any changes in the browser, as soon I switched to another than the default language. The CSS style didn’t work anymore:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <style>
      .red { color:rgb(255,0,0 ) }
   </style>
   <p>TEXT</p>
</xp:view>

[Default Language, it worked]

[Other Language, not working anymore]

The CSS style was transformed by the localization feature and was now „translated“. In the generated HTML code, the tag looked like this:

<style>[en| .red { color:rgb(255,0,0 ) } ]</style>

Same result for directly copied JavaScript-Code, Text etc. (as shown in the screenshot above).

But I still love this feature. It makes developers life a lot easier.

Veröffentlicht unter Allgemein, CSS, HTML, Java Script, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

That’s not me!

Hey! That’s not me on CollaborationToday.info!

That’s me:

http://www.gravatar.com/avatar/e3383e9bf66559af149b4b91e2f8787c?s=128&d=identicon&r=PG

Veröffentlicht unter Allgemein, Bug | Verschlagwortet mit | 2 Kommentare

XPages: The Outputstream and binary data

As wiritten in the comments, Verne is correct. I just missed the „facesContext.responseComplete()“ in the beforeRenderResponse event.

If you want to get control over the outputstream of a XPage, you can use the response object from the ExternalContext:

var response = facesContext.getExternalContext().getResponse()

This will give you access to an object of type com.ibm.xsp.webapp.XspHttpServletResponse which allows some basic operations, but the response will always be encoded to UTF-8. You can not return any binary data directly.

But if you access the underlying LCDAdapterHttpServletResponse directly, it is possible to get the full control for the outputstream.

var exCon = facesContext.getExternalContext();
var response = exCon.getResponse().getDelegate();

This allows you to send any data you want to the browser. Here is an example for a JPEG rendered directly in the XPage:

1. The XPage

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="true">

   <xp:this.afterRenderResponse>
      <![CDATA[#{javascript:
         importPackage( ch.hasselba.xpages.demo );
         var exCon = facesContext.getExternalContext();
         var response = exCon.getResponse().getDelegate();
         ch.hasselba.xpages.demo.JPEGGenerator.generate( response );
      }]]>
   </xp:this.afterRenderResponse>

</xp:view>

 

2. The Java object to generate a JPEG

package ch.hasselba.xpages.demo;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import java.awt.Color;
import java.awt.Font;

/**
 * JPEG Generator for a simple demonstration of controlling the
 * HttpServletResponse output
 * 
 * @author Sven Hasselbach
 * @category Demo
 * @category Servlet
 * @category Image
 * @version 1.0
 */

public class JPEGGenerator {

    private static final long serialVersionUID = 1L;
    private static final int WIDTH = 800;
    private static final int HEIGHT = 200;
    private static final Color BACKGROUND_COLOR = new Color(224, 224, 224);
    private static final Color COLOR = new Color(0, 0, 0);
    private static final Font FONT = new Font("Times New Roman", Font.BOLD, 46);

    /**
     * generates a JPEG image and sends the result to the outputstream of the
     * HttpServlet
     * 
     * @param response
     *            HttpServletResponse
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static void generate(final HttpServletResponse response) {
        ServletOutputStream out = null;
        try {

            // set the content type for JPEG
            response.setContentType("image/jpg");

            // get the ouput stream
            out = response.getOutputStream();

            // generate a image to convert
            BufferedImage img = createImage();

            // convert image to jpeg and send to output
            JPEGCodec.createJPEGEncoder(out).encode(img);

        } catch (ImageFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * creates a simple "Hello World" image
     * 
     * @return BufferedImage
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static BufferedImage createImage() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
                BufferedImage.TYPE_BYTE_INDEXED);
        Graphics graphics = image.getGraphics();
        graphics.setColor(BACKGROUND_COLOR);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        graphics.setColor(COLOR);
        graphics.setFont(FONT);
        graphics.drawString("Hello World!", 10, HEIGHT / 2);

        return image;
    }

}

There is no need for splitting this example in a separated Java class. You can directly access the output in SSJS and send your binary data directly via write method of the ServletOutputStream.

Veröffentlicht unter Java, Server, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , | 7 Kommentare

Security: Another XSS Vulnerability in Domino

Stephan Wissel wrote about a XSS vulnerabilty for Domino servers (< 8.5.4) and in his post you will get an advise how to protect your domino server against this attack. Thanks for this! Works great!

But there is still a problem with another URL pattern:

*/xsp/.ibmmodres/*

This resolves resources from databases, that’s why it only works in a database URL. But normally domcgf.nsf is reachable from outside.

Update:

The blog post was updated on wissel.net. Please update your server configuration!

Veröffentlicht unter Infrastruktur, Java Script, Security, Server, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Quick-n-Dirty: Disable Domino’s Cache for easier development

I am currently developing a larger application and have a lot of different mobile devices for testing purposes. After making some changes it is required that all test devices have the latest version of my XPages running, and that the clients get the latest version of the frontend libraries (CSJS, CSS, Images, etc.).

Because it is rather annoying to always check whether the browser cache is cleared or not I decided to disable the caching on the domino server directly via a web rule. This works really great and helps a lot during testing and designing.

In this example the caching for databases and their ressources in the folder /dev will be disabled:

1. Create a web rule for your Internet Sites

2. Set the type of the rule to „HTTP response headers

3. Fill in incoming pattern to /dev/*

4. For the „Expires header“ select „Always add header“ and set it to a date in the past

5. Add two custom headers and check the „override“ option:

  • Cache-Control: no-cache
  • Pragma: no-cache

6. Open the admin console and refresh the HTTP task:

tell http refresh

From now on the headers are always added and will force the browsers to reload the resource every time. Every ressource delivered by the domino server which is inside the /dev folder (contained in a databases) won’t be cached anymore. It works even for minimized libraries and SSL.

Veröffentlicht unter Allgemein, Infrastruktur, Server, Web | Verschlagwortet mit , , , , , | 2 Kommentare