From e1594c00f12bbbaf34adb7ee22838f6817931e8f Mon Sep 17 00:00:00 2001 From: tcdlpds Date: Wed, 31 Jul 2024 13:58:41 +0530 Subject: [PATCH] Improve tenant deleting logic --- .../common/services/ApplicationManager.java | 14 ++--- .../pom.xml | 11 ---- .../mgt/core/impl/ApplicationManagerImpl.java | 35 +++--------- ...ApplicationManagementServiceComponent.java | 22 -------- .../mgt/core/internal/DataHolder.java | 10 ---- .../admin/UserManagementAdminServiceImpl.java | 56 ++++++++++++------- .../DeviceManagementProviderService.java | 10 +++- .../DeviceManagementProviderServiceImpl.java | 28 +++++----- .../pom.xml | 5 ++ 9 files changed, 77 insertions(+), 114 deletions(-) diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java index 2b30b85cf9..d40855db19 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java @@ -25,11 +25,9 @@ import io.entgra.device.mgt.core.application.mgt.common.response.Application; import io.entgra.device.mgt.core.application.mgt.common.response.ApplicationRelease; import io.entgra.device.mgt.core.application.mgt.common.response.Category; import io.entgra.device.mgt.core.application.mgt.common.response.Tag; -import io.entgra.device.mgt.core.application.mgt.common.wrapper.*; import io.entgra.device.mgt.core.device.mgt.common.Base64File; import io.entgra.device.mgt.core.application.mgt.common.dto.ApplicationDTO; import io.entgra.device.mgt.core.device.mgt.common.PaginationRequest; -import io.entgra.device.mgt.core.device.mgt.common.exceptions.MetadataManagementException; import org.apache.cxf.jaxrs.ext.multipart.Attachment; import io.entgra.device.mgt.core.application.mgt.common.ApplicationArtifact; import io.entgra.device.mgt.core.application.mgt.common.LifecycleChanger; @@ -549,18 +547,18 @@ public interface ApplicationManager { void deleteApplicationDataOfTenant(int tenantId) throws ApplicationManagementException; /** - * Delete all application related data of a tenant by tenant Domain + * Delete all application related data of a tenant by tenant Id * - * @param tenantDomain Domain of the Tenant + * @param tenantId Id of the Tenant * @throws ApplicationManagementException thrown if an error occurs when deleting data */ - void deleteApplicationDataByTenantDomain(String tenantDomain) throws ApplicationManagementException; + void deleteApplicationDataByTenantId(int tenantId) throws ApplicationManagementException; /** - * Delete all Application artifacts related to a tenant by Tenant Domain + * Delete all Application artifacts related to a tenant by Tenant Id * - * @param tenantDomain Domain of the Tenant + * @param tenantId Id of the Tenant * @throws ApplicationManagementException thrown if an error occurs when deleting app folders */ - void deleteApplicationArtifactsByTenantDomain(String tenantDomain) throws ApplicationManagementException; + void deleteApplicationArtifactsByTenantId(int tenantId) throws ApplicationManagementException; } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml index 1944506e53..171fa4557d 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml @@ -99,7 +99,6 @@ io.entgra.device.mgt.core.identity.jwt.client.extension.dto;version="[5.0,6)", io.entgra.device.mgt.core.identity.jwt.client.extension.exception;version="[5.0,6)", io.entgra.device.mgt.core.identity.jwt.client.extension.service;version="[5.0,6)", - io.entgra.device.mgt.core.tenant.mgt.common.*;version="[5.0,6)", javax.annotation;version="[1.0,2)", javax.naming, javax.sql, @@ -439,16 +438,6 @@ okio ${okio.version} - - org.wso2.carbon.multitenancy - org.wso2.carbon.tenant.mgt - compile - - - io.entgra.device.mgt.core - io.entgra.device.mgt.core.tenant.mgt.common - provided - org.wso2.orbit.javax.xml.bind jaxb-api diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java index fc90d59035..29d7d84ae5 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java @@ -82,7 +82,6 @@ import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.Metadata; import io.entgra.device.mgt.core.device.mgt.core.common.exception.StorageManagementException; import io.entgra.device.mgt.core.device.mgt.core.dto.DeviceType; import io.entgra.device.mgt.core.device.mgt.core.service.DeviceManagementProviderService; -import io.entgra.device.mgt.core.tenant.mgt.common.exception.TenantMgtException; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; @@ -4439,13 +4438,9 @@ public class ApplicationManagerImpl implements ApplicationManager { } @Override - public void deleteApplicationDataByTenantDomain(String tenantDomain) throws ApplicationManagementException { - int tenantId; + public void deleteApplicationDataByTenantId(int tenantId) throws ApplicationManagementException { try { - tenantId = DataHolder.getInstance().getTenantManagerAdminService().getTenantId(tenantDomain); - ConnectionManagerUtil.beginDBTransaction(); - vppApplicationDAO.deleteAssociationByTenant(tenantId); vppApplicationDAO.deleteVppUserByTenant(tenantId); vppApplicationDAO.deleteAssetsByTenant(tenantId); @@ -4471,48 +4466,36 @@ public class ApplicationManagerImpl implements ApplicationManager { ConnectionManagerUtil.commitDBTransaction(); } catch (DBConnectionException e) { String msg = "Error occurred while observing the database connection to delete applications for tenant with " + - "domain: " + tenantDomain; + "tenant ID: " + tenantId; log.error(msg, e); throw new ApplicationManagementException(msg, e); } catch (ApplicationManagementDAOException e) { ConnectionManagerUtil.rollbackDBTransaction(); - String msg = "Database access error is occurred when getting applications for tenant with domain: " - + tenantDomain; + String msg = "Database access error is occurred when getting applications for tenant with tenant Id: " + + tenantId; log.error(msg, e); throw new ApplicationManagementException(msg, e); } catch (LifeCycleManagementDAOException e) { ConnectionManagerUtil.rollbackDBTransaction(); String msg = "Error occurred while deleting life-cycle state data of application releases of the tenant" - + " of domain: " + tenantDomain ; + + " of id: " + tenantId ; log.error(msg, e); throw new ApplicationManagementException(msg, e); } catch (ReviewManagementDAOException e) { ConnectionManagerUtil.rollbackDBTransaction(); String msg = "Error occurred while deleting reviews of application releases of the applications" - + " of tenant of domain: " + tenantDomain ; - log.error(msg, e); - throw new ApplicationManagementException(msg, e); - } catch (Exception e) { - String msg = "Error getting tenant ID from domain: " - + tenantDomain; + + " of tenant of id: " + tenantId ; log.error(msg, e); throw new ApplicationManagementException(msg, e); } } @Override - public void deleteApplicationArtifactsByTenantDomain(String tenantDomain) throws ApplicationManagementException { - int tenantId; + public void deleteApplicationArtifactsByTenantId(int tenantId) throws ApplicationManagementException { try { - tenantId = DataHolder.getInstance().getTenantManagerAdminService().getTenantId(tenantDomain); DataHolder.getInstance().getApplicationStorageManager().deleteAppFolderOfTenant(tenantId); - } catch (ApplicationStorageManagementException e) { - String msg = "Error deleting app artifacts of tenant of domain: " + tenantDomain ; - log.error(msg, e); - throw new ApplicationManagementException(msg, e); - } catch (TenantMgtException e) { - String msg = "Error getting tenant ID from domain: " - + tenantDomain + " when trying to delete application artifacts of tenant"; + } catch (ApplicationStorageManagementException e) { + String msg = "Error deleting app artifacts of tenant of Id: " + tenantId; log.error(msg, e); throw new ApplicationManagementException(msg, e); } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java index cc042e6ecb..f3e6b56b50 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java @@ -34,7 +34,6 @@ import io.entgra.device.mgt.core.application.mgt.core.lifecycle.LifecycleStateMa import io.entgra.device.mgt.core.application.mgt.core.task.ScheduledAppSubscriptionTaskManager; import io.entgra.device.mgt.core.application.mgt.core.util.ApplicationManagementUtil; import io.entgra.device.mgt.core.device.mgt.core.service.DeviceManagementProviderService; -import io.entgra.device.mgt.core.tenant.mgt.common.spi.TenantManagerAdminService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.BundleContext; @@ -203,25 +202,4 @@ public class ApplicationManagementServiceComponent { } DataHolder.getInstance().setTaskService(null); } - - @Reference( - name = "io.entgra.device.mgt.core.tenant.manager", - service = io.entgra.device.mgt.core.tenant.mgt.common.spi.TenantManagerAdminService.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.DYNAMIC, - unbind = "unsetTenantManagementAdminService") - protected void setTenantManagementAdminService(TenantManagerAdminService tenantManagerAdminService) { - if (log.isDebugEnabled()) { - log.debug("Setting Tenant management admin Service"); - } - DataHolder.getInstance().setTenantManagerAdminService(tenantManagerAdminService); - } - - @SuppressWarnings("unused") - protected void unsetTenantManagementAdminService(TenantManagerAdminService tenantManagerAdminService) { - if (log.isDebugEnabled()) { - log.debug("Un setting Tenant management admin service"); - } - DataHolder.getInstance().setTenantManagerAdminService(null); - } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java index df1eb4ee02..80416dcd59 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java @@ -27,7 +27,6 @@ import io.entgra.device.mgt.core.application.mgt.common.services.SubscriptionMan import io.entgra.device.mgt.core.application.mgt.common.services.VPPApplicationManager; import io.entgra.device.mgt.core.application.mgt.core.lifecycle.LifecycleStateManager; import io.entgra.device.mgt.core.device.mgt.core.service.DeviceManagementProviderService; -import io.entgra.device.mgt.core.tenant.mgt.common.spi.TenantManagerAdminService; import org.wso2.carbon.ntask.core.service.TaskService; import org.wso2.carbon.user.core.service.RealmService; @@ -58,7 +57,6 @@ public class DataHolder { private TaskService taskService; private FileTransferService fileTransferService; - private TenantManagerAdminService tenantManagerAdminService; private static final DataHolder applicationMgtDataHolder = new DataHolder(); @@ -165,12 +163,4 @@ public class DataHolder { public void setFileTransferService(FileTransferService fileTransferService) { this.fileTransferService = fileTransferService; } - - public TenantManagerAdminService getTenantManagerAdminService() { - return tenantManagerAdminService; - } - - public void setTenantManagerAdminService(TenantManagerAdminService tenantManagerAdminService) { - this.tenantManagerAdminService = tenantManagerAdminService; - } } diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/admin/UserManagementAdminServiceImpl.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/admin/UserManagementAdminServiceImpl.java index 91ed3f6d42..858f31e66c 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/admin/UserManagementAdminServiceImpl.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/admin/UserManagementAdminServiceImpl.java @@ -20,6 +20,7 @@ package io.entgra.device.mgt.core.device.mgt.api.jaxrs.service.impl.admin; import io.entgra.device.mgt.core.application.mgt.common.exception.ApplicationManagementException; import io.entgra.device.mgt.core.device.mgt.common.exceptions.DeviceManagementException; import io.entgra.device.mgt.core.tenant.mgt.common.exception.TenantMgtException; +import io.entgra.device.mgt.core.tenant.mgt.common.spi.TenantManagerAdminService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import io.entgra.device.mgt.core.device.mgt.common.DeviceIdentifier; @@ -32,7 +33,13 @@ import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.context.CarbonContext; import javax.validation.constraints.Size; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -89,37 +96,46 @@ public class UserManagementAdminServiceImpl implements UserManagementAdminServic @DELETE @Path("/domain/{tenantDomain}") @Override - public Response deleteTenantByDomain(@PathParam("tenantDomain") String tenantDomain, @QueryParam("deleteAppArtifacts") boolean deleteAppArtifacts) { + public Response deleteTenantByDomain(@PathParam("tenantDomain") String tenantDomain, + @QueryParam("deleteAppArtifacts") boolean deleteAppArtifacts) { try { - int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId(); - if (tenantId != MultitenantConstants.SUPER_TENANT_ID){ - String msg = "Only super tenants are allowed to delete tenants"; + if (CarbonContext.getThreadLocalCarbonContext().getTenantId() != MultitenantConstants.SUPER_TENANT_ID){ + String msg = "Only super tenants are allowed to delete tenants."; log.error(msg); return Response.status(Response.Status.UNAUTHORIZED).entity(msg).build(); - } else { - if (deleteAppArtifacts) { - DeviceMgtAPIUtils.getApplicationManager().deleteApplicationArtifactsByTenantDomain(tenantDomain); - } - DeviceMgtAPIUtils.getApplicationManager().deleteApplicationDataByTenantDomain(tenantDomain); - DeviceMgtAPIUtils.getDeviceManagementService().deleteDeviceDataByTenantDomain(tenantDomain); - DeviceMgtAPIUtils.getTenantManagerAdminService().deleteTenant(tenantDomain); - String msg = "Tenant Deletion process has been initiated for tenant:" + tenantDomain; - if (log.isDebugEnabled()) { - log.debug(msg); - } - return Response.status(Response.Status.OK).entity(msg).build(); } + if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + String msg = "You are not allowed to delete the super tenant."; + log.error(msg); + return Response.status(Response.Status.UNAUTHORIZED).entity(msg).build(); + } + + if (log.isDebugEnabled()) { + log.debug("Tenant Deletion process has been initiated for tenant:" + tenantDomain); + } + + TenantManagerAdminService tenantManagerAdminService = DeviceMgtAPIUtils.getTenantManagerAdminService(); + int tenantId = tenantManagerAdminService.getTenantId(tenantDomain); + + if (deleteAppArtifacts) { + DeviceMgtAPIUtils.getApplicationManager().deleteApplicationArtifactsByTenantId(tenantId); + } + DeviceMgtAPIUtils.getApplicationManager().deleteApplicationDataByTenantId(tenantId); + DeviceMgtAPIUtils.getDeviceManagementService().deleteDeviceDataByTenantId(tenantId); + DeviceMgtAPIUtils.getTenantManagerAdminService().deleteTenant(tenantDomain); + return Response.status(Response.Status.OK).entity("Tenant Deletion process has been completed " + + "successfully for tenant: " + tenantDomain).build(); } catch (TenantMgtException e) { - String msg = "Error deleting tenant: " + tenantDomain; + String msg = "Error occurred while deleting tenant: " + tenantDomain; log.error(msg, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); } catch (ApplicationManagementException e) { - String msg = "Error deleting application data of tenant: " + tenantDomain; + String msg = "Error occurred while deleting application data of tenant: " + tenantDomain; log.error(msg, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); } catch (DeviceManagementException e) { - String msg = "Error deleting device data of tenant: " + tenantDomain; + String msg = "Error occurred while deleting device data of tenant: " + tenantDomain; log.error(msg, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); } diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderService.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderService.java index 53e8aa5796..ee8c5b5871 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderService.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderService.java @@ -46,7 +46,6 @@ import io.entgra.device.mgt.core.device.mgt.common.type.mgt.DeviceStatus; import io.entgra.device.mgt.core.device.mgt.core.dao.DeviceManagementDAOException; import io.entgra.device.mgt.core.device.mgt.core.dto.DeviceType; import io.entgra.device.mgt.core.device.mgt.core.dto.DeviceTypeVersion; -import org.apache.commons.collections.map.SingletonMap; import java.sql.SQLException; import java.sql.Timestamp; @@ -1067,7 +1066,14 @@ public interface DeviceManagementProviderService { List getInstalledApplicationsOnDevice(Device device) throws DeviceManagementException; List getEnrolledDevicesSince(Date since) throws DeviceManagementException; List getEnrolledDevicesPriorTo(Date before) throws DeviceManagementException; - void deleteDeviceDataByTenantDomain(String tenantDomain) throws DeviceManagementException; + + /** + * Delete all the device related data for a given Tenant by Tenant Id + * + * @param tenantId Id of the Tenant + * @throws DeviceManagementException if error occurs when deleting device data by using tenant Id + */ + void deleteDeviceDataByTenantId(int tenantId) throws DeviceManagementException; /** * Get owner details and device IDs for a given owner and tenant. diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderServiceImpl.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderServiceImpl.java index 1ab272b43e..6184382668 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderServiceImpl.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/service/DeviceManagementProviderServiceImpl.java @@ -165,7 +165,18 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; public class DeviceManagementProviderServiceImpl implements DeviceManagementProviderService, @@ -5272,20 +5283,7 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv } @Override - public void deleteDeviceDataByTenantDomain(String tenantDomain) throws DeviceManagementException { - int tenantId; - try{ - TenantMgtAdminService tenantMgtAdminService = new TenantMgtAdminService(); - TenantInfoBean tenantInfoBean = tenantMgtAdminService.getTenant(tenantDomain); - tenantId = tenantInfoBean.getTenantId(); - - } catch (Exception e) { - String msg = "Error getting tenant ID from domain: " - + tenantDomain; - log.error(msg); - throw new DeviceManagementException(msg, e); - } - + public void deleteDeviceDataByTenantId(int tenantId) throws DeviceManagementException { try { DeviceManagementDAOFactory.beginTransaction(); diff --git a/components/tenant-mgt/io.entgra.device.mgt.core.tenant.mgt.core/pom.xml b/components/tenant-mgt/io.entgra.device.mgt.core.tenant.mgt.core/pom.xml index bef09da463..6347269f93 100644 --- a/components/tenant-mgt/io.entgra.device.mgt.core.tenant.mgt.core/pom.xml +++ b/components/tenant-mgt/io.entgra.device.mgt.core.tenant.mgt.core/pom.xml @@ -139,5 +139,10 @@ org.wso2.carbon org.wso2.carbon.utils + + org.wso2.carbon.multitenancy + org.wso2.carbon.tenant.mgt + provided + \ No newline at end of file -- 2.36.3