Customize Tomahawk scheduler to support grouped entries and lazy loading

I am using myfaces (1.1.5) in my web application, and I was in the need of having some sort of a calendar/scheduler where the user would be able to add multiple records from one category/group/datatype. Imagine that you have multiple collections of some types of data, and each item within each type have date properties that could be presented in a calendar. I checked the scheduler of the Tomahawk library (1.1.6), but this was not supported by default. You could only add “standalone” entries, with no possibility of grouping them together.

Still, I decided to give it a try, and wanted to extend the scheduler to support this functionality. Another thought I had was that I wanted to only retreive the data I needed from the server, in other words: If a datatype have several thousand items, but in the month I’m currently viewing there are really just 18 representations, I would like to only fetch those 18 instances, not the whole collection every time.

In the following example I’ve only done the customization for the “month” mode, as this was the one of importance to me. But it should not be difficult to do this also for the other modes if that is needed.

To start, I decided to create a new class called SchedulerEntryGroup. Basically you can think of this as a group that holds from n to many items that should be entries in our calendar.


public class SchedulerEntryGroup
{
    private String name;
    private String dataTypeName;
    private List<Item> items;
    private String color;
    private boolean visible;
    private String dateProperty;
    private String displayProperty;

   public SchedulerEntryGroup()
    {
        items = new ArrayList<Items>();
    }

    publc SchedulerEntryGroup(String name, String color, boolean visible, String dataTypeName, String dateProperty,String displayProperty)
    {
        setName(name);
        setColor(color);
        setVisible(visible);
        setDateProperty(dateProperty);
        setDisplayProperty(displayProperty);
        setDataTypeName(dataTypeName);

    }

   .........
   SET/GET METHODS
   ......... 

}

As you can see the group holds properties like name (label), dataTypeName (which object-type it contains), the list of items and if it should be visible or not (to enable the possibility of activating/deactivating the displayed entries of a group with a checkbox e.g). It also contains a color property, as this should be common for all the entries of the same group. The dateProperty says which property that contains the dates, and the displayProperty which property should be displayed for the entry in the calendar.In my world the Item object holds a map with objects, and that’s why I pass properties as Strings to get them from the object. It’s basically the key in the properties-map. For reference a “short-version” of my Item-class looks like this:


public class Item
{
    private Map<String,Object> properties;

    public Item()
    {
        properties = new HashMap<String,Object>();
    }

    public Object getProperty(String key)
    {
        return properties.get(key);
    }

}

Ok, I have my entry-group now, but I also made a small change to the Entry class itself, as I wanted to have a color property. I extended the DefaultScheduleEntry and created a class SchedulerEntryExtended, the only difference adding a property color:


public class SchedulerEntryExtended extends DefaultScheduleEntry
{
    private String color;

    public SchedulerEntryExtended()
    {
        super();
    }

    public SchedulerEntryExtended(String description, boolean allDay, Date startTime, Date endTime, String title, String subTitle, String color)
    {
        super();
        super.setDescription(description);
        super.setAllDay(allDay);
        super.setStartTime(startTime);
        super.setEndTime(endTime);
        super.setTitle(title);
        super.setSubtitle(subTitle);
        setColor(color);
    }

    public String getColor()
    {
        return color;
    }
    public void setColor(String color)
    {
        this.color = color;
    }
}

Then I had to change the rendering of entries in month-mode, as I want the group’s color to be set as background-color for each entry. To do this I extended and overrode the DefaultScheduleEntryRenderer and created the class ScheduleEntryRenderer. There are two methods I override, the getColor and the renderCompactContent. The getColor was originally implemented like this:


public String getColor(FacesContext context, HtmlSchedule schedule,
                           ScheduleEntry entry, boolean selected)
    {
        return null;
    }

And the renderCompactContent was implemented like this:


protected void renderCompactContent(FacesContext context, ResponseWriter writer, HtmlSchedule schedule, ScheduleDay day, ScheduleEntry entry, boolean selected) throws IOException
    {
        StringBuffer text = new StringBuffer();
        Date startTime = entry.getStartTime();

        if (day.getDayStart().after(entry.getStartTime()))
        {
            startTime = day.getDayStart();
        }

        Date endTime = entry.getEndTime();

        if (day.getDayEnd().before(entry.getEndTime()))
        {
            endTime = day.getDayEnd();
        }

        if (!entry.isAllDay())
        {
            DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT);
            text.append(format.format(startTime));
            if (!startTime.equals(endTime)) {
                text.append("-");
                text.append(format.format(endTime));
            }
            text.append(": ");
        }
        text.append(entry.getTitle());

        writer.writeText(text.toString(), null);
    }

My ScheduleEntryRenderer class came out like this:

public class SchedulerEntryRenderer extends DefaultScheduleEntryRenderer
{
     public String getColor(FacesContext context, HtmlSchedule schedule,
             ScheduleEntry entry, boolean selected)
     {

	        SchedulerEntryExtended ent = (SchedulerEntryExtended) entry;
         return ent.getColor();
     }

