<< Tutorial myCBR
External Similarity >>

Tutorial Web Demo

Contents

Introduction

About this Tutorial

This tutorial describes how to set up a web application for myCBR's standalone retrieval engine. Rather than to give a general (more complex) solution, our aim is to develop a web application which is based on a specific project.
For this purpose we assume that the necessary similarity measures for the project are already defined and that we know the names of the project's slots, their types and their allowed values etc. The result will be a JSP file whose functionality is similar to the one which can be found at: http://www.dfki.uni-kl.de/myCbrWeb/
In case you have not modelled your similarity measures, there is another tutorial describing how to do so: http://mycbr-project.net/tutorial.html

The result will be adapted to the project used_cars_flat which can be found in the directory samples/Used Cars (flat)/ of the downloadable zip file myCBR-version-bin.zip.
Furthermore, you can download the resultant web application from:
http://mycbr-project.net/downloads/myCBRWebDemo.zip

System Requirements

It is possible to run the jsp file without using Ant, however, it is easier (especially if your project gets more complex) to make a war file out of it. This war file is created with Ant and then automatically deployed by your Tomcat.

Folder Structure

To run our example properly, create the following folder structure:

- myCBRWebDemo
  - src
    - WEB-INF
      - lib
    - projects

The projects folder contains the project's xml files, e.g. used_cars_flat_CBR_CASEBASE.XML used_cars_flat_CBR_OPTIONS.XML and used_cars_flat_CBR_SMF.XML. They are included in samples/. We are going to put our JSP file index.jsp in the src folder and the build.xml file in myCBRWebDemo. The jar files jdom.jar and myCbr.jar have to be copied into the lib folder. Furthermore, the WEB-INF folder containts a web.xml file.
If you download the myCBRWebDemo.zip file and just extract it to any location, the folder structure will be the one described here.

Project Structure

The following table shows the slots with their type and allowed values and ranges respectively.\newline

NameType
Body Symbol convertible, coupe, fastback, roadster, sedan, station_wagon
Car Code Integer 0...10,000,000
CCM Integer 1,000...6,000
Color Symbol anthracite, black, blue, dark_blue, dark_gray, dark_green, dark_red, gray, green,
light_blue, light_gray, orange, red, silver, turquoise, violet, white, yellow
Doors Integer 2...7
Gas Symbol diesel, gasoline
Manufacturer Symbol audi, bmw, mercedes-benz, vw
Miles Integer 0...1,000,000
Model Symbol 316i, 318i, 320i, 323i, 325td, 325tds, 328i, 520i, 523i, 525tds, 528i, 535i, 540i,
a4_1.6, a4_1.8, a4_1.8_t, a4_1.9_tdi,
a4_2.8, a6_1.8_t, a6_1.9_tdi, a6_2.4, a6_2.8, c_180, c_200,
c_220_diesel, c_230_kompressor, c_240, c_250_diesel, c_280, e_200,
e_220_diesel, e_280, e_290_diesel, e_300_diesel,
e_320, e_430, golf, m3, m_roadster, passat, slk_200, slk_230_kompressor, z3_1.8,
z3_1.9, z3_2.8, 3_series, AUDI, c_class, e_class,
z3_series, 5_series, slk, VW, BMW, Mercedes
Power Integer 10...1000
Price Float [1;100,000]
Speed Integer 100...300
Year Integer 1950...2007
ZIP Integer 0...9

Implementation

In this section, we are going to describe the essential parts of the index.jsp file step by step. You can find the complete file in the appendix. The line numbers in the code snippets correspond with the line numbers in the file. There are some JavaScript functions in the beginning which are not going to be discussed here.

Initialization

The variable projectsPath contains the path where the project's xml files are located. You should use the directory
TOMCAT_HOME/webapps/myCbrWebDemo/src/projects. In order to determine the xml files which should be used, you have to specify the project's name (projectName). Furthermore, you have to specify the class name for which to use the myCBR's retrieval engine.

  final String projectsPath = 
    "/opt/apache-tomcat-5.5.25/webapps/myCBRWebDemo/projects"; 
  final String projectName = "used_cars_flat";
  final String className = "Car";

