1717
1818package org .apache .cloudstack .api ;
1919
20+ import java .lang .reflect .Field ;
2021import java .text .DateFormat ;
2122import java .text .SimpleDateFormat ;
23+ import java .util .ArrayList ;
2224import java .util .Date ;
2325import java .util .HashMap ;
26+ import java .util .List ;
2427import java .util .Map ;
2528import java .util .regex .Pattern ;
2629
2730import javax .inject .Inject ;
2831
2932import org .apache .log4j .Logger ;
3033
34+ import org .apache .cloudstack .acl .RoleType ;
3135import org .apache .cloudstack .affinity .AffinityGroupService ;
3236import org .apache .cloudstack .alert .AlertService ;
37+ import org .apache .cloudstack .context .CallContext ;
3338import org .apache .cloudstack .network .element .InternalLoadBalancerElementService ;
3439import org .apache .cloudstack .network .lb .ApplicationLoadBalancerService ;
3540import org .apache .cloudstack .network .lb .InternalLoadBalancerVMService ;
7580import com .cloud .user .AccountService ;
7681import com .cloud .user .DomainService ;
7782import com .cloud .user .ResourceLimitService ;
83+ import com .cloud .utils .ReflectUtil ;
7884import com .cloud .utils .db .EntityManager ;
7985import com .cloud .vm .UserVmService ;
8086import com .cloud .vm .snapshot .VMSnapshotService ;
@@ -97,6 +103,8 @@ public enum CommandType {
97103 public static Pattern newInputDateFormat = Pattern .compile ("[\\ d]+-[\\ d]+-[\\ d]+ [\\ d]+:[\\ d]+:[\\ d]+" );
98104 private static final DateFormat s_outputFormat = new SimpleDateFormat ("yyyy-MM-dd'T'HH:mm:ssZ" );
99105
106+ protected static final Map <Class <?>, List <Field >> fieldsForCmdClass = new HashMap <Class <?>, List <Field >>();
107+
100108 private Object _responseObject = null ;
101109 private Map <String , String > fullUrlParams ;
102110
@@ -203,7 +211,7 @@ public HTTPMethod getHttpMethod() {
203211 return httpMethod ;
204212 }
205213
206- public void setHttpMethod (String method ) {
214+ public void setHttpMethod (final String method ) {
207215 if (method != null ) {
208216 if (method .equalsIgnoreCase ("GET" ))
209217 httpMethod = HTTPMethod .GET ;
@@ -225,7 +233,7 @@ public String getResponseType() {
225233 return responseType ;
226234 }
227235
228- public void setResponseType (String responseType ) {
236+ public void setResponseType (final String responseType ) {
229237 this .responseType = responseType ;
230238 }
231239
@@ -243,15 +251,15 @@ public Object getResponseObject() {
243251 return _responseObject ;
244252 }
245253
246- public void setResponseObject (Object responseObject ) {
254+ public void setResponseObject (final Object responseObject ) {
247255 _responseObject = responseObject ;
248256 }
249257
250258 public ManagementService getMgmtServiceRef () {
251259 return _mgr ;
252260 }
253261
254- public static String getDateString (Date date ) {
262+ public static String getDateString (final Date date ) {
255263 if (date == null ) {
256264 return "" ;
257265 }
@@ -262,120 +270,102 @@ public static String getDateString(Date date) {
262270 return formattedString ;
263271 }
264272
265- // FIXME: move this to a utils method so that maps can be unpacked and integer/long values can be appropriately cast
266- @ SuppressWarnings ({"unchecked" , "rawtypes" })
267- public Map <String , Object > unpackParams (Map <String , String > params ) {
268- Map <String , Object > lowercaseParams = new HashMap <String , Object >();
269- for (String key : params .keySet ()) {
270- int arrayStartIndex = key .indexOf ('[' );
271- int arrayStartLastIndex = key .lastIndexOf ('[' );
272- if (arrayStartIndex != arrayStartLastIndex ) {
273- throw new ServerApiException (ApiErrorCode .MALFORMED_PARAMETER_ERROR , "Unable to decode parameter " + key +
274- "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup" );
275- }
273+ protected List <Field > getAllFieldsForClass (final Class <?> clazz ) {
274+ List <Field > filteredFields = fieldsForCmdClass .get (clazz );
276275
277- if (arrayStartIndex > 0 ) {
278- int arrayEndIndex = key .indexOf (']' );
279- int arrayEndLastIndex = key .lastIndexOf (']' );
280- if ((arrayEndIndex < arrayStartIndex ) || (arrayEndIndex != arrayEndLastIndex )) {
281- // malformed parameter
282- throw new ServerApiException (ApiErrorCode .MALFORMED_PARAMETER_ERROR , "Unable to decode parameter " + key +
283- "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup" );
284- }
276+ // If list of fields was not cached yet
277+ if (filteredFields == null ) {
278+ final List <Field > allFields = ReflectUtil .getAllFieldsForClass (this .getClass (), BaseCmd .class );
279+ filteredFields = new ArrayList <Field >();
285280
286- // Now that we have an array object, check for a field name in the case of a complex object
287- int fieldIndex = key .indexOf ('.' );
288- String fieldName = null ;
289- if (fieldIndex < arrayEndIndex ) {
290- throw new ServerApiException (ApiErrorCode .MALFORMED_PARAMETER_ERROR , "Unable to decode parameter " + key +
291- "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup" );
292- } else {
293- fieldName = key .substring (fieldIndex + 1 );
281+ for (final Field field : allFields ) {
282+ final Parameter parameterAnnotation = field .getAnnotation (Parameter .class );
283+ if ((parameterAnnotation != null ) && parameterAnnotation .expose ()) {
284+ filteredFields .add (field );
294285 }
286+ }
295287
296- // parse the parameter name as the text before the first '[' character
297- String paramName = key .substring (0 , arrayStartIndex );
298- paramName = paramName .toLowerCase ();
299-
300- Map <Integer , Map > mapArray = null ;
301- Map <String , Object > mapValue = null ;
302- String indexStr = key .substring (arrayStartIndex + 1 , arrayEndIndex );
303- int index = 0 ;
304- boolean parsedIndex = false ;
305- try {
306- if (indexStr != null ) {
307- index = Integer .parseInt (indexStr );
308- parsedIndex = true ;
309- }
310- } catch (NumberFormatException nfe ) {
311- s_logger .warn ("Invalid parameter " + key + " received, unable to parse object array, returning an error." );
312- }
288+ // Cache the prepared list for future use
289+ fieldsForCmdClass .put (clazz , filteredFields );
290+ }
291+ return filteredFields ;
292+ }
313293
314- if (!parsedIndex ) {
315- throw new ServerApiException (ApiErrorCode .MALFORMED_PARAMETER_ERROR , "Unable to decode parameter " + key +
316- "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup" );
317- }
294+ protected Account getCurrentContextAccount () {
295+ return CallContext .current ().getCallingAccount ();
296+ }
318297
319- Object value = lowercaseParams .get (paramName );
320- if (value == null ) {
321- // for now, assume object array with sub fields
322- mapArray = new HashMap <Integer , Map >();
323- mapValue = new HashMap <String , Object >();
324- mapArray .put (Integer .valueOf (index ), mapValue );
325- } else if (value instanceof Map ) {
326- mapArray = (HashMap )value ;
327- mapValue = mapArray .get (Integer .valueOf (index ));
328- if (mapValue == null ) {
329- mapValue = new HashMap <String , Object >();
330- mapArray .put (Integer .valueOf (index ), mapValue );
298+ /**
299+ * this method doesn't return all the @{link Parameter}, but only the ones exposed
300+ * and allowed for current @{link RoleType}
301+ *
302+ * @return
303+ */
304+ public List <Field > getParamFields () {
305+ final List <Field > allFields = getAllFieldsForClass (this .getClass ());
306+ final List <Field > validFields = new ArrayList <Field >();
307+ final Account caller = getCurrentContextAccount ();
308+
309+ for (final Field field : allFields ) {
310+ final Parameter parameterAnnotation = field .getAnnotation (Parameter .class );
311+
312+ //TODO: Annotate @Validate on API Cmd classes, FIXME how to process Validate
313+ final RoleType [] allowedRoles = parameterAnnotation .authorized ();
314+ boolean roleIsAllowed = true ;
315+ if (allowedRoles .length > 0 ) {
316+ roleIsAllowed = false ;
317+ for (final RoleType allowedRole : allowedRoles ) {
318+ if (allowedRole .getValue () == caller .getType ()) {
319+ roleIsAllowed = true ;
320+ break ;
331321 }
332322 }
323+ }
333324
334- // we are ready to store the value for a particular field into the map for this object
335- mapValue .put (fieldName , params .get (key ));
336-
337- lowercaseParams .put (paramName , mapArray );
325+ if (roleIsAllowed ) {
326+ validFields .add (field );
338327 } else {
339- lowercaseParams . put ( key . toLowerCase (), params . get ( key ) );
328+ s_logger . debug ( "Ignoring paremeter " + parameterAnnotation . name () + " as the caller is not authorized to pass it in" );
340329 }
341330 }
342- return lowercaseParams ;
331+
332+ return validFields ;
343333 }
344334
345- protected long getInstanceIdFromJobSuccessResult (String result ) {
335+ protected long getInstanceIdFromJobSuccessResult (final String result ) {
346336 s_logger .debug ("getInstanceIdFromJobSuccessResult not overridden in subclass " + this .getClass ().getName ());
347337 return 0 ;
348338 }
349339
350- public static boolean isAdmin (short accountType ) {
340+ public static boolean isAdmin (final short accountType ) {
351341 return ((accountType == Account .ACCOUNT_TYPE_ADMIN ) || (accountType == Account .ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN ) ||
352342 (accountType == Account .ACCOUNT_TYPE_DOMAIN_ADMIN ) || (accountType == Account .ACCOUNT_TYPE_READ_ONLY_ADMIN ));
353343 }
354344
355- public static boolean isRootAdmin (short accountType ) {
345+ public static boolean isRootAdmin (final short accountType ) {
356346 return ((accountType == Account .ACCOUNT_TYPE_ADMIN ));
357347 }
358348
359- public void setFullUrlParams (Map <String , String > map ) {
349+ public void setFullUrlParams (final Map <String , String > map ) {
360350 fullUrlParams = map ;
361351 }
362352
363353 public Map <String , String > getFullUrlParams () {
364354 return fullUrlParams ;
365355 }
366356
367- public Long finalyzeAccountId (String accountName , Long domainId , Long projectId , boolean enabledOnly ) {
357+ public Long finalyzeAccountId (final String accountName , final Long domainId , final Long projectId , final boolean enabledOnly ) {
368358 if (accountName != null ) {
369359 if (domainId == null ) {
370360 throw new InvalidParameterValueException ("Account must be specified with domainId parameter" );
371361 }
372362
373- Domain domain = _domainService .getDomain (domainId );
363+ final Domain domain = _domainService .getDomain (domainId );
374364 if (domain == null ) {
375365 throw new InvalidParameterValueException ("Unable to find domain by id" );
376366 }
377367
378- Account account = _accountService .getActiveAccountByName (accountName , domainId );
368+ final Account account = _accountService .getActiveAccountByName (accountName , domainId );
379369 if (account != null && account .getType () != Account .ACCOUNT_TYPE_PROJECT ) {
380370 if (!enabledOnly || account .getState () == Account .State .enabled ) {
381371 return account .getId ();
@@ -392,12 +382,12 @@ public Long finalyzeAccountId(String accountName, Long domainId, Long projectId,
392382 }
393383
394384 if (projectId != null ) {
395- Project project = _projectService .getProject (projectId );
385+ final Project project = _projectService .getProject (projectId );
396386 if (project != null ) {
397387 if (!enabledOnly || project .getState () == Project .State .Active ) {
398388 return project .getProjectAccountId ();
399389 } else {
400- PermissionDeniedException ex =
390+ final PermissionDeniedException ex =
401391 new PermissionDeniedException ("Can't add resources to the project with specified projectId in state=" + project .getState () +
402392 " as it's no longer active" );
403393 ex .addProxyObject (project .getUuid (), "projectId" );
@@ -409,4 +399,11 @@ public Long finalyzeAccountId(String accountName, Long domainId, Long projectId,
409399 }
410400 return null ;
411401 }
402+
403+ /**
404+ * To be overwritten by any class who needs specific validation
405+ */
406+ public void validateSpecificParameters (final Map <String , Object > params ){
407+ // To be overwritten by any class who needs specific validation
408+ }
412409}
0 commit comments