
org.specrunner.objects.AbstractPluginObject Maven / Gradle / Ivy
The newest version!
/*
SpecRunner - Acceptance Test Driven Development Tool
Copyright (C) 2011-2013 Thiago Santos
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
package org.specrunner.objects;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Nodes;
import org.apache.commons.beanutils.PropertyUtils;
import org.specrunner.SpecRunnerServices;
import org.specrunner.context.IContext;
import org.specrunner.parameters.DontEval;
import org.specrunner.parameters.impl.UtilParametrized;
import org.specrunner.plugins.PluginException;
import org.specrunner.plugins.impl.AbstractPluginTable;
import org.specrunner.result.IResultSet;
import org.specrunner.result.status.Failure;
import org.specrunner.result.status.Success;
import org.specrunner.result.status.Warning;
import org.specrunner.source.ISource;
import org.specrunner.source.ISourceFactory;
import org.specrunner.source.SourceException;
import org.specrunner.util.UtilEvaluator;
import org.specrunner.util.UtilLog;
import org.specrunner.util.converter.ConverterException;
import org.specrunner.util.converter.IConverter;
import org.specrunner.util.converter.IConverterManager;
import org.specrunner.util.xom.CellAdapter;
import org.specrunner.util.xom.RowAdapter;
import org.specrunner.util.xom.TableAdapter;
import org.specrunner.util.xom.UtilNode;
/**
* Generic object plugin. To write object plugins override method
* isMapped()
and action(...)
. i.e.
* SpecRunner-Hibernate3 extends the object manipulation to save/update/delete
* data using Hibernate3 infra-structure.
*
* @author Thiago Santos
*
*/
public abstract class AbstractPluginObject extends AbstractPluginTable {
/**
* Object class name.
*/
protected String type;
/**
* Object class.
*/
protected Class> typeInstance;
/**
* Object creator name.
*/
protected String creator;
/**
* Object creator instance.
*/
protected IObjectCreator creatorInstance;
/**
* The identification fields.
*/
protected String reference;
/**
* The identification fields list.
*/
protected List references = new LinkedList();
/**
* Separator of identification attributes.
*/
protected String separator;
/**
* The map of generic fields to be used as reference.
*/
protected String mapping;
/**
* List of generic definition fields.
*/
protected Map generic = new HashMap();
/**
* List of fields.
*/
protected List fields = new LinkedList();
/**
* Mapping of key before processing to the ones after processing.
*/
protected Map keysBefore = new HashMap();
/**
* Mapping of identifiers to object instances.
*/
protected Map instances = new HashMap();
/**
* The object type of the plugin. i.e.
* type='system.entity.Person'
.
*
* @return The type.
*/
public String getType() {
return type;
}
/**
* Set the type.
*
* @param type
* A new type.
*/
public void setType(String type) {
this.type = type;
}
/**
* Gets the corresponding class to type.
*
* @return The class.
*/
public Class> getTypeInstance() {
return typeInstance;
}
/**
* Sets the instance type.
*
* @param typeInstance
* A new type.
*/
public void setTypeInstance(Class> typeInstance) {
this.typeInstance = typeInstance;
}
/**
* Returns the object creator of embeddable objects.
*
* @return The creator class name.
*/
public String getCreator() {
return creator;
}
/**
* Sets the creator class.
*
* @param creator
* A new creator class name.
*/
public void setCreator(String creator) {
this.creator = creator;
}
/**
* Object creator type.
*
* @return The creator instance.
*/
public IObjectCreator getCreatorInstance() {
return creatorInstance;
}
/**
* Sets the creator.
*
* @param creatorInstance
* A new creator instance.
*/
public void setCreatorInstance(IObjectCreator creatorInstance) {
this.creatorInstance = creatorInstance;
}
/**
* Sets the attribute which represents the keys of the entity. These fields
* are used to create the instance key. i.e. if
* reference='id,name'
is used, the 'id' attribute and 'name'
* attribute are used as the object key, separated by 'separator' field.
*
* @return The list of attribute references in the expected order.
*/
public String getReference() {
return reference;
}
/**
* Sets the references.
*
* @param reference
* A new reference list.
*/
@DontEval
public void setReference(String reference) {
this.reference = reference;
if (reference != null) {
references.clear();
String[] refs = reference.split(",");
for (String s : refs) {
references.add(s);
}
}
}
/**
* Gets the string used to separate fields of reference. i.e. if
* reference='id,name'
and separator='/'
, the
* object instance corresponding to a given line will be 'id/name'.
*
* @return The keys separator.
*/
public String getSeparator() {
return separator;
}
/**
* Sets identifier separator.
*
* @param separator
* The separator.
*/
public void setSeparator(String separator) {
this.separator = separator;
}
/**
* Return the map of field information. For example, if there is a mapping
* for object City already defined in a file name '/city.html' or
* '/city.xml', just add map='/cities.html' to the tag.
*
* @return The mapping.
*/
public String getMapping() {
return mapping;
}
/**
* Sets the object mapping.
*
* @param mapping
* The mapping.
*/
public void setMapping(String mapping) {
this.mapping = mapping;
}
@Override
public void initialize(IContext context, TableAdapter table) throws PluginException {
super.initialize(context, table);
if (mapping != null) {
loadMapping(context, table);
} else {
setObjectInformation();
}
}
/**
* Load mapping with predefined values.
*
* @param context
* The context.
* @param table
* The table.
* @throws PluginException
* On mapping errors.
*/
protected void loadMapping(IContext context, TableAdapter table) throws PluginException {
try {
if (UtilLog.LOG.isInfoEnabled()) {
UtilLog.LOG.info("Loading object mapping>" + mapping);
}
URL file = getClass().getResource(mapping);
if (file == null) {
throw new PluginException("The object mapping file '" + mapping + "' not found.");
}
if (UtilLog.LOG.isInfoEnabled()) {
UtilLog.LOG.info("Loading object mapping file>" + file);
}
ISource source = SpecRunnerServices.get(ISourceFactory.class).newSource(file.toString());
Document doc = source.getDocument();
Nodes ns = doc.query("//table");
if (ns.size() == 0) {
throw new PluginException("The mapping file must have a table element with the field information.");
}
Element n = (Element) ns.get(0);
TableAdapter ta = UtilNode.newTableAdapter(n);
if (ta.getRowCount() == 0) {
throw new PluginException("The mapping file might have at least one row (usually a header) with the generic field information.");
}
RowAdapter information = ta.getRow(0);
// set table properties such as type/separator/id/etc.
UtilParametrized.setProperties(context, this, n);
// to replace specific settings back.
UtilParametrized.setProperties(context, this, table.getElement());
// set type objects.
setObjectInformation();
// load fields.
List general = new LinkedList();
loadFields(context, information, general);
for (Field field : general) {
generic.put(field.getFieldName(), field);
}
} catch (SourceException e) {
if (UtilLog.LOG.isInfoEnabled()) {
UtilLog.LOG.info(e.getMessage(), e);
}
throw new PluginException("Fail on loading mapping information: '" + mapping + "'.", e);
}
}
/**
* Set object and/or creator information.
*
* @throws PluginException
* On setting errors.
*/
protected void setObjectInformation() throws PluginException {
try {
if (type != null) {
typeInstance = Class.forName(type);
}
if (creator != null) {
creatorInstance = (IObjectCreator) Class.forName(creator).newInstance();
}
} catch (Exception e) {
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug(e.getMessage(), e);
}
throw new PluginException(e);
}
if (typeInstance == null) {
throw new PluginException("Set 'type' with object class name.");
}
}
@Override
public void doEnd(IContext context, IResultSet result, TableAdapter table) throws PluginException {
if (isMapped()) {
PluginObjectManager.get().bind(this);
}
for (int i = 0; i < table.getRowCount(); i++) {
if (i == 0) {
try {
loadFields(context, table.getRow(i), fields);
result.addResult(Success.INSTANCE, context.newBlock(table.getRow(i).getElement(), this));
} catch (Exception e) {
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug(e.getMessage(), e);
}
result.addResult(Failure.INSTANCE, context.newBlock(table.getRow(i).getElement(), this), e);
break;
}
} else {
try {
processLine(context, table.getRow(i), result);
} catch (Exception e) {
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug(e.getMessage(), e);
}
result.addResult(Failure.INSTANCE, context.newBlock(table.getRow(i).getElement(), this), e);
}
}
}
}
/**
* Load fields based on th
tags.
*
* @param context
* The context.
* @param row
* The row.
* @param list
* List of fields.
* @throws PluginException
* On load errors.
*/
protected void loadFields(IContext context, RowAdapter row, List list) throws PluginException {
int index = 0;
for (CellAdapter cell : row.getCells()) {
boolean ignore = false;
if (cell.hasAttribute("ignore")) {
ignore = Boolean.parseBoolean(cell.getAttribute("ignore"));
}
String fieldName = cell.getValue().trim();
String name;
if (cell.hasAttribute("field")) {
name = cell.getAttribute("field");
} else {
name = normalize(fieldName);
}
Field f = generic.get(fieldName);
if (f == null) {
f = new Field();
f.setFieldName(fieldName);
} else {
name = f.getFullName();
}
f.setIndex(index++);
f.setReference(references.contains(name));
f.setIgnore(ignore);
if (!ignore) {
StringTokenizer st = new StringTokenizer(name, ".");
String[] names = new String[st.countTokens()];
for (int i = 0; i < names.length; i++) {
names[i] = st.nextToken();
}
if (names.length > 0) {
f.setNames(names);
}
Class>[] types = new Class>[names.length];
Class> currentType = typeInstance;
for (int i = 0; currentType != null && i < types.length; i++) {
Method m = null;
try {
m = currentType.getMethod("get" + Character.toUpperCase(names[i].charAt(0)) + names[i].substring(1));
} catch (Exception e) {
try {
m = currentType.getMethod("is" + Character.toUpperCase(names[i].charAt(0)) + names[i].substring(1));
} catch (Exception e1) {
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug(e1.getMessage(), e1);
}
}
}
if (m == null) {
throw new PluginException("Getter method for " + names[i] + " not found for type '" + currentType + "'.");
}
types[i] = m.getReturnType();
currentType = types[i];
}
if (types.length > 0) {
f.setTypes(types);
}
String def = null;
if (cell.hasAttribute("default")) {
def = cell.getAttribute("default");
}
if (def != null) {
f.setDef(def);
}
String converter = cell.hasAttribute("converter") ? cell.getAttribute("converter") : null;
String[] converters = converter != null ? converter.split(",") : new String[0];
if (f.getConverters() == null || converters.length > 0) {
f.setConverters(converters);
}
int i = 0;
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy