org.simplity.tp.Service Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2015 EXILANT Technologies Private Limited (www.exilant.com)
* Copyright (c) 2016 simplity.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.simplity.tp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.transaction.UserTransaction;
import org.simplity.jms.JmsConnector;
import org.simplity.jms.JmsUsage;
import org.simplity.kernel.Application;
import org.simplity.kernel.ApplicationError;
import org.simplity.kernel.FormattedMessage;
import org.simplity.kernel.MessageBox;
import org.simplity.kernel.MessageType;
import org.simplity.kernel.Messages;
import org.simplity.kernel.Tracer;
import org.simplity.kernel.comp.ComponentManager;
import org.simplity.kernel.comp.ComponentType;
import org.simplity.kernel.comp.ValidationContext;
import org.simplity.kernel.data.DataPurpose;
import org.simplity.kernel.data.DataSheet;
import org.simplity.kernel.db.DbAccessType;
import org.simplity.kernel.db.DbDriver;
import org.simplity.kernel.dm.Record;
import org.simplity.kernel.dt.DataType;
import org.simplity.kernel.util.TextUtil;
import org.simplity.kernel.value.Value;
import org.simplity.service.ServiceContext;
import org.simplity.service.ServiceData;
import org.simplity.service.ServiceInterface;
import org.simplity.service.ServiceProtocol;
/**
* Transaction Processing Service
*
* @author simplity.org
*
*/
public class Service implements ServiceInterface {
/*
* constants used by on-the-fly services
*/
private static final char PREFIX_DELIMITER = '_';
private static final String GET = "get";
private static final String FILTER = "filter";
private static final String SAVE = "save";
/**
* used by suggestion service as well
*/
public static final String SUGGEST = "suggest";
/**
* list service needs to use this
*/
private static final String LIST = "list";
/**
* stop the execution of this service as success
*/
public static final Value STOP_VALUE = Value.newTextValue("_s");
/**
* field name with which result of an action is available in service context
*/
public static final String RESULT_SUFFIX = "Result";
private static final ComponentType MY_TYPE = ComponentType.SERVICE;
/**
* simple name
*/
String name;
/**
* module name.simpleName would be fully qualified name.
*/
String moduleName;
/**
* if this is implemented as a java code. If this is specified, no attribute
* (other than name and module name) are relevant
*/
String className;
/**
* database access type
*/
DbAccessType dbAccessType = DbAccessType.NONE;
/**
* input fields/grids for this service. not valid if requestTextFieldName is
* specified
*/
InputData inputData;
/**
* copy input records from another service
*/
String referredServiceForInput;
/**
* copy output records from another service
*/
String referredServiceForOutput;
/**
* schema name, different from the default schema, to be used specifically
* for this service
*/
String schemaName;
/**
* output fields and grids for this service. Not valid if
* responseTextFieldName is specified
*/
OutputData outputData;
/**
* actions that make up this service
*/
Action[] actions;
/**
* should this be executed in the background ALWAYS?.
*/
boolean executeInBackground;
/**
* can the response from this service be cached? If so what are the input
* fields that this response depends on? provide comma separated list of
* field names. Null (default) implies that this service can not be cashed.
* Empty string implies that the response does not depend on the input at
* all. If it is dependent on userId, then "_userId" must be the first field
* name. A cache manager can keep the response from this service and re-use
* it so long as the input values for these fields are same.
*/
String canBeCachedByFields;
/**
* does this service use jms? if so with what kind of transaction management
*/
JmsUsage jmsUsage;
/**
* action names indexed to respond to navigation requests
*/
private final HashMap indexedActions = new HashMap();
/**
* flag to avoid repeated getReady() calls
*/
private boolean gotReady;
/**
* instance of className to be used as body of this service
*/
private ServiceInterface serviceInstance;
@Override
public DbAccessType getDataAccessType() {
return this.dbAccessType;
}
@Override
public String getSimpleName() {
return this.name;
}
@Override
public boolean toBeRunInBackground() {
return this.executeInBackground;
}
@Override
public boolean okToCache() {
return true;
}
@Override
public String getQualifiedName() {
if (this.moduleName == null) {
return this.name;
}
return this.moduleName + '.' + this.name;
}
@Override
public ServiceData respond(ServiceData inData) {
if (this.serviceInstance != null) {
return this.serviceInstance.respond(inData);
}
ServiceContext ctx = new ServiceContext(this.name, inData.getUserId());
/*
* copy values and data sheets sent by the client agent.
* These are typically session-stored, but not necessarily that
*/
for (String key : inData.getFieldNames()) {
Object val = inData.get(key);
if (val instanceof Value) {
ctx.setValue(key, (Value) val);
} else if (val instanceof DataSheet) {
ctx.putDataSheet(key, (DataSheet) val);
} else {
ctx.setObject(key, val);
}
}
MessageBox box = inData.getMessageBox();
if(box != null){
ctx.setMessageBox(box);
}
/*
* process input specification
*/
this.extractInput(ctx, inData.getPayLoad());
/*
* if input is in error, we return to caller without processing this
* service
*/
if (ctx.isInError()) {
return this.prepareResponse(ctx);
}
if (this.outputData != null) {
this.outputData.onServiceStart(ctx);
}
/*
* our service context is ready to execute this service now
*/
ApplicationError exception = this.executeService(ctx);
if (exception != null) {
throw exception;
}
return this.prepareResponse(ctx);
}
/**
* execute this service carefully managing the resources. Ensure that there
* is no leakage
*
* @param ctx
* service context
* @return application error if the service generated one. null is all ok
*/
private ApplicationError executeService(ServiceContext ctx) {
/*
* resources that need to be released without fail..
*/
JmsConnector jmsConnector = null;
UserTransaction userTransaciton = null;
ApplicationError exception = null;
BlockWorker worker = new BlockWorker(this.actions, this.indexedActions, ctx);
/*
* execute all actions
*/
try {
/*
* acquire resources that are needed for this service
*/
if (this.jmsUsage != null) {
jmsConnector = JmsConnector.borrowConnector(this.jmsUsage);
ctx.setJmsSession(jmsConnector.getSession());
}
DbAccessType access = this.dbAccessType;
/*
* is this a JTA transaction?
*/
if (access == DbAccessType.EXTERNAL) {
userTransaciton = Application.getUserTransaction();
userTransaciton.begin();
}
if (access == DbAccessType.NONE) {
worker.act(null);
} else {
/*
* Also, sub-Service means we open a read-only, and then
* call sub-service and complex-logic to manage their own
* connections
*/
if (access == DbAccessType.SUB_SERVICE) {
access = DbAccessType.READ_ONLY;
}
DbDriver.workWithDriver(worker, access, this.schemaName);
}
} catch (ApplicationError e) {
exception = e;
} catch (Exception e) {
exception = new ApplicationError(e, "Exception during execution of service. ");
}
/*
* close/return resources
*/
if (jmsConnector != null) {
JmsConnector.returnConnector(jmsConnector, exception == null && ctx.isInError() == false);
}
if (userTransaciton != null) {
try {
if (exception == null && ctx.isInError() == false) {
userTransaciton.commit();
} else {
Tracer.trace("Service is in error. User transaction rolled-back");
userTransaciton.rollback();
}
} catch (Exception e) {
exception = new ApplicationError(e, "Error while commit/rollback of user transaction");
}
}
return exception;
}
/**
* prepare service data to be returned to caller based on the content of
* service context
*
* @param ctx
* @return
*/
private ServiceData prepareResponse(ServiceContext ctx) {
ServiceData response = new ServiceData(ctx.getUserId(), this.getQualifiedName());
int nbrErrors = 0;
for (FormattedMessage msg : ctx.getMessages()) {
if (msg.messageType == MessageType.ERROR) {
nbrErrors++;
response.addMessage(msg);
}
}
/*
* create output pay load, but only if service succeeded. Dirty job of
* telling the bad news is left to the Client Agent :-)
*/
if (nbrErrors == 0) {
this.prepareResponse(ctx, response);
if (this.inputData != null) {
this.inputData.cleanup(ctx);
}
if (this.canBeCachedByFields != null) {
response.setCacheForInput(this.canBeCachedByFields);
}
}
return response;
}
protected void extractInput(ServiceContext ctx, String requestText) {
if (requestText == null || requestText.length() == 0) {
Tracer.trace("No input received from client");
return;
}
if (this.inputData == null) {
Tracer.trace("We received input data, but this service is designed not to make use of input.");
return;
}
try {
this.inputData.extractFromJson(requestText, ctx);
} catch (Exception e) {
ctx.addMessage(Messages.INVALID_DATA, "Invalid input data format. " + e.getMessage());
}
}
protected void prepareResponse(ServiceContext ctx, ServiceData response) {
if (this.outputData == null) {
Tracer.trace("Service " + this.name + " is designed to send no response.");
response.setPayLoad(OutputData.EMPTY_RESPONSE);
} else {
this.outputData.setResponse(ctx, response);
}
}
private boolean canWorkWithDriver(DbDriver driver) {
/*
* use of JMS may trigger this irrespective of db access
*/
if (this.jmsUsage == JmsUsage.SERVICE_MANAGED || this.jmsUsage == JmsUsage.EXTERNALLY_MANAGED) {
return false;
}
/*
* if we do not need it all, anything will do..
*/
if (this.dbAccessType == null || this.dbAccessType == DbAccessType.NONE) {
return true;
}
/*
* can not work with null.
*/
if (driver == null) {
return false;
}
/*
* may be we can get away for reads
*/
if (this.dbAccessType == DbAccessType.READ_ONLY) {
if (this.schemaName == null || this.schemaName.equalsIgnoreCase(driver.getSchema())) {
return true;
}
}
/*
* we tried our best to re-use... but failed
*/
return false;
}
@Override
public Value executeAsAction(ServiceContext ctx, DbDriver driver, boolean transactionIsDelegated) {
/*
* are we to manage our own transaction?
*/
if (transactionIsDelegated) {
if (this.canWorkWithDriver(driver) == false) {
/*
* execute this as a service
*/
ApplicationError err = this.executeService(ctx);
if (err != null) {
throw err;
}
return Value.VALUE_TRUE;
}
}
/*
* is this a custom service?
*/
if(this.serviceInstance != null){
return this.serviceInstance.executeAsAction(ctx, driver, transactionIsDelegated);
}
/*
* this is a simple action
*/
BlockWorker worker = new BlockWorker(this.actions, this.indexedActions, ctx);
boolean result = worker.workWithDriver(driver);
if (result) {
return Value.VALUE_TRUE;
}
return Value.VALUE_FALSE;
}
@Override
public void getReady() {
if (this.gotReady) {
Tracer.trace("Service " + this.getQualifiedName()
+ " is being harassed by repeatedly asking it to getReady(). Please look into this..");
return;
}
this.gotReady = true;
if (this.className != null) {
try {
this.serviceInstance = Application.getBean(this.className, ServiceInterface.class);
} catch (Exception e) {
throw new ApplicationError(e,
"Unable to get an instance of service using class name " + this.className);
}
}
if (this.actions == null) {
Tracer.trace("Service " + this.getQualifiedName() + " has no actions.");
this.actions = new Action[0];
} else {
this.prepareChildren();
}
/*
* input record may have to be copied form referred service
*/
if (this.referredServiceForInput != null) {
if (this.inputData != null) {
throw new ApplicationError("Service " + this.getQualifiedName() + " refers to service "
+ this.referredServiceForInput + " but also specifies its own input records.");
}
ServiceInterface service = ComponentManager.getService(this.referredServiceForInput);
if (service instanceof Service == false) {
throw new ApplicationError("Service " + this.getQualifiedName() + " refers to another service "
+ this.referredServiceForInput + ", but that is not an xml-based service.");
}
this.inputData = ((Service) service).inputData;
}
if (this.inputData != null) {
this.inputData.getReady();
}
/*
* output record may have to be copied form referred service
*/
if (this.referredServiceForOutput != null) {
if (this.outputData != null) {
throw new ApplicationError("Service " + this.getQualifiedName() + " refers to service "
+ this.referredServiceForOutput + " but also specifies its own output records.");
}
ServiceInterface service = ComponentManager.getService(this.referredServiceForOutput);
if (service instanceof Service == false) {
throw new ApplicationError("Service " + this.getQualifiedName() + " refers to another service "
+ this.referredServiceForOutput + ", but that is not an xml-based service.");
}
this.outputData = ((Service) service).outputData;
}
if (this.outputData != null) {
this.outputData.getReady();
}
}
private void prepareChildren() {
int i = 0;
boolean delegated = this.dbAccessType == DbAccessType.SUB_SERVICE;
for (Action action : this.actions) {
action.getReady(i, this);
if (this.indexedActions.containsKey(action.actionName)) {
throw new ApplicationError("Service " + this.name + " has duplicate action name " + action.actionName
+ " as its action nbr " + (i + 1));
}
this.indexedActions.put(action.getName(), new Integer(i));
i++;
/*
* programmers routinely forget to set the dbaccess type..
* we think it is worth this run-time over-head to validate it
* again
*/
if (delegated && (action instanceof SubService)) {
continue;
}
if (this.dbAccessType.canWorkWithChildType(action.getDataAccessType()) == false) {
throw new ApplicationError(
"Service " + this.getQualifiedName() + " uses dbAccessTYpe=" + this.dbAccessType
+ " but action " + action.getName() + " requires " + action.getDataAccessType());
}
}
}
/**
* @param serviceName
* @param record
* @return a service that is designed to read a row for the primary key from
* the table associated with this record
*/
public static Service getReadService(String serviceName, Record record) {
String recordName = record.getQualifiedName();
Service service = new Service();
service.dbAccessType = DbAccessType.READ_ONLY;
service.setName(serviceName);
service.schemaName = record.getSchemaName();
/*
* what is to be input
*/
InputRecord inRec = new InputRecord();
inRec.recordName = recordName;
inRec.purpose = DataPurpose.READ;
InputRecord[] inRecs = { inRec };
InputData inData = new InputData();
inData.inputRecords = inRecs;
service.inputData = inData;
/*
* We have just one action : read action
*/
Action action = new Read(record);
action.failureMessageName = Messages.NO_ROWS;
Action[] actions = { action };
service.actions = actions;
/*
* output fields from record.
*/
OutputRecord[] outRecs = getOutputRecords(record);
OutputData outData = new OutputData();
outData.outputRecords = outRecs;
service.outputData = outData;
return service;
}
/**
*
* @param serviceName
* @param record
* @return service that filter rows from table associated with this record,
* and possibly reads related rows from child records
*/
public static Service getFilterService(String serviceName, Record record) {
String recordName = record.getQualifiedName();
Service service = new Service();
service.dbAccessType = DbAccessType.READ_ONLY;
service.setName(serviceName);
service.schemaName = record.getSchemaName();
/*
* input for filter
*/
InputRecord inRec = new InputRecord();
inRec.recordName = recordName;
inRec.purpose = DataPurpose.FILTER;
InputRecord[] inRecs = { inRec };
InputData inData = new InputData();
inData.inputRecords = inRecs;
service.inputData = inData;
Action action;
OutputData outData = new OutputData();
service.outputData = outData;
/*
* if we have to read children, we use filter action, else we use
* filterToJson
*/
if (record.getChildrenToOutput() == null) {
action = new FilterToJson(record);
outData.outputFromWriter = true;
} else {
action = new Filter(record);
OutputRecord[] outRecs = getOutputRecords(record);
outData.outputRecords = outRecs;
}
action.failureMessageName = Messages.NO_ROWS;
Action[] actions = { action };
service.actions = actions;
/*
* getReady() is called by component manager any ways..
*/
return service;
}
/**
*
* @param serviceName
* @param record
* @return service that returns a sheet with suggested rows for the supplied
* text value
*/
public static Service getSuggestionService(String serviceName, Record record) {
Service service = new Service();
service.dbAccessType = DbAccessType.READ_ONLY;
service.setName(serviceName);
service.schemaName = record.getSchemaName();
/*
* input for suggest
*/
InputField f1 = new InputField(ServiceProtocol.LIST_SERVICE_KEY, DataType.DEFAULT_TEXT, true, null);
InputField f2 = new InputField(ServiceProtocol.SUGGEST_STARTING, DataType.DEFAULT_BOOLEAN, false, null);
InputField[] inFields = { f1, f2 };
InputData inData = new InputData();
inData.inputFields = inFields;
service.inputData = inData;
/*
* use a suggest action to do the job
*/
Action action = new Suggest(record);
action.failureMessageName = Messages.NO_ROWS;
Action[] actions = { action };
service.actions = actions;
/*
* output as sheet
*/
OutputRecord outRec = new OutputRecord(record);
OutputRecord[] outRecs = { outRec };
OutputData outData = new OutputData();
outData.outputRecords = outRecs;
service.outputData = outData;
/*
* getReady() is called by component manager any ways..
*/
return service;
}
/**
*
* @param serviceName
* @param record
* @return service that returns a sheet with suggested rows for the supplied
* text value
*/
public static Service getListService(String serviceName, Record record) {
Service service = new Service();
service.dbAccessType = DbAccessType.READ_ONLY;
service.setName(serviceName);
service.schemaName = record.getSchemaName();
if (record.getOkToCache()) {
String keyName = record.getValueListKeyName();
if (keyName == null) {
keyName = "";
}
service.canBeCachedByFields = keyName;
}
/*
* do we need any input? we are flexible
*/
InputField f1 = new InputField(ServiceProtocol.LIST_SERVICE_KEY, DataType.DEFAULT_TEXT, false, null);
InputField[] inFields = { f1 };
InputData inData = new InputData();
inData.inputFields = inFields;
service.inputData = inData;
/*
* use a List action to do the job
*/
Action action = new KeyValueList(record);
Action[] actions = { action };
service.actions = actions;
/*
* output as sheet
*/
OutputRecord outRec = new OutputRecord(record);
OutputRecord[] outRecs = { outRec };
OutputData outData = new OutputData();
outData.outputRecords = outRecs;
service.outputData = outData;
/*
* getReady() is called by component manager any ways..
*/
return service;
}
/**
* create a service that would save a rows, possibly along with some child
* rows
*
* @param serviceName
* @param record
* @return a service that would save a rows, possibly along with some child
* rows
*/
public static ServiceInterface getSaveService(String serviceName, Record record) {
String recordName = record.getQualifiedName();
Service service = new Service();
service.dbAccessType = DbAccessType.READ_WRITE;
service.setName(serviceName);
service.schemaName = record.getSchemaName();
/*
* data for this record is expected in fields, while rows for
* child-records in data sheets
*/
InputData inData = new InputData();
inData.inputRecords = getInputRecords(record);
service.inputData = inData;
/*
* save action
*/
Save action = new Save(record, getChildRecords(record, false));
action.failureMessageName = Messages.NO_UPDATE;
Action[] actions = { action };
service.actions = actions;
/*
* we think we have to read back the row, but not suer.. Here the action
* commented for that
*/
// Read action1 = new Read();
// action1.executeOnCondition = "saveResult != 0";
// action1.name = "read";
// action1.recordName = recordName;
// action1.childRecords = getChildRecords(record, true);
// Action[] actions = { action, action1 };
/*
* what should we output? We are not sure. As of now let us send back
* fields
*/
OutputRecord outRec = new OutputRecord();
outRec.recordName = recordName;
OutputData outData = new OutputData();
OutputRecord[] outRecs = { outRec };
outData.outputRecords = outRecs;
service.outputData = outData;
return service;
}
/*
* check for name and module name based on the requested name
*/
protected void setName(String possiblyQualifiedName) {
int idx = possiblyQualifiedName.lastIndexOf('.');
if (idx == -1) {
this.name = possiblyQualifiedName;
this.moduleName = null;
} else {
this.name = possiblyQualifiedName.substring(idx + 1);
this.moduleName = possiblyQualifiedName.substring(0, idx);
}
Tracer.trace("service name set to " + this.name + " and " + this.moduleName);
}
protected static RelatedRecord[] getChildRecords(Record record, boolean forRead) {
String[] children;
if (forRead) {
children = record.getChildrenToOutput();
} else {
children = record.getChildrenToInput();
}
if (children == null) {
return null;
}
RelatedRecord[] recs = new RelatedRecord[children.length];
int i = 0;
for (String child : children) {
RelatedRecord rr = new RelatedRecord();
rr.recordName = child;
rr.sheetName = TextUtil.getSimpleName(child);
rr.getReady();
recs[i++] = rr;
}
return recs;
}
protected static OutputRecord[] getOutputRecords(Record record) {
List recs = new ArrayList();
record.addOutputRecords(recs);
return recs.toArray(new OutputRecord[0]);
}
/**
*
* @param record
* @return
*/
protected static InputRecord[] getInputRecords(Record record) {
String recordName = record.getQualifiedName();
String[] children = record.getChildrenToInput();
int nrecs = 1;
if (children != null) {
nrecs = children.length + 1;
}
InputRecord inRec = new InputRecord();
inRec.recordName = recordName;
inRec.purpose = DataPurpose.SAVE;
inRec.saveActionExpected = true;
/*
* inputRecord is lenient about sheetName. It is okay to specify that.
* In case one row comes from client as fields, inputRecord manages that
*/
inRec.sheetName = record.getDefaultSheetName();
InputRecord[] recs = new InputRecord[nrecs];
recs[0] = inRec;
if (children != null) {
String sheetName = record.getDefaultSheetName();
int i = 1;
for (String child : children) {
recs[i++] = ComponentManager.getRecord(child).getInputRecord(sheetName);
}
}
return recs;
}
@Override
public int validate(ValidationContext ctx) {
ctx.beginValidation(MY_TYPE, this.getQualifiedName());
/*
* it is important that we endValidtion() before returning
*/
try {
int count = 0;
if (this.className != null) {
try {
Object obj = Class.forName(this.className).newInstance();
if (obj instanceof ServiceInterface == false) {
ctx.addError(
this.className + " is set as className but it does not implement ServiceInterface");
count++;
}
} catch (Exception e) {
ctx.addError(this.className + " could not be used to instantiate an object.\n" + e.getMessage());
count++;
}
if (this.actions != null) {
ctx.addError(this.className
+ " is set as className. This java code is used as service definition. Actions are not valid.");
count++;
}
return count;
}
if (this.actions == null) {
ctx.addError("No actions.");
count++;
} else {
count += this.validateChildren(ctx);
}
if (this.referredServiceForInput != null) {
if (this.inputData != null) {
ctx.reportUnusualSetting("referredServiceForInput is used, and hence inputData element is ignored");
}
ServiceInterface service = ComponentManager.getServiceOrNull(this.referredServiceForInput);
if (service == null) {
ctx.addError("referredServiceForInput set to " + this.referredServiceForInput
+ " but that service is not defined");
count++;
}
}
if (this.referredServiceForOutput != null) {
if (this.outputData != null) {
ctx.reportUnusualSetting(
"referredServiceForOutput is used, and hence outputData element is ignored");
}
ServiceInterface service = ComponentManager.getServiceOrNull(this.referredServiceForOutput);
if (service == null) {
ctx.addError("referredServiceForOutput set to " + this.referredServiceForOutput
+ " but that service is not defined");
count++;
}
}
if (this.schemaName != null && DbDriver.isSchmeaDefined(this.schemaName) == false) {
ctx.addError("schemaName is set to " + this.schemaName
+ " but it is not defined as one of additional schema names in application.xml");
}
return count;
} finally {
ctx.endValidation();
}
}
/**
* validate child actions
*
* @param ctx
* @return number of errors added
*/
private int validateChildren(ValidationContext ctx) {
int count = 0;
Set addedSoFar = new HashSet();
int actionNbr = 0;
boolean dbAccessErrorRaised = false;
boolean delegated = this.dbAccessType == DbAccessType.SUB_SERVICE;
for (Action action : this.actions) {
actionNbr++;
if (action.actionName != null) {
if (addedSoFar.add(action.actionName) == false) {
ctx.addError("Duplicate action name " + action.actionName + " at " + actionNbr);
count++;
}
}
count += action.validate(ctx, this);
if (dbAccessErrorRaised || (delegated && (action instanceof SubService))) {
continue;
}
if (this.dbAccessType.canWorkWithChildType(action.getDataAccessType()) == false) {
ctx.addError(
"dbAccessType of service is not compatible for the cbAccessType of its actions. Please review your db access design thoroughly based on your actions design.");
count++;
dbAccessErrorRaised = true;
}
}
return count;
}
@Override
public ComponentType getComponentType() {
return MY_TYPE;
}
/**
* generate a service on the fly, if possible
*
* @param serviceName
* that follows on-the-fly-service-name pattern
* @return service, or null if name is not a valid on-the-fly service name
*/
public static ServiceInterface generateService(String serviceName) {
Tracer.trace("Trying to generate service " + serviceName + " on-the-fly");
int idx = serviceName.indexOf(PREFIX_DELIMITER);
if (idx == -1) {
return null;
}
String operation = serviceName.substring(0, idx);
String recordName = serviceName.substring(idx + 1);
/*
* once we have established that this name follows our on-the-fly naming
* convention, we are quite likely to to get the record.
*/
Record record = ComponentManager.getRecordOrNull(recordName);
if (record == null) {
Tracer.trace(
recordName + " is not defined as a record, and hence we are unable to generate a service named "
+ serviceName);
return null;
}
if (operation.equals(LIST)) {
return getListService(serviceName, record);
}
if (operation.equals(FILTER)) {
return getFilterService(serviceName, record);
}
if (operation.equals(GET)) {
return getReadService(serviceName, record);
}
if (operation.equals(SAVE)) {
return getSaveService(serviceName, record);
}
if (operation.equals(SUGGEST)) {
return getSuggestionService(serviceName, record);
}
Tracer.trace("We have no on-the-fly service generator for operation " + operation);
return null;
}
/**
* generate a service based on the java class
*
* @param serviceName
* @param className
* Could be a ServiceInterface, LogicInterface or
* COmplexLogicInterface
* @return service if this is a valid class, or null if it is not a valid
* class, or some error with that
*/
public static ServiceInterface getLogicService(String serviceName, String className) {
Action action = null;
DbAccessType accessType = DbAccessType.NONE;
try {
Class> cls = Class.forName(className);
if (ServiceInterface.class.isAssignableFrom(cls)) {
return Application.getBean(className, ServiceInterface.class);
}
if (cls.isAssignableFrom(LogicInterface.class)) {
Logic act = new Logic();
act.className = className;
} else if (cls.isAssignableFrom(ComplexLogicInterface.class)) {
ComplexLogic act = new ComplexLogic();
act.className = className;
accessType = DbAccessType.READ_WRITE;
} else {
Tracer.trace(className
+ " is a valid class name, but it is not a ServiceInterafce, or a LogicInterface, or a ComplexLogicInterface. A service can not be constructed for this class.");
return null;
}
} catch (ClassNotFoundException e) {
Tracer.trace(className + " is designated as a class for service " + serviceName
+ " but the class can not be located.");
return null;
} catch (Exception e) {
Tracer.trace("Error while getting class for " + className + " " + e.getMessage());
return null;
}
Service service = new Service();
service.dbAccessType = accessType;
service.setName(serviceName);
/*
* we have no idea what this service wants as input. May be we should
* add that to the interface, so that any service has to tell what input
* it expects. Till such time, here is a dirty short-cut
*/
InputData inputData = new InputData();
inputData.justInputEveryThing = true;
OutputData outputData = new OutputData();
outputData.justOutputEveryThing = true;
service.inputData = inputData;
service.outputData = outputData;
Action[] act = { action };
service.actions = act;
return service;
}
}