cziegeler 2003/01/09 23:42:11 Added: src/java/org/apache/cocoon/components/pipeline/impl NewAbstractCachingProcessingPipeline.java Log: Start refactoring caching algorithm for three reasons: a) It's not working 100% b) It's too complicated to understand (the methods are too long etc.) c) It's doing too much in some cases (optimizing) Revision Changes Path 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/pipeline/impl/NewAbstractCachingProcessingPipeline.java Index: NewAbstractCachingProcessingPipeline.java =================================================================== /* ============================================================================ The Apache Software License, Version 1.1 ============================================================================ Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modifica- tion, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Apache Cocoon" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact [EMAIL PROTECTED] 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation and was originally created by Stefano Mazzocchi <[EMAIL PROTECTED]>. For more information on the Apache Software Foundation, please see <http://www.apache.org/>. */ package org.apache.cocoon.components.pipeline.impl; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ConnectionResetException; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.caching.*; import org.apache.cocoon.components.pipeline.AbstractProcessingPipeline; import org.apache.cocoon.components.sax.XMLDeserializer; import org.apache.cocoon.components.sax.XMLSerializer; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.transformation.Transformer; import org.apache.excalibur.source.SourceValidity; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.net.SocketException; import java.util.ArrayList; /** * This is the base class for all caching pipeline implementations. * * * @since 2.1 * @author <a href="mailto:[EMAIL PROTECTED]">Carsten Ziegeler</a> * @author <a href="mailto:[EMAIL PROTECTED]">Michael Melhem</a> * @version CVS $Id: NewAbstractCachingProcessingPipeline.java,v 1.1 2003/01/10 07:42:11 cziegeler Exp $ */ public abstract class NewAbstractCachingProcessingPipeline extends AbstractProcessingPipeline implements Disposable { /** This is the Cache holding cached responses */ protected Cache cache; /** The role name of the generator */ protected String generatorRole; /** The role names of the transfomrers */ protected ArrayList transformerRoles = new ArrayList(); /** The role name of the serializer */ protected String serializerRole; /** The role name of the reader */ protected String readerRole; /** The deserializer */ protected XMLDeserializer xmlDeserializer; /** The serializer */ protected XMLSerializer xmlSerializer; /** The cached byte stream */ protected byte[] cachedResponse; /** The index indicating the first transformer getting input from the cache */ protected int firstProcessedTransformerIndex; /** Complete response is cached */ protected boolean completeResponseIsCached; /** This key indicates the response that is fetched from the cache */ protected PipelineCacheKey fromCacheKey; /** This key indicates the response that will get into the cache */ protected PipelineCacheKey toCacheKey; /** The validity objects of the generated response */ protected SourceValidity[] toCacheValidityObjects; /** The index indicating to the first transformer which is not cacheable */ protected int firstNotCacheableTransformerIndex; /** Cache complete response */ protected boolean cacheCompleteResponse; protected boolean generatorIsCacheableProcessingComponent; protected boolean serializerIsCacheableProcessingComponent; protected boolean[] transformerIsCacheableProcessingComponent; /** Smart caching ? */ protected boolean doSmartCaching; /** * Abstract methods defined in subclasses */ protected abstract void cacheResults(Environment environment, OutputStream os) throws Exception; protected abstract ComponentCacheKey newComponentCacheKey(int type, String role,Serializable key); protected abstract void connectCachingPipeline(Environment environment) throws ProcessingException; /** * Composable Interface */ public void compose (ComponentManager manager) throws ComponentException { super.compose(manager); this.cache = (Cache)this.manager.lookup(Cache.ROLE); } /** * Setup this component */ public void setup(Parameters params) { super.setup(params); this.doSmartCaching = params.getParameterAsBoolean("smart-caching", this.configuration.getParameterAsBoolean("smart-caching", true)); } /** * Set the generator. */ public void setGenerator (String role, String source, Parameters param, Parameters hintParam) throws ProcessingException { super.setGenerator(role, source, param, hintParam); this.generatorRole = role; } /** * Add a transformer. */ public void addTransformer (String role, String source, Parameters param, Parameters hintParam) throws ProcessingException { super.addTransformer(role, source, param, hintParam); this.transformerRoles.add(role); } /** * Set the serializer. */ public void setSerializer (String role, String source, Parameters param, Parameters hintParam, String mimeType) throws ProcessingException { super.setSerializer(role, source, param, hintParam, mimeType); this.serializerRole = role; } /** * Set the Reader. */ public void setReader (String role, String source, Parameters param, String mimeType) throws ProcessingException { super.setReader(role, source, param, mimeType); this.readerRole = role; } /** * Process the given <code>Environment</code>, producing the output. */ protected boolean processXMLPipeline(Environment environment) throws ProcessingException { if (this.toCacheKey == null && this.cachedResponse == null) { return super.processXMLPipeline( environment ); } else if (this.cachedResponse != null && this.completeResponseIsCached) { try { final OutputStream outputStream = environment.getOutputStream(0); if (this.cachedResponse.length > 0) { environment.setContentLength(this.cachedResponse.length); outputStream.write(this.cachedResponse); } } catch ( SocketException se ) { if (se.getMessage().indexOf("reset") > 0 || se.getMessage().indexOf("aborted") > 0) { throw new ConnectionResetException("Connection reset by peer", se); } else { throw new ProcessingException("Failed to execute reader pipeline.", se); } } catch ( Exception e ) { if (e instanceof ProcessingException) throw (ProcessingException)e; throw new ProcessingException("Error executing reader pipeline.",e); } } else { if (this.getLogger().isDebugEnabled() && this.toCacheKey != null) { this.getLogger().debug("Caching content for further requests of '" + environment.getURI() + "' using key " + this.toCacheKey); } try { OutputStream os = null; if ( this.cacheCompleteResponse && this.toCacheKey != null) { os = new CachingOutputStream( environment.getOutputStream(this.outputBufferSize) ); } if ( super.serializer != super.lastConsumer ) { if (os == null) { os = environment.getOutputStream(this.outputBufferSize); } // internal processing if ( this.xmlDeserializer != null ) { this.xmlDeserializer.deserialize(this.cachedResponse); } else { this.generator.generate(); } } else { if (this.serializer.shouldSetContentLength()) { if (os == null) { os = environment.getOutputStream(0); } // set the output stream ByteArrayOutputStream baos = new ByteArrayOutputStream(); this.serializer.setOutputStream(baos); // execute the pipeline: if ( this.xmlDeserializer != null ) { this.xmlDeserializer.deserialize(this.cachedResponse); } else { this.generator.generate(); } byte[] data = baos.toByteArray(); environment.setContentLength(data.length); os.write(data); } else { if (os == null) { os = environment.getOutputStream(this.outputBufferSize); } // set the output stream this.serializer.setOutputStream( os ); // execute the pipeline: if ( this.xmlDeserializer != null ) { this.xmlDeserializer.deserialize(this.cachedResponse); } else { this.generator.generate(); } } } // // Now that we have processed the pipeline, // we do the actual caching // this.cacheResults(environment,os); } catch ( SocketException se ) { if (se.getMessage().indexOf("reset") > 0 || se.getMessage().indexOf("aborted") > 0) { throw new ConnectionResetException("Connection reset by peer", se); } else { throw new ProcessingException("Failed to execute reader pipeline.", se); } } catch ( ProcessingException e ) { throw e; } catch ( Exception e ) { throw new ProcessingException("Failed to execute pipeline.", e); } return true; } return true; } /** * The components of the pipeline are checked if they are Cacheable. */ protected void generateCachingKey(Environment environment) throws ProcessingException { this.toCacheKey = null; Serializable key = null; this.generatorIsCacheableProcessingComponent = false; this.serializerIsCacheableProcessingComponent = false; this.transformerIsCacheableProcessingComponent = new boolean[this.transformers.size()]; this.firstNotCacheableTransformerIndex = 0; this.cacheCompleteResponse = false; // first step is to generate the key: // All pipeline components starting with the generator // are tested if they are either a CacheableProcessingComponent // or Cacheable (deprecated). The returned keys are chained together // to build a unique key of the request // is the generator cacheable? if (super.generator instanceof CacheableProcessingComponent) { key = ((CacheableProcessingComponent)super.generator).generateKey(); this.generatorIsCacheableProcessingComponent = true; } else if (super.generator instanceof Cacheable) { key = new Long(((Cacheable)super.generator).generateKey()); } if (key != null) { this.toCacheKey = new PipelineCacheKey(); this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Generator, this.generatorRole, key) ); // now testing transformers final int transformerSize = super.transformers.size(); boolean continueTest = true; while (this.firstNotCacheableTransformerIndex < transformerSize && continueTest) { final Transformer trans = (Transformer)super.transformers.get(this.firstNotCacheableTransformerIndex); key = null; if (trans instanceof CacheableProcessingComponent) { key = ((CacheableProcessingComponent)trans).generateKey(); this.transformerIsCacheableProcessingComponent[this.firstNotCacheableTransformerIndex] = true; } else if (trans instanceof Cacheable) { key = new Long(((Cacheable)trans).generateKey()); } if (key != null) { this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Transformer, (String)this.transformerRoles.get(this.firstNotCacheableTransformerIndex), key)); this.firstNotCacheableTransformerIndex++; } else { continueTest = false; } } // all transformers are cacheable => pipeline is cacheable // test serializer if this is not an internal request if (this.firstNotCacheableTransformerIndex == transformerSize && super.serializer == this.lastConsumer) { key = null; if (super.serializer instanceof CacheableProcessingComponent) { key = ((CacheableProcessingComponent)this.serializer).generateKey(); this.serializerIsCacheableProcessingComponent = true; } else if (this.serializer instanceof Cacheable) { key = new Long(((Cacheable)this.serializer).generateKey()); } if (key != null) { this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Serializer, (String)this.serializerRole, key) ); this.cacheCompleteResponse = true; } } } } /** * Calculate the key that can be used to get something from the cache */ protected void validatePipeline(Environment environment) throws ProcessingException { this.cacheCompleteResponse = this.completeResponseIsCached; this.fromCacheKey = this.toCacheKey.copy(); this.firstProcessedTransformerIndex = this.firstNotCacheableTransformerIndex; boolean finished = false; while (this.fromCacheKey != null && !finished) { finished = true; CachedResponse response = this.cache.get( this.fromCacheKey ); // now test validity if (response != null) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug( "Found cached response for '" + environment.getURI() + "' using key: " + this.fromCacheKey ); } boolean responseIsValid = true; boolean responseIsUsable = true; this.toCacheValidityObjects = response.getValidityObjects(); int i = 0; while (responseIsValid && i < this.toCacheValidityObjects.length) { boolean isValid = false; // BH check if validities[i] is null, may happen // if exception was thrown due to malformed content SourceValidity validity = this.toCacheValidityObjects[i]; int valid = validity != null ? validity.isValid() : -1; if ( valid == 0) { // don't know if valid, make second test if (i == 0) { // test generator if (this.generatorIsCacheableProcessingComponent) { validity = ((CacheableProcessingComponent)super.generator).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.generator).generateValidity()); } } else if (i <= this.firstProcessedTransformerIndex) { // test transformer final Transformer trans = (Transformer)super.transformers.get(i-1); if (this.transformerIsCacheableProcessingComponent[i-1]) { validity = ((CacheableProcessingComponent)trans).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)trans).generateValidity()); } } else { // test serializer if (this.serializerIsCacheableProcessingComponent) { validity = ((CacheableProcessingComponent)super.serializer).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.serializer).generateValidity()); } } if (validity != null) { isValid = this.toCacheValidityObjects[i].isValid( validity ); } } else { isValid = (valid == 1); } if ( !isValid ) { responseIsValid = false; // update validity this.toCacheValidityObjects[i] = validity; if (validity == null) responseIsUsable = false; } else { i++; } } if ( responseIsValid ) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Using valid cached content for '" + environment.getURI() + "'."); } // we are valid, ok that's it this.cachedResponse = response.getResponse(); } else { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Cached content is invalid for '" + environment.getURI() + "'."); } // we are not valid! this.completeResponseIsCached = false; finished = false; if (!responseIsUsable) { // we could not compare, because we got no // validity object, so shorten pipeline key if (i > 0) { int deleteCount = this.toCacheValidityObjects.length - i; if (i > 0 && i <= firstNotCacheableTransformerIndex + 1) { this.firstNotCacheableTransformerIndex = i-1; } for(int x=0; x < deleteCount; x++) { this.toCacheKey.removeLastKey(); } finished = false; } else { this.toCacheKey = null; } this.cacheCompleteResponse = false; } else { // the entry is invalid, remove it this.cache.remove( this.fromCacheKey ); } // try a shorter key if (i > 0) { this.fromCacheKey.removeLastKey(); this.firstProcessedTransformerIndex--; } else { this.fromCacheKey = null; } } } else { // no cached response found if (this.getLogger().isDebugEnabled()) { this.getLogger().debug( "Cached response not found for '" + environment.getURI() + "' using key: " + this.fromCacheKey ); } if (!this.doSmartCaching) { // try a shorter key if (this.fromCacheKey.size() > 1) { this.fromCacheKey.removeLastKey(); if (!this.completeResponseIsCached) { this.firstProcessedTransformerIndex--; } finished = false; } else { this.fromCacheKey = null; finished = true; } this.completeResponseIsCached = false; } else { // stop on longest key for smart caching finished = true; this.completeResponseIsCached = false; this.fromCacheKey = null; } } } } /** * Setup the evenet pipeline. * The components of the pipeline are checked if they are * Cacheable. */ protected void setupPipeline(Environment environment) throws ProcessingException { super.setupPipeline( environment ); // generate the key to fill the cache this.generateCachingKey(environment); // test the cache for a valid response if (this.toCacheKey != null) { this.validatePipeline(environment); } // now generate validity objects for the new response if (this.toCacheKey != null) { // only update validity objects if we cannot use // a cached response or when the cached response does // cache less than now is cacheable if (this.fromCacheKey == null || this.fromCacheKey.size() < this.toCacheKey.size()) { this.toCacheValidityObjects = new SourceValidity[this.toCacheKey.size()]; int start = 0; if (this.fromCacheKey != null) { start = this.fromCacheKey.size(); for(int i=0; i<start;i++) { // THIS CAN BE NULL, RIGHT?? //this.pipelineValidityObjects[i] = cachedValidityObjects[i]; } } int len = this.toCacheValidityObjects.length; int i = start; while (i < len) { final SourceValidity validity; if (i == 0) { // test generator if (generatorIsCacheableProcessingComponent) { validity = ((CacheableProcessingComponent)super.generator).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.generator).generateValidity()); } } else if (i <= firstNotCacheableTransformerIndex) { // test transformer final Transformer trans = (Transformer)super.transformers.get(i-1); if (transformerIsCacheableProcessingComponent[i-1]) { validity = ((CacheableProcessingComponent)trans).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)trans).generateValidity()); } } else { // test serializer if (serializerIsCacheableProcessingComponent) { validity = ((CacheableProcessingComponent)super.serializer).generateValidity(); } else { validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.serializer).generateValidity()); } } if (validity == null) { if (i > 0 && i > this.fromCacheKey.size()) { // shorten key for(int m=i; m < this.toCacheValidityObjects.length; m++) { this.toCacheKey.removeLastKey(); } SourceValidity[] copy = new SourceValidity[i]; System.arraycopy(this.toCacheValidityObjects, 0, copy, 0, copy.length); this.toCacheValidityObjects = copy; len = this.toCacheValidityObjects.length; } else { this.toCacheKey = null; this.toCacheValidityObjects = null; len = 0; } } else { this.toCacheValidityObjects[i] = validity; } i++; } } } } /** * Connect the pipeline. */ protected void connectPipeline(Environment environment) throws ProcessingException { if ( this.toCacheKey == null && this.cachedResponse == null) { super.connectPipeline( environment ); return; } else if (this.completeResponseIsCached) { // do nothing return; } else { this.connectCachingPipeline(environment); } } /** Process the pipeline using a reader. * @throws ProcessingException if an error occurs */ protected boolean processReader(Environment environment) throws ProcessingException { try { boolean usedCache = false; OutputStream outputStream = null; SourceValidity readerValidity = null; PipelineCacheKey pcKey = null; // test if reader is cacheable Serializable readerKey = null; boolean isCacheableProcessingComponent = false; if (super.reader instanceof CacheableProcessingComponent) { readerKey = ((CacheableProcessingComponent)super.reader).generateKey(); isCacheableProcessingComponent = true; } else if (super.reader instanceof Cacheable) { readerKey = new Long(((Cacheable)super.reader).generateKey()); } if ( readerKey != null) { // response is cacheable, build the key pcKey = new PipelineCacheKey(); pcKey.addKey(new ComponentCacheKey(ComponentCacheKey.ComponentType_Reader, this.readerRole, readerKey) ); // now we have the key to get the cached object CachedResponse cachedObject = (CachedResponse)this.cache.get( pcKey ); if (cachedObject != null) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug( "Found cached response for '" + environment.getURI() + "' using key: " + pcKey ); } SourceValidity[] validities = cachedObject.getValidityObjects(); if (validities == null || validities.length != 1) { throw new ProcessingException("Cached response is not correct."); } SourceValidity cachedValidity = validities[0]; int result = cachedValidity.isValid(); boolean valid = false; if ( result == 0 ) { // get reader validity and compare if (isCacheableProcessingComponent) { readerValidity = ((CacheableProcessingComponent)super.reader).generateValidity(); } else { CacheValidity cv = ((Cacheable)super.reader).generateValidity(); if ( cv != null ) { readerValidity = CacheValidityToSourceValidity.createValidity( cv ); } } if (readerValidity != null) { valid = cachedValidity.isValid(readerValidity); } } else { valid = (result > 0); } if (valid) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Using valid cached content for '" + environment.getURI() + "'."); } byte[] response = cachedObject.getResponse(); if (response.length > 0) { usedCache = true; outputStream = environment.getOutputStream(0); environment.setContentLength(response.length); outputStream.write(response); } } else { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Cached content is invalid for '" + environment.getURI() + "'."); } // remove invalid cached object this.cache.remove(pcKey); } } } if (!usedCache) { if ( pcKey != null ) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Caching content for further requests of '" + environment.getURI() + "'."); } if (readerValidity == null) { if (isCacheableProcessingComponent) { readerValidity = ((CacheableProcessingComponent)super.reader).generateValidity(); } else { CacheValidity cv = ((Cacheable)super.reader).generateValidity(); if ( cv != null ) { readerValidity = CacheValidityToSourceValidity.createValidity( cv ); } } } if (readerValidity != null) { outputStream = environment.getOutputStream(this.outputBufferSize); outputStream = new CachingOutputStream(outputStream); } else { pcKey = null; } } if (this.reader.shouldSetContentLength()) { ByteArrayOutputStream os = new ByteArrayOutputStream(); this.reader.setOutputStream(os); this.reader.generate(); byte[] data = os.toByteArray(); environment.setContentLength(data.length); if (outputStream == null) { outputStream = environment.getOutputStream(0); } environment.getOutputStream(0).write(data); } else { if (outputStream == null) { outputStream = environment.getOutputStream(this.outputBufferSize); } this.reader.setOutputStream(outputStream); this.reader.generate(); } // store the response if (pcKey != null) { this.cache.store( environment.getObjectModel(), pcKey, new CachedResponse( new SourceValidity[] {readerValidity}, ((CachingOutputStream)outputStream).getContent()) ); } } } catch ( SocketException se ) { if (se.getMessage().indexOf("reset") > 0 || se.getMessage().indexOf("aborted") > 0) { throw new ConnectionResetException("Connection reset by peer", se); } else { throw new ProcessingException("Failed to execute pipeline.", se); } } catch ( ProcessingException e ) { throw e; } catch ( Exception e ) { throw new ProcessingException("Failed to execute pipeline.", e); } return true; } /** * Return valid validity objects for the event pipeline * If the "event pipeline" (= the complete pipeline without the * serializer) is cacheable and valid, return all validity objects. * Otherwise return <code>null</code> */ public SourceValidity[] getValiditiesForEventPipeline() { if (!this.completeResponseIsCached && this.firstNotCacheableTransformerIndex == super.transformers.size()) { return this.toCacheValidityObjects; } return null; } /** * Recyclable Interface */ public void recycle() { super.recycle(); this.manager.release( this.xmlDeserializer ); this.xmlDeserializer = null; this.manager.release( this.xmlSerializer ); this.xmlSerializer = null; this.generatorRole = null; this.transformerRoles.clear(); this.serializerRole = null; this.readerRole = null; this.toCacheKey = null; this.toCacheValidityObjects = null; this.cachedResponse = null; this.transformerIsCacheableProcessingComponent = null; this.toCacheKey = null; } /** * Disposable Interface */ public void dispose() { this.manager.release(this.cache); this.cache = null; this.manager = null; } }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]