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
- Apache Ant (http://ant.apache.org/bindownload.cgi)
- Webserver (e.g. Tomcat http://tomcat.apache.org/download-55.cgi)
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
| Name | Type | |
|---|---|---|
| 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:
-
init -
dist -
clean
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:
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:
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.
Appendix: index.jsp
The index.jsp file can be downloaded here.
| Logo Design by Stefan Schulz valid XHTML 1.1 and CSS 2