	    protected void renderCompactContent(FacesContext context, ResponseWriter writer, HtmlSchedule schedule, ScheduleDay day, ScheduleEntry entry, boolean selected) throws IOException
     {

	        StringBuffer text = new StringBuffer();
         Date startTime = entry.getStartTime();

	        if (day.getDayStart().after(entry.getStartTime()))
         {
             startTime = day.getDayStart();
         }

	        Date endTime = entry.getEndTime();

	        if (day.getDayEnd().before(entry.getEndTime()))
         {
             endTime = day.getDayEnd();
         }

	        if (!entry.isAllDay())
         {
         	DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT);
         	text.append(format.format(startTime));
         	if (!startTime.equals(endTime)) {
         		text.append("-");
         		text.append(format.format(endTime));
         	}
         	text.append(": ");
         }
         text.append(entry.getTitle());

	        StringBuffer entryStyle = new StringBuffer();
 		  entryStyle.append("height: 100%; width: 100%;");
 		  String entryColor = getColor(context, schedule, entry, selected);
 		  if (entryColor != null)
 		  {
 		      entryStyle.append("border-color: ");
 		      entryStyle.append(entryColor);
 		      entryStyle.append(";");
 		      entryStyle.append("background-color: ");
 		      entryStyle.append(entryColor);
 		      entryStyle.append(";");
 		  }
 		  writer.startElement(HTML.DIV_ELEM, null);
 		  writer.writeAttribute(HTML.STYLE_ATTR,entryStyle.toString(), null);
 		  writer.writeText(text.toString(), null);
 		  writer.endElement(HTML.DIV_ELEM);
 	}
}

Notice that in getColor() I cast and use my SchedulerEntryExtended to retrieve the color. What I do in the renderCompactContent method is that I create an entryStyle string, and you can see that I apply the entryColor both to the border and the background. Then I wrap the text inside a div, and apply the entryStyle on this div. Thanks for examples at IRIAN and the tomahawk-examples.zip to point me in the direction of how to do this.

Then the main thing that remains is to have a handler that holds the model for the calendar, as well as having business-logic for using our entry-groups.This is my ScheduleHandler, which have methods for adding, deleting, displaying and hiding groups, as well as the method that creates the model of the calendar, createModel(). The “container” for all the groups that is to be rendered is a linked hashmap named schedulerMap:


public class SchedulerHandler implements Serializable
{
    private static final long serialVersionUID = -8815771399735333108L;

    private ScheduleModel model;
    private Map<String, SchedulerEntryGroup> schedulerMap;
    private SchedulerEntryGroup entryGroupToDelete;
    private SchedulerEntryGroup entryGroupToHide;
    private SchedulerEntryGroup entryGroupToDisplay;
    private Date selectedDate;

    public SchedulerHandler()
    {
        model = new SimpleScheduleModel();
        model.setMode(3);
        model.setSelectedDate(new Date());
        schedulerMap = new LinkedHashMap<String, SchedulerEntryGroup>();
        selectedDate = new Date();
        createModel();
    }

    public Collection<SchedulerEntryGroup> getEntryGroups()
    {
        return schedulerMap.values();
    }

    public void addGroup(SchedulerEntryGroup entryGroup)
    {
        schedulerMap.put(entryGroup.getName(), entryGroup);
        createModel();
    }

    public void hideEntryGroup()
    {
           SchedulerEntryGroup group = schedulerMap.get(getEntryGroupToHide().getName());
        group.setVisible(false);
        schedulerMap.put(group.getName(), group);
        createModel();
    }

    public void displayEntryGroup()
    {
        SchedulerEntryGroup group = schedulerMap.get(getEntryGroupToDisplay().getName());
        group.setVisible(true);
        schedulerMap.put(group.getName(), group);
        createModel();
    }

    public void deleteEntryGroup()
    {
        if(schedulerMap.containsValue(getEntryGroupToDelete()))
        {
            schedulerMap.remove(entryGroupToDelete.getName());
            createModel();
        }
    }

    public ScheduleModel getModel()
    {
        return model;
    }

    public void setModel(ScheduleModel model)
    {
        this.model = model;
    }

