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.io.IOException; 020import java.net.CookieHandler; 021import java.net.CookieManager; 022import java.net.CookiePolicy; 023import java.net.HttpURLConnection; 024import java.net.SocketTimeoutException; 025import java.net.URL; 026import java.net.UnknownHostException; 027import java.util.Collection; 028import java.util.Collections; 029 030import javax.net.ssl.SSLHandshakeException; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.logger.AbstractLogEnabled; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037 038import org.ametys.core.version.Version; 039import org.ametys.core.version.VersionsHandler; 040import org.ametys.runtime.i18n.I18nizableText; 041 042/** 043 * Consistency checker component. 044 * Checks the state of the links extracted as outgoing references for a content. 045 */ 046public class ConsistencyChecker extends AbstractLogEnabled implements Serviceable, Component 047{ 048 /** Avalon role */ 049 public static final String ROLE = ConsistencyChecker.class.getName(); 050 051 /** Constants for the special external type for outgoing references */ 052 public static final String CONSISTENCY_EXTERNAL_REFERENCE_TYPE = "__external"; 053 054 /** The state of the check */ 055 public enum CHECK 056 { 057 /** If the check was all right */ 058 SUCCESS, 059 /** Server Error. */ 060 SERVER_ERROR, 061 /** Not Found.*/ 062 NOT_FOUND, 063 /** Unauthorized. */ 064 UNAUTHORIZED, 065 /** If the check may be too long */ 066 UNKNOWN 067 } 068 069 /** The ametys uri resolver */ 070 protected URIResolverExtensionPoint _uriResolverEP; 071 /** The version handler */ 072 protected VersionsHandler _versionsHandler; 073 074 @Override 075 public void service(ServiceManager manager) throws ServiceException 076 { 077 _uriResolverEP = (URIResolverExtensionPoint) manager.lookup(URIResolverExtensionPoint.ROLE); 078 _versionsHandler = (VersionsHandler) manager.lookup(VersionsHandler.ROLE); 079 } 080 081 /** 082 * Check the consistency of a reference 083 * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...) 084 * @param referenceValue The value of the reference to test 085 * @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. 086 * @return CHECK enum value 087 */ 088 public CHECK checkConsistency(String referenceType, String referenceValue, boolean shortTest) 089 { 090 if (getLogger().isDebugEnabled()) 091 { 092 getLogger().debug("Checking consistency for URI of type '" + referenceType + "' with uri '" + referenceValue + "'"); 093 } 094 095 URIResolver resolver = _uriResolverEP.getResolverForType(referenceType); 096 if (resolver == null) 097 { 098 // No resolver for external references 099 if (CONSISTENCY_EXTERNAL_REFERENCE_TYPE.equals(referenceType)) 100 { 101 if (referenceValue.startsWith("http:") || referenceValue.startsWith("https:")) 102 { 103 return _checkHTTPLink(referenceValue, shortTest); 104 } 105 else if (referenceValue.startsWith("mailto:")) 106 { 107 return CHECK.SUCCESS; 108 } 109 } 110 111 if (getLogger().isDebugEnabled()) 112 { 113 getLogger().debug("Cannot test external link '" + referenceValue + "'"); 114 } 115 116 return CHECK.UNKNOWN; 117 } 118 else 119 { 120 return resolver.checkLink(referenceValue, shortTest); 121 } 122 } 123 124 /** 125 * Get the label of a reference. 126 * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...) 127 * @param referenceValue The value of the reference to test 128 * @return the element label. 129 */ 130 public I18nizableText getLabel(String referenceType, String referenceValue) 131 { 132 URIResolver resolver = _uriResolverEP.getResolverForType(referenceType); 133 if (resolver == null) 134 { 135 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_EXTERNAL_LABEL", Collections.singletonList(referenceValue)); 136 } 137 else 138 { 139 return resolver.getLabel(referenceValue); 140 } 141 } 142 143 /** 144 * Test an http link 145 * @param linkValue The http url to test 146 * @param shortTest true to make a short test (with short timeout) 147 * @return The state. UNKNOWN if test cannot be done fully 148 */ 149 protected CHECK _checkHTTPLink(String linkValue, boolean shortTest) 150 { 151 try 152 { 153 CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); 154 155 URL url = new URL(linkValue.replaceAll(" ", "%20")); 156 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 157 if (shortTest) 158 { 159 connection.setConnectTimeout(1000); 160 connection.setReadTimeout(2000); 161 } 162 connection.setInstanceFollowRedirects(true); 163 connection.addRequestProperty("User-Agent", "Ametys/" + _getCMSVersion()); 164 165 if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) 166 { 167 if (getLogger().isDebugEnabled()) 168 { 169 getLogger().debug("Check consistency OK for URI of external link with uri '" + linkValue + "'"); 170 } 171 return CHECK.SUCCESS; 172 } 173 else if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP /* 302 */ || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM /* 301 */) 174 { 175 // redirection changing the protocol 176 return CHECK.UNKNOWN; 177 } 178 else 179 { 180 if (getLogger().isDebugEnabled()) 181 { 182 getLogger().debug("Check consistency " + connection.getResponseCode() + " for URI of external link with uri '" + linkValue + "'"); 183 } 184 185 int responseCode = connection.getResponseCode(); 186 187 switch (responseCode) 188 { 189 case HttpURLConnection.HTTP_NOT_FOUND: 190 return CHECK.NOT_FOUND; 191 case HttpURLConnection.HTTP_FORBIDDEN: 192 case HttpURLConnection.HTTP_UNAUTHORIZED: 193 return CHECK.UNAUTHORIZED; 194 case HttpURLConnection.HTTP_INTERNAL_ERROR: 195 default: 196 return CHECK.SERVER_ERROR; 197 } 198 } 199 } 200 catch (SSLHandshakeException e) 201 { 202 if (getLogger().isDebugEnabled()) 203 { 204 getLogger().debug("Certificate error for link '" + linkValue + "'", e); 205 } 206 return CHECK.UNKNOWN; 207 } 208 catch (SocketTimeoutException e) 209 { 210 if (getLogger().isDebugEnabled()) 211 { 212 getLogger().debug("Aborting test for link '" + linkValue + "' because too long", e); 213 } 214 return CHECK.UNKNOWN; 215 } 216 catch (UnknownHostException e) 217 { 218 if (getLogger().isDebugEnabled()) 219 { 220 getLogger().debug("Unknown host for link '" + linkValue + "'", e); 221 } 222 return CHECK.NOT_FOUND; 223 } 224 catch (IOException e) 225 { 226 getLogger().error("Cannot test link '" + linkValue + "'", e); 227 return CHECK.SERVER_ERROR; 228 } 229 finally 230 { 231 ((CookieManager) CookieHandler.getDefault()).getCookieStore().removeAll(); 232 } 233 234 } 235 236 private String _getCMSVersion() 237 { 238 Collection<Version> versions = _versionsHandler.getVersions(); 239 for (Version version : versions) 240 { 241 if (version.getName().equals("CMS")) 242 { 243 return version.getVersion(); 244 } 245 } 246 return null; 247 } 248}