The class MyCBR_Facade provides access to the myCBR functionality. So we create a facade by passing the project's path and name to this class. We get the CBRProject and the ModelCls object from it. The CBRProject object provides access to the project's casebase, similarity measures and the vocabulary. We can get a list of all slots and all cases from the case base that are of this type via the ModelCls object. Moreover, we create a default query query which creates a query where for each slot of the current class, the query value is set to SpecialValueHandler.SPECIAL_VALUE_UNDEFINED. Those slots are disregarded by the retrieval by default. You can also create a default query from a case instance. If you chose to base your query upon a case, a parameter case will be specified in the url to load the case's values.

  MyCBR_Facade facade = new MyCBR_Facade(projectsPath + "/" + projectName + "_CBR_CASEBASE.XML");
  CBRProject project = facade.getProject();
  
  ModelCls modelClass = facade.getModelClsByName(className);
  DefaultQuery query = new DefaultQuery(modelClass);
  
  ModelSlot slot = null;
  RetrievalResults results = null;
  
  // caseInstances is used for submitting a query from a case.
  Vector<CaseInstance> caseInstances = 
    new Vector<CaseInstance>(modelClass.getDirectCaseInstances());  
  // If a case has been chosen, a parameter case with a number 
  // is specified in the request
  String index = request.getParameter("case");
  String undefined = SpecialValueHandler.SPECIAL_VALUE_UNDEFINED.toString();

Query Values

There are two possible reason why the web demo could be reloaded: Either you chose query from case or you submitted the form to start the retrieval (when you load the demo the first time it is the same as submitting an empty query). So you either create a query of the specified case or you have to set the submitted values as query values for the corresponding slots.
If there is at least one query value (except SpecialValueHandler.SPECIAL_VALUE_UNDEFINED) the retrieval is performed. We do this by taking an AbstractRetrievalEngine object from our facade and calling the method retrieve, which is based on the query, the active similarity measure function of the class and the AbstractRetrievalEngine.GENERATE_EXPLANATIONS option. We receive the retrieval results for displaying them later on.

  boolean isEmpty = true;
  if ( ( index != null ) && ( index != "" ) ) {
    
    // If a case has been chosen the current query is based on it
    CaseInstance caseInstance = caseInstances.get(Integer.parseInt(index)-1);
    query = new DefaultQuery(caseInstance);
    
  } else {
    
    // if the page has been loaded by submitting a query a value for
    // each slot used by this query is specified by its name as POST variable			 
    for ( Iterator it = modelCLass.listSlots().iterator(); it.hasNext(); ) {
      
      slot = (ModelSlot) it.next();
      // Get value of POST variable
      Object queryValue = request.getParameter(slot.getName().replaceAll(" ", "_"));
      
      // if the slot was not used by the query
      // the value should be SpecialValueHandler.SPECIAL_VALUE_UNDEFINED
      if ( queryValue == null || "".equals(queryValue) || 
                                             undefined.equals(queryValue) ) {
	query.setSlotValue(slot, SpecialValueHandler.SPECIAL_VALUE_UNDEFINED);
	continue;
      }
      isEmpty = false;
      
      // set the value for the slot of the current query to the value used
      // by the submitted query, so that the last submitted query is visible
      Object valueType = slot.getValueType().newInstance(queryValue.toString());
      query.setSlotValue(slot, valueType);
    }
  }
  
  // perform retrieval
  if ( !isEmpty ) {
    RetrievalEngine retrievalEngine = facade.getRetrievalEngine();
    results = retrievalEngine.retrieve(query, project.getActiveSMFs(modelClass), 
      true);
  }%> 

There is a table which is based on the slots of the current class. There are four slots with an input field in each row. We can iterate through the slots as follows:

    for ( Iterator it = modelClass.listSlots().iterator(); it.hasNext(); ) {
      
      slot = (ModelSlot) it.next();
      ...
    }

Now we have to get the query value for each slot to display it in the corresponding input field.

      Object queryValue = query.getSlotValue(slot);
      String queryValueString = null;

      if ( queryValue == null ) {
	queryValueString = undefined;
      } else {
	queryValueString = queryValue.toString();
      }