   private void createModel()
    {

        setModel(new SimpleScheduleModel());

        int i = 0;
        String id = "";
        for(Entry<String, SchedulerEntryGroup> e : schedulerMap.entrySet())
        {
            id = "Entry_"+e.getValue().getName()+i;
            SchedulerEntryGroup entryGroup = e.getValue();
            if(entryGroup.isVisible())
            {

                List<Item> items = entryGroup.getItems();

                for(Item i : items)
                {
                    SchedulerEntryExtended entry = new SchedulerEntryExtended();
                    entry.setColor(entryGroup.getColor());

                     Object o = i.getProperty(entryGroup.getDateProperty());

                     String s = (String) o;
                     Date d = null;
                     if(StringUtils.isNotBlank(s))
                     {
                        try
                        {
                            d = DateUtil.parseDate((String)o);
                        } catch (ParseException pe)
                        {
                            // TODO Auto-generated catch block
                            pe.printStackTrace();
                        }
                        model.setSelectedDate(d);
                        entry.setId(id);
                        entry.setTitle(i.getProperty(entryGroup.getDisplayProperty()));

                        entry.setStartTime(d);
                        entry.setEndTime(d);
                        entry.setAllDay(true);
                        model.addEntry(entry);
                     }
                     i++;
                }
           }
        }

        if(getSelectedDate() != null)
            model.setSelectedDate(getSelectedDate());
        else
            model.setSelectedDate(new Date());
        model.setMode(3);
        model.refresh();
    }

      public SchedulerEntryGroup getEntryGroupToDelete()
    {
        return entryGroupToDelete;
    }
    public void setEntryGroupToDelete(SchedulerEntryGroup entryGroupToDelete)
    {
        this.entryGroupToDelete = entryGroupToDelete;
    }
    public Date getSelectedDate()
    {
        return selectedDate;
    }
    public void setSelectedDate(Date selectedDate)
    {
        if(selectedDate != null)
        {
            this.selectedDate = selectedDate;
            getModel().setSelectedDate(selectedDate);
            createModel();
        }
    }
    public SchedulerEntryGroup getEntryGroupToHide()
    {
        return entryGroupToHide;
    }
    public void setEntryGroupToHide(SchedulerEntryGroup entryGroupToHide)
    {
        this.entryGroupToHide = entryGroupToHide;
    }
    public SchedulerEntryGroup getEntryGroupToDisplay()
    {
        return entryGroupToDisplay;
    }
    public void setEntryGroupToDisplay(SchedulerEntryGroup entryGroupToDisplay)
    {
        this.entryGroupToDisplay = entryGroupToDisplay;
    }

}

To add a group just create a new instance of it, fill up and set the Item-collection, and pass the group as an argument to the addGroup()-method like this:


public void addEntryGroup()
{
        boolean visible = true;
        SchedulerEntryGroup entryGroup = new SchedulerEntryGroup("myGroup","#CCDD00",visible, "THE_TYPE_OF_DATA", "THE_DATE_PROPERTY", "THE_DISPLAY_PROPERTY");
        SomeService service = new SomeService();
        entryGroup.setItems(someService.getItems("THE_TYPE_OF_DATA", "THE_DATE_PROPERTY"));
        getSchedulerHandler().addGroup(entryGroup);
}

And as you can see the createModel()-method loops the map, and if the group is visible it adds all the items of this group as entries to the calendar. I call createModel() whenever a group is updated or added/deleted, as well as if the selected date of the calendar changes. The “setEntryGroupToDisplay/Hide/Delete()”-methods are used from a jsp/menu to be able to manage the content of the calendar. To hide a group e.g. you could do it very easily with a commandLink and an updateActionListener:


<h:commandLink id="hideLink"  styleClass="myLink" rendered="#{entryGroup.visible}" action="#{schedulerHandler.hideEntryGroup}"  >
          <t:graphicImage value="/images/checkBoxChecked.gif" border="0"></t:graphicImage>
          <t:updateActionListener  value="#{entryGroup}"  property="#{schedulerHandler.entryGroupToHide}"></t:updateActionListener>
</h:commandLink>

Following this example you should now be able to use your calendar with grouped entries. Nice!

But in the beginning of this post I said that I didn’t want to retrieve more items from my backend than I needed to. As the code is now, all the items in the list of the entryGroup are added to the model, and the entryGroup is created before it is aware of which date the calender is currently displaying. This means that it’s impossible to know which items to retreive, as you don’t know which ones are needed.To give the entryGroup knowledge of this, I added a property called dateToFetchFrom, and I move the responsibility of filling up the items-list inside the group-object itself. This is solved by the “fetchItems()”-method. In the calendar-view, if you display a month, it might also contain some days of the previous month as well as the next month. Because of that the date-range I decided to retrieve items for is those three months, the current month, and that month -1 and that month + 1. My SchedulerEntryGroup then became like this:


public class SchedulerEntryGroup
{
    private String name;
    private String dataTypeName;
    private List<Item> items;
    private String color;
    private boolean visible;
    private String dateProperty;
    private String displayProperty;
    private Date dateToFetchFrom;

   public SchedulerEntryGroup()
    {
        dateToFetchFrom = new Date();
	items = new ArrayList<Items>();
    }

    publc SchedulerEntryGroup(String name, String color, boolean visible, String dataTypeName, String dateProperty,String displayProperty)
    {
        setName(name);
        setColor(color);
        setVisible(visible);
        setDateProperty(dateProperty);
        setDisplayProperty(displayProperty);
        setDataTypeName(dataTypeName);

    }

