== Pet Store explained: tutorial ==
''note: This tutorial supposed you had your own eclipse environment correctly set to work with magma.''
The Pet Store is a magma demo site, it implements most of the magma features and functionalities.
For clearness the project in eclipse is divided into two subprojects "petstore-domain" and "petstore-web", the petstore-domain project contains all the application specific domain objects, the petstore-web project is the place where we collect our handlers and template files including resources like .html .css files.
'''The POM'''
The first thing to do with a magma project is to edit the pom to satisfy our needs. So let's take a closer look to the petstore-web project pom (the domain pom is quite the same, but its dependencies references to "domain" packages).
{{{
#!text/html
4.0.0org.apache.magmapetstore-webmagma0.0.2-SNAPSHOT
}}}
As you can see this is the standard basics of a common pom, here the particularity lies only on the packaging type, for a magma project you need a magma packaging ;)
To bring magma in action you need also to declare the magma plugin and its repository like in the following snippet:
{{{
#!text/html
// MAVEN-MAGMA PLUGIN
...
org.apache.magma.toolsmaven-magma-plugin0.0.3-rev13true
...
semeru-snapsSemeru snapshotshttp://svn.semeru.it/maven-snapstruetruesemeru-snapsSemeru snapshotshttp://svn.semeru.it/maven-snapstruetrue
...
}}}
The dependencies part of the pom is actually the heart of your magma project. First of all you have to declare your domain project dependency, then you start to include all the packages you suppose to use, magma offers a lot of different "fragments" to meet most of your needs, anyway you can create your own "fragment" or extend an existing one to reach your particular goal.
The petstore pom include a lot of fragments, to implement in example, social user functionalities (fragment-socialUser-web), or forum features (fragment-forum-web) and so on.
{{{
#!text/html
// DOMAIN PROJECT DEPENDENCY
org.apache.magmapetstore-domain0.0.2-SNAPSHOT
// FRAGMENTS DEPENDENCIES...
...
org.apache.magmafragment-socialUser-web0.0.2-SNAPSHOT
...
// MAGMA BASE PACKAGES....
}}}
The other "non-fragment" dependencies are internal basic magma packages, if your project needs a database you have to include the dependency for "foundation-database", "website-beansview" provide an interface to edit or just display your domain beans...
{{{
#!text/html
...
org.apache.magmafoundation-database0.0.2-SNAPSHOT
...
}}}
* [wiki:fulldomainpom petstore-domain pom]
* [wiki:fullwebpom petstore-web pom]
'''Start up with the Domain Package: !MagmaBean '''
Once your pom for the petstore-domain and the petstore-web project are correctly set, you can start to create your domain objects, the petstore-domain needs only four beans: [wiki:petAddress Address.java], [wiki:petPet Pet.java], [wiki:petRace Race.java] and [wiki:petSpecies Species.java] (other beans like User.java or Content.java are provided by the different ''fragments'' you've included in the pom as dependencies).
In 'src/main/java' folder, create a package (and name it, i.e. "org.apache.magma.petstore.domain") this package will contain your four petstore classes, so create them as well.
It's time to get a closer look to a couple of this beans:
* The Address class
{{{
#!java
...
@Entity
@MagmaBean
public class Address implements GeoResolvable{
private long id;
private String address;
private String zipcode;
private String city;
private String country;
...
}}}
The @Entity annotation tells to your application that this bean has to be managed by [http://openjpa.apache.org/ openjpa] (openjpa is included in your domain pom as dependency...).
Magma affects the usage of beans in its aspect libraries. The @!MagmaBean annotation tells Magma which classes in your projects are considered beans.
The Address bean ''implements'' "!GeoResolvable", this is a particular class of the ''foundation-maps'' package (another dependency on your pom that implements !GoogleMaps features in your projects...).
Now, let's take a look at the getters annotations on your bean:
{{{
#!java
@Id
@GeneratedValue
public long getId() {
return id;
}
//Note: if you set a ''javax.persistence.GeneratedValue'' annotation on the getter the setter must be not public.
protected void setId(long id) {
this.id = id;
}
@View
@Order(1)
@Required
public String getAddress() {
return address;
}
...
}}}
Above the id getter you can see ''javax.persistence'' annotations, these are not specific magma annotations so there's no reason to waste time analyzing them.
Pointing your attention to the address getter you will notice three peculiar magma annotations, two of them comes from the ''org.apache.magma.view'' package (@View and @Order) the other one (@Required) comes from the ''org.apache.magma.validation.validators'' package.
The ''@View'' annotation tells magma that this field must be shown when your application is displaying the related bean's form(you will see later on how this appends). The ''@Order'' annotation add another view information: you are saying to the form in wich order you want this field must be displayed, in this case the field will be displayed for first (the number between the brackets indicate the displaying order). This is a form to add a new Address object, so there's no reason to leave void the address field, the ''@Required'' annotation forces the user to fill in this field, if not an error message will be displayed, quite simple and useful.
* The Pet class :
{{{
#!java
@MagmaBean
@Entity
public class Pet implements GeoAware {
private long id;
private Race race;
private String name;
private Double price;
private Address address;
private InputStream photo;
private String summary;
private String text;
private User user;
...
}}}
As you can see, this class implements the class ''!GeoAware'' always from the ''foundation-maps'' package, this means that you want this bean to be aware of its geo location, this is possible because one of the fields is an Address (a !GeoResolvable object!), now you have to add a simple method:
{{{
#!java
...
@Transient
public GeoLocated getGeoLocatedObject() {
return this.getAddress();
}
...
}}}
That's it! Now your pets can be located on a map.
{{{
#!java
...
@View
@Order(2)
@Listed
@ListOrder(1)
@Required
@Regex("[A-Za-z]*")
public String getName() {
return name;
}
...
}}}
The getter for the field "name" has some new annotations ''@Listed'', ''@!ListOrder''(from the ''org.apache.magma.view'' package), ''@Regex''(from the ''org.apache.magma.validation.validators'' package). ''@Listed'' tells that this field will be a displayed element on a list of pets, ''@!ListOrder'' as you can imagine is the same of ''@Order'' but referred to the elements that your list will display. ''@Regex'' allow you to add a regular expression rule ([http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html see the java Pattern documentation]).
{{{
#!java
...
@RichText(permitColors=false, permitEmphasis=false, permitHeadings=false,permitImages=false,
permitIndents=false, permitJustify=false, permitLists=false,permitObjects=false,
permitTables=false, permitScripts=false, permitBackgroundColors=false)
public String getText() {
return text;
}
...
}}}
The ''@!RichText'' annotation (from the ''org.apache.magma.validation.validators'' package) transforms your String field in a !RichText editor (this one is a magma implementation of the [http://www.dojotoolkit.org/ dojo] editor, you can choose how many functions implement on your String field.
'''Pet Store, the Web Project: .properties files'''
In order to set up correctly the pestore-web project, you have to create a new ''META-INF'' folder in your ''src/main/resources'' directory.
In this folder add a new file ''magma.properties'', in this file you will manage the database connection, email support, etc.
{{{
#!text/html
openjpa.ConnectionProperties=DiverClassName=com.mysql.jdbc.Driver,
Url=jdbc:mysql://localhost:3306/petstore,MaxActive=20,MaxWait=10000,TestOnBorrow=true,Username=root
openjpa.ConnectionDriverName=org.apache.commons.dbcp.BasicDataSource
email.smtpHost=your smtp Host
email.smtpPort=25
email.smtpProtocol=your smtp protocol
email.username=your@username
email.password=your password
email.from=your site email
email.debug=true or false
google.maps.key=your googleMaps key ('abcdefg' is the dummy key for developers)
}}}
Another file you'll probably want in your ''META-INF'' folder is ''messages.properties'' in this file you can edit all your ''i18n'' keys, to find out your application's ''i18n'' keys you can use the developer tools, provided with the website-developer package(another dependency in your web project pom).
messages.properties is a generic internationalization file but you can create several of this files, one for each language you wish to use naming them like this: messages.en.properties, messages.es.properties, messages.it.properties and so on (see more on [wiki:FoundationI18n magma i18n]).
'''Pet Store, the Web Project: Handlers'''
In ''src/main/java'' create a new package, in this case the name is ''org.apache.magma.web.store'' in this package you will collect your handlers and few aspectj files.
With the ''website-administration-inline'' package(a pom dependency...) magma bring in your project a powerful tool to Create, Rename, Update and Delete(''crud'') your domain's beans. The only thing you have to do is create a simple java file for each bean you want to administrate and a small aspectj file to install them.
This is the !CrudSpecies java file:
{{{
#!java
package org.apache.magma.petstore.web.store;
import org.apache.magma.petstore.domain.Species;
import org.apache.magma.website.admin.CompleteCrudHandler;
public class CrudSpecies extends CompleteCrudHandler{
public CrudSpecies(){
super(Species.class);
}
}
}}}
The !CrudPet is a little more complex because you need to override a method to fully use the maps features:
{{{
#!java
package org.apache.magma.petstore.web.store;
import org.apache.magma.database.Database;
import org.apache.magma.maps.GeoAware;
import org.apache.magma.maps.GeoLocated;
import org.apache.magma.maps.GeoResolvable;
import org.apache.magma.maps.GeoResolver;
import org.apache.magma.maps.GeoResolver.ResolveResult;
import org.apache.magma.petstore.domain.Pet;
import org.apache.magma.view.tree.SimpleViewFilter;
import org.apache.magma.website.HtmlProducer;
import org.apache.magma.website.admin.CompleteCrudHandler;
import org.apache.magma.website.producers.TemplatingProducer;
public class CrudPet extends CompleteCrudHandler{
public CrudPet(){
super (Pet.class);
editCustomizer = new SimpleViewFilter("+address");
}
@Override
public HtmlProducer hiddenSave(Pet bean) {
GeoLocated loc = ((GeoAware)bean).getGeoLocatedObject();
if (loc instanceof GeoResolvable) {
GeoResolver resolver = new GeoResolver();
ResolveResult result = resolver.resolve((GeoResolvable)loc);
new Database().save(bean);
if (result == ResolveResult.DONE) {
return doShow(bean);
} else if (result == ResolveResult.ERROR) {
return new TemplatingProducer("requireLocalization-error");
} else {
return new TemplatingProducer("requireLocalization-unresolvable");
}
} else {
return new TemplatingProducer("requireLocalization-notgeoresolvable");
}
}
}
}}}
''note:'' the ''!SimpleViewFilter'' gives you the possibility to hide or expand fields on your bean, so in this case the "''+address''" means that fields in the bean Address (a field in the Pet bean), will be ''expanded'' and shown on the the Pet bean form.
Now we need to install our admin cruds, to do this we have to create a small ''aspect'': ''!InstallStoreAdminCruds.aj''
{{{
#!java
package org.apache.magma.petstore.web.store;
import org.apache.magma.website.admin.Manages;
import org.apache.magma.website.admin.AdminWebHandler;
import org.apache.magma.petstore.domain.Pet;
import org.apache.magma.petstore.domain.Species;
import org.apache.magma.petstore.domain.Race;
public aspect InstallStoreAdminCruds {
@Manages(Pet.class)
public CrudPet AdminWebHandler.handlePet(){
return new CrudPet();
}
@Manages(Species.class)
public CrudSpecies AdminWebHandler.handleSpecies(){
return new CrudSpecies();
}
@Manages(Race.class)
public CrudRace AdminWebHandler.handleRace(){
return new CrudRace();
}
}
}}}
With these few lines you can now create and edit your pet objects.
Now you have to implement some functions to add interactivity to your site.
''PetWebHandler'' must extends ''!WebHandler'' a ''magma'' class.
{{{
#!java
public class PetWebHandler extends WebHandler{
protected ViewCustomizer expandAddress = new SimpleViewFilter("+address");
private User sessionUser;
private PetSearchRequest sessionSearchRequest = null;
...
}}}
In this class, there's no need of getters and setters for fields that starts with the word "session" (''sessionUser'' and ''sessionSearchRequest''), magma recognize them and knows that these fields had to be used in "sessions".
{{{
#!java
...
public SmartForm handleNewPet(){
Pet pet = new Pet();
HtmlProducer header = new TemplatingProducer("newpet");
return new SmartForm(pet, "hiddenNewPet", header).setFilter(expandAddress);
}
...
}}}
''handleNewPet()'' is a simple method that instantiate a form for the creation of a new Pet object. The ''!SmartForm'' class gets as parameters the ''pet'' and use it to create all the needed fields, an hidden method to call on submit, and the ''newpet'' !TemplatingProducer ([wiki:htmlprod more on TemplatingProducers]). ''setFilter()'' is a !SmartForm method, in this case you use it to call the expandAddress !ViewCustomizer.
{{{
#!java
...
public HtmlProducer hiddenNewPet(Pet pet){
Database db = new Database();
Race race = pet.getRace();
race.getPets().add(pet);
if(sessionUser != null){
pet.setUser(sessionUser);
}
db.save(pet);
db.save(race);
return doShowPet(pet);
}
...
}}}
This is the method called when the user hit on the submit button, first of all it instantiate the magma Database class that will provide the db connection and all the needed methods. In the if statement is used the ''sessionUser'' variable, when the ''pet'' and its ''race'' are saved the method returns the ''doShowPet()'' method.
{{{
#!java
...
public HtmlProducer doShowPet(Pet pet){
ShowBean sb = new ShowBean(pet);
HtmlProducer header = new TemplatingProducer("title").addParameter("bean", pet);
sb.setCustomizer(expandAddress);
sb.compoundWith(header, CompoundType.HEAD);
if(pet.getUser() != null){
User user = pet.getUser();
sb.compoundWith(doPetUser(user), CompoundType.SIDEA);
}
return sb;
}
...
}}}
The !ShowBean class gets your saved object and render it as piece of html in your web page, you can customize your output creating another !HtmlProducer to , in this case, obtain a "title" for your show page, the ''addParameter'' method of the !TemplatingProducer permits to pass a variable to the html. Once you have created this producer you can attach it to the output form via the ''compoundWith()'' method, the !CompoundType indicates where you want this code to be displayed. ''setCustomizer()'' does the same thing of the ''setFilter()'' in the ''!SmartForm'' class.
The if statement presents another ''compoundType()'' method, this time you will compound the showBean with another methods of your handler (''doPetUser(user)'').
Now that you have understand the !PetWebHandler, you can write down on your own the remaining methods, including search methods (assuming you have create the [wiki:search PetSearchRequest] class...]). Once you have finished with your Handler you have to install it via aspectj as you already done with cruds, so create the !InstallPetWebHandlerIntoRoot aspectj file:
{{{
#!java
package org.apache.magma.petstore.web.store;
import org.apache.magma.website.RootWebHandler;
public aspect InstallPetWebHandlerIntoRoot {
public PetWebHandler RootWebHandler.handlePet(){
return new PetWebHandler();
}
}
}}}
You will see later what concerns the !HomePageWebHandler...
'''Pet Store, the Template system'''
It's time to get an overview to the magma template system.
In your web project under "src/main/java" create a new package (for the petstore the name is: ''org.apache.magma.petstore.web.template''), under "src/main/resources" add a new folder following the path of your new package ("/org/apache/magma/petstore/web/template"), in this folder you will store all your resources like image files, .js, .css and .html.
A template system needs a template, so starts writing down your ''template.html'' (in the head you can link all the stylsheets you want to use). Magma needs that you follow only one simple rule to fill your template with contents provided by your !WebHandlers: ''where zone is, layout content will be''. What does it means?
{{{
#!text/html
//Magma: PetStore-Demo
}}}
Divs with ''id'' names starting with the "zone" word will be parsed by magma and then passed to the ''*Template.java'' in your ''org.apache.magma.petstore.web.template'' package.
The petstore's ''!BaseTemplate.java'':
{{{
#!java
package org.apache.magma.petstore.web.template;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.magma.domain.banner.BannerCategory;
import org.apache.magma.domain.content.ContentCategory;
import org.apache.magma.domain.forum.Forum;
import org.apache.magma.website.CompoundType;
import org.apache.magma.website.HtmlProducer;
import org.apache.magma.website.templating.DefaultTemplate;
public class BaseTemplate extends DefaultTemplate{
public BaseTemplate(){
super.fileName = "template.html";
}
public void layoutBanner(){
always(root().handleBanner().doBannerRotation(BannerCategory.byName("header")));
}
@Override
public void layoutRight(){
mainCompound(CompoundType.SIDEA);
always(root().handleLogin().doLoginBox());
always(root().handleContent().doLastBoxWithDate(5, ContentCategory.byName("News")));
always(root().handleGallery().doRandomGalleryBox());
always(root().handleForum().doUpdatedTopics(Forum.byName("F.A.Q."),3));
}
}
}}}
The first public method declares the name and the path of the html file to parse, the other two methods will fill with content the respective zones, so ''layoutBanner()'' will fill the ''zoneBanner'' div and ''layoutRight()'' will fill the ''zoneRight'' div of your template file.
''always'' is a method of the Template magma class extended by !DefaultTemplate wich is extended by your !BaseTemplate. When you are calling ''always'' in your ''layoutRight()'' method, you are saying that your template's ''zoneRight'' must always displays the content from a specific !WebHandler method, the first ''always'', for example, invoke the !LoginWebHandler(''handleLogin()'') and its ''doLoginBox()'' method from the ''fragment-socialUser-web'' package that you have included in the pom.
Now you need to handle your home page to display your home contents, to do this simply create a new ''!HomeTemplate.java''.
{{{
#!java
package org.apache.magma.petstore.web.template;
public class HomeTemplate extends BaseTemplate{
public HomeTemplate() {
super.fileName = "template.html";
}
}
}}}
Well, now you need three small aspectj files to install these templates: ''InstallBaseTemplateAsDefault.aj'', ''ReplaceDefaultWithHome.aj'' and ''UseHomeTemplateOnHomeHandler.aj''. Once you have write down these files you are ready to come back to the handlers package (''org.apache.magma.petstore.web.store'') and to create the ''!HomePageHandler''.
{{{
#!java
package org.apache.magma.petstore.web.store;
import org.apache.magma.domain.content.ContentCategory;
import org.apache.magma.website.HtmlProducer;
import org.apache.magma.website.RootWebHandler;
import org.apache.magma.website.WebHandler;
import org.apache.magma.website.layouts.YUITabs;
public class HomePageHandler extends WebHandler{
public HtmlProducer do_default(){
ContentCategory category = null;
YUITabs tabs = new YUITabs();
tabs.addPanel("News", RootWebHandler.getInstance().handleContent().doShowLast(ContentCategory.byName("News")));
tabs.addPanel("Best of the Week", RootWebHandler.getInstance().handleContent().doShowLastElement(ContentCategory.byName("Best")));
tabs.addPanel("Five Best", RootWebHandler.getInstance().handleContent().doLastBox(5, ContentCategory.byName("Best")));
return tabs;
}
}
}}}
This method creates the content for the zoneMain in your !HomeTemplate.[[BR]]
''note'': YUITabs is a magma implementation of the YUI interface.