Depending on the slot's type, a combo box or a text field is used as input field. We get the type by getValueType(). It can be one of ValueType.STRING, ValueType.INTEGER, ValueType.FLOAT and ValueType.SYMBOL. In the first three cases we use a text field. If the slot has a minimum and maximum value, those will be displayed as tool tips and will be saved in an hidden input field (used by JavaScript functions). If the slot's type is ValueType.SYMBOL, there will be allowed values which have to be displayed in a combo box.
In either case be sure to specify unique tag names, since these names are used to submit the query values as request parameter.

         
        <%ValueType valueType = slot.getValueType();
      
        if ( valueType == ValueType.STRING || valueType == ValueType.INTEGER || 
             valueType = ValueType.FLOAT ){
	
	  if ( ( slot.getMinimumValue() != null ) && 
             ( slot.getMaximumValue() != null ) ) { %>
	    <input id="min<%= (slot.getName()).replaceAll(" ", "_") %>" 
                   type="hidden" value="<%=slot.getMinimumValue() %>" />
	    <input id="max<%= (slot.getName()).replaceAll(" ", "_") %>" 
                   type="hidden" value="<%=slot.getMaximumValue()%>" />
	  
	    <input title="min:<%=slot.getMinimumValue() %> 
                   max:<%=slot.getMaximumValue() %>" 
	           name="<%= slot.getName().replaceAll(" ", "_") %>" 
	           id="<%=slot.getName().replaceAll(" ", "_")%>" 
	           type="text" 
	           value="<%= query.getSlotValue(slot) %>" />

	  <% } else { %>

	    <input name="<%= slot.getName().replaceAll(" ", "_") %>" 
	           id="<%=slot.getName().replaceAll(" ", "_")%>" 
	           type="text" 
	           value="<%= query.getSlotValue(slot) %>" />

	  <% } %>	

	<% } else if ( slot.getValueType() == ValueType.SYMBOL ) { %>
	  
	  <select name="<%= (slot.getName()).replaceAll(" ", "_") %>" >
	    <option <%= (undefined.equals(queryValueString)? 
                                          "selected=\"selected\"":"") %>>
              <%= undefined %>
            </option>
	  
            <% for ( Iterator itVals = slot.getAllowedValues().iterator(); 
                     itVals.hasNext(); ) {
	    
              Object allowedValue = itVals.next();
	  
              if (!(allowedValue instanceof String)) { // do nothing
	        	continue;
	      	  }
	  
              String allowedValueString = (String) allowedValue;%>
	      		<!-- else select the index if it was used in the last query -->
	      		<option <%= (allowedValueString.equals(queryValueString)? 
                                               "selected=\"selected\"":"") %>>
                <%= allowedValueString %>
              </option>
	    <% } %>
	  </select>
	  
	<% } else { %>				
	  N.A.
	<% } %>

The last row but one contains a combo box displaying all cases. If you choose a case, there is a JavaScript function to reload the page specifying the case index as a get parameter. The input fields for the query use the case values by the strategy described above.

    <tr>
      <td>From Case:</td>
      <td>
	<select title="select query from case" name="fromCase" id="fromCase" 
                onchange="openCase();" >
	  <option value="0"></option>
	
	  <%

	  int value = 1;

          // if a case was chosen in the last query, it should still be selected
	  int caseIndex = 0; 
	  if ( ( index != null ) && ( index != "" ) ) {
	    caseIndex = Integer.parseInt(index);
	  }
	
	  for( Object caseInstanceObject: caseInstances ) {
	    
            // if a selection was made in an earlier than the last query else
            // if a selection was made in the last query
	    if ( (Integer.toString(value)).equals(
                                        request.getParameter("fromCase")) ) { %>

	      <option value="<%= value %>" selected="selected" >
                <%= caseInstanceObject.toString() %>
              </option>

	    <% } else if ( (Integer.toString(value)).equals(
                                        request.getParameter("case")) ) { %>

	      <option value="<%= value %>" selected="selected" >
                <%= caseInstanceObject.toString() %>
              </option>

	    <% } else { %>

	      <option value="<%= value %>">
                <%= caseInstanceObject.toString() %>
              </option>

	    <% }
	    value++;
	    
	  } %>

	</select>		
      </td>
      <td> </td>
      <td> </td>
    </tr>

The Retrieval Results

