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