Package org.wso2.carbon.caching.service

Source Code of org.wso2.carbon.caching.service.CachingConfigAdminService

/*
* Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wso2.carbon.caching.service;

import org.apache.axis2.AxisFault;
import org.apache.axis2.description.AxisDescription;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.PolicyInclude;
import org.apache.axis2.description.PolicySubject;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.neethi.Policy;
import org.wso2.carbon.caching.CachingComponentConstants;
import org.wso2.carbon.caching.CachingComponentException;
import org.wso2.carbon.caching.CachingConfigData;
import org.wso2.carbon.caching.CachingPolicyUtils;
import org.wso2.carbon.core.AbstractAdmin;
import org.wso2.carbon.core.RegistryResources;
import org.wso2.carbon.core.persistence.PersistenceUtils;
import org.wso2.carbon.registry.core.Association;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;

import javax.xml.namespace.QName;
import java.util.Collection;
import java.util.Iterator;

/**
* The <code>CachingConfigAdminService</code> class provides service methods to configure the
* caching module for a given service.
*/
public class CachingConfigAdminService extends AbstractAdmin {

    /**
     * The logger object for this class.
     */
    private static final Log log = LogFactory.getLog(CachingConfigAdminService.class);

    /**
     * Reference to configuration registry instance inside Carbon.
     */
    private Registry configRegistry = null;

    /**
     * Reference to Axis configuration.
     */
    private AxisConfiguration axisConfig = null;

    /**
     * The admin provides the implementations of the caching configuration options.
     */
    private CachingPolicyUtils cachingPolicyUtils;

    private static final String GLOBALLY_ENGAGED_PARAM_NAME = "globallyEngaged";

    private static final String GLOBALLY_ENGAGED_CUSTOM = "globallyEngagedCustom";

    private static final String ADMIN_SERVICE_PARAM_NAME = "adminService";

    /**
     * Creates a new instance of the <code>CachingConfigAdminService</code>.
     */
    public CachingConfigAdminService() {
        axisConfig = getAxisConfig();
        configRegistry = getConfigSystemRegistry();
        cachingPolicyUtils = new CachingPolicyUtils(configRegistry);
    }