The retrieval results give us a ranking of the cases most similar to our query. There are hidden input fields which contain the range of the rankings to be displayed. In our example there are always 5 results displayed and the user is able to browse through the ranked results. For example, if the user wants to see the rankings 6 to 10 the values of the hidden fields have to be adapted (JavaScript).

  <% if ( results != null ) {
	  
    // get the rankings which should be displayed
    Vector ranking = results.getRanking();
    Vector<ModelSlot> slots = new Vector<ModelSlot>(modelClass.listSlots());
	  
    // By default, the rankings to be displayed 
    // are the first five rankings 
    int begin = 0;
    int end = Math.min(ranking.size(), 5);
    
    // if the hidden field "begin" does exist and has a value
    // use this as the first ranking to be displayed.
    // Use the value of the hidden field "end" as the 
    // last ranking to be displayed
    if((request.getParameter("begin")!=null)&&(request.getParameter("begin")!="")){
      begin = Integer.parseInt(request.getParameter("begin"));
      end = Integer.parseInt(request.getParameter("end"));
    } %>
	  
    <p>
      // if the first ranking is not shown, enable the first and second arrow
      <% if ( begin != 0 ) {  %>
        <a href="javascript:searchRankings('first');">
          [1 - <%= 5 %>]
        </a>
        <a href="javascript:searchRankings('back');">
          [<%= begin %> - <%= end-1 %>]
        </a>
      <% }

      // if the last ranking is not shown, enable the third and fourth arrow
      if ( end < ranking.size() ) {  %>
	<a href="javascript:searchRankings('forward');">
          [<%= begin + 2 %> - <%= end + 1 %>]
        </a>
	<a href="javascript:searchRankings('last');">
          [<%= ranking.size() - 4 %> - <%= ranking.size() %>]
        </a>			
      <% } %>
    </p>

The results are shown in a table. The first column contains the slots name. The second contains the query values. The other cells belong to a ranking and a slot and therefore have an explanation containing a comment and the retrieved similarity.

	  for ( int i = begin; i < end; i++ ) {
		      
	    AssessedInstance assessedInstance = (AssessedInstance)ranking.get(i);
	    Explanation explanation = assessedInstance.explanation; 
		      
	    if ( slot != null ) {
	      explanation = assessedInstance.explanation.getLocalExplanation(slot);
	    }
		      
	    String explanationTxt = "?";
	    if ( explanation != null ) {
	      explanationTxt = explanation.getComments().replaceAll("\n", "; ") 
                       + "Similarity: " 
                       + Helper.getSimilarityStr(explanation.getSimilarity());
	    }
		      
	    if ( slot == null ) { %>			
	      <td title="<%= explanationTxt %>">
	        <%= "Rank: " + (i+1) + "<br />Name: " 
                             + assessedInstance.inst.getName() 
                             + "<br />Similarity: " 
                             + Helper.getSimilarityStr(assessedInstance.similarity) %>
	      </td>
			
	    <% } else if ( row != -1 ) {
			
	      if ( row%2 == 0 ) { %>	
			
	        <td title="<%= explanationTxt %>">
	          <%= assessedInstance.inst.getSlotValue(slot)%> 
			
	      <% } else { %>
			
	        <td style="background-color : #efefef;" title="<%= expTxt %>">
		<%= assessedInstance.inst.getSlotValue(slot)%> 
		
	      <% } %> 
			
	        </td>
			
	    <% } %>
			
	  <% } %> <!-- end for -->

Running MyCBR Web

Using Ant

We use a minimal build.xml file to create a war file out of our project. For this purpose, there are three targets:

A target can be executed by typing ant <TARGET_NAME>

<project name="myCbrWeb" default="dist" basedir=".">
	
  <target name="init">
    <mkdir dir="dist"/>
  	<tstamp/>
  </target>

  <target name="dist" depends="clean, init">
  	<jar jarfile="./dist/myCBRWebDemo.war" basedir="./src" />
  </target>

  <target name="clean">
    <delete dir="dist"/>
  </target>

</project>

Init is used to create further folder structures. Here the folder dist is created which will later contain our war file.

    <target name="init">
        <mkdir dir="dist"/>
        <tstamp/>
    </target>

<tstamp/> creates an ant-internal time stamp.

The clean target simply deletes everything that does not belong to the original project.

    <target name="clean">
         <delete dir="dist"/>
    </target>

It is recommended that you delete the lastest dist folder before creating it again. For this reason dist depends on clean and init.

    <target name="dist" depends="clean,init">
         <jar jarfile="./dist/myCBRWebDemo.war" basedir="./src" />
    </target>

This means, that each time you run the dist target clean and init are executed first. The war file contains everything that is located in ./src.

The surrounding tag specifies the project's name, default target and base directory.

<project name="myCbrWeb" default="dist" basedir=".">

If your project gets more complex there are maybe a lot of files that should not be included in your war file. Hence, you should extend the init target so that it creates the folder structure needed for your war file in the dist directory. Moreover, the dist target should be extended to copy the necessary files into the dist folder. Then the base directory for creating the war file should be changed to ./dist.
You can get further information about ANT and build files under:
http://ant.apache.org/manual/

Deploying the Application

Change to the web application's root directory e.g. cd /home/user/myCBRWebDemo/ Then start the dist target by typing ant dist In case dist is your default target then the command ant is sufficient, too. You should get an output similar to the following: dist output

Now, there is a new folder dist containing the war file MyCBRWebDemo.war. Copy this file to your Tomcat's webapps folder cp MyCBRWebDemo.war <TOMCAT_HOME>/webapps

After starting Tomcat you can access the web application by opening the following location in your browser: http://localhost:8080/MyCBRWebDemo/index.jsp Your web application should look like to this one: application snapshot

Common Errors

If you get an error similar to the one in the next picture, you probably didn't specify the project's path or name properly. error png

Appendix: index.jsp

The index.jsp file can be downloaded here.

© 2006-2008 DFKI GmbH | Legal Information | | Logo Design by Stefan Schulz
valid XHTML 1.1 and CSS 2