]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
5aae52668287770e5b33783d3292eed010a40a38
[tmp/jakarta-migration.git] /
1 /**
2  * This document is a part of the source code and related artifacts
3  * for CollectionSpace, an open source collections management system
4  * for museums and related institutions:
5  *
6  * http://www.collectionspace.org
7  * http://wiki.collectionspace.org
8  *
9  * Copyright © 2009 Regents of the University of California
10  *
11  * Licensed under the Educational Community License (ECL), Version 2.0.
12  * You may not use this file except in compliance with this License.
13  *
14  * You may obtain a copy of the ECL 2.0 License at
15  * https://source.collectionspace.org/collection-space/LICENSE.txt
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permRoles and
21  * limitations under the License.
22  */
23 package org.collectionspace.services.authorization.client.test;
24
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Hashtable;
28 import java.util.List;
29 import javax.ws.rs.core.Response;
30 import org.collectionspace.services.authorization.EffectType;
31
32 import org.collectionspace.services.authorization.Permission;
33 import org.collectionspace.services.authorization.PermissionAction;
34 import org.collectionspace.services.authorization.PermissionRole;
35 import org.collectionspace.services.authorization.PermissionValue;
36 import org.collectionspace.services.authorization.Role;
37 import org.collectionspace.services.authorization.RoleValue;
38 import org.collectionspace.services.client.CollectionSpaceClient;
39 import org.collectionspace.services.client.PermissionClient;
40 import org.collectionspace.services.client.PermissionFactory;
41 import org.collectionspace.services.client.PermissionRoleClient;
42 import org.collectionspace.services.client.PermissionRoleFactory;
43 import org.collectionspace.services.client.RoleClient;
44 import org.collectionspace.services.client.RoleFactory;
45 import org.collectionspace.services.client.test.AbstractServiceTestImpl;
46 import org.collectionspace.services.client.test.ServiceRequestType;
47 import org.collectionspace.services.jaxb.AbstractCommonList;
48 import org.jboss.resteasy.client.ClientResponse;
49
50 import org.testng.Assert;
51 import org.testng.annotations.Test;
52
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.testng.annotations.AfterClass;
56 import org.testng.annotations.BeforeClass;
57
58 /**
59  * PermissionServiceTest, carries out tests against a
60  * deployed and running Permission, Role and PermissionRole Services.
61  * 
62  * $LastChangedRevision: 917 $
63  * $LastChangedDate: 2009-11-06 12:20:28 -0800 (Fri, 06 Nov 2009) $
64  */
65 public class PermissionRoleServiceTest extends AbstractServiceTestImpl {
66
67     /** The Constant logger. */
68     static private final Logger logger =
69             LoggerFactory.getLogger(PermissionRoleServiceTest.class);
70     // Instance variables specific to this test.
71     /** The known resource id. */
72     private String knownResourceId = null;
73     /** The all resource ids created. */
74     private List<String> allResourceIdsCreated = new ArrayList<String>();
75     final private static String TEST_MARKER = "_PermissionRoleServiceTest";
76     final private static String TEST_SERVICE_NAME = "fakeservice";
77     /** The perm values. */
78     private Hashtable<String, PermissionValue> permValues = new Hashtable<String, PermissionValue>();
79     /** The role values. */
80     private Hashtable<String, RoleValue> roleValues = new Hashtable<String, RoleValue>();
81     /*
82      * This method is called only by the parent class, AbstractServiceTestImpl
83      */
84
85     /* (non-Javadoc)
86      * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent()
87      */
88     @Override
89     protected String getServicePathComponent() {
90         return new PermissionRoleClient().getServicePathComponent();
91     }
92
93     /**
94      * Seed data.
95      */
96     @BeforeClass(alwaysRun = true)
97     public void seedData() {
98         String ra = TEST_SERVICE_NAME + TEST_MARKER;
99         String accPermId = createPermission(ra, EffectType.PERMIT);
100         PermissionValue pva = new PermissionValue();
101         pva.setResourceName(ra);
102         pva.setPermissionId(accPermId);
103         permValues.put(pva.getResourceName(), pva);
104
105 //        String rc = "collectionobjects";
106 //        String coPermId = createPermission(rc, EffectType.DENY);
107 //        PermissionValue pvc = new PermissionValue();
108 //        pvc.setResourceName(rc);
109 //        pvc.setPermissionId(coPermId);
110 //        permValues.put(pvc.getResourceName(), pvc);
111 //
112 //        String ri = "intakes";
113 //        String iPermId = createPermission(ri, EffectType.DENY);
114 //        PermissionValue pvi = new PermissionValue();
115 //        pvi.setResourceName(ri);
116 //        pvi.setPermissionId(iPermId);
117 //        permValues.put(pvi.getResourceName(), pvi);
118
119         String rn1 = "ROLE_CO1" + TEST_MARKER;
120         String r1RoleId = createRole(rn1);
121         RoleValue rv1 = new RoleValue();
122         rv1.setRoleId(r1RoleId);
123         rv1.setRoleName(rn1);
124         roleValues.put(rv1.getRoleName(), rv1);
125
126         String rn2 = "ROLE_CO2" + TEST_MARKER;
127         String r2RoleId = createRole(rn2);
128         RoleValue rv2 = new RoleValue();
129         rv2.setRoleId(r2RoleId);
130         rv2.setRoleName(rn2);
131         roleValues.put(rv2.getRoleName(), rv2);
132     }
133
134     /* (non-Javadoc)
135      * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance()
136      */
137     @Override
138     protected CollectionSpaceClient getClientInstance() {
139         return new PermissionRoleClient();
140     }
141
142     /* (non-Javadoc)
143      * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse)
144      */
145     @Override
146     protected AbstractCommonList getAbstractCommonList(
147             ClientResponse<AbstractCommonList> response) {
148         //FIXME: http://issues.collectionspace.org/browse/CSPACE-1697
149         throw new UnsupportedOperationException();
150     }
151
152     /* (non-Javadoc)
153      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#readPaginatedList(java.lang.String)
154      */
155     @Test(dataProvider = "testName")
156     @Override
157     public void readPaginatedList(String testName) throws Exception {
158         //FIXME: http://issues.collectionspace.org/browse/CSPACE-1697
159     }
160     // ---------------------------------------------------------------
161     // CRUD tests : CREATE tests
162     // ---------------------------------------------------------------
163     // Success outcomes
164     /* (non-Javadoc)
165      * @see org.collectionspace.services.client.test.ServiceTest#create(java.lang.String)
166      */
167
168     @Override
169     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
170     public void create(String testName) throws Exception {
171
172         // Perform setup, such as initializing the type of service request
173         // (e.g. CREATE, DELETE), its valid and expected status codes, and
174         // its associated HTTP method name (e.g. POST, DELETE).
175         setupCreate(testName);
176
177         // Submit the request to the service and store the response.
178         PermissionValue pv = permValues.get(TEST_SERVICE_NAME + TEST_MARKER);
179         PermissionRole permRole = createPermissionRoleInstance(pv,
180                 roleValues.values(), true, true);
181         PermissionRoleClient client = new PermissionRoleClient();
182         ClientResponse<Response> res = null;
183         try {
184             res = client.create(pv.getPermissionId(), permRole);
185             int statusCode = res.getStatus();
186
187             if (logger.isDebugEnabled()) {
188                 logger.debug(testName + ": status = " + statusCode);
189             }
190             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
191                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
192             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
193             res.releaseConnection();
194             // Store the ID returned from this create operation
195             // for additional tests below.
196             //this is is not important in case of this relationship
197             knownResourceId = extractId(res);
198             if (logger.isDebugEnabled()) {
199                 logger.debug(testName + ": knownResourceId=" + knownResourceId);
200             }
201         } finally {
202             if (res != null) {
203                 res.releaseConnection();
204             }
205         }
206     }
207
208     //to not cause uniqueness violation for permRole, createList is removed
209     /* (non-Javadoc)
210      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#createList(java.lang.String)
211      */
212     @Override
213     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
214     dependsOnMethods = {"create"})
215     public void createList(String testName) throws Exception {
216         //Should this really be empty?
217     }
218
219     // Failure outcomes
220     // Placeholders until the three tests below can be uncommented.
221     // See Issue CSPACE-401.
222     /* (non-Javadoc)
223      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#createWithEmptyEntityBody(java.lang.String)
224      */
225     @Override
226     public void createWithEmptyEntityBody(String testName) throws Exception {
227         //Should this really be empty?
228     }
229
230     /* (non-Javadoc)
231      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#createWithMalformedXml(java.lang.String)
232      */
233     @Override
234     public void createWithMalformedXml(String testName) throws Exception {
235         //Should this really be empty?
236     }
237
238     /* (non-Javadoc)
239      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#createWithWrongXmlSchema(java.lang.String)
240      */
241     @Override
242     public void createWithWrongXmlSchema(String testName) throws Exception {
243         //Should this really be empty?
244     }
245
246     // ---------------------------------------------------------------
247     // CRUD tests : READ tests
248     // ---------------------------------------------------------------
249     // Success outcomes
250     /* (non-Javadoc)
251      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#read(java.lang.String)
252      */
253     @Override
254     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
255     dependsOnMethods = {"create"})
256     public void read(String testName) throws Exception {
257
258         // Perform setup.
259         setupRead(testName);
260
261         // Submit the request to the service and store the response.
262         PermissionRoleClient client = new PermissionRoleClient();
263         ClientResponse<PermissionRole> res = null;
264         try {
265             res = client.read(
266                     permValues.get(TEST_SERVICE_NAME + TEST_MARKER).getPermissionId(), "123");
267             int statusCode = res.getStatus();
268
269             // Check the status code of the response: does it match
270             // the expected response(s)?
271             if (logger.isDebugEnabled()) {
272                 logger.debug(testName + ": status = " + statusCode);
273             }
274             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
275                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
276             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
277
278             PermissionRole output = (PermissionRole) res.getEntity();
279             Assert.assertNotNull(output);
280         } finally {
281             if (res != null) {
282                 res.releaseConnection();
283             }
284         }
285
286     }
287
288     // Failure outcomes
289     /* (non-Javadoc)
290      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#readNonExistent(java.lang.String)
291      */
292     @Override
293     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
294     public void readNonExistent(String testName) throws Exception {
295
296         // Perform setup.
297         setupReadNonExistent(testName);
298
299         // Submit the request to the service and store the response.
300         PermissionRoleClient client = new PermissionRoleClient();
301         ClientResponse<PermissionRole> res = null;
302         try {
303             res = client.read(NON_EXISTENT_ID, "123");
304             int statusCode = res.getStatus();
305
306             // Check the status code of the response: does it match
307             // the expected response(s)?
308             if (logger.isDebugEnabled()) {
309                 logger.debug(testName + ": status = " + statusCode);
310             }
311             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
312                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
313             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
314         } finally {
315             if (res != null) {
316                 res.releaseConnection();
317             }
318         }
319     }
320
321     // ---------------------------------------------------------------
322     // CRUD tests : READ_LIST tests
323     // ---------------------------------------------------------------
324     // Success outcomes
325     /* (non-Javadoc)
326      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#readList(java.lang.String)
327      */
328     @Override
329     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
330     dependsOnMethods = {"createList", "read"})
331     public void readList(String testName) throws Exception {
332         //Should this really be empty?
333     }
334
335     // Failure outcomes
336     // None at present.
337     // ---------------------------------------------------------------
338     // CRUD tests : UPDATE tests
339     // ---------------------------------------------------------------
340     // Success outcomes
341     /* (non-Javadoc)
342      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#update(java.lang.String)
343      */
344     @Override
345     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
346     dependsOnMethods = {"read", "readList", "readNonExistent"})
347     public void update(String testName) throws Exception {
348         //Should this really be empty?
349     }
350
351     // Failure outcomes
352     // Placeholders until the three tests below can be uncommented.
353     // See Issue CSPACE-401.
354     /* (non-Javadoc)
355      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#updateWithEmptyEntityBody(java.lang.String)
356      */
357     @Override
358     public void updateWithEmptyEntityBody(String testName) throws Exception {
359         //Should this really be empty?
360     }
361
362     /* (non-Javadoc)
363      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#updateWithMalformedXml(java.lang.String)
364      */
365     @Override
366     public void updateWithMalformedXml(String testName) throws Exception {
367         //Should this really be empty?
368     }
369
370     /* (non-Javadoc)
371      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#updateWithWrongXmlSchema(java.lang.String)
372      */
373     @Override
374     public void updateWithWrongXmlSchema(String testName) throws Exception {
375         //Should this really be empty?
376     }
377
378     /* (non-Javadoc)
379      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#updateNonExistent(java.lang.String)
380      */
381     @Override
382     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
383     dependsOnMethods = {"readNonExistent", "testSubmitRequest"})
384     public void updateNonExistent(String testName) throws Exception {
385         //Should this really be empty?
386     }
387
388     // ---------------------------------------------------------------
389     // CRUD tests : DELETE tests
390     // ---------------------------------------------------------------
391     // Success outcomes
392     /* (non-Javadoc)
393      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#delete(java.lang.String)
394      */
395     @Override
396     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
397     dependsOnMethods = {"read"})
398     public void delete(String testName) throws Exception {
399
400         // Perform setup.
401         setupDelete(testName);
402
403         // Submit the request to the service and store the response.
404         PermissionRoleClient client = new PermissionRoleClient();
405         ClientResponse<Response> res = null;
406         try {
407             res = client.delete(
408                     permValues.get(TEST_SERVICE_NAME + TEST_MARKER).getPermissionId(), "123");
409             int statusCode = res.getStatus();
410
411             // Check the status code of the response: does it match
412             // the expected response(s)?
413             if (logger.isDebugEnabled()) {
414                 logger.debug(testName + ": status = " + statusCode);
415             }
416             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
417                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
418             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
419         } finally {
420             if (res != null) {
421                 res.releaseConnection();
422             }
423         }
424     }
425
426     // Failure outcomes
427     /* (non-Javadoc)
428      * @see org.collectionspace.services.client.test.AbstractServiceTestImpl#deleteNonExistent(java.lang.String)
429      */
430     @Override
431     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
432     public void deleteNonExistent(String testName) throws Exception {
433         //ignoring this test as the service side returns 200 now even if it does
434         //not find a record in the db
435     }
436
437     // ---------------------------------------------------------------
438     // Utility tests : tests of code used in tests above
439     // ---------------------------------------------------------------
440     /**
441      * Tests the code for manually submitting data that is used by several
442      * of the methods above.
443      * @throws Exception 
444      */
445     @Test(dependsOnMethods = {"create"})
446     public void testSubmitRequest() throws Exception {
447
448         // Expected status code: 200 OK
449         final int EXPECTED_STATUS = Response.Status.OK.getStatusCode();
450
451         // Submit the request to the service and store the response.
452         String method = ServiceRequestType.READ.httpMethodName();
453         String url = getResourceURL(permValues.get(TEST_SERVICE_NAME + TEST_MARKER).getPermissionId());
454         int statusCode = submitRequest(method, url);
455
456         // Check the status code of the response: does it match
457         // the expected response(s)?
458         if (logger.isDebugEnabled()) {
459             logger.debug("testSubmitRequest: url=" + url
460                     + " status=" + statusCode);
461         }
462         Assert.assertEquals(statusCode, EXPECTED_STATUS);
463
464
465     }
466
467     // ---------------------------------------------------------------
468     // Utility methods used by tests above
469     // ---------------------------------------------------------------
470     /**
471      * create permRolerole instance
472      * @param pv permissionvalue
473      * @param rvs rolevalue array
474      * @param usePermId 
475      * @param useRoleId
476      * @return PermissionRole
477      */
478     public static PermissionRole createPermissionRoleInstance(PermissionValue pv,
479             Collection<RoleValue> rvs,
480             boolean usePermId,
481             boolean useRoleId) {
482
483         List<RoleValue> rvls = new ArrayList<RoleValue>();
484         rvls.addAll(rvs);
485         PermissionRole permRole = PermissionRoleFactory.createPermissionRoleInstance(
486                 pv, rvls, usePermId, useRoleId);
487         if (logger.isDebugEnabled()) {
488             logger.debug("to be created, permRole");
489             logger.debug(objectAsXmlString(permRole, PermissionRole.class));
490         }
491         return permRole;
492     }
493
494     /**
495      * Clean up.
496      */
497     @AfterClass(alwaysRun = true)
498     @Override
499     public void cleanUp() {
500         setupDelete("cleanUp");
501         String noTest = System.getProperty("noTestCleanup");
502         if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) {
503             if (logger.isDebugEnabled()) {
504                 logger.debug("Skipping Cleanup phase ...");
505             }
506             return;
507         }
508         if (logger.isDebugEnabled()) {
509             logger.debug("Cleaning up temporary resources created for testing ...");
510         }
511
512         PermissionRoleClient client = new PermissionRoleClient();
513         for (String resourceId : allResourceIdsCreated) {
514
515             ClientResponse<Response> res = client.delete(resourceId, "123");
516             int statusCode = res.getStatus();
517             try {
518                 if (logger.isDebugEnabled()) {
519                     logger.debug("cleanup: delete relationships for permission id="
520                             + resourceId + " status=" + statusCode);
521                 }
522                 Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
523                         invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
524                 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
525             } finally {
526                 res.releaseConnection();
527             }
528         }
529
530         for (PermissionValue pv : permValues.values()) {
531             deletePermission(pv.getPermissionId());
532         }
533         for (RoleValue rv : roleValues.values()) {
534             deleteRole(rv.getRoleId());
535         }
536     }
537
538     /**
539      * Creates the permission.
540      *
541      * @param resName the res name
542      * @param effect the effect
543      * @return the string
544      */
545     private String createPermission(String resName, EffectType effect) {
546         setupCreate("createPermission");
547         PermissionClient permClient = new PermissionClient();
548         List<PermissionAction> actions = PermissionFactory.createDefaultActions();
549         Permission permission = PermissionFactory.createPermissionInstance(resName,
550                 "default permissions for " + resName,
551                 actions, effect, true, true, true);
552         String id = null;
553         ClientResponse<Response> res = null;
554         try {
555             res = permClient.create(permission);
556
557             int statusCode = res.getStatus();
558             if (logger.isDebugEnabled()) {
559                 logger.debug("createPermission: resName=" + resName
560                         + " status = " + statusCode);
561             }
562             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
563                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
564             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
565             id = extractId(res);
566         } finally {
567             if (res != null) {
568                 res.releaseConnection();
569             }
570         }
571         return id;
572     }
573
574     /**
575      * Delete permission.
576      *
577      * @param permId the perm id
578      */
579     private void deletePermission(String permId) {
580         setupDelete("deletePermission");
581         PermissionClient permClient = new PermissionClient();
582         ClientResponse<Response> res = null;
583         try {
584             res = permClient.delete(permId);
585             int statusCode = res.getStatus();
586             if (logger.isDebugEnabled()) {
587                 logger.debug("deletePermission: delete permission id="
588                         + permId + " status=" + statusCode);
589             }
590             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
591                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
592             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
593         } finally {
594             res.releaseConnection();
595         }
596
597     }
598
599     /**
600      * Creates the role.
601      *
602      * @param roleName the role name
603      * @return the string
604      */
605     private String createRole(String roleName) {
606         setupCreate("createRole");
607         RoleClient roleClient = new RoleClient();
608
609         Role role = RoleFactory.createRoleInstance(roleName,
610                 "role for " + roleName, true);
611         ClientResponse<Response> res = null;
612         String id = null;
613         try {
614             res = roleClient.create(role);
615             int statusCode = res.getStatus();
616             if (logger.isDebugEnabled()) {
617                 logger.debug("createRole: name=" + roleName
618                         + " status = " + statusCode);
619             }
620             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
621                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
622             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
623
624             id = extractId(res);
625         } finally {
626             res.releaseConnection();
627         }
628         return id;
629     }
630
631     /**
632      * Delete role.
633      *
634      * @param roleId the role id
635      */
636     private void deleteRole(String roleId) {
637         setupDelete("deleteRole");
638         RoleClient roleClient = new RoleClient();
639         ClientResponse<Response> res = null;
640         try {
641             res = roleClient.delete(roleId);
642             int statusCode = res.getStatus();
643             if (logger.isDebugEnabled()) {
644                 logger.debug("deleteRole: delete role id=" + roleId
645                         + " status=" + statusCode);
646             }
647             Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
648                     invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
649             Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
650         } finally {
651             res.releaseConnection();
652         }
653     }
654 }