]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
a47dca1dc8f46014885f5440251bb8bca8aec146
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.client.test;
2
3 import java.util.List;
4
5 import javax.ws.rs.core.Response;
6 import org.jboss.resteasy.client.ClientResponse;
7
8 import org.collectionspace.services.client.AbstractCommonListUtils;
9 import org.collectionspace.services.client.AuthorityClient;
10 import org.collectionspace.services.client.AuthorityClientImpl;
11 import org.collectionspace.services.client.AuthorityProxy;
12 import org.collectionspace.services.client.PayloadInputPart;
13 import org.collectionspace.services.client.PayloadOutputPart;
14 import org.collectionspace.services.client.PoxPayloadOut;
15 import org.collectionspace.services.jaxb.AbstractCommonList;
16
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.testng.Assert;
20 import org.testng.annotations.Test;
21
22 /**
23  * 
24  * @author remillet
25  *
26  * @param <AUTHORITY_COMMON_TYPE>
27  * @param <AUTHORITY_ITEM_TYPE>
28  * 
29  * All CRUD related authority test classes should extend this class.
30  * 
31  */
32 public abstract class AbstractAuthorityServiceTest<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_TYPE> 
33         extends AbstractPoxServiceTestImpl<AbstractCommonList, AUTHORITY_COMMON_TYPE> {
34
35     private final Logger logger = LoggerFactory.getLogger(AbstractAuthorityServiceTest.class);
36         
37     protected String knownResourceShortIdentifer = null;
38         protected static final String READITEMS_SHORT_IDENTIFIER = "resourceWithItems"; 
39         protected String knownAuthorityWithItems = null;
40         
41         protected String knownResourceRefName = null;
42     protected String knownItemResourceId = null;
43     protected String knownItemResourceShortIdentifer = null;    
44     protected int nItemsToCreateInList = 5;
45                 
46         public abstract void authorityTests(String testName);
47     protected abstract String createItemInAuthority(String authorityId);
48  
49     protected abstract AUTHORITY_ITEM_TYPE updateItemInstance(final AUTHORITY_ITEM_TYPE authorityItem);    
50     protected abstract void compareUpdatedItemInstances(AUTHORITY_ITEM_TYPE original, AUTHORITY_ITEM_TYPE updated) throws Exception;
51     
52     protected void setKnownItemResource(String id, String shortIdentifer ) {
53         knownItemResourceId = id;
54         knownItemResourceShortIdentifer = shortIdentifer;
55     }
56
57     protected void setKnownResource(String id, String shortIdentifer,
58             String refName) {
59         knownResourceId = id;
60         knownResourceShortIdentifer = shortIdentifer;
61         knownResourceRefName = refName;
62     }
63
64     /**
65      * Returns the root URL for a service.
66      *
67      * This URL consists of a base URL for all services, followed by
68      * a path component for the owning vocabulary, followed by the 
69      * path component for the items.
70      *
71      * @return The root URL for a service.
72      */
73     protected String getItemServiceRootURL(String parentResourceIdentifier) {
74         return getResourceURL(parentResourceIdentifier) + "/" + getServicePathItemsComponent();
75     }
76
77     /**
78      * Returns the URL of a specific resource managed by a service, and
79      * designated by an identifier (such as a universally unique ID, or UUID).
80      *
81      * @param  resourceIdentifier  An identifier (such as a UUID) for a resource.
82      *
83      * @return The URL of a specific resource managed by a service.
84      */
85     protected String getItemResourceURL(String parentResourceIdentifier, String resourceIdentifier) {
86         return getItemServiceRootURL(parentResourceIdentifier) + "/" + resourceIdentifier;
87     }
88         
89     /**
90      * For authorities we override this method so we can save the shortid.
91      */
92     @Override
93     protected String createWithIdentifier(String testName, String identifier) throws Exception {
94         String csid = createResource(testName, identifier);
95         // Store the ID returned from the first resource created
96         // for additional tests below.
97         if (getKnowResourceId() == null) {
98                 setKnownResource(csid, identifier /*shortId*/, null /*refname*/ );
99             if (logger.isDebugEnabled()) {
100                 logger.debug(testName + ": Setting knownResourceId=" + getKnowResourceId());
101             }
102         }
103         
104         return identifier;
105     }    
106     
107     @Test(dependsOnMethods = {"readItem", "CRUDTests"})
108     public void testItemSubmitRequest() {
109
110         // Expected status code: 200 OK
111         final int EXPECTED_STATUS = Response.Status.OK.getStatusCode();
112
113         // Submit the request to the service and store the response.
114         String method = ServiceRequestType.READ.httpMethodName();
115         String url = getItemResourceURL(knownResourceId, knownItemResourceId);
116         int statusCode = submitRequest(method, url);
117
118         // Check the status code of the response: does it match
119         // the expected response(s)?
120         if (logger.isDebugEnabled()) {
121             logger.debug("testItemSubmitRequest: url=" + url
122                     + " status=" + statusCode);
123         }
124         Assert.assertEquals(statusCode, EXPECTED_STATUS);
125     }    
126
127     
128     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
129         dependsOnMethods = {"readItem"})
130     public void verifyIgnoredUpdateWithInAuthority(String testName) throws Exception {
131         // Perform setup.
132         setupUpdate();
133
134         // Submit the request to the service and store the response.
135         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = 
136                         (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
137         Response res = client.readItem(knownResourceId, knownItemResourceId);
138         AUTHORITY_ITEM_TYPE vitem = null;
139         try {
140                 int statusCode = res.getStatus();
141         
142                 // Check the status code of the response: does it match
143                 // the expected response(s)?
144                 if (logger.isDebugEnabled()) {
145                         logger.debug(testName + " read authority:" + knownResourceId + "/Item:"
146                                         + knownItemResourceId + " status = " + statusCode);
147                 }
148                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
149                                 invalidStatusCodeMessage(testRequestType, statusCode));
150                 Assert.assertEquals(statusCode, Response.Status.OK.getStatusCode());
151         
152                 vitem = extractItemCommonPartValue(res);
153                 Assert.assertNotNull(vitem);
154                 // Try to Update with new parent vocab (use self, for test).
155                 Assert.assertEquals(client.getInAuthority(vitem), knownResourceId,
156                                 "VocabularyItem inAuthority does not match knownResourceId.");
157                 client.setInAuthority(vitem, knownItemResourceId);
158
159         } finally {
160                 res.close();
161         }
162         
163         // Submit the updated resource to the service and store the response.
164         PoxPayloadOut output = this.createItemRequestTypeInstance(vitem);
165         res = client.updateItem(knownResourceId, knownItemResourceId, output);
166         try {
167                 int statusCode = res.getStatus();
168         
169                 // Check the status code of the response: does it match the expected response(s)?
170                 if (logger.isDebugEnabled()) {
171                         logger.debug(testName + ": status = " + statusCode);
172                 }
173                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
174                                 invalidStatusCodeMessage(testRequestType, statusCode));
175                 Assert.assertEquals(statusCode, testExpectedStatusCode);
176         
177                 // Retrieve the updated resource and verify that the parent did not change
178                 res = client.readItem(knownResourceId, knownItemResourceId);
179                 AUTHORITY_ITEM_TYPE updatedVocabularyItem = extractItemCommonPartValue(res);
180                 Assert.assertNotNull(updatedVocabularyItem);
181         
182                 // Verify that the updated resource received the correct data.
183                 Assert.assertEquals(client.getInAuthority(updatedVocabularyItem),
184                                 knownResourceId,
185                                 "VocabularyItem allowed update to the parent (inAuthority).");
186         } finally {
187                 res.close();
188         }
189     }
190     
191     @Test(dataProvider = "testName",
192                 dependsOnMethods = {"CRUDTests"})
193     public void createItem(String testName) {
194         // Perform setup.
195         setupCreate();
196
197         String newID = createItemInAuthority(knownResourceId);
198
199         // Store the ID returned from the first item resource created
200         // for additional tests below.
201         if (knownItemResourceId == null) {
202             knownItemResourceId = newID;
203             if (null != testName && logger.isDebugEnabled()) {
204                 logger.debug(testName + ": knownItemResourceId=" + knownItemResourceId);
205             }
206         }
207     }
208     
209     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
210                 dependsOnMethods = {"createItem"})
211     public void createItemList(String testName) throws Exception {
212         knownAuthorityWithItems = createResource(testName, READITEMS_SHORT_IDENTIFIER);
213         for (int j = 0; j < nItemsToCreateInList; j++) {
214                 createItemInAuthority(knownAuthorityWithItems);
215         }
216     }
217
218     /**
219      * Read by name.
220      *
221      * @param testName the test name
222      * @throws Exception the exception
223      */
224     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
225                 dependsOnMethods = {"CRUDTests"})
226     public void readByName(String testName) throws Exception {
227         // Perform setup.
228         setupRead();
229
230         // Submit the request to the service and store the response.
231         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client =
232                         (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
233         Response res = client.readByName(getKnowResourceIdentifier());
234         try {
235                 int statusCode = res.getStatus();
236         
237                 // Check the status code of the response: does it match
238                 // the expected response(s)?
239                 if (logger.isDebugEnabled()) {
240                     logger.debug(testName + ": status = " + statusCode);
241                 }
242                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
243                         invalidStatusCodeMessage(testRequestType, statusCode));
244                 Assert.assertEquals(statusCode, testExpectedStatusCode);
245                 
246                 AUTHORITY_COMMON_TYPE commonPart = extractCommonPartValue(res);
247                 Assert.assertNotNull(commonPart);
248         } finally {
249                 res.close();
250         }
251     }
252     
253     /**
254      * Extracts the common part item from a service's item payload.
255      * 
256      * @param res
257      * @return
258      * @throws Exception
259      */
260         public AUTHORITY_ITEM_TYPE extractItemCommonPartValue(Response res) throws Exception {
261                 AUTHORITY_ITEM_TYPE result = null;
262                 
263         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)
264                         this.getClientInstance();
265                 PayloadInputPart payloadInputPart = extractPart(res, client.getItemCommonPartName());
266                 if (payloadInputPart != null) {
267                         result = (AUTHORITY_ITEM_TYPE) payloadInputPart.getBody();
268                 }
269                 Assert.assertNotNull(result,
270                                 "Part or body of part " + client.getCommonPartName() + " was unexpectedly null.");
271                 
272                 return result;
273         }
274     
275     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
276                 dependsOnMethods = {"readItem"})
277     public void readItemNonExistent(String testName) {
278         // Perform setup.
279         setupReadNonExistent();
280
281         // Submit the request to the service and store the response.
282         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)
283                         this.getClientInstance();
284         Response res = client.readItem(knownResourceId, NON_EXISTENT_ID);
285         try {
286                 int statusCode = res.getStatus();
287         
288                 // Check the status code of the response: does it match
289                 // the expected response(s)?
290                 if (logger.isDebugEnabled()) {
291                     logger.debug(testName + ": status = " + statusCode);
292                 }
293                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
294                         invalidStatusCodeMessage(testRequestType, statusCode));
295                 Assert.assertEquals(statusCode, testExpectedStatusCode);
296         } finally {
297                 res.close();
298         }
299     }
300         
301     @Test(dataProvider = "testName",
302                 dependsOnMethods = {"createItem"})
303     public void readItem(String testName) throws Exception {
304         // Perform setup.
305         setupRead();
306
307         // Submit the request to the service and store the response.
308         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
309         Response res = client.readItem(knownResourceId, knownItemResourceId);
310         try {
311                 int statusCode = res.getStatus();
312         
313                 // Check the status code of the response: does it match
314                 // the expected response(s)?
315                 if (logger.isDebugEnabled()) {
316                     logger.debug(testName + ": status = " + statusCode);
317                 }
318                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
319                         invalidStatusCodeMessage(testRequestType, statusCode));
320                 Assert.assertEquals(statusCode, testExpectedStatusCode);
321         
322                 AUTHORITY_ITEM_TYPE itemCommonPart = extractItemCommonPartValue(res);
323                 Assert.assertNotNull(itemCommonPart);
324                 Assert.assertEquals(client.getInAuthority(itemCommonPart), knownResourceId);
325                 verifyReadItemInstance(itemCommonPart);
326         } finally {
327                 res.close();
328         }
329     }
330     
331     protected abstract void verifyReadItemInstance(AUTHORITY_ITEM_TYPE item) throws Exception;
332         
333     @Test(dataProvider = "testName",
334                 dependsOnMethods = {"testItemSubmitRequest", "updateItem", "verifyIgnoredUpdateWithInAuthority"})    
335     public void deleteItem(String testName) throws Exception {
336         // Perform setup.
337         setupDelete();
338
339         // Submit the request to the service and store the response.
340         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)
341                         this.getClientInstance();
342         Response res = client.deleteItem(knownResourceId, knownItemResourceId);
343         int statusCode;
344         try {
345                 statusCode = res.getStatus();
346         } finally {
347                 res.close();
348         }
349
350         // Check the status code of the response: does it match
351         // the expected response(s)?
352         if (logger.isDebugEnabled()) {
353             logger.debug("delete: status = " + statusCode);
354         }
355         Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
356                 invalidStatusCodeMessage(testRequestType, statusCode));
357         Assert.assertEquals(statusCode, testExpectedStatusCode);
358     }
359     
360     protected void readItemListInt(String vcsid, String shortId, String testName) {
361         // Perform setup.
362         setupReadList();
363
364         // Submit the request to the service and store the response.
365         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
366         Response res = null;
367         if (vcsid != null) {
368             res = client.readItemList(vcsid, null, null);
369         } else if (shortId != null) {
370             res = client.readItemListForNamedAuthority(shortId, null, null);
371         } else {
372             Assert.fail("Internal Error: readItemList both vcsid and shortId are null!");
373         }
374         try {
375                 int statusCode = res.getStatus();
376         
377                 // Check the status code of the response: does it match
378                 // the expected response(s)?
379                 if (logger.isDebugEnabled()) {
380                     logger.debug("  " + testName + ": status = " + statusCode);
381                 }
382                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
383                         invalidStatusCodeMessage(testRequestType, statusCode));
384                 Assert.assertEquals(statusCode, testExpectedStatusCode);
385         
386                 AbstractCommonList list = res.readEntity(AbstractCommonList.class);
387                 List<AbstractCommonList.ListItem> items = list.getListItem();
388                 int nItemsReturned = items.size();
389                 long nItemsTotal = list.getTotalItems();
390                 if (logger.isDebugEnabled()) {
391                     logger.debug("  " + testName + ": Expected "
392                             + nItemsToCreateInList + " items; got: " + nItemsReturned + " of: " + nItemsTotal);
393                 }
394                 Assert.assertEquals(nItemsTotal, nItemsToCreateInList);
395         
396                 if(logger.isTraceEnabled()){
397                         AbstractCommonListUtils.ListItemsInAbstractCommonList(list, logger, testName);
398                 }
399         } finally {
400                 res.close();
401         }
402     }
403     
404     @Test(dataProvider = "testName",
405                 dependsOnMethods = {"createItemList"})
406     public void readItemList(String testName) {
407         readItemListInt(knownAuthorityWithItems, null, testName);
408     }
409
410     @Test(dataProvider = "testName",
411                 dependsOnMethods = {"readItem"})
412     public void readItemListByName(String testName) {
413         readItemListInt(null, READITEMS_SHORT_IDENTIFIER, testName);
414     }
415
416     @Test(dataProvider = "testName",
417                 dependsOnMethods = {"deleteItem"})
418     public void deleteNonExistentItem(String testName) {
419         // Perform setup.
420         setupDeleteNonExistent();
421
422         // Submit the request to the service and store the response.
423         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
424         Response res = client.deleteItem(knownResourceId, NON_EXISTENT_ID);
425         int statusCode;
426         try {
427                 statusCode = res.getStatus();
428         } finally {
429                 res.close();
430         }
431
432         // Check the status code of the response: does it match
433         // the expected response(s)?
434         if (logger.isDebugEnabled()) {
435             logger.debug(testName + ": status = " + statusCode);
436         }
437         Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
438                 invalidStatusCodeMessage(testRequestType, statusCode));
439         Assert.assertEquals(statusCode, testExpectedStatusCode);
440     }
441     
442     protected String getServicePathItemsComponent() {
443         return AuthorityClient.ITEMS;
444     }
445     
446         public PoxPayloadOut createItemRequestTypeInstance(AUTHORITY_ITEM_TYPE itemTypeInstance) {
447                 PoxPayloadOut result = null;
448                 
449         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client = (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
450         PoxPayloadOut payloadOut = new PoxPayloadOut(this.getServicePathItemsComponent());
451         PayloadOutputPart part = payloadOut.addPart(client.getItemCommonPartName(), itemTypeInstance);
452         result = payloadOut;
453                 
454                 return result;
455         }
456
457         /**
458          * Update an Authority item.
459          * 
460          * @param testName
461          * @throws Exception
462          */
463     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
464                 dependsOnMethods = {"readItem", "CRUDTests", "verifyIgnoredUpdateWithInAuthority"})
465     public void updateItem(String testName) throws Exception {
466         // Perform setup.
467         setupUpdate();
468         AUTHORITY_ITEM_TYPE theUpdate = null;
469
470         // Retrieve the contents of a resource to update.
471         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client =
472                         (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
473         Response res = client.readItem(knownResourceId, knownItemResourceId);
474         try {
475                 if (logger.isDebugEnabled()) {
476                     logger.debug(testName + ": read status = " + res.getStatus());
477                 }
478                 Assert.assertEquals(res.getStatus(), testExpectedStatusCode);
479         
480                 if (logger.isDebugEnabled()) {
481                     logger.debug("got Authority item to update with ID: "
482                             + knownItemResourceId
483                             + " in authority: " + knownResourceId);
484                 }
485                 AUTHORITY_ITEM_TYPE authorityItem = extractItemCommonPartValue(res);
486                 Assert.assertNotNull(authorityItem);
487
488                 // Update the contents of this resource.
489                 theUpdate = updateItemInstance(authorityItem);
490                 if (logger.isDebugEnabled()) {
491                     logger.debug("\n\nTo be updated fields: CSID = "  + knownItemResourceId + "\n"
492                                 + objectAsXmlString(theUpdate));
493                 }
494         } finally {
495                 res.close();
496         }
497
498         // Submit the updated resource to the service and store the response.
499         PoxPayloadOut output = this.createItemRequestTypeInstance(theUpdate);
500         res = client.updateItem(knownResourceId, knownItemResourceId, output);
501         try {
502                 int statusCode = res.getStatus();
503         
504                 // Check the status code of the response: does it match the expected response(s)?
505                 if (logger.isDebugEnabled()) {
506                     logger.debug("updateItem: status = " + statusCode);
507                 }
508                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
509                         invalidStatusCodeMessage(testRequestType, statusCode));
510                 Assert.assertEquals(statusCode, testExpectedStatusCode);
511         
512                 // Retrieve the updated resource and verify that its contents exist.
513                 AUTHORITY_ITEM_TYPE updatedVocabularyItem = extractItemCommonPartValue(res);
514                 Assert.assertNotNull(updatedVocabularyItem);
515
516                 compareUpdatedItemInstances(theUpdate, updatedVocabularyItem);
517         } finally {
518                 res.close();
519         }
520     }
521     
522     protected abstract PoxPayloadOut createNonExistenceItemInstance(String commonPartName, String identifier);
523     
524     /* (non-Javadoc)
525      * @see org.collectionspace.services.client.test.ServiceTest#updateNonExistent(java.lang.String)
526      */
527     @Test(dataProvider = "testName",
528         dependsOnMethods = {"create", "update", "updateNonExistent"})
529     public void updateNonExistentItem(String testName) throws Exception {
530         // Perform setup.
531         setupUpdateNonExistent();
532
533         // Submit the request to the service and store the response.
534         // Note: The ID used in this 'create' call may be arbitrary.
535         // The only relevant ID may be the one used in update(), below.
536         AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy> client =
537                         (AuthorityClientImpl<AUTHORITY_ITEM_TYPE, AuthorityProxy>)this.getClientInstance();
538         PoxPayloadOut multipart = createNonExistenceItemInstance(client.getItemCommonPartName(), NON_EXISTENT_ID);
539         Response res = client.updateItem(knownResourceId, NON_EXISTENT_ID, multipart);
540         try {
541                 int statusCode = res.getStatus();
542         
543                 // Check the status code of the response: does it match
544                 // the expected response(s)?
545                 if (logger.isDebugEnabled()) {
546                         logger.debug(testName + ": status = " + statusCode);
547                 }
548                 Assert.assertTrue(testRequestType.isValidStatusCode(statusCode),
549                                 invalidStatusCodeMessage(testRequestType, statusCode));
550                 Assert.assertEquals(statusCode, testExpectedStatusCode);
551         } finally {
552                 res.close();
553         }
554     }
555         
556     //
557     // Methods to persuade TestNG to follow the correct test dependency path
558     //
559     
560     @Test(dataProvider = "testName",
561                 dependsOnMethods = {"createItem"})
562     public void baseAuthorityTests(String testName) {
563         // Do nothing.  Here just to setup a test dependency chain.
564     }
565     
566     /*
567      * For convenience and terseness, this test method is the base of the test execution dependency chain.  Other test methods may
568      * refer to this method in their @Test annotation declarations.
569      */
570     @Override
571     @Test(dataProvider = "testName",
572                 dependsOnMethods = {
573                         "org.collectionspace.services.client.test.AbstractServiceTestImpl.baseCRUDTests"})    
574         public void CRUDTests(String testName) {
575                 // TODO Auto-generated method stub
576         }
577     
578 }