    public void fetchItems()
	{
		Calendar cal = new GregorianCalendar();
		cal.setTime(getDateToFetchFrom());

		cal.add(Calendar.MONTH, -1);
		Date fetchFrom = cal.getTime();
		cal.add(Calendar.MONTH, +1);
		Date fetchTo = cal.getTime();

		List<Item> itemsFetched = new ArrayList<Item>();
		SomeBackendService service = new SomeBackendService();
		service.getItems(getDataTypeName(), getDateProperty(), getDisplayProperty(), fetchFrom, fetchTo );
		setItems(itemsFetched);
	}
   .........
   SET/GET METHODS
   ......... 

}

Of course we then need to modify the createModel() of the SchedulerHandler as well, as we need to take use of the new dateToFetchFrom property as well as the fetchItems()-method. The modified version of createModel() is like this:

private void createModel()
    {
    	setModel(new SimpleScheduleModel());
    	int i = 0;
    	String id = "";
    	for(Entry<String, SchedulerEntryGroup> e : schedulerMap.entrySet())
    	{
    		id = "Entry_"+e.getValue().getName()+i;

    		SchedulerEntryGroup entryGroup = e.getValue();
    		if(entryGroup.isVisible())
    		{

    			entryGroup.setDateToFetchFrom(getSelectedDate());
    			entryGroup.fetchItems();

			List<Item> items = entryGroup.getItems();

                for(Item i : items)
                {
                    SchedulerEntryExtended entry = new SchedulerEntryExtended();
                    entry.setColor(entryGroup.getColor());

                     Object o = i.getProperty(entryGroup.getDateProperty());

                     String s = (String) o;
                     Date d = null;
                     if(StringUtils.isNotBlank(s))
                     {
                        try
                        {
                            d = DateUtil.parseDate((String)o);
                        } catch (ParseException pe)
                        {
                            // TODO Auto-generated catch block
                            pe.printStackTrace();
                        }
                        model.setSelectedDate(d);
                        entry.setId(id);
                        entry.setTitle(i.getProperty(entryGroup.getDisplayProperty()));

                        entry.setStartTime(d);
                        entry.setEndTime(d);
                        entry.setAllDay(true);
                        model.addEntry(entry);
                     }
                     i++;
                }
           }
        }

        if(getSelectedDate() != null)
            model.setSelectedDate(getSelectedDate());
        else
            model.setSelectedDate(new Date());
        model.setMode(3);
        model.refresh();
    }

We see that for each group that is visible, we set the selectedDate of the calendar, and we then execute the fetchItems()-method based on this. If somebody changes the date of the calendar, createModel() is called again, and a new date-range will be the criteria for retrieving items.

As a last comment on the scheduler, I can say that I was troubled by some css-styling and that the borders of the scheduler didn’t render correctly. In my web application I have a style-property on the table tag that says “border-collapse: collapse”. This also effected the rendering of the calendar in Firefox, there were no borders around each day, so the view looked a bit messy. For other reasons I could not remove or change the table tag, so I had to edit the stylesheet of the calendar myself. In the library this can be found under tomahawk-1.1.6.jar\org\apache\myfaces\custom\schedule\resource\css and is named schedule.css. I made the following change to apply separate borders for tables created inside the scheduler only (apply the style to all tables that are children of the div named schedule-compact-default):


div[class=schedule-compact-default]>table
{
border-collapse: separate !important;
}

Ok, I hope that this example have given you some tips on how you can customize the scheduler component. Good luck!

Lazy loading of tables in JSF – working with large amount of data

The challenge working with a large amount of data in user-presentation is to serve the user an easy and effective way of getting to the information he/she is looking for. If you have a table with 50.000 rows, you would in some way need to chop the information in smaller pieces, for example having multiple pages displaying 50 rows each, and then having a way of navigating through the pages.

But still, there are some considerations to do this in a way that opts for performance. If the user selects do display page 2, meaning rows 50-100, wouldn’t you like to fetch only those, not all the 50.000 rows? And like in JSF, if you have multiple components (dataTables, dataScrollers) that are dependent on/using the same data, you would like that the data is only retrieved once, not one time for each component. Doing web applications, being able to limit the amount of data that is fetched from the backend is a key to success when it comes to performance and operability issues.

My example is based on Cagatay Civici’s post http://cagataycivici.wordpress.com/2006/07/10/jsf_datatable_with_custom_paging/,
so I will not go through the technical solution and how the “lazy” data model works once more, I suggest you read his post for that. But I give a complete example of how to use his PagedListDataModel, and in addition how to make sure the data is retrieved only once by using phaseListeners.

To keep it simple, let’s imagine that we have a Dictionary in the backend, and a jsp page with static links with letters like ‘A’, ‘B’, ‘C’ and so on throughout the alphabet. Pressing on one of the letters would display all the words that starts with it in the dataTable, and we can use a dataScroller to navigate through multiple pages.

