11package org .labkey .api .workflow ;
22
3+ import com .fasterxml .jackson .annotation .JsonIgnore ;
34import org .apache .commons .lang3 .StringUtils ;
5+ import org .json .JSONArray ;
46import org .json .JSONObject ;
57import org .labkey .api .data .CreatedModified ;
8+ import org .labkey .api .exp .api .ExpProtocol ;
9+ import org .labkey .api .exp .api .ExperimentService ;
10+ import org .labkey .api .exp .api .SampleTypeService ;
611import org .labkey .api .util .GUID ;
712
13+ import java .util .ArrayList ;
14+ import java .util .Collections ;
15+ import java .util .HashSet ;
816import java .util .LinkedHashMap ;
17+ import java .util .List ;
918import java .util .Map ;
19+ import java .util .Set ;
1020
1121public abstract class Action extends CreatedModified
1222{
1323 public static final String ASSAY_TYPES_KEY = "assayTypes" ;
24+ public static final String NUM_PER_PARENT_KEY = "numPerParent" ;
1425 protected Long _rowId ;
26+ protected int _ordinal ;
1527 protected GUID _containerId ;
1628 protected String _name ;
1729 protected boolean _isUpdatable = false ;
@@ -50,6 +62,16 @@ public void setName(String name)
5062 _name = name ;
5163 }
5264
65+ public int getOrdinal ()
66+ {
67+ return _ordinal ;
68+ }
69+
70+ public void setOrdinal (int ordinal )
71+ {
72+ _ordinal = ordinal ;
73+ }
74+
5375 public boolean getIsUpdatable ()
5476 {
5577 return _isUpdatable ;
@@ -90,6 +112,112 @@ public void setInputParameters(JSONObject inputParameters)
90112 _inputParameters = inputParameters ;
91113 }
92114
115+ @ JsonIgnore
116+ public List <String > validateInputParameters (int ordinal )
117+ {
118+ String prefix = "Action #" + ordinal + ": " ;
119+ if (_type == WorkflowService .ActionType .AssayImport )
120+ {
121+ if (_inputParameters != null && _inputParameters .has (ASSAY_TYPES_KEY ))
122+ {
123+ try
124+ {
125+ JSONArray assayTypes = _inputParameters .getJSONArray (ASSAY_TYPES_KEY );
126+ // When assay types are updatable, there may be none provided. Usually that means the inputParameters will
127+ // be empty or null, but it also works if the assay types array is empty.
128+ if (assayTypes .isEmpty ())
129+ return Collections .emptyList ();
130+
131+ Set <Object > invalidAssayIds = new HashSet <>();
132+ assayTypes .toList ().forEach (assayId ->
133+ {
134+ try
135+ {
136+ int protocolId = (assayId instanceof String ) ? Integer .valueOf ((String ) assayId ) : (Integer ) assayId ;
137+ ExpProtocol assay = ExperimentService .get ().getExpProtocol (protocolId );
138+ if (null == assay )
139+ invalidAssayIds .add (protocolId );
140+ }
141+ catch (Exception e )
142+ {
143+ invalidAssayIds .add (assayId );
144+ }
145+ });
146+ if (!invalidAssayIds .isEmpty ())
147+ return List .of (prefix + "invalid assay IDs " + invalidAssayIds + "." );
148+ }
149+ catch (Exception e )
150+ {
151+ return List .of (prefix + ASSAY_TYPES_KEY + " must be an array." );
152+ }
153+ }
154+
155+ }
156+ else if (_type == WorkflowService .ActionType .AliquotSamples )
157+ {
158+ if (_inputParameters == null || !_inputParameters .has (NUM_PER_PARENT_KEY ))
159+ return List .of (prefix + NUM_PER_PARENT_KEY + " is required for action of type " + _type + "." );
160+ else
161+ {
162+ try {
163+ int numPerParent = _inputParameters .getInt (NUM_PER_PARENT_KEY );
164+ if (numPerParent < 0 )
165+ return List .of (prefix + NUM_PER_PARENT_KEY + " cannot be negative." );
166+ }
167+ catch (Exception e ) {
168+ return List .of (prefix + NUM_PER_PARENT_KEY + " must be an integer." );
169+ }
170+ }
171+ }
172+ else
173+ {
174+ if (_inputParameters == null || _inputParameters .isEmpty ())
175+ return List .of (prefix + "data about sample types and sample counts per parent is required for action of type " + _type + "." );
176+ if (_type == WorkflowService .ActionType .PoolSamples && _inputParameters .length () > 1 )
177+ return List .of (prefix + "only one sample type can be specified for action of type " + _type + "." );
178+ SampleTypeService sampleTypeService = SampleTypeService .get ();
179+ Set <String > invalidSampleTypeIds = new HashSet <>();
180+ List <Object > invalidCounts = new ArrayList <>();
181+
182+ _inputParameters .keys ().forEachRemaining (id -> {
183+ try
184+ {
185+ if (sampleTypeService .getSampleType (Long .valueOf (id )) == null )
186+ invalidSampleTypeIds .add (id );
187+ }
188+ catch (NumberFormatException e )
189+ {
190+ invalidSampleTypeIds .add (id );
191+ }
192+ Object countObj = _inputParameters .get (id );
193+ if ((countObj instanceof String ))
194+ try
195+ {
196+ if (Integer .parseInt ((String ) countObj ) < 0 )
197+ invalidCounts .add (countObj );
198+ }
199+ catch (NumberFormatException e )
200+ {
201+ invalidCounts .add (countObj );
202+ }
203+ else if (countObj instanceof Integer )
204+ {
205+ if (((Integer ) countObj ) < 0 )
206+ invalidCounts .add (countObj );
207+ }
208+ else
209+ invalidCounts .add (countObj );
210+ });
211+ List <String > messages = new ArrayList <>();
212+ if (!invalidSampleTypeIds .isEmpty ())
213+ messages .add (prefix + "invalid sample type IDs " + invalidSampleTypeIds + "." );
214+ if (!invalidCounts .isEmpty ())
215+ messages .add (prefix + "invalid sample count values " + invalidCounts + "." );
216+ return messages ;
217+ }
218+ return Collections .emptyList ();
219+ }
220+
93221 @ Override
94222 public boolean equals (Object o )
95223 {
@@ -103,6 +231,7 @@ public boolean equals(Object o)
103231 return false ;
104232
105233 return _isUpdatable == action ._isUpdatable &&
234+ _ordinal == action ._ordinal &&
106235 java .util .Objects .equals (_rowId , action ._rowId ) &&
107236 java .util .Objects .equals (_taskId , action ._taskId ) &&
108237 java .util .Objects .equals (_type , action ._type ) &&
@@ -117,6 +246,7 @@ public Map<String, Object> toAuditDetailMap()
117246 Map <String , Object > map = new LinkedHashMap <>();
118247 map .put ("rowId" , _rowId );
119248 map .put ("name" , _name );
249+ map .put ("ordinal" , _ordinal );
120250 map .put ("isUpdatable" , _isUpdatable );
121251 map .put ("taskId" , _taskId );
122252 if (_type != null )
0 commit comments