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 contentId The id of the content containing the reference
110     * @param dataPath The path of the data containing the reference
111     * @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.
112     * @return CHECK enum value
113     */
114    public CHECK checkConsistency(String referenceType, String referenceValue, String contentId, String dataPath, boolean shortTest)
115    {
116        if (getLogger().isDebugEnabled())
117        {
118            getLogger().debug("Checking consistency for URI of type '" + referenceType + "' with uri '" + referenceValue + "'");
119        }
120        
121        URIResolver resolver = _uriResolverEP.getResolverForType(referenceType);
122        if (resolver == null)
123        {
124            // No resolver for external references
125            if (CONSISTENCY_EXTERNAL_REFERENCE_TYPE.equals(referenceType))
126            {
127                if (referenceValue.startsWith("http:") || referenceValue.startsWith("https:"))
128                {
129                    return _checkHTTPLink(referenceValue, shortTest);
130                }
131                else if (referenceValue.startsWith("mailto:"))
132                {
133                    return CHECK.SUCCESS;
134                }
135                else if (referenceValue.startsWith("tel:"))
136                {
137                    return CHECK.SUCCESS;
138                }
139            }
140            
141            if (getLogger().isDebugEnabled())
142            {
143                getLogger().debug("Cannot test external link '" + referenceValue + "'");
144            }
145            
146            return CHECK.UNKNOWN;
147        }
148        else
149        {
150            String uri = _getURIFromAttachment(referenceType, referenceValue, contentId, dataPath);
151            return resolver.checkLink(uri, shortTest);
152        }
153    }
154    
155    /**
156     * Get the label of a reference.
157     * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...)
158     * @param referenceValue The value of the reference to test
159     * @param contentId The id of the content containing the reference
160     * @param dataPath The path of the data containing the reference
161     * @return the element label.
162     */
163    public I18nizableText getLabel(String referenceType, String referenceValue, String contentId, String dataPath)
164    {
165        URIResolver resolver = _uriResolverEP.getResolverForType(referenceType);
166        if (resolver == null)
167        {
168            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_EXTERNAL_LABEL", Collections.singletonList(referenceValue));
169        }
170        else
171        {
172            String uri = _getURIFromAttachment(referenceType, referenceValue, contentId, dataPath);
173            return resolver.getLabel(uri);
174        }
175    }
176    
177    private String _getURIFromAttachment(String referenceType, String referenceValue, String contentId, String dataPath)
178    {
179        return "local".equals(referenceType) ? contentId + "@" + dataPath + ";" + referenceValue : referenceValue;
180    }
181    
182    /**
183     * Test an http link
184     * @param linkValue The http url to test
185     * @param shortTest true to make a short test (with short timeout)
186     * @return The state. UNKNOWN if test cannot be done fully
187     */
188    protected CHECK _checkHTTPLink(String linkValue, boolean shortTest)
189    {
190        try
191        {
192            CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
193            
194            String httpUrl = linkValue.replaceAll(" ", "%20");
195            String userAgent = "Ametys/" + _getCMSVersion();
196            int timeout = shortTest ? 1000 : -1;
197            int readTimeout = shortTest ? 2000 : -1;
198            HttpCheck checkURL = HttpUrlUtils.checkHttpUrl(httpUrl, userAgent, null, timeout, readTimeout, true);
199            
200            return CHECK.from(checkURL);
201        }
202        finally
203        {
204            ((CookieManager) CookieHandler.getDefault()).getCookieStore().removeAll();
205        }
206    }
207    
208    private String _getCMSVersion()
209    {
210        Collection<Version> versions = _versionsHandler.getVersions();
211        for (Version version : versions)
212        {
213            if (version.getName().equals("CMS"))
214            {
215                return version.getVersion();
216            }
217        }
218        return null;
219    }
220}