This is my jsp, in which I’ve removed the columns of the dataTable as they are of no importance. Note that the dataTable is set with a valueBinding, and that the value of it is pointing to a dataModel. Also note that we do not render the table if no one has selected a letter. I also just give one link as an example of how to update the property we can refer to as the “selected letter”:

<h:commandLink styleClass="myLink" value="A">
	<t:updateActionListener value="A" property="#{myDataModelHandler.selectedLetter}"></t:updateActionListener>
</h:commandLink>
<h:panelGrid id="mainPanel" width="100%">
	<t:dataTable
	  id="htmlTableView"
	  value="#{myDataModelHandler.activeDataModel}" rendered="#{myDataModelHandler.selectedLetter != null}"
	  binding="#{myHtmlDataTableHandler.htmlDataTable}"
	  headerClass="tableViewHeader"
	  var="row">
	</t:dataTable>
	<t:panelGrid id="footerPager">
		 <t:dataScroller id="ds5"
				for="htmlTableView"
				fastStep="5"
				styleClass="scroller"
				paginator="true"
				paginatorMaxPages="5"
				paginatorTableClass="paginator"
				paginatorActiveColumnStyle="font-weight:bold;"
				rowsCountVar="rowsCount"
				renderFacetsIfSinglePage="true"
			>
			<f:facet name="previous">
				<t:graphicImage id="datascrollerPrevious" value="/images/left-one-step.gif" border="1" styleClass="dataScrollerImage"/>
			</f:facet>
			<f:facet name="next">
				 <t:graphicImage id="datascrollerNext" value="/images/right-one-step.gif" border="1" styleClass="dataScrollerImage"/>
			</f:facet>
			<f:facet name="first">
				<t:graphicImage id="datascrollerFirst"
				value="/images/left.gif" border="1" styleClass="dataScrollerImage"/>
			</f:facet>
			<f:facet name="last">
				 <t:graphicImage id="datascrollerLast"
				 value="/images/right.gif" border="1" styleClass="dataScrollerImage" />
			</f:facet>
		 </t:dataScroller>
	 </t:panelGrid>
</h:panelGrid>

This is the handler (backing bean) that holds the htmlDataTable:

public class MyHtmlDataTableHandler
{
    private HtmlDataTable htmlDataTable;
    public MyHtmlDataTableHandler()
    {

    }

    public HtmlDataTable getHtmlDataTable()
    {
        if(htmlDataTable == null)
        {
            log.debug("htmlDataTable == null");
            HtmlDataTable dataTable = new HtmlDataTable();
            dataTable.setRows(50);
            setHtmlDataTable(dataTable);
        }
        return htmlDataTable;
    }

    public void setHtmlDataTable(HtmlDataTable htmlDataTable)
    {
            this.htmlDataTable = htmlDataTable;
    }
}

You can see that I have modified the getter to create a new instance if the instance it holds is null. It’s important to set the number of rows here, or it will fail when trying to build the lazy data model.The dataModel we use for our dataTable is created and set before the RENDER_RESPONSE phase, as mentioned earlier this is to avoid that the data itself is retrieved multiple times. To be able to do this we need to use a phaseListener, so we implement it in our constructor of the dataModelHandler. Note that we have references here to the dataTableHandler as well as a util for helping us creating a paged data model. As you can see in the phaseListener beforePhase()-method, if no letter has been selected, we don’t create or set the model.

public class MyDataModelHandler
{
	private String selectedLetter;
	private PagedListDataModel activeDataModel;

	private MyHtmlDataTableHandler myHtmlDataTableHandler; //BEAN_REFERENCE SET IN FACES-CONFIG
	private PagedDataModelUtil pagedDataModelUtil; //BEAN_REFERENCE SET IN FACES-CONFIG

	public MyDataModelHandler()
	{
		LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
		Lifecycle lifecycle = lifecycleFactory.getLifecycle(lifecycleFactory.DEFAULT_LIFECYCLE);

		lifecycle.addPhaseListener
		(
			new PhaseListener()
			{
				public void afterPhase(PhaseEvent arg0)
				{
				}
				public void beforePhase(PhaseEvent arg0)
				{
					if(selectedLetter != null)
					{
						setActiveDataModel(pagedDataModelUtil.createPagedDataModel(selectedLetter, getMyHtmlDataTableHandler().getHtmlDataTable()));
					}
				}	

				public PhaseId getPhaseId()
				{
					return PhaseId.RENDER_RESPONSE;
				}
			}
		);
	}

	public String getSelectedLetter()
	{
		return selectedLetter;
	}

	public void setSelectedLetter(String selectedLetter)
	{
		this.selectedLetter = selectedLetter;
	}

	public PagedListDataModel getActiveDataModel()
	{
		return activeDataModel;
	}

	public void setActiveDataModel(PagedListDataModel activeDataModel)
	{
		this.activeDataModel = activeDataModel;
	}

