The following section explains how to get the server and the automatatutor website running for the first time.
The website is written in Scala using the Lift webframework. We use sbt for the actual build and to manage the dependencies. Since Lift is distributed as a library for Scala, you just need the following things installed:
- Scala 2.9.2
- SBT 0.13.5
The versions given are known to be working. Later versions should work, but no guarantee is given for this.
Before you compile and start the website for the first time, you will need to configure the initial user and the connection to the grading engine. This section shows you how.
Whenever we talk of properties in this file, we mean lift-properties,
which are stored in
src/main/resources/props/default.props in a simple
format of the form
This handling of properties is a feature of Lift. More information about properties and how they are used in the code can be found here
In order to promote users to instructors and admins, an admin account is
needed. One admin account is created on the first startup in order to
create other admins. In the standard configuration, its email and password
are set to
admin respectively. Also,
since every user needs a name, the admin's name is set to "Donald Knuth"
initially. These values may be changed before you start up the server in
the next section, but will have no effect once the server is running.
To change these values, simply edit the properties
If some of these values are not set, they default to the "Admin", "Admin",
Connection to grading engine
Since we communicate with the backend, i.e., the grading engine, via HTTP,
we need to tell the frontend where to find the backend. The configuration
of this connection is stored as properties as well. The property of interest to
grader.url. Set this to the
.asmx page that contains the backend-
grader.methodnamespace can remain set to
http://automatagrader.com as long as you do not change the namespace in the
backend. This is needed for building the post-request to the backend correctly.
After you have set this property correctly, you can continue to build the frontend
Automatatutor at some points sends out mail to users, for example to verify
email addresses or to send information about solutions that were handed in.
In order to do so, it needs to connect to a mailserver.
This mailserver must be configured using the properties
Automatatutor crucially requires a relational database to run.
The connection to this database must be configured using the properties
We assume that you are in the folder AutomataApp_v2. Start sbt as follows
On the first start, this will take a while as sbt downloads all dependencies of the project, including the complete lift framework on startup. When sbt is done, it should greet you with a prompt. Type
to compile the frontend into java class files. Once this is done, type
to package the newly compiled class files into a .war-file, together with
all their dependencies and deploy these to a jetty
-server, which sbt starts on its own. Once sbt returns, you should have the
frontend up and running at
http://localhost:8080. You can stop the frontend
How to Contribute
This section explains the ideas that went into the design of the frontend, including the actual code. As the frontend is written in Scala and Lift, it is assumed that you have at least a passing familiarity with functional programming and may even have looked into Scala a little bit. I try to make this as accessible as possible by explaining some peculiarities of Scala and Lift as we go along. I will mark these paragraphs and sections with Scala and Lift, respectively, so if you already know one or both of these, you may want to skip these parts
Lift's Request Handling
There are three main components involved in rendering a site request: The sitemap, a website template and a so-called snippet. When a browser issues a request for, say, the site at /courses/index, the following happens:
Lift checks the sitemap if there is a site at /courses/index and if it may be served. The sitemap is a global configuration that is set in
Menu.i("Courses") / "courses" / "index" >> loggedInPredicate
loggedInPredicateis defined a couple of lines further up as
val loggedInPredicate = If(() => User.loggedIn_?, () => RedirectResponse("/index"))
The first line tells Lift that there is a site titled "Courses" at
courses/index, which has an additional
loggedInPredicate. The combination of site and its title is called a
LocParamtells Lift that this page may only be accessed if there is a user logged in. If no user is logged in, then instead of rendering a site, lift should redirect the user to
/indexWe just assume that a user is logged in, so lift is satisfied and proceeds to the second step.
Lift looks for a view template called
src/main/webapp/. This is a simple website with some extra tags that control how Lift processes it before it serves it. In this case, we have
<div id="main" class="lift:surround?with=default;at=content">, <lift:Courses.showall>
The first tag tells lift that before serving the website, it should first load
/templates-hidden/default.htmland substitute the
contenttag it finds in there with the content of
/courses/index. The other two tags tell lift to find a Snippet called
Coursesand use the methods
renderenrollmentformrespectively to figure out what to put there.
Lift finds the snippet
code.snippet.Coursesand calls its method
showallto produce some XML that replaces the tag
<lift:Courses.showall>. This method gets passed the children of the
<lift:Courses.showall>tag, which in this case is the empty
The whole work of querying the database and communicating happens in these snippets. As usual with web frameworks, as much content as possible should be put in the template and as little as possible should be generated dynamically.
Scala. Scala offers native support for XML, including the parsing of XML literals, XPath support and checking the well-formedness of XML at compile-time. A
Noderepresents a single XML-node with its children, a
NodeSeqis a sequence of XML nodes. More information on this can be found here and here
When rendering a page in a snippet, we often query the database for problems,
users and many other things. A very simple, early, design featured only
code.snippet) and data access objects (DAOs, called mappers in
com.automatatutor.model). The snippet would query the DAO for the
objects it needed for displaying and then transform these objects into XML.
This combined two responsibilities in the snippet: Loading the objects needed
from the database and rendering them as needed. In order to promote separation
of concerns, I decided to move these functionalities to two different classes:
A snippet and a renderer. A snippet now only queries the DAOs as needed and
hands all objects to a renderer, which produces a
NodeSeq from the given
Since this is a quite large refactoring, as of revision 935, the functionality
is still spread out between snippets and renderers. Also, even though the
renderers should eventually have their own package, they are currently placed
code.snippet as well.
One major design goal was to make sure that new types of problems can be
included as easily as possible. For this, we introduced the notion of a
ProblemType (to be found in
model/Problem.scala). An example of a problem
type would be "English to DFA", i.e., the type of all problems in which the
student has to construct a DFA from a description in English.
ProblemType has a method onStartup that asserts that all known problem types
are present in the database. This method is not called by Lift automatically on
startup, but we manually call it in
Boot (to be found in
bootstrap/Boot.scala). After startup, we do not change the known problem types
anymore. This entails that we cannot add a new problem type at runtime, but
have to restart/redeploy the frontend every time we add a new problem type.
ProblemType has a method
getProblemSnippet, which returns a
This is a trait (defined in
snippet/ProblemSnippet) that defines the main
operations that can be performed with problems: They can be created, edited,
solved and deleted. You might have noticed that three of these operations take
Problem. This class encapsulates the properties that all problems have in
common, most importantly a short and a long description as well as a
Whenever the frontend wants to perform an operation on a problem, it loads the
Problem, gets its
ProblemSnippet via its
ProblemType and then calls the
relevant action on the
ProblemSnippet is then
responsible for carrying out these actions.
Hands-On: How to add a new type of problem
Adding a new type of problem is quite simple in general, as far as the frontend is concerned:
- Define an object with the trait
ProblemSnippetthat implements all of its methods. Please refer to the scaladoc-documentation of these methods for more information on the implementation.
- Think of a short title for your new problem type and add an entry that maps
this title to your newly created object to
- Restart the frontend. This will cause
ProblemType.onStartupto be called again, which writes your new problem type to the database and makes it known to the rest of the frontend.
The challenge in adding a new problem type lies in the correct implementation
ProblemSnippet. This new implementation has to take care of both rendering
the website for the user to create, edit and solve problems, as well as handling
the user's responses, i.e., interpreting the user's input, storing it in the
database and grading students' attempts.
For an example how to implement
RegExConstructionSnippet (to be found in
may be a good starting point, since the frontend consists mainly of a single
text field in which the user enters a regular expression.
These rules are the basic guidelines that emerged during the first writing of the code. Since some of these only became clear after a substantial part of the code was already written, not all of the code adheres to these guidelines. They are to be thought of as guidelines for new code. Also, they should not be taken as gospel. If there are good reasons to deviate from these rules, please feel free to do so.
For each class in the model there should be a corresponding renderer that is parametrized with an instance of this class and creates HTML from the model instance. This renderer can then be used in the snippets once the relevant model instances have been loaded.
Do not query the database directly anywhere but in the model class. For example, if you want to find a user with a given name, do not write
User.findAll(By(user.firstname, firstname), By(user.lastname, lastname))
in the snippet, but rather implement a method
User.findByName(firstname : String, lastname : String) : Seq[User]
com.automatatutor.model.Userthat does this. This helps keep the snippets as clean and small as possible.
If possible, do not use any magical numbers in the code. The only exception to this is
com.automatatutor.lib.Config, where all configuration is to be stored. In particular, this also implies non-usage of
net.liftweb.util.Propseverywhere but in
New code should be formatted using scalariform. A profile for this can be found at
TODO: Write down more guidelines
Rationale for using scala 2.10 instead of current 2.11
At the time of writing (Dec 11, 2014), Lift libraries do exist for Scala 2.11, but they have not been formally released. They only exist in the form of milestones and/or release candidates. Thus, we opted for the official release which only works with scala 2.10 instead of 2.11.