From 5682ef0c346e67a54abf7a890d744137a343b8f4 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Thu, 24 Nov 2016 21:05:13 +0000 Subject: [PATCH] Undo fix for REPO-1022: MNT-16271. Tests working locally but race conditions are possible. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@133098 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repo/site/SiteServiceImpl.java | 340 +++++++++--------- .../repo/site/SiteServiceImplTest.java | 168 ++------- 2 files changed, 192 insertions(+), 316 deletions(-) diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index f32e9c63e9..6f08007cfb 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -1,133 +1,133 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.site; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.events.types.Event; -import org.alfresco.events.types.SiteManagementEvent; -import org.alfresco.model.ContentModel; -import org.alfresco.query.CannedQuery; -import org.alfresco.query.CannedQueryFactory; -import org.alfresco.query.CannedQueryPageDetails; -import org.alfresco.query.CannedQueryParameters; -import org.alfresco.query.CannedQueryResults; -import org.alfresco.query.CannedQuerySortDetails; -import org.alfresco.query.CannedQuerySortDetails.SortOrder; -import org.alfresco.query.PageDetails; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.activities.ActivityType; -import org.alfresco.repo.admin.SysAdminParams; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.events.EventPreparator; -import org.alfresco.repo.events.EventPublisher; -import org.alfresco.repo.node.NodeArchiveServicePolicies; -import org.alfresco.repo.node.NodeArchiveServicePolicies.BeforePurgeNodePolicy; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy; -import org.alfresco.repo.node.getchildren.FilterProp; -import org.alfresco.repo.node.getchildren.FilterPropString; -import org.alfresco.repo.node.getchildren.FilterPropString.FilterTypeString; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQueryFactory; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.activities.ActivityService; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.preference.PreferenceService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.NoSuchPersonException; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.security.PublicServiceAccessService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteMemberInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.cmr.site.SiteVisibility; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.GUID; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.PropertyMap; -import org.alfresco.util.SearchLanguageConversion; -import org.alfresco.util.registry.NamedObjectRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.json.JSONObject; -import org.springframework.context.ApplicationEvent; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.springframework.extensions.surf.util.ParameterCheck; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.events.types.Event; +import org.alfresco.events.types.SiteManagementEvent; +import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryFactory; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQueryResults; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; +import org.alfresco.query.PageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.events.EventPreparator; +import org.alfresco.repo.events.EventPublisher; +import org.alfresco.repo.node.NodeArchiveServicePolicies; +import org.alfresco.repo.node.NodeArchiveServicePolicies.BeforePurgeNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy; +import org.alfresco.repo.node.getchildren.FilterProp; +import org.alfresco.repo.node.getchildren.FilterPropString; +import org.alfresco.repo.node.getchildren.FilterPropString.FilterTypeString; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQueryFactory; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.security.PublicServiceAccessService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteMemberInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.GUID; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.PropertyMap; +import org.alfresco.util.SearchLanguageConversion; +import org.alfresco.util.registry.NamedObjectRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.ParameterCheck; /** * Site Service Implementation. Also bootstraps the site DM stores. @@ -1662,14 +1662,6 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic final QName siteType = this.directNodeService.getType(nodeRef); final String shortName = getSite(nodeRef).getShortName(); - // If there is a live site, we must not wipe out the authority data. - // Authority data for sites is associated by a naming convention. - if (hasSite(shortName)) - { - // A live site exists, so leave the authority data alone - return; - } - // Delete the associated groups AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { @@ -2430,7 +2422,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic ACTIVITY_TOOL, getActivityUserData(authorityName, ""), authorityName); } else if (authorityType == AuthorityType.GROUP) - { + { String authorityDisplayName = authorityService.getAuthorityDisplayName(authorityName); activityService.postActivity( ActivityType.SITE_GROUP_REMOVED, shortName, @@ -2530,43 +2522,43 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } }, AuthenticationUtil.SYSTEM_USER_NAME); - - AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName); - String authorityDisplayName = authorityName; - if (authorityType == AuthorityType.GROUP) - { - authorityDisplayName = authorityService.getAuthorityDisplayName(authorityName); - } - - if (currentRole == null) - { - if (authorityType == AuthorityType.USER) - { - activityService.postActivity( - ActivityType.SITE_USER_JOINED, shortName, - ACTIVITY_TOOL, getActivityUserData(authorityDisplayName, role), authorityName); - } - else if (authorityType == AuthorityType.GROUP) - { - activityService.postActivity( - ActivityType.SITE_GROUP_ADDED, shortName, - ACTIVITY_TOOL, getActivityGroupData(authorityDisplayName, role)); - } - } - else - { - if (authorityType == AuthorityType.USER) - { - activityService.postActivity( - ActivityType.SITE_USER_ROLE_UPDATE, shortName, - ACTIVITY_TOOL, getActivityUserData(authorityDisplayName, role)); - } - else if (authorityType == AuthorityType.GROUP) - { - activityService.postActivity( - ActivityType.SITE_GROUP_ROLE_UPDATE, shortName, - ACTIVITY_TOOL, getActivityGroupData(authorityDisplayName, role)); - } + + AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName); + String authorityDisplayName = authorityName; + if (authorityType == AuthorityType.GROUP) + { + authorityDisplayName = authorityService.getAuthorityDisplayName(authorityName); + } + + if (currentRole == null) + { + if (authorityType == AuthorityType.USER) + { + activityService.postActivity( + ActivityType.SITE_USER_JOINED, shortName, + ACTIVITY_TOOL, getActivityUserData(authorityDisplayName, role), authorityName); + } + else if (authorityType == AuthorityType.GROUP) + { + activityService.postActivity( + ActivityType.SITE_GROUP_ADDED, shortName, + ACTIVITY_TOOL, getActivityGroupData(authorityDisplayName, role)); + } + } + else + { + if (authorityType == AuthorityType.USER) + { + activityService.postActivity( + ActivityType.SITE_USER_ROLE_UPDATE, shortName, + ACTIVITY_TOOL, getActivityUserData(authorityDisplayName, role)); + } + else if (authorityType == AuthorityType.GROUP) + { + activityService.postActivity( + ActivityType.SITE_GROUP_ROLE_UPDATE, shortName, + ACTIVITY_TOOL, getActivityGroupData(authorityDisplayName, role)); + } } } else diff --git a/source/test-java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/test-java/org/alfresco/repo/site/SiteServiceImplTest.java index afec9c7e62..af8fe61fb9 100644 --- a/source/test-java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/test-java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.site; import java.io.Serializable; @@ -1229,7 +1229,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest }, AuthenticationUtil.getAdminUserName()); // Create a test site - String siteShortName = "testDeleteSite-" + GUID.generate(); + String siteShortName = "testUpdateSite"; this.siteService.createSite(TEST_SITE_PRESET, siteShortName, TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); SiteInfo siteInfo = this.siteService.getSite(siteShortName); assertNotNull(siteInfo); @@ -1258,122 +1258,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest assertTrue(authorityService.authorityExists(testGroup)); } - @SuppressWarnings("deprecation") - public void testPurgeSiteSimple() - { - SiteService smallSiteService = (SiteService)this.applicationContext.getBean("siteService"); - // Create a test group - final String testGroupName = "siteServiceImplTestGroup_" + GUID.generate(); - String testGroup = AuthenticationUtil.runAs( - new AuthenticationUtil.RunAsWork() - { - public String doWork() throws Exception - { - return authorityService.createAuthority(AuthorityType.GROUP, testGroupName); - } - }, AuthenticationUtil.getAdminUserName()); - - // Create a test site - String siteShortName = "testPurgeSite-" + GUID.generate(); - this.siteService.createSite(TEST_SITE_PRESET, siteShortName, TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); - SiteInfo siteInfo = this.siteService.getSite(siteShortName); - assertNotNull(siteInfo); - - // Add the test group as a member of the site - this.siteService.setMembership(siteShortName, testGroup, SiteModel.SITE_CONTRIBUTOR); - - // Delete the site - this.siteService.deleteSite(siteShortName); - assertNull(this.siteService.getSite(siteShortName)); - NodeRef archivedNodeRef = nodeArchiveService.getArchivedNode(siteInfo.getNodeRef()); - assertTrue("Deleted sites can be recovered from the Trash.", nodeService.exists(archivedNodeRef)); - - // Commit these site structures - setComplete(); - endTransaction(); - - // We now do two transactions that will be thrown away to handle the purge use cases - - // Now purge the site. - RetryingTransactionCallback purgeWork = new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - // We already check that the authorities remain alive. - nodeArchiveService.purgeArchivedNode(archivedNodeRef); // service call starts a new txn - - // Site-related groups should be wiped - assertFalse(authorityService.authorityExists(((SiteServiceImpl)smallSiteService).getSiteGroup(siteShortName, true))); - assertFalse(authorityService.authorityExists(((SiteServiceImpl) smallSiteService).getSiteGroup(siteShortName))); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - for (String permission : permissions) - { - String siteRoleGroup = ((SiteServiceImpl)smallSiteService).getSiteRoleGroup(siteShortName, permission, true); - assertFalse(authorityService.authorityExists(siteRoleGroup)); - } - - // Ensure that the added "normal" groups have not been deleted - assertTrue(authorityService.authorityExists(testGroup)); - - throw new RuntimeException("Expected $$"); - } - }; - try - { - transactionService.getRetryingTransactionHelper().doInTransaction(purgeWork); - } - catch (Exception e) - { - if (!e.getMessage().contains("$$")) - { - throw e; - } - } - - // Create a new site and ensure that the purge does NOT wipe out the authorities ... but still works otherwise (MNT-16271) - RetryingTransactionCallback purgeWithExistingWork = new RetryingTransactionCallback() - { - @Override - public Void execute() throws Throwable - { - siteService.createSite(TEST_SITE_PRESET, siteShortName, TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); - - // We already check that the authorities remain alive. - nodeArchiveService.purgeArchivedNode(archivedNodeRef); // service call starts a new txn - - // Site-related groups should still exist - assertTrue(authorityService.authorityExists(((SiteServiceImpl)smallSiteService).getSiteGroup(siteShortName, true))); - assertTrue(authorityService.authorityExists(((SiteServiceImpl) smallSiteService).getSiteGroup(siteShortName))); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - for (String permission : permissions) - { - String siteRoleGroup = ((SiteServiceImpl)smallSiteService).getSiteRoleGroup(siteShortName, permission, true); - assertTrue(authorityService.authorityExists(siteRoleGroup)); - } - - // Ensure that the added "normal" groups have not been deleted - assertTrue(authorityService.authorityExists(testGroup)); - - throw new RuntimeException("Expected $$"); - } - }; - try - { - transactionService.getRetryingTransactionHelper().doInTransaction(purgeWithExistingWork); - } - catch (Exception e) - { - if (!e.getMessage().contains("$$")) - { - throw e; - } - } - - // Start a new transaction to keep Spring's sequencing happy - startNewTransaction(); - } - public void testIsPublic() { RetryingTransactionCallback work = new RetryingTransactionCallback()