	public MyHtmlDataTableHandler getMyHtmlDataTableHandler()
	{
		return myHtmlDataTableHandler;
	}

	public void setMyHtmlDataTableHandler(MyHtmlDataTableHandler myHtmlDataTableHandler)
	{
		this.myHtmlDataTableHandler = myHtmlDataTableHandler;
	}

	public PagedDataModelUtil getPagedDataModelUtil()
	{
		return pagedDataModelUtil;
	}

	public void setPagedDataModelUtil(PagedDataModelUtil pagedDataModelUtil)
	{
		this.pagedDataModelUtil = pagedDataModelUtil;
	}
}

The pagedDataModel we create is an instance of PagedListDataModel which is exactly the same as in Cagatay Civici’s post:

public class PagedListDataModel extends DataModel
{
  private int rowIndex = -1;
  private int totalNumRows;
  private int pageSize;
  private List list;

  public PagedListDataModel(List list, int totalNumRows, int pageSize)
  {
    setWrappedData(list);
    this.totalNumRows = totalNumRows;
    this.pageSize = pageSize;
  }

  public boolean isRowAvailable()
  {
    if(list == null)
      return false;
    int rowIndex = getRowIndex();
    if(rowIndex >=0 && rowIndex < list.size())
      return true;
    else
      return false;
  }

  public int getRowCount()
  {
    return totalNumRows;
  }

  public Object getRowData()
  {
    if(list == null)
      return null;
    else if(!isRowAvailable())
      throw new IllegalArgumentException();
    else
	{
      int dataIndex = getRowIndex();
      return list.get(dataIndex);
    }
  }

  public int getRowIndex()
  {
    return (rowIndex % pageSize);
  }

  public void setRowIndex(int rowIndex)
  {
    this.rowIndex = rowIndex;
  }

  public Object getWrappedData()
  {
    return list;
  }

  public void setWrappedData(Object list)
  {
    this.list = (List) list;
  }

}

&#91;/sourcecode&#93;
As you see from the beforePhase()-method in the phaseListener I use a util to create the pagedDataModel. In my application I have a index on the back, but this can of course differ from your situation. I will therefore refer to the backend as "service", and whatever you use that holds and gets the data should not matter.
&#91;sourcecode language='java'&#93;
public class PagedDataModelUtil
{
	public PagedListDataModel createPagedDataModel(String selectedLetter, HtmlDataTable htmlDataTable)
	{
			int startPosition = htmlDataTable.getFirst();
			int dimensionToUse = htmlDataTable.getRows();
			Service service = new Service();
			int totalListSize = service.getTotalNumberOfHits(selectedLetter);

			List<Word> words = new ArrayList<Word>();
			words.addAll(service.getHits(selectedLetter, startPosition, dimensionToUse ));
			PagedListDataModel dataModel = new PagedListDataModel(words, totalListSize, dimensionToUse);
			return dataModel;
	}
}

So, we have now a table that retrieves only the data the user wants to see, and the data is not retrieved multiple times. Fine!But then, what about sorting?? Let’s say that one of the columns we have in our table is length, meaning the length of the word in number of characters. What if I want to sort by this?You could do this as well. I assume you have a header in your table, giving the labels for each column. You can create a commandLink on each label, and if you press one of them you set that value into a property, for example “sortedBy” in the dataModelHandler.First, the link:

 			<t:column>
				<f:facet name="header">
					<t:commandLink id="sortBySomethingLink" value="myColumnLink">
						<t:updateActionListener property="#{myDataModelHandler.sortedBy}" value="theFieldNameIWantToSortOn"></t:updateActionListener>

					</t:commandLink>
				</f:facet>
				<h:outputText value="#{someObject.someValue}" />

			</t:column>

We see that this link updates the property sortedBy in the MyDataModelHandler with the value specified. In the phaseListener, when we call the createPagedDataModel, we now need to pass this property.In other words we modify the createPagedDataModel() method to add support for sorting. We have a new parameter called sortedBy, and use StringUtils to test whether we should bring this one into play or not. If it is null or “” (blank), we don’t sort. We could also keep the sortedBy parameter as a property in our PagedDataModelUtil, in this way we can have an ascending / descending functionality. If the sortedBy parameters equals the sortedBy property, we flip the ascending boolean, if not we sort ascending by default:


private String sortedByLast = "";
private boolean ascending = true;

public PagedListDataModel createPagedDataModel(String selectedLetter, HtmlDataTable htmlDataTable, String sortedBy)
{
			int startPosition = htmlDataTable.getFirst();
			int dimensionToUse = htmlDataTable.getRows();
			int totalListSize = service.getTotalNumberOfHits(selectedLetter);

			List<Word> words = new ArrayList<Word>();
			if(StringUtils.isNotBlank(sortedBy))
			{
				if(sortedBy.equalsIgnoreCase(sortedByLast))
					ascending = !ascending;
				else
					ascending = true;

				words.addAll(service.getHitsSorted(selectedLetter, startPosition, dimensionToUse, sortedBy, ascending));
			}
			else
				words.addAll(service.getHits(selectedLetter, startPosition, dimensionToUse ));

			PagedListDataModel dataModel = new PagedListDataModel(words, totalListSize, dimensionToUse);
			return dataModel;
}

