One of the main features of MD4J is that it allows you to work on your model iteratively, providing a complete J2EE application following your model changes. So, each time you edit your model and build your project, MD4J will generate high quality code from JSPs and Struts classes, to Session EJBs and DAOs, allowing you to test CRUD and Search functionality on top of your model right away.
This tutorial will help you get started quickly (hopefully in ten minutes or so) using Quickstarter, a Maven-based J2EE project template for MD4J. The template has MD4J code generation setup for you, plus all the plumbing you are used to in your Maven or Ant projects, such as XDoclet for your EJBs and Struts οr other web classes. XDoclet is also applied on EJB and Struts code generated by MD4J from your Hibernate mappings.
A Maven archetype based on the template will be available soon, while an Ant-based project template is also in the works. Stay tuned for EJB 3 and Struts 2 code generation support!
To folow the tutorial you need the following:
mvn install:install-file -Dfile=md4j-0.2.jar -DgroupId=gr.abiss.md4j
-DartifactId=md4j -Dversion=0.2 -Dpackaging=jar
% mysql -u root -p
password: *****
mysql> create database quickstarter;
mysql> quit
Where "*****" is the password (default is empty for MySQL). You may want to create a user
for the quickstarter database and grant related privileges, in any case update
connection.username and connection.password in filter.properties
accordingly. If you use another database you will need to update the related properties
accordingly, including the hibernate.dialect.
<property name="population" type="integer" not-null="true" />
Note: When working with Hibernate mappings keep in mind the requirements. Your City mapping should now look like City.hbm.xml. To build the Domain module simply run the following command within the
domain directory:
mvn clean install
/ejb
|-src
|---main
|-----java: EJBs and other business level code like DAOs etc.
|-----resources: property files and othr resources
|-target
|-pom.xml: The EJB module POM
To generate a JAR with EJBs and DAOs for the domain model entities you edited while working with the Domain module, all you have to do is execute the following from within the EJB module directory:
mvn clean install
With the above command, MD4J will read the Hibernate mappings in the Domain module to generate EJBs and DAOs. For example, the CityManagerBean.java, a Stateless Session EJB created by MD4J using the from City.hbm.xml mapping as input:
/*
* This file was automatically generated by MD4J.
*
* Licensed under the GNU General Public License, Version 2.0 (the "License") or above;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* *http://www.gnu.org/licenses/gpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package gr.abiss.md4j.sampledomain.business;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;
import org.apache.commons.lang.math.NumberUtils;
import gr.abiss.md4j.dao.Page;
import gr.abiss.md4j.dao.Order;
import gr.abiss.md4j.business.BrokenConstraintsException;
import gr.abiss.md4j.sampledomain.Country;
import gr.abiss.md4j.sampledomain.City;
import gr.abiss.md4j.sampledomain.dao.CityDAO;
/**
* @ejb.bean name="CityManager"
* display-name="CityManager"
* description="City EJB"
* jndi-name="ejb/CityManagerBeanHome"
* local-jndi-name="ejb/CityManagerBeanLocalHome"
* view-type="both"
* type="Stateless"
* @ejb.home extends="javax.ejb.EJBHome" local-extends="javax.ejb.EJBLocalHome"
* @ejb.transaction type="Required"
*
* @ejb.interface
*
*/
public class CityManagerBean implements SessionBean{
private static Logger log = Logger.getLogger(CityManagerBean.class);
/**
* @ejb.create-method
*/
public void ejbCreate() throws CreateException {
}
public void ejbActivate() throws EJBException {
}
public void ejbPassivate() throws EJBException {
}
public void ejbRemove() throws EJBException {
}
public void setSessionContext(SessionContext newContext) throws EJBException {
}
/**
* Get a page of results matching the given parameters
* @param params the search parameters
* @param pageNumber the results page number
* @param pageSize the results page size
* @return a search results page constructed based on the given parameters
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Page search(Map params, Order order, int pageNumber, int pageSize) throws EJBException {
Page page = null;
try {
CityDAO dao = new CityDAO();
page = dao.getPage(params, order, pageNumber, pageSize);
} catch (Exception e) {
throw new EJBException(e);
}
return page;
}
/**
* Get a page of results matching the given parameters
* @param params the search parameters
* @param pageNumber the results page number
* @param pageSize the results page size
* @return a search results page constructed based on the given parameters
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Page search(Set projectionProps, Map params, Order order, int pageNumber, int pageSize) throws EJBException {
Page page = null;
try {
CityDAO dao = new CityDAO();
page = dao.getPage(projectionProps, params, order, pageNumber, pageSize);
} catch (Exception e) {
throw new EJBException(e);
}
return page;
}
/**
* Save a new City instance based on the property-value pairs in the map
* @param map
* @return the persisted City instance identifier
*
* @ejb.interface-method view-type = "both"
* @throws javax.ejb.EJBException possibly wrapping a BrokenConstraintsException
* @ejb.transaction type="Required"
*/
public Serializable save(Map map) throws EJBException {
try {
CityDAO dao = new CityDAO();
// check for unique constraint violations
Set brokens = dao.getBrokenUConstraints(map, null);
log.info("Broken constaints: "+ brokens.size());
if(brokens != null && brokens.size() > 0){
throw new BrokenConstraintsException(brokens);
}
return dao.save(map);
} catch (Exception e) {
throw new EJBException(e);
}
}
/**
* Retreive the City matching the given identifier if any such match is found, <code>null</code> otherwise
* @return the City matching the given identifier if any such match is found, <code>null</code> otherwise
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Serializable get(Serializable identifier) throws EJBException {
Serializable pojo = null;
try {
CityDAO dao = new CityDAO();
pojo = (Serializable) dao.get(identifier);
} catch (Exception e) {
throw new EJBException(e);
}
return pojo;
}
/**
* Retreive the City matching the given identifier as a map if any such match is found, <code>null</code> otherwise
* @return the City matching the given identifier as a map if any such match is found, <code>null</code> otherwise
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Map get(Serializable identifier, Set projectionProperties) throws EJBException {
Map map = null;
try {
CityDAO dao = new CityDAO();
map = dao.get(identifier, projectionProperties);
} catch (Exception e) {
throw new EJBException(e);
}
return map;
}
/**
* Retreive the requested properties of the City matching the given identifier as a map that <b>includes parent options for the web tier<b>
* The parent options are essentially the data needed to construct HTML select elements in views.
* @return the map containing the bunch if a match for the identifier is found, <code>null</code> otherwise
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Map getWithOptions(Serializable identifier, Set projectionProperties) throws EJBException {
Map map = null;
try {
CityDAO dao = new CityDAO();
map = dao.get(identifier, projectionProperties);
// add data for form drop downs refering to parent classes
dao.addParentOptions(map);
} catch (Exception e) {
throw new EJBException(e);
}
return map;
}
/**
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
*/
public Map getParentOptions() throws EJBException {
Map map = null;
try {
CityDAO dao = new CityDAO();
// get data for form drop downs refering to parent classes
map = dao.getParentOptions();
} catch (Exception e) {
throw new EJBException(e);
}
return map;
}
/**
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
* @ejb.transaction type="Required"
*/
public void update(Map map) throws EJBException {
try {
CityDAO dao = new CityDAO();
String sId = (String) map.get("id");
if(sId != null){
Long id = NumberUtils.createLong(sId);
// check for unique constraint violations
Set brokens = dao.getBrokenUConstraints(map, id);
if(brokens != null && brokens.size() > 0){
throw new BrokenConstraintsException(brokens);
}
dao.update(map);
}
else{
throw new Exception("Parameter id was null");
}
} catch (Exception e) {
throw new EJBException(e);
}
}
/**
*
* @ejb.interface-method view-type = "both"
* @throws EJBException
* @ejb.transaction type="Required"
*/
public void delete(Serializable pojo) throws EJBException {
try {
CityDAO dao = new CityDAO();
dao.delete((Serializable) pojo);
} catch (Exception e) {
throw new EJBException(e);
}
}
}
and the CityDAO.java, a Hibernate based Data Access Object also created by MD4J from the same Hibernate maping:
package gr.abiss.md4j.sampledomain.dao;
/*
* This file was automatically generated by MD4J.
*
* Licensed under the GNU General Public License, Version 2.0 (the "License") or above;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* *http://www.gnu.org/licenses/gpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Date;
import org.apache.commons.lang.BooleanUtils;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.apache.log4j.Logger;
import org.apache.commons.lang.math.NumberUtils;
import gr.abiss.md4j.dao.Page;
import gr.abiss.md4j.dao.hibernate.Helper;
import gr.abiss.md4j.dao.DataAccessException;
import gr.abiss.md4j.util.Md4jGlobals;
import gr.abiss.md4j.util.ConvertUtils;
import gr.abiss.md4j.dao.hibernate.AbstractHbmDAO;
import gr.abiss.md4j.sampledomain.City;
import gr.abiss.md4j.sampledomain.Country;
/**
* Data access operations for City entities
*/
public class CityDAO extends AbstractHbmDAO {
private static Logger log = Logger.getLogger(CityDAO.class);
/**
* The property names bound by uniqueness constraints
*/
public static final Set uniConstrainedPropertyNames = new HashSet();
static {
uniConstrainedPropertyNames.add("id");
uniConstrainedPropertyNames.add("name");
}
/**
* Default Constructor
*/
public CityDAO() {
super(City.class, "id");
}
/**
* Constructor to be used by subclasses essentially bubbles up the object hierarchy
* up to the constructor of AbstractHbmDAO passing along the
* Class and Identifier namme for the Entity/POJO this DAO is about
* @see gr.abiss.md4j.hbm.AbstractHbmDAO#AbstractHbmDAO(java.lang.Class, java.io.Serializable)
*/
protected CityDAO(Class clazz, String identifier) {
super(clazz, identifier);
}
/**
* Creates a new City instance, using the map property-value
* pairs and persists it, then returns the identifier (PK). This method is <b>not</b> for updating
* @returns the identifier of the newly persisted POJO as Serializable that can be safely casted as Long
*/
public Serializable save(Map map) {
Object pojo = this.createFromParams(map);
return this.sf.getCurrentSession().save(pojo);
}
/**
* Persists the given City instance
* This method is <b>not</b> for updating
* @returns the identifier of the newly persisted POJO as Serializable that can be safely casted as Long
*/
public Serializable save(City pojo) {
return this.sf.getCurrentSession().save(pojo);
}
/**
* Convert the identifier to the right class and call the generic load method
* @see gr.abiss.md4j.dao.hibernate.AbstractHbmDAO#load(java.io.Serializable)
*/
public Object load(Serializable identifier) {
Long typedIdentifier = NumberUtils.createLong((String) identifier);
return super.load(typedIdentifier);
}
/**
* Convert the identifier to the right class and call the generic get method
* @see gr.abiss.md4j.dao.hibernate.AbstractHbmDAO#get(java.io.Serializable)
*/
public Object get(Serializable identifier) {
Long typedIdentifier = NumberUtils.createLong((String) identifier);
return super.get(typedIdentifier);
}
/**
* Convert the identifier to the right class and call the generic get method
* @see gr.abiss.md4j.dao.hibernate.AbstractHbmDAO#get(Serializable, Set)
*/
public Map get(Serializable identifier, Set projectionProperties) {
Long typedIdentifier = NumberUtils.createLong((String) identifier);
return super.get(typedIdentifier, projectionProperties);
}
/**
* Copy properties from the given map to the given City instance
* @param map The Map object to copy properties from
* @tparam obj The instance of City you wish to copy the properties to
* @throws ClassCastException in case the given object is not an instance of City
* @see gr.abiss.md4j.hbm.AbstractHbmDAO#copyProperties(java.util.Map, java.lang.Object)
*/
public void copyProperties(Map map, Object obj) {
City pojo = (City) obj;
if ( map.containsKey("id")) {
String _stringid = (String) map.get("id");
Long _id = NumberUtils.createLong(_stringid);
pojo.setId(_id);
}
if ( map.containsKey("name")) {
String _name = (String) map.get("name");
pojo.setName(_name);
}
if ( map.containsKey("region")) {
String _region = (String) map.get("region");
pojo.setRegion(_region);
}
Serializable _countryIdentifier = (Serializable) map.get("country");
if (_countryIdentifier != null) {
Country _country = null;
String countryId = (String) _countryIdentifier;
_country = (Country) this.sf.getCurrentSession().load(Country.class, countryId);
pojo.setCountry(_country);
}
}
/**
* Create the criteria for a City search using the given <code>params</code>, then bubble the
* operation to the superclass up to AbstractHbmDAO at witch point all <code>params</code>
* have bheen converted to criteria and the actual Criteria query can be executed to return a Page of results.
* @see gr.abiss.md4j.hbm.AbstractHbmDAO#getPage(org.hibernate.Session, java.util.Set, java.util.Map, org.hibernate.Criteria, int, int)
* @param sess The Hibernate Session object to use for the search
* @param projectionProps The set of properties to return for each result
* @param params The set of search criteria to use
* @param criteria The criteria to use for the search, produced by each DAO in the object hierarchy using the <code>params</code> map
* @param pageNumber The page number to return
* @param pageSize The page size
* @return The resulting Page of results
*/
protected void populateCriteria(Set projectionProps, Map map, Criteria criteria) {
//criteriaFromParamsMap prosessing: id, id: searchType:Long, hbmtype: java.lang.Long, relatedClass:
if ( map.get("id") != null) {
String _stringid = (String) map.get("id");
Long _id = NumberUtils.createLong(_stringid);
criteria.add(Restrictions.eq("id", _id));
}
//criteriaFromParamsMap prosessing: property, name: searchType:String, hbmtype: string, relatedClass:
if ( map.get("name") != null) {
String _name = (String) map.get("name");
criteria.add(Restrictions.like("name", "%" + _name + "%"));
}
//criteriaFromParamsMap prosessing: property, region: searchType:String, hbmtype: string, relatedClass:
if ( map.get("region") != null) {
String _region = (String) map.get("region");
criteria.add(Restrictions.like("region", "%" + _region + "%"));
}
//criteriaFromParamsMap prosessing: many-to-one, country: searchType:String, hbmtype: string, relatedClass: Country
criteria.createAlias("country", "country");
Serializable _countryIdentifier = (Serializable) map.get("country");
if (_countryIdentifier != null) {
String countryId = (String) _countryIdentifier;
criteria.add(Restrictions.eq("country.id", countryId));
}
}
/**
* @return The resulting Page of results
*/
public void addParentOptions(Map map) {
Session session = this.sf.getCurrentSession();
map.put(Md4jGlobals.PARENT_OPTIONS+"#country", Helper.getQueryResultAsMap(session.createCriteria(Country.class).addOrder(Order.asc("id")), "id", "id"));
}
public Set getUniquePropertyNames(){
return CityDAO.uniConstrainedPropertyNames;
}
}
in the mean time, Maven will use XDoclet to generate the EJB descriptors and package everything in target/md4j-quickstarter-ejb-0.2.jar.
Of course you can develop your own EJBs and other business-level code and place it in md4j-quickstarter-ejb/src/main/java. Maven will process that and package it in the EJB JAR, after XDoclet processing if neccessary.
In this module you develop your web classes (Struts, Servlets, etc.) in src/main/java. JSPs and other web files like CSS and images are placed in src/main/wbapp. Md4J reads the Hibernate mappings from the Domain module to create Struts code in target/generated-sources/main/java/ and JSPs in target/md4j-quickstarter-war-0.2/
/web
|-src
|---main
|-----java: Java web sources (Struts, Servlets etc)
|-----merge: Webdoclet merge files for web and Struts
|-----webapp: JSPs, images, CSS etc
|-target
|-pom.xml: The module POM
Similarly to the EJB module, to generate a JAR with your web files like Struts Actions and Forms, JSPs and everything else (including the Struts and JSP code MD4J will generate from your Domain mappings), all you have to do is execute the following from within the md4j-quickstarter-struts module directory:
mvn clean install
This will create the WAR file as target/md4j-quickstarter-struts-0.2.war. Let's see what MD4J generated from our City mapping:
jbossHome element before executing the following:
mvn clean install jboss:harddeploy