    /**
     * Enables caching for the given service using the given configuration.
     *
     * @param serviceName the name of the service to which caching should be enabled.
     * @param confData    the caching configuration to be used.
     * @throws CachingComponentException if engaging of caching is unsuccessful.
     */
    public void engageCachingForService(String serviceName, CachingConfigData confData)
            throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Enabling caching for the service: " + serviceName);
        }

        //get AxisService from service name
        AxisService service = this.retrieveAxisService(serviceName);

        //create service path in configRegistry
        String servicePath = RegistryResources.SERVICE_GROUPS
                             + service.getAxisServiceGroup().getServiceGroupName()
                             + RegistryResources.SERVICES + serviceName;

        try {
            this.enableCaching(service, confData, servicePath);
        } catch (AxisFault af) {
            throw new CachingComponentException("errorEngagingModuleToService",
                                                new String[]{serviceName}, af, log);
        }

        if (log.isDebugEnabled()) {
            log.debug("Engaged caching for the Axis service: " + serviceName);
        }
    }

    /**
     * Enables caching for the given service using the given configuration.
     *
     * @param serviceName   the name of the service to which caching should be enabled.
     * @param confData      the caching configuration to be used.
     * @param operationName name of the operation
     * @return true if already engaged caching at the service level, else false
     * @throws CachingComponentException if engaging of caching is unsuccessful.
     */
    public boolean engageCachingForOperation(String serviceName, String
            operationName, CachingConfigData confData) throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Enabling caching for the operation: " + operationName
                      + " of service : " + serviceName);
        }

        //get AxisService from service name
        AxisService service = this.retrieveAxisService(serviceName);

        // Retrieves caching module.
        AxisModule cachingModule = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);

        if (service.isEngaged(cachingModule)) {
            return true;
        }

        //get AxisOperation
        AxisOperation operation = service.getOperation(new QName(operationName));

        //create operation path in configRegistry
        String operationPath = RegistryResources.SERVICE_GROUPS
                               + service.getAxisServiceGroup().getServiceGroupName()
                               + RegistryResources.SERVICES + serviceName +
                               RegistryResources.OPERATIONS + operationName;

        try {
            this.enableCaching(operation, confData, operationPath);
        } catch (AxisFault af) {
            throw new CachingComponentException("errorEngagingModuleToOperation",
                                                new String[]{operationName}, af, log);
        }

        if (log.isDebugEnabled()) {
            log.debug("Engaged caching for the Axis operation: " + serviceName
                      + " of service : " + serviceName);
        }
        return false;
    }

    /**
     * Engages caching for service or operation
     *
     * @param description    - AxisService or AxisOperation
     * @param confData       - incomming config data
     * @param engagementPath - service path or operation path
     * @throws CachingComponentException - errors
     * @throws AxisFault                 - errors on AxisDescription
     */
    private void enableCaching(AxisDescription description, CachingConfigData confData,
                               String engagementPath)
            throws CachingComponentException, AxisFault {

        // Retrieves caching module.
        AxisModule cachingModule = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);

        try {
            configRegistry.beginTransaction();
            // Checks if an association exists between engagementPath and moduleResourcePath.
            Association[] associations = configRegistry.getAssociations(engagementPath,
                                                                  RegistryResources.Associations.ENGAGED_MODULES);
            boolean associationExist = false;
            for (Association association : associations) {
                if (association.getDestinationPath().equals(getModuleResourcePath(cachingModule))) {
                    associationExist = true;
                    break;
                }
            }

            // If no association exist between engagementPath and moduleResourcePath then
            // add new association between them.
            if (!associationExist) {
                configRegistry.addAssociation(engagementPath, getModuleResourcePath(cachingModule),
                                        RegistryResources.Associations.ENGAGED_MODULES);
            }

            // Gets a Policy object representing the configuration data.
            Policy policy = confData.toPolicy();

            //Add new policy to the description
            this.handleNewPolicyAddition(policy, description.getPolicySubject(), confData);

            // Save the policy in the configRegistry.
            Resource policyResource = configRegistry.newResource();
            String policyType = "" + PolicyInclude.AXIS_SERVICE_POLICY;
            String policyPath = engagementPath;
            if (description instanceof AxisOperation) {
                policyPath = engagementPath.substring(0, engagementPath
                        .indexOf(RegistryResources.OPERATIONS));
                policyType = "" + PolicyInclude.AXIS_OPERATION_POLICY;
            }

            policyResource.setProperty(RegistryResources.ServiceProperties.POLICY_TYPE, policyType);
            policyResource.setProperty(RegistryResources.ServiceProperties.POLICY_UUID,
                                       policy.getId());

            cachingPolicyUtils.persistPoliciesToService(policy, policyPath, engagementPath,
                                                        policyResource);
            if (log.isDebugEnabled()) {
                log.debug("Caching policy is saved in the configRegistry");
            }
            description.engageModule(cachingModule);
            configRegistry.commitTransaction();
        } catch (Exception e) {
            rollbackTransaction(e);
            throw new CachingComponentException("errorSavingPolicy", e, log);
        }

        // TODO - At the moment there is no notification mechanism to notify that the policy used by the particular
        // service has changed. So all we can do is to engage the module each time after doing everything with policy.
    }


    public void globallyEngageCaching(CachingConfigData confData) throws AxisFault,
                                                                         CachingComponentException {
        // Retrieves caching module.
        AxisModule cachingModule = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);
        // Engage caching only if it is not engaged already.

        String resourcePath = getModuleResourcePath(cachingModule);

        try {
            configRegistry.beginTransaction();
            String globalPath = PersistenceUtils.getResourcePath(cachingModule);
            if (configRegistry.resourceExists(globalPath)) {
                Resource resource = configRegistry.get(globalPath);
                if (!Boolean.parseBoolean(resource
                        .getProperty(GLOBALLY_ENGAGED_CUSTOM))) {
                    resource.removeProperty(GLOBALLY_ENGAGED_CUSTOM);
                    resource.addProperty(GLOBALLY_ENGAGED_CUSTOM, "true");
                    configRegistry.put(globalPath, resource);
                }
            } else {
                Resource globalResource = configRegistry.newResource();
                globalResource.addProperty(GLOBALLY_ENGAGED_CUSTOM, "true");
                configRegistry.put(globalPath, globalResource);
            }

            // Gets a Policy object representing the configuration data.
            Policy policy = confData.toPolicy();

            // Add new policy to module
            this.handleNewPolicyAddition(policy, cachingModule.getPolicySubject(), confData);

            // Save the policy in the configRegistry.
            Resource policyResource = configRegistry.newResource();
            policyResource.setProperty(RegistryResources.ModuleProperties.POLICY_TYPE,
                                       "" + PolicyInclude.AXIS_MODULE_POLICY);
            policyResource.setProperty(RegistryResources.ModuleProperties.POLICY_UUID,
                                       policy.getId());
            policyResource.setProperty(RegistryResources.ModuleProperties.VERSION,
                                       cachingModule.getVersion().toString());
            cachingPolicyUtils.persistPoliciesToService(policy, resourcePath, null,
                                                        policyResource);
            if (log.isDebugEnabled()) {
                log.debug("Caching policy is saved in the configRegistry");
            }

            cachingModule.addParameter(new Parameter(GLOBALLY_ENGAGED_PARAM_NAME, "true"));

            //engage the module for every service which is not an admin service
            for (Iterator serviceIter = this.axisConfig.getServices().values().iterator();
                 serviceIter.hasNext();) {
                AxisService service = (AxisService) serviceIter.next();
                String adminParamValue =
                        (String) service.getParent().getParameterValue(ADMIN_SERVICE_PARAM_NAME);

                //avoid admin services
                if (adminParamValue != null && adminParamValue.length() != 0 &&
                    Boolean.parseBoolean(adminParamValue.trim())) {
                    continue;
                }
                this.engageCachingForService(service.getName(), confData);
            }
            configRegistry.commitTransaction();
        } catch (Exception e) {
            rollbackTransaction(e);
            log.error("Error occured in globally engaging caching", e);
            throw new CachingComponentException("errorEngagingModuleAtRegistry", log);
        }
    }

    /**
     * Disables caching from the given service
     *
     * @param serviceName the name of the service from which caching should be disabled
     * @throws CachingComponentException if disengaging caching is unsuccessful
     */
    public void disengageCachingForService(String serviceName) throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Disabling caching for the service: " + serviceName);
        }

        // Retrieves the AxisService instance corresponding to the serviceName.
        AxisService service = retrieveAxisService(serviceName);

        String servicePath = RegistryResources.SERVICE_GROUPS
                             + service.getAxisServiceGroup().getServiceGroupName()
                             + RegistryResources.SERVICES + serviceName;

        try {
            this.disableCaching(service, servicePath);
        } catch (AxisFault af) {
            throw new CachingComponentException("errorDisablingCaching",
                                                new String[]{serviceName}, af, log);
        }

        if (log.isDebugEnabled()) {
            log.debug("Disengaged caching for the Axis service: " + serviceName);
        }
    }

    /**
     * Disables caching from the given operation
     *
     * @param serviceName   the name of the service from which caching should be disabled
     * @param operationName the name of the operation
     * @return true if already engaged caching at the service level, else false
     * @throws CachingComponentException if disengaging caching is unsuccessful
     */
    public boolean disengageCachingForOperation(String serviceName, String
            operationName) throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Disabling caching for the operation: " + operationName +
                      "service: " + serviceName);
        }

        // Retrieves the AxisService instance corresponding to the serviceName.
        AxisService service = retrieveAxisService(serviceName);

        // Retrieves caching module.
        AxisModule cachingModule = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);

        if (service.isEngaged(cachingModule)) {
            return true;
        }

        //get AxisOperation
        AxisOperation operation = service.getOperation(new QName(operationName));

        String operationPath = RegistryResources.SERVICE_GROUPS
                               + service.getAxisServiceGroup().getServiceGroupName()
                               + RegistryResources.SERVICES + serviceName + RegistryResources.OPERATIONS
                               + operationName;

        try {
            this.disableCaching(operation, operationPath);
        } catch (AxisFault af) {
            throw new CachingComponentException("errorDisablingCaching",
                                                new String[]{serviceName + "operation : " + operationName}, af, log);
        }

        if (log.isDebugEnabled()) {
            log.debug("Disengaged caching for the Axis operation: " + operationName +
                      "service: " + serviceName);
        }
        return false;
    }


    /**
     * disengages caching from description
     *
     * @param description    - AxisService or AxisOperation
     * @param engagementPath - service path or operation path
     * @throws CachingComponentException - error
     * @throws AxisFault                 - error on AxisDescription
     */
    private void disableCaching(AxisDescription description, String engagementPath)
            throws CachingComponentException, AxisFault {
        // Removes caching from both the configRegistry and the description
        try {
            AxisModule cachingModule = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);

            configRegistry.beginTransaction();
            /* TODO - replace these two lines with moduleResourcePath += cachingModule.getVersion()
           This is done at the moment
            */
            // Removes the association from the configRegistry.
            configRegistry.removeAssociation(engagementPath, getModuleResourcePath(cachingModule),
                                       RegistryResources.Associations.ENGAGED_MODULES);

            // Disengage from description
            description.disengageModule(cachingModule);
            configRegistry.commitTransaction();
        } catch (Exception e) {
            rollbackTransaction(e);
            throw new CachingComponentException("errorDisablingAtRegistry", e, log);
        }
    }

    private void rollbackTransaction(Exception e) {
        try {
            configRegistry.rollbackTransaction();
        } catch (RegistryException e1) {
            log.error("Cannot rollback configRegistry transaction", e);
        }
    }


    public void disengageGlobalCaching() throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Disengaging globally engaged caching");
        }
        //disengage the caching module
        try {
            //get the caching module from the current axis config
            AxisModule module = this.axisConfig
                    .getModule(CachingComponentConstants.CACHING_MODULE);

            String resourcePath = getModuleResourcePath(module);
            configRegistry.beginTransaction();

            String globalPath = PersistenceUtils.getResourcePath(module);
            if (configRegistry.resourceExists(globalPath)) {
                Resource resource = configRegistry.get(globalPath);
                if (Boolean.parseBoolean(resource
                        .getProperty(GLOBALLY_ENGAGED_CUSTOM))) {
                    resource.removeProperty(GLOBALLY_ENGAGED_CUSTOM);
                    resource.addProperty(GLOBALLY_ENGAGED_CUSTOM, "false");
                    configRegistry.put(globalPath, resource);
                }
            }

            Parameter param = module.getParameter(GLOBALLY_ENGAGED_PARAM_NAME);
            if (param != null) {
                module.removeParameter(module.getParameter(GLOBALLY_ENGAGED_PARAM_NAME));
            }

            //disengage throttling from all the services which are not admin services
            for (Iterator serviceIter = this.axisConfig.getServices().values().iterator();
                 serviceIter.hasNext();) {
                AxisService service = (AxisService) serviceIter.next();
                String adminParamValue =
                        (String) service.getParent().getParameterValue(ADMIN_SERVICE_PARAM_NAME);
                if (adminParamValue != null && adminParamValue.length() != 0 &&
                    Boolean.parseBoolean(adminParamValue.trim())) {
                    continue;
                }
                this.disengageCachingForService(service.getName());
            }
            configRegistry.commitTransaction();
        } catch (RegistryException e) {
            rollbackTransaction(e);
            log.error("Error occured while removing global caching from configRegistry", e);
            throw new CachingComponentException("errorDisablingAtRegistry", log);
        } catch (AxisFault e) {
            rollbackTransaction(e);
            log.error("Error occured while disengaging module from AxisService", e);
            throw new CachingComponentException("errorDisablingCaching", log);
        }
    }

    /**
     * Retrieves the caching configuration associated with the given service
     *
     * @param serviceName the name of the service from which caching configuration
     *                    should be retrieved
     * @return <code>CachingConfigData</code> instance containing the configuration details
     * @throws CachingComponentException if retrieving configuration is unsuccessful
     */
    public CachingConfigData getCachingPolicyForService(String serviceName)
            throws CachingComponentException {
        // Retrieves the AxisService instance corresponding to the serviceName.
        AxisService service = retrieveAxisService(serviceName);

        Collection policyComponents = service.getPolicySubject().getAttachedPolicyComponents();
        return getCachingConfig(policyComponents);
    }

    /**
     * Retrieves the caching configuration associated with the given service
     *
     * @param serviceName   the name of the service from which caching configuration
     *                      should be retrieved
     * @param operationName name of the operation
     * @return <code>CachingConfigData</code> instance containing the configuration details
     * @throws CachingComponentException if retrieving configuration is unsuccessful
     */
    public CachingConfigData getCachingPolicyForOperation(String serviceName, String operationName)
            throws CachingComponentException {
        // Retrieves the AxisService instance corresponding to the serviceName.
        AxisService service = retrieveAxisService(serviceName);
        AxisOperation operation = service.getOperation(new QName(operationName));

        AxisModule module = this.axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);

        Policy[] arr = null;
        Collection policyComponents = null;

        if (service.isEngaged(module)) {
            policyComponents = service.getPolicySubject().getAttachedPolicyComponents();
            arr = cachingPolicyUtils.retrieveCachingAssertionAndPolicy(policyComponents);
        }
        if (arr == null) {
            policyComponents = operation.getPolicySubject().getAttachedPolicyComponents();
        }
        return getCachingConfig(policyComponents);
    }


    /**
     * Returns the current global configuration
     *
     * @return CachingConfigData
     * @throws CachingComponentException - on error
     */
    public CachingConfigData getGlobalCachingPolicy() throws CachingComponentException {
        AxisModule module = this.axisConfig
                .getModule(CachingComponentConstants.CACHING_MODULE);

        Collection policyComponents = module.getPolicySubject().getAttachedPolicyComponents();
        return getCachingConfig(policyComponents);
    }


    /**
     * Handles the policy addition to given policy subject
     *
     * @param builtPolicy   - policy built from new configs
     * @param policySubject - service or operation level policy subject
     * @param confData      - new config data
     */
    private void handleNewPolicyAddition(Policy builtPolicy, PolicySubject
            policySubject, CachingConfigData confData) {
        Collection policyComponents = policySubject.getAttachedPolicyComponents();
        if (policyComponents == null) {

            // No policy components found. So we add the new caching policy directly.
            policySubject.attachPolicy(builtPolicy);
            if (log.isDebugEnabled()) {
                log.debug("Used the new policy configuration as no " +
                          "existing policy components were found");
            }
        } else {
            Policy[] arr = cachingPolicyUtils.retrieveCachingAssertionAndPolicy(policyComponents);
            if (arr == null) {

                // No caching assertion found. So we add the new caching policy directly.
                policySubject.attachPolicy(builtPolicy);
                if (log.isDebugEnabled()) {
                    log.debug("Used the new policy configuration as no existing " +
                              "caching assertion was found");
                }
            } else {

                // Caching assertion found in service policy. So we update it with the given
                // configuration values.
                cachingPolicyUtils.updateCachingAssertion(arr[0], confData);
                policySubject.updatePolicy(arr[1]);
                if (log.isDebugEnabled()) {
                    log.debug("The existing caching policy is updated with the new configuration data");
                }
            }
        }
    }

    private CachingConfigData getCachingConfig(Collection policyComponents) {
        Policy[] arr;

        // Checks whether the service is already associated with any caching policy.
        if (policyComponents != null &&
            (arr = cachingPolicyUtils.retrieveCachingAssertionAndPolicy(policyComponents)) != null) {
            if (log.isDebugEnabled()) {
                log.debug("Returns the configuration data generated from the exisiting caching policy");
            }
            return cachingPolicyUtils.generateConfigurationFromPolicy(arr[0]);
        }
        return null;
    }

    /**
     * Returns true if the given eservice is engaged with caching
     *
     * @param serviceName the name of the service which is to be checked for the availability of
     *                    caching
     * @return <cdoe>true</code> if the service for the given <code>serviceName</code> is engaged
     *         with caching and else <code>false</code>
     * @throws CachingComponentException if retrieving of the availability of caching is unsuccessful
     */
    public boolean isCachingEnabledForService(String serviceName) throws CachingComponentException {
        AxisService service = retrieveAxisService(serviceName);
        AxisModule module = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);
        return service.isEngaged(module);
    }

    /**
     * Returns true if the given eservice is engaged with caching
     *
     * @param serviceName   the name of the service which is to be checked for the availability of
     *                      caching
     * @param operationName name of the operation
     * @return <cdoe>true</code> if the service for the given <code>serviceName</code> is engaged
     *         with caching and else <code>false</code>
     * @throws CachingComponentException if retrieving of the availability of caching is unsuccessful
     */
    public boolean isCachingEnabledForOperation(String serviceName, String operationName)
            throws CachingComponentException {
        AxisService service = retrieveAxisService(serviceName);
        AxisOperation operation = service.getOperation(new QName(operationName));
        AxisModule module = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);
        return operation.isEngaged(module) || service.isEngaged(module);
    }


    public boolean isCachingGloballyEnabled() throws CachingComponentException {
        AxisModule module = axisConfig.getModule(CachingComponentConstants.CACHING_MODULE);
        Parameter param = module.getParameter(GLOBALLY_ENGAGED_PARAM_NAME);
        if (param != null) {
            String globallyEngaged = (String) param.getValue();
            if (globallyEngaged != null && globallyEngaged.length() != 0) {
                return Boolean.parseBoolean(globallyEngaged.trim());
            }
        }
        return false;
    }

    /**
     * Retrieves the <code>AxisService</code> instance for the given <code>serviceName</code>.
     *
     * @param serviceName the name of the axis service to be retrieved
     * @return the <code>AxisService</code> instance for the given <code>serviceName</code>.
     * @throws CachingComponentException if the retrieval is unsuccessful
     */
    private AxisService retrieveAxisService(String serviceName) throws CachingComponentException {
        if (log.isDebugEnabled()) {
            log.debug("Retrieving Axis service: " + serviceName);
        }
        AxisService axisService = axisConfig.getServiceForActivation(serviceName);
        if (axisService == null) {
            throw new CachingComponentException("noSuchService",
                                                new String[]{serviceName}, log);
        }
        return axisService;
    }

    /**
     * Resource path of the caching module including its version.
     *
     * @param axisModule AxisModule
     * @return module loation
     */
    private String getModuleResourcePath(AxisModule axisModule) {
        String moduleName = axisModule.getName();
        String moduleVersion = axisModule.getVersion().toString();
        if (moduleVersion == null || moduleVersion.length() == 0) {
            moduleVersion = "SNAPSHOT";
        }
        return RegistryResources.MODULES + moduleName + "/" + moduleVersion;
    }
}
TOP

Related Classes of org.wso2.carbon.caching.service.CachingConfigAdminService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.