Validate User entered attribute against values that reside in an external text file
I have a "Ticket Number" attribute on a Work Item that needs to be validate against a list of valid Ticket Numbers that reside on an external text file. If a User enters a ticket number value that does not exist on the text file, he will not be allowed to save the work item. How do I go about implementing this?
3 answers
Hello,
you may want to use an HTTP ValueSet Provider to populate the possible and valid values for ticket number,
so that:
- user will not enter but select a valid value
- no validation will be required
The only constraint here is to have the file content as XML accessible from HTTP
Eric
Comments
There will be several hundreds of ticket number values. This will be overwhelming to be displayed to the User in a drop down.
You could use a Value Set Picker as the presentation kind to prevent such an overwhelming list in a dropdown. See number 5 in the procedure here:
http://pic.dhe.ibm.com/infocenter/clmhelp/v4r0m3/index.jsp?re=1&topic=/com.ibm.team.workitem.doc/topics/t_configuring_http_filtered_value_set.html&scope=null
also, what behavior do you expect? can't exit the field if the number isn't good or can't save the record.
all the server side solutions are the latter, some of the client side solutions are the former.
I think you would have to create a custom web application to read in and manage the list of valid numbers,
and provide a web service UI (rest or somesuch) to validate a selection. this service would handle caching and list reload, etc.. and u could do it right now, today.
then write an advisor that takes the input data on workitem save and calls that outside RTC web service.
(in the same server or not). the advisor could get the web service URL and attribute name from config parameters. I think the amount of code for both is pretty tiny.
I would probably do REST based service that returns 0 or 1 (false/true) to minimize the footprint,
and enable using the httpclient java support.
note: to do the http valueset you have to do the web app as the server anyhow and can only return XML with the LIST of items.. (which you don't want to do due to the size and UI usability impacts).
this solution is UI neutral, but only implements the error on save approach.
all the server side solutions are the latter, some of the client side solutions are the former.
I think you would have to create a custom web application to read in and manage the list of valid numbers,
and provide a web service UI (rest or somesuch) to validate a selection. this service would handle caching and list reload, etc.. and u could do it right now, today.
then write an advisor that takes the input data on workitem save and calls that outside RTC web service.
(in the same server or not). the advisor could get the web service URL and attribute name from config parameters. I think the amount of code for both is pretty tiny.
I would probably do REST based service that returns 0 or 1 (false/true) to minimize the footprint,
and enable using the httpclient java support.
note: to do the http valueset you have to do the web app as the server anyhow and can only return XML with the LIST of items.. (which you don't want to do due to the size and UI usability impacts).
this solution is UI neutral, but only implements the error on save approach.
here is a draft plugin that would implement my suggestion to use an external web service via rest.
this compiles successfully, but has not been tested in any way.
properties are xml in the process config of the advisor
here is one I did a while back.
<followup-action id="PerforceJobs.createJobs" name="Create Perforce Jobs">
<properties>
<property id="debug" value="true"/>
<property id="server" value="perforce"/>
<property id="port" value="1666"/>
<property id="service.userid" value="service"/>
and the code to get the parms
<code>
// get the plugin parameters into an easy access hash
Hashtable<String,String> props = new Hashtable<String,String>();
for (IProcessConfigurationElement child : participantConfig.getChildren())
{
for (IProcessConfigurationElement child1 : child.getChildren())
{
// if the configuration property name matches the one we are interested in
if(OurProperty.equalsIgnoreCase(child1.getName()))
props.put(child1.getAttribute("id"), child1.getAttribute("value"));
}
}
</code>
<code>
package com.sd.test;
import java.io.IOException;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.eclipse.core.runtime.IProgressMonitor;
import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.IAuditable;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.service.AbstractService;
import com.ibm.team.workitem.common.IAuditableCommon;
import com.ibm.team.workitem.common.ISaveParameter;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.IAttribute;
public class ValidateViaHttp extends AbstractService implements
IOperationAdvisor
{
@Override
public void run(AdvisableOperation operation,
IProcessConfigurationElement advisorConfiguration,
IAdvisorInfoCollector collector, IProgressMonitor monitor)
throws TeamRepositoryException
{
// get the operation data
Object data = operation.getOperationData();
// is this a 'save' operation?
if (data instanceof ISaveParameter)
{
// get the affected object
IAuditable auditable = ((ISaveParameter) data).getNewState();
// if this is a workitem
if (auditable instanceof IWorkItem)
{
// get the worker objects
IAuditableCommon iac = ((ISaveParameter) data)
.getSaveOperationParameter().getAuditableCommon();
// reference the right object type (cast)
IWorkItem workItem = (IWorkItem) auditable;
// access the workitem common service
// must be defined in the plugin.xml as required.
IWorkItemCommon iwc = getService(IWorkItemCommon.class);
HttpClient client = new HttpClient();
String serverurl = getStringConfigProperty("serverurl");
IAttribute attribute = iwc.findAttribute(workItem.getProjectArea(), advisorConfiguration
.getAttribute("attribute_id_string"), null);
// Create a method instance.
GetMethod method = new GetMethod(serverurl + "?validate="
+ workItem.getValue(attribute).toString());
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
try
{
// Execute the method.
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK)
{
System.err.println("Method failed: "
+ method.getStatusLine());
}
// Read the response body.
byte[] responseBody = method.getResponseBody();
// Deal with the response.
// Use caution: ensure correct character encoding and is not
// binary data
System.out.println(new String(responseBody));
if (responseBody[0] != '1')
{
IAdvisorInfo info = collector.createProblemInfo(
"your error", "message here", "error");
// tell the caller to post an error, and reject the
// action
collector.addInfo(info);
}
}
catch (HttpException e)
{
System.err.println("Fatal protocol violation: "
+ e.getMessage());
e.printStackTrace();
}
catch (IOException e)
{
System.err.println("Fatal transport error: "
+ e.getMessage());
e.printStackTrace();
}
finally
{
// Release the connection.
method.releaseConnection();
}
}
}
}
}
</code>
this compiles successfully, but has not been tested in any way.
properties are xml in the process config of the advisor
here is one I did a while back.
<followup-action id="PerforceJobs.createJobs" name="Create Perforce Jobs">
<properties>
<property id="debug" value="true"/>
<property id="server" value="perforce"/>
<property id="port" value="1666"/>
<property id="service.userid" value="service"/>
and the code to get the parms
<code>
// get the plugin parameters into an easy access hash
Hashtable<String,String> props = new Hashtable<String,String>();
for (IProcessConfigurationElement child : participantConfig.getChildren())
{
for (IProcessConfigurationElement child1 : child.getChildren())
{
// if the configuration property name matches the one we are interested in
if(OurProperty.equalsIgnoreCase(child1.getName()))
props.put(child1.getAttribute("id"), child1.getAttribute("value"));
}
}
</code>
<code>
package com.sd.test;
import java.io.IOException;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.eclipse.core.runtime.IProgressMonitor;
import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.IAuditable;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.service.AbstractService;
import com.ibm.team.workitem.common.IAuditableCommon;
import com.ibm.team.workitem.common.ISaveParameter;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.IAttribute;
public class ValidateViaHttp extends AbstractService implements
IOperationAdvisor
{
@Override
public void run(AdvisableOperation operation,
IProcessConfigurationElement advisorConfiguration,
IAdvisorInfoCollector collector, IProgressMonitor monitor)
throws TeamRepositoryException
{
// get the operation data
Object data = operation.getOperationData();
// is this a 'save' operation?
if (data instanceof ISaveParameter)
{
// get the affected object
IAuditable auditable = ((ISaveParameter) data).getNewState();
// if this is a workitem
if (auditable instanceof IWorkItem)
{
// get the worker objects
IAuditableCommon iac = ((ISaveParameter) data)
.getSaveOperationParameter().getAuditableCommon();
// reference the right object type (cast)
IWorkItem workItem = (IWorkItem) auditable;
// access the workitem common service
// must be defined in the plugin.xml as required.
IWorkItemCommon iwc = getService(IWorkItemCommon.class);
HttpClient client = new HttpClient();
String serverurl = getStringConfigProperty("serverurl");
IAttribute attribute = iwc.findAttribute(workItem.getProjectArea(), advisorConfiguration
.getAttribute("attribute_id_string"), null);
// Create a method instance.
GetMethod method = new GetMethod(serverurl + "?validate="
+ workItem.getValue(attribute).toString());
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
try
{
// Execute the method.
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK)
{
System.err.println("Method failed: "
+ method.getStatusLine());
}
// Read the response body.
byte[] responseBody = method.getResponseBody();
// Deal with the response.
// Use caution: ensure correct character encoding and is not
// binary data
System.out.println(new String(responseBody));
if (responseBody[0] != '1')
{
IAdvisorInfo info = collector.createProblemInfo(
"your error", "message here", "error");
// tell the caller to post an error, and reject the
// action
collector.addInfo(info);
}
}
catch (HttpException e)
{
System.err.println("Fatal protocol violation: "
+ e.getMessage());
e.printStackTrace();
}
catch (IOException e)
{
System.err.println("Fatal transport error: "
+ e.getMessage());
e.printStackTrace();
}
finally
{
// Release the connection.
method.releaseConnection();
}
}
}
}
}
</code>
Comments
sam detweiler
Jan 03 '14, 12:45 a.m.where is this file? server or client side?
note that many servers restrict where in the filesystem web apps can access.
how do you want to do this validation? check on save is probably doable, but allows the user to enter bad data.
how many items are in the list? it might be better to provide a selection list, with the http filtered value set.
Jack C
Jan 03 '14, 10:34 a.m.The text file containing all the valid ticket numbers resides on the Server side so that all Users that complete the work item can have their ticket number validated.
Users won't allow to enter any data that does not meet the formatting requirements of the ticket number. We have a validation set up to validate the formatting. But even if a User enters a ticket number value that meets the correct formatting, the value entered may not be on the list.
There are hundreds of items and the list of numbers are updated periodically.
sam detweiler
Jan 03 '14, 10:40 a.m.yeh.. hundreds of items in the dropdown list is not really useful for the users.
we had the same problem with a support ticket number field.. we could validate it (numbers/text in the right order) but couldn't tell if it was a truly valid entry.
my proposed solution was a live integration between the two systems, such that the rtc user never entered the ticket number, the rtc workitem was created because of the ticket and there was a link between the two (in both systems).
some data was synched back & forth.
altho that design was accepted, built and tested, a new mgmt team came in and changed directions on the systems involved so this never went to production.
Jack C
Jan 03 '14, 1:31 p.m.That's an interesting solution. But our current business process does not work like the one described in your proposed solution. Hmmmm.... Can you explain more about the 'live integration linkage'? How did you intend to do this live integration?
Many Thanks in advance.
sam detweiler
Jan 03 '14, 1:48 p.m.there were two implementations
1. I wrote an RTC External Repository Manager which communicated with the support system. this required a web service application to take the incoming requests from the support system and interact with RTC (create/update workitems). this is a lot of work.. recovery, queuing, retstart, config on the fly, RTC version interoperability testing, etc..
2. we used an external vendor product, Kovair Omnibus, to do this. (we were going to use the same product to synch RTC with other systems as well). they already had all the other operational stuff as part of their product, so there was only some business rule mapping and data object field mappings/translations to configure.