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 else if (referenceValue.startsWith("tel:")) 110 { 111 return CHECK.SUCCESS; 112 } 113 } 114 115 if (getLogger().isDebugEnabled()) 116 { 117 getLogger().debug("Cannot test external link '" + referenceValue + "'"); 118 } 119 120 return CHECK.UNKNOWN; 121 } 122 else 123 { 124 return resolver.checkLink(referenceValue, shortTest); 125 } 126 } 127 128 /** 129 * Get the label of a reference. 130 * @param referenceType The type of the reference to test (can be 'attachment', 'explorer', 'metadata', '__external', etc...) 131 * @param referenceValue The value of the reference to test 132 * @return the element label. 133 */ 134 public I18nizableText getLabel(String referenceType, String referenceValue) 135 { 136 URIResolver resolver = _uriResolverEP.getResolverForType(referenceType); 137 if (resolver == null) 138 { 139 return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_EXTERNAL_LABEL", Collections.singletonList(referenceValue)); 140 } 141 else 142 { 143 return resolver.getLabel(referenceValue); 144 } 145 } 146 147 /** 148 * Test an http link 149 * @param linkValue The http url to test 150 * @param shortTest true to make a short test (with short timeout) 151 * @return The state. UNKNOWN if test cannot be done fully 152 */ 153 protected CHECK _checkHTTPLink(String linkValue, boolean shortTest) 154 { 155 try 156 { 157 CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); 158 159 URL url = new URL(linkValue.replaceAll(" ", "%20")); 160 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 161 if (shortTest) 162 { 163 connection.setConnectTimeout(1000); 164 connection.setReadTimeout(2000); 165 } 166 connection.setInstanceFollowRedirects(true); 167 connection.addRequestProperty("User-Agent", "Ametys/" + _getCMSVersion()); 168 169 if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) 170 { 171 if (getLogger().isDebugEnabled()) 172 { 173 getLogger().debug("Check consistency OK for URI of external link with uri '" + linkValue + "'"); 174 } 175 return CHECK.SUCCESS; 176 } 177 else if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP /* 302 */ || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM /* 301 */) 178 { 179 // redirection changing the protocol 180 return CHECK.UNKNOWN; 181 } 182 else 183 { 184 if (getLogger().isDebugEnabled()) 185 { 186 getLogger().debug("Check consistency " + connection.getResponseCode() + " for URI of external link with uri '" + linkValue + "'"); 187 } 188 189 int responseCode = connection.getResponseCode(); 190 191 switch (responseCode) 192 { 193 case HttpURLConnection.HTTP_NOT_FOUND: 194 return CHECK.NOT_FOUND; 195 case HttpURLConnection.HTTP_FORBIDDEN: 196 case HttpURLConnection.HTTP_UNAUTHORIZED: 197 return CHECK.UNAUTHORIZED; 198 case HttpURLConnection.HTTP_INTERNAL_ERROR: 199 default: 200 return CHECK.SERVER_ERROR; 201 } 202 } 203 } 204 catch (SSLHandshakeException e) 205 { 206 if (getLogger().isDebugEnabled()) 207 { 208 getLogger().debug("Certificate error for link '" + linkValue + "'", e); 209 } 210 return CHECK.UNKNOWN; 211 } 212 catch (SocketTimeoutException e) 213 { 214 if (getLogger().isDebugEnabled()) 215 { 216 getLogger().debug("Aborting test for link '" + linkValue + "' because too long", e); 217 } 218 return CHECK.UNKNOWN; 219 } 220 catch (UnknownHostException e) 221 { 222 if (getLogger().isDebugEnabled()) 223 { 224 getLogger().debug("Unknown host for link '" + linkValue + "'", e); 225 } 226 return CHECK.NOT_FOUND; 227 } 228 catch (IOException e) 229 { 230 getLogger().error("Cannot test link '" + linkValue + "'", e); 231 return CHECK.SERVER_ERROR; 232 } 233 finally 234 { 235 ((CookieManager) CookieHandler.getDefault()).getCookieStore().removeAll(); 236 } 237 238 } 239 240 private String _getCMSVersion() 241 { 242 Collection<Version> versions = _versionsHandler.getVersions(); 243 for (Version version : versions) 244 { 245 if (version.getName().equals("CMS")) 246 { 247 return version.getVersion(); 248 } 249 } 250 return null; 251 } 252}