Of course, this puts the responsibility of sorting on the backend service. Somewhere we need to do the sorting. 🙂 If you use a database and have a high number of rows/items, this might be a performance issue. In my application I use Lucene (index), and even with a very high number of rows I don’t have any problems with performance. But doing the sorting in the backend and getting the first 25 items e.g. is anyway faster than retrieving all the items and then do sorting.Ok, that was all this time!

Using JSON with JSF

This post will describe what to do if you want to use JSON in your web applications together with JSF. JSON (JavaScript Object Notation) is a way of doing information interchange between your programming language and javascript. In other words you can use it to access for example java objects in the world of javascript, and this will give you the possibility of creating more advanced web applications. Some basic knowledge of javascript is recommended.

  1. First thing to do is to download the JSON RPC library from JsonRpc and put it in your WEB-INF/lib directory (or the directory you use that holds the libs and is packaged together with the war-file). Also remember to add the library to the classpath of your project in Eclipse or other software frameworks you use to develop.
  2. Then secondly you need to include the jsonrpc.js javascript in the header of your page. The script can be found within the downloaded lib inside \json-rpc-java-1.0.1\webapps\jsonrpc. You include it by writing something similar like this in your header:
    <script type="text/javascript" src="${pageContext.request.contextPath}/javascript/jsonrpc.js"></script>
  3. Then you need to add the servlet configuration to the web.xml
    <servlet>
    
    	<servlet-name>com.metaparadigm.jsonrpc.JSONRPCServlet</servlet-name>
    
    	<servlet-class>com.metaparadigm.jsonrpc.JSONRPCServlet</servlet-class>
    
    </servlet>
    
    <servlet-mapping>
    
    	<servlet-name>com.metaparadigm.jsonrpc.JSONRPCServlet</servlet-name>
    
    	<url-pattern>/JSON-RPC</url-pattern>
    
    </servlet-mapping>
  4. Then, on top of your page (outside the <f:view> tags) you write:
    <jsp:useBean id="JSONRPCBridge" scope="session"class="com.metaparadigm.jsonrpc.JSONRPCBridge" />
  5. Then you must register the handler/backing bean you want the json to access. Do this inside the <f:view> tags but within jsp code tags
    </pre>
    <pre><%
    
    MyHandler myHandler = (MyHandler )FacesContext.getCurrentInstance().getApplication ()
    
    .getVariableResolver().resolveVariable(FacesContext.getCurrentInstance(),"myHandler ");
    
    JSONRPCBridge.registerObject("myHandler ", myHandler );%>
  6. From javascript side, you can now access you handler, set properties and execute methods like this:
    function JsonTest()
    
    {
    
    	try
    
    	{
    
    		//The input-parameter to the JSONRpcClient is basically the path to
    		//the servlet you registered in web.xml
    
    		jsonrpc = new JSONRpcClient("/YOUR_APPLICATION_CONTEXT_ROOT/JSON-RPC");
    
    		// Call a Java method
    
    		var result = jsonrpc.myHandler.getTestValue();
    
    		//Do something with result.....
    
    		jsonrpc.myHandler.setTestData("SET A PROPERTY TO SOMETHING");
    
    		jsonrpc.myHandler.execute(); //Execute a method
    	}
    
    	catch(e)
    
    	{
    
    		alert(e);
    
    	}
    
    }

Good luck!

Automatic logon to a web application (JSF on jboss-4.2.2.GA) using JCIFS NTLM HTTP Authentication

The goal was to be able to make users which are logged in on a windows domain to automatically authenticate with their windows credentials when starting a web application, and also to make this solution possible both for IE and Firefox. In other words, the user doesn’t have to enter user-name/password when starting a corporate web application.

