001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.cms.transformation;
018
019import java.net.CookieHandler;
020import java.net.CookieManager;
021import java.net.CookiePolicy;
022import java.util.Collection;
023import java.util.Collections;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.core.util.HttpUrlUtils;
032import org.ametys.core.util.HttpUrlUtils.HttpCheck;
033import org.ametys.core.version.Version;
034import org.ametys.core.version.VersionsHandler;
035import org.ametys.runtime.i18n.I18nizableText;
036
037/**
038 * Consistency checker component.
039 * Checks the state of the links extracted as outgoing references for a content.
040 */
041public class ConsistencyChecker extends AbstractLogEnabled implements Serviceable, Component
042{
043    /** Avalon role */
044    public static final String ROLE = ConsistencyChecker.class.getName();
045    
046    /** Constants for the special external type for outgoing references */
047    public static final String CONSISTENCY_EXTERNAL_REFERENCE_TYPE = "__external";
048    
049    /** The state of the check */
050    public enum CHECK
051    {
052        /** If the check was all right */
053        SUCCESS,
054        /** Server Error. */
055        SERVER_ERROR,
056        /** Not Found.*/
057        NOT_FOUND,
058        /** Unauthorized. */
059        UNAUTHORIZED,
060        /** If the check may be too long */
061        UNKNOWN;
062        
063        /**
064         * Convert a {@link HttpCheck} value to a {@link CHECK} value
065         * @param httpCheck the HTTP check
066         * @return the CHECK value
067         */
068        public static CHECK from(HttpCheck httpCheck)
069        {
070            switch (httpCheck)
071            {
072                case SUCCESS:
073                    return CHECK.SUCCESS;
074                    
075                case TIMEOUT:
076                case SECURITY_LEVEL_ERROR:
077                case REDIRECT:
078                    return CHECK.UNKNOWN;
079                  
080                case NOT_FOUND:
081                    return CHECK.NOT_FOUND;
082                    
083                case SERVER_ERROR:
084                default:
085                    return CHECK.SERVER_ERROR;
086            }
087        }
088    }
089    
090    /** The ametys uri resolver */
091    protected URIResolverExtensionPoint _uriResolverEP;
092    /** The version handler */
093    protected VersionsHandler _versionsHandler;
094    /** The URL utils */
095    protected HttpUrlUtils _urlUtils;
096    
097    @Override
098    public void service(ServiceManager manager) throws ServiceException
099    {
100        _uriResolverEP = (URIResolverExtensionPoint) manager.lookup(URIResolverExtensionPoint.ROLE);
101        _versionsHandler = (VersionsHandler) manager.lookup(VersionsHandler.ROLE);
102        _urlUtils = (HttpUrlUtils) manager.lookup(HttpUrlUtils.ROLE);
103    }
104    
105    /**
106     * Check the consistency of a reference
107     * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...)
108     * @param referenceValue The value of the reference to test
109     * @param shortTest true to make a short test, that means that long tests will return UNKNOWN immediately. If false, you will always have a SUCCESS or a FAILURE.
110     * @return CHECK enum value
111     */
112    public CHECK checkConsistency(String referenceType, String referenceValue, boolean shortTest)
113    {
114        if (getLogger().isDebugEnabled())
115        {
116            getLogger().debug("Checking consistency for URI of type '" + referenceType + "' with uri '" + referenceValue + "'");
117        }
118        
119        URIResolver resolver = _uriResolverEP.getResolverForType(referenceType);
120        if (resolver == null)
121        {
122            // No resolver for external references
123            if (CONSISTENCY_EXTERNAL_REFERENCE_TYPE.equals(referenceType))
124            {
125                if (referenceValue.startsWith("http:") || referenceValue.startsWith("https:"))
126                {
127                    return _checkHTTPLink(referenceValue, shortTest);
128                }
129                else if (referenceValue.startsWith("mailto:"))
130                {
131                    return CHECK.SUCCESS;
132                }
133                else if (referenceValue.startsWith("tel:"))
134                {
135                    return CHECK.SUCCESS;
136                }
137            }
138            
139            if (getLogger().isDebugEnabled())
140            {
141                getLogger().debug("Cannot test external link '" + referenceValue + "'");
142            }
143            
144            return CHECK.UNKNOWN;
145        }
146        else
147        {
148            return resolver.checkLink(referenceValue, shortTest);
149        }
150    }
151    
152    /**
153     * Get the label of a reference.
154     * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...)
155     * @param referenceValue The value of the reference to test
156     * @return the element label.
157     */
158    public I18nizableText getLabel(String referenceType, String referenceValue)
159    {
160        URIResolver resolver = _uriResolverEP.getResolverForType(referenceType);
161        if (resolver == null)
162        {
163            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_EXTERNAL_LABEL", Collections.singletonList(referenceValue));
164        }
165        else
166        {
167            return resolver.getLabel(referenceValue);
168        }
169    }
170    
171    /**
172     * Test an http link
173     * @param linkValue The http url to test
174     * @param shortTest true to make a short test (with short timeout)
175     * @return The state. UNKNOWN if test cannot be done fully
176     */
177    protected CHECK _checkHTTPLink(String linkValue, boolean shortTest)
178    {
179        try
180        {
181            CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
182            
183            String httpUrl = linkValue.replaceAll(" ", "%20");
184            String userAgent = "Ametys/" + _getCMSVersion();
185            int timeout = shortTest ? 1000 : -1;
186            int readTimeout = shortTest ? 2000 : -1;
187            HttpCheck checkURL = HttpUrlUtils.checkHttpUrl(httpUrl, userAgent, null, timeout, readTimeout, true);
188            
189            return CHECK.from(checkURL);
190        }
191        finally
192        {
193            ((CookieManager) CookieHandler.getDefault()).getCookieStore().removeAll();
194        }
195    }
196    
197    private String _getCMSVersion()
198    {
199        Collection<Version> versions = _versionsHandler.getVersions();
200        for (Version version : versions)
201        {
202            if (version.getName().equals("CMS"))
203            {
204                return version.getVersion();
205            }
206        }
207        return null;
208    }
209}