The web-app is done in JSF (myfaces 1.1.5), the application server is jboss-4.2.2.GA and the library I use to “grab” the windows user is JCIFS NTLM HTTP Authentication version 1.2.17 (http://jcifs.samba.org/). I will try to give a step-by-step explanation to what I did to make this work, and I will point out the differences you need to do compared to JAAS authentication mechanism (which is commonly used with applications running on jboss).

  1. The first ting you need to do is to download and put the jcifs library (jcifs-1.2.17.jar – or newer version) into your JBOSS_HOME/server/<NAME-OF-SERVER>/lib -folder.
  2. In your web.xml, you need to apply the following:
    <filter>
    
    	<filter-name>NtlmHttpFilter</filter-name>
    
    	<filter-class>jcifs.http.NtlmHttpFilter</filter-class>
    
    	<init-param>
    
    		<param-name>jcifs.http.domainController</param-name>
    
    		<param-value>IP ADDRESS_OF_THE_DOMAIN_CONTROLLER</param-value>
    
    	</init-param>
    
    	<init-param>
    
    		<param-name>jcifs.smb.client.domain</param-name>
    
    		<param-value>NAME_OF_DOMAIN</param-value>
    
    	</init-param>
    
    	<init-param>
    
    		<param-name>jcifs.smb.client.username</param-name>
    
    		<param-value>A_USERNAME_IN_ACTIVE_DIRECTORY</param-value>
    
    	</init-param>
    
    	<init-param>
    
    		<param-name>jcifs.smb.client.password</param-name>
    
    		<param-value>PASSWORD_FOR_THIS_USER</param-value>
    
    	</init-param>
    
    	<init-param>
    
    		<param-name>jcifs.util.loglevel</param-name>
    
    		<param-value>3</param-value>
    
    	</init-param>
    
    </filter>
    
    <filter-mapping>
    
    	<filter-name>NtlmHttpFilter</filter-name>
    
    	<url-pattern>/*</url-pattern>
    
    </filter-mapping>

    For the jcifs.http.domainController you specify the ip-address of the domainController you are authenticating against, e.g. 192.168.1.100.

    For jcifs.smb.client.domain you specify the domain that the users are logging in to. If you are uncertain about this check the logon-box in windows (XP login to domain) and write the domain name as its written here.

    In some examples on the net the jcifs.smb.client.username and jcifs.smb.client.password are not included. These are used for pre-authentication, think of it as a user that has access to the active directory so that it can retrieve information about the other users trying to log in. My experience is that if I leave out these two parameters, it works fine for the first user logging in, but for the first user only. When the second user logs in you get a 0xC0000022: jcifs.smb.SmbAuthException in the server log, and the negotiation of user-name/password fails. Including those parameters solves this issue. You could perhaps create a “dummy user” in the Active Directory just for this purpose (if you have administrator rights), if not use any other user of the AD.

    At least when trying to get this up and running I would recommend a logging-level set to 3. And of course you would set the url-pattern of the filter according to the path of the resources you want to protect, e.g. <url-pattern>/myJsps/*</url-pattern>

  3. If you are used to restricting access to your web application, you might have something like this in your web.xml:
    <security-constraint>
    
    	<web-resource-collection>
    
    		<web-resource-name>PROTECTED AREA</web-resource-name>
    
    		<description>Require users to authenticate</description>
    
    		<url-pattern>/*</url-pattern>
    
    	</web-resource-collection>
    
    	<auth-constraint>
    
    		<role-name>SOME_ROLE</role-name>
    
    	</auth-constraint>
    
    </security-constraint>
    
    <login-config>
    
    	<auth-method>BASIC</auth-method>
    
    	<realm-name>MY_NAME</realm-name>
    
    </login-config>
    
    <security-role>
    
    	<role-name>SOME_ROLE</role-name>
    
    </security-role>

    And then you would also have a security-domain set up in jboss-web.xml that points to an application-policy in login-config.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
    
    "http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">
    
    <jboss-web>
    
    	<security-domain>java:/jaas/MY_APPLICATION_POLICY</security-domain> -->
    
    	<context-root>/MY_CONTEXT_ROOT</context-root>
    
    </jboss-web>

    To be able to use the security filter with success, you would need to remove all of the settings related to security and JAAS. Remove the <security-constraint> (including children) , <login-config> and <security-role> tags from web.xml, and also delete the line specifying the <security-domain> in jboss-web.xml

  4. All the settings server-side are done now, what remains is a very simple configuration of the client/web browser. You need to tell your browser that the host/site you are trying to reach should be considered a trusted site. If you don’t do this, the browser would pop up the usual login-dialog and ask for username and password.
    • Enable it in IE:Go to tools->Internet Options->SecuritySelect Local Intranet icon and press “Sites”Press “Advanced”Fill in the url to the host of your application and press add. (Do not use the full application path or port settings, only the host address – for example: http://192.168.1.100)
    • Enable it in Firefox:Type about:config in the url-barLocate the key network.automatic-ntlm-auth.trusted-urisAdd the url to the host in the value-field. If there is other urls specified, separate them with a comma.
  5. If you now try to start your application, you should be automatically logged in with your windows user. The server log should say something like this if you have a successful authentication:
    [STDOUT] NtlmHttpFilter: THE_DOMAIN\user_name successfully authenticated
    
    against 0.0.0.0<00>/IP_OF_DOMAIN_CONTROLLER

I hope this overview can be helpful, but of course there are situations and special cases that I haven’t covered here. I would have to point you to the documentation and the FAQ on the homepage of the JCIFS library (see link in the start of this post). Still, I spent some time sorting out configurations, parameters and mistakes, so hopefully this will give others a shorter path to the goal.

Good luck!