001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.workplace.tools.content.check; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.i18n.CmsLocaleManager; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.util.CmsStringUtil; 037import org.opencms.xml.content.CmsXmlContent; 038import org.opencms.xml.content.CmsXmlContentFactory; 039 040import java.util.ArrayList; 041import java.util.List; 042import java.util.Locale; 043import java.util.regex.Pattern; 044 045import org.apache.commons.logging.Log; 046 047/** 048 * This implementation of the I_CmsContentCheck interface implements a check for 049 * resource properties.<p> 050 * 051 * The following items can be configured and checked: 052 * <ul> 053 * <li>Property not set</li> 054 * <li>Property value contains filename</li> 055 * <li>Property value is shorter than a minimum size</li> 056 * <li>Property value contains a given value (with RegEx)</li> 057 * <li>Property value does not contain a given value (with RegEx)</li> 058 * </ul> 059 * 060 * @since 6.1.2 061 */ 062public class CmsContentCheckProperty extends A_CmsContentCheck { 063 064 /** Path to the configuration file. */ 065 private static final String CONFIGURATION = CmsContentCheck.VFS_PATH_PLUGIN_FOLDER 066 + "propertycheck/configuration.xml"; 067 068 /** Name of the dialog parameter. */ 069 private static final String DIALOG_PARAMETER = "property"; 070 071 /** Path to the configuration icon. */ 072 private static final String ICONPATH = "tools/contenttools/icons/big/contentcheck_property_configuration.png"; 073 074 /** The log object for this class. */ 075 private static final Log LOG = CmsLog.getLog(CmsContentCheckProperty.class); 076 077 /** Name of this content check. */ 078 private static final String NAME = "Property Check"; 079 080 /** The xpath for the empty configuration. */ 081 private static final String XPATH_EMPTY = "empty"; 082 083 /** The xpath for the error configuration. */ 084 private static final String XPATH_ERROR = "error"; 085 086 /** The xpath for the filename configuration. */ 087 private static final String XPATH_FILENAME = "filename"; 088 089 /** The xpath for the length configuration. */ 090 private static final String XPATH_LENGTH = "length"; 091 092 /** The xpath for the propertyname configuration. */ 093 private static final String XPATH_PROPERTYNAME = "propertyname"; 094 095 /** The xpath for the type configuration. */ 096 private static final String XPATH_TYPE = "type"; 097 098 /** The xpath for the value configuration. */ 099 private static final String XPATH_VALUE = "value"; 100 101 /** The xpath for the warning configuration. */ 102 private static final String XPATH_WARNUING = "warning"; 103 104 /** The active flag, signaling if this content check is active. */ 105 private boolean m_active = true; 106 107 /** The CmsObject. */ 108 private CmsObject m_cms; 109 110 /** List of all configured error checks. */ 111 private List m_configuredErrorChecks; 112 113 /** List of all configured warning checks. */ 114 private List m_configuredWarningChecks; 115 116 /** Locale to be used to extract XML content. */ 117 private Locale m_locale; 118 119 /** 120 * 121 * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#executeContentCheck(org.opencms.file.CmsObject, org.opencms.workplace.tools.content.check.CmsContentCheckResource) 122 */ 123 @Override 124 public CmsContentCheckResource executeContentCheck(CmsObject cms, CmsContentCheckResource testResource) 125 throws CmsException { 126 127 getConfiguration(); 128 if (LOG.isDebugEnabled()) { 129 LOG.debug( 130 Messages.get().getBundle().key( 131 Messages.LOG_DEBUG_PROPERTY_CONFIGURED_ERRORS_2, 132 new Object[] {testResource.getResourceName(), m_configuredErrorChecks})); 133 } 134 // check for errors 135 List errors = processProperties(m_configuredErrorChecks, testResource); 136 if (errors.size() > 0) { 137 testResource.addErrors(errors); 138 } 139 if (LOG.isDebugEnabled()) { 140 LOG.debug( 141 Messages.get().getBundle().key( 142 Messages.LOG_DEBUG_PROPERTY_CONFIGURED_WARNINGS_2, 143 testResource.getResourceName(), 144 m_configuredErrorChecks)); 145 } 146 // check for warnings 147 List warnings = processProperties(m_configuredWarningChecks, testResource); 148 if (warnings.size() > 0) { 149 testResource.addWarnings(warnings); 150 } 151 return testResource; 152 } 153 154 /** 155 * 156 * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#getDialogParameterName() 157 */ 158 @Override 159 public String getDialogParameterName() { 160 161 return DIALOG_PARAMETER; 162 } 163 164 /** 165 * @see org.opencms.workplace.tools.I_CmsToolHandler#getHelpText() 166 */ 167 @Override 168 public String getHelpText() { 169 170 return Messages.get().getBundle().key(Messages.GUI_CHECKCONTENT_CONFIGURATION_PROPERTY_HELP_0); 171 } 172 173 /** 174 * @see org.opencms.workplace.tools.I_CmsToolHandler#getIconPath() 175 */ 176 @Override 177 public String getIconPath() { 178 179 return ICONPATH; 180 } 181 182 /** 183 * @see org.opencms.workplace.tools.I_CmsToolHandler#getLink() 184 */ 185 @Override 186 public String getLink() { 187 188 return "/system/workplace/views/admin/admin-editor.jsp?resource=/system/workplace/admin/contenttools/check/plugin/propertycheck/configuration.xml"; 189 } 190 191 /** 192 * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#getMessageBundles() 193 */ 194 @Override 195 public List getMessageBundles() { 196 197 List messages = new ArrayList(); 198 messages.add(org.opencms.workplace.tools.content.check.Messages.get().getBundleName()); 199 return messages; 200 } 201 202 /** 203 * @see org.opencms.workplace.tools.I_CmsToolHandler#getName() 204 */ 205 @Override 206 public String getName() { 207 208 return NAME; 209 } 210 211 /** 212 * @see org.opencms.workplace.tools.I_CmsToolHandler#getPath() 213 */ 214 @Override 215 public String getPath() { 216 217 return "/contenttools/checkconfig/checkproperty"; 218 } 219 220 /** 221 * @see org.opencms.workplace.tools.I_CmsToolHandler#getPosition() 222 */ 223 @Override 224 public float getPosition() { 225 226 return 1; 227 } 228 229 /** 230 * @see org.opencms.workplace.tools.I_CmsToolHandler#getShortName() 231 */ 232 @Override 233 public String getShortName() { 234 235 return NAME; 236 } 237 238 /** 239 * @see org.opencms.workplace.tools.content.check.I_CmsContentCheck#init(org.opencms.file.CmsObject) 240 */ 241 @Override 242 public void init(CmsObject cms) { 243 244 m_cms = cms; 245 m_locale = CmsLocaleManager.getLocale("en"); 246 } 247 248 /** 249 * Gets the active flag.<p> 250 * 251 * @return true if this content check is active, false otherwise. 252 */ 253 @Override 254 public boolean isActive() { 255 256 return m_active; 257 } 258 259 /** 260 * Sets the active flag.<p> 261 * 262 * This method is required to build the widget dialog frontend. 263 * 264 * @param value true if this content check is set to be active, false otherwise. 265 */ 266 @Override 267 public void setActive(boolean value) { 268 269 m_active = value; 270 } 271 272 /** 273 * Gets the configuration of the property check.<p> 274 * 275 *@throws CmsException if an error occurs reading the configuration 276 */ 277 private void getConfiguration() throws CmsException { 278 279 if ((m_configuredErrorChecks == null) || (m_configuredWarningChecks == null)) { 280 // get the configuration file 281 CmsResource res = m_cms.readResource(CONFIGURATION); 282 if (LOG.isDebugEnabled()) { 283 LOG.debug( 284 Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_CONFIG_FILENAME_1, res.getRootPath())); 285 } 286 CmsFile file = m_cms.readFile(res); 287 if (LOG.isDebugEnabled()) { 288 LOG.debug( 289 Messages.get().getBundle().key( 290 Messages.LOG_DEBUG_PROPERTY_CONFIG_FILE_1, 291 new String(file.getContents()))); 292 } 293 CmsXmlContent configuration = CmsXmlContentFactory.unmarshal(m_cms, file); 294 295 // now extract the configured error checks from it 296 m_configuredErrorChecks = getConfiguredChecks(configuration, XPATH_ERROR); 297 m_configuredWarningChecks = getConfiguredChecks(configuration, XPATH_WARNUING); 298 } 299 } 300 301 /** 302 * Reads the configuration for a given xpath and stored all results in a list.<p> 303 * 304 * @param configuration the configuration to read from 305 * @param xpath the xpath prefix 306 * @return list of CmsContentCheckProperetyObject objects 307 */ 308 private List getConfiguredChecks(CmsXmlContent configuration, String xpath) { 309 310 List checks = new ArrayList(); 311 312 if (LOG.isDebugEnabled()) { 313 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_CONFIG_XPATH_2, xpath, m_locale)); 314 } 315 316 int size = configuration.getIndexCount(xpath, m_locale); 317 for (int i = 1; i <= size; i++) { 318 // extract the values from the configuration 319 String propertyname = configuration.getValue( 320 xpath + "[" + i + "]/" + XPATH_PROPERTYNAME, 321 m_locale).getStringValue(m_cms); 322 String type = configuration.getValue(xpath + "[" + i + "]/" + XPATH_TYPE, m_locale).getStringValue(m_cms); 323 String empty = configuration.getValue(xpath + "[" + i + "]/" + XPATH_EMPTY, m_locale).getStringValue(m_cms); 324 String filename = configuration.getValue(xpath + "[" + i + "]/" + XPATH_FILENAME, m_locale).getStringValue( 325 m_cms); 326 String length = configuration.getValue(xpath + "[" + i + "]/" + XPATH_LENGTH, m_locale).getStringValue( 327 m_cms); 328 int values = configuration.getIndexCount(xpath + "[" + i + "]/" + XPATH_VALUE, m_locale); 329 330 //String value = configuration.getValue(xpath + "[" + i + "]/" + XPATH_VALUE, m_locale).getStringValue(m_cms); 331 332 // store them in the CmsContentCheckProperetyObject object for further processing 333 CmsContentCheckPropertyObject propObject = new CmsContentCheckPropertyObject(); 334 335 if (CmsStringUtil.isNotEmpty(propertyname)) { 336 propObject.setPropertyname(propertyname); 337 } 338 if (CmsStringUtil.isNotEmpty(type)) { 339 propObject.setType(type); 340 } 341 if (CmsStringUtil.isNotEmpty(empty)) { 342 propObject.setEmpty(empty.equals("true")); 343 } 344 if (CmsStringUtil.isNotEmpty(filename)) { 345 propObject.setFilename(filename.equals("true")); 346 } 347 if (CmsStringUtil.isNotEmpty(length)) { 348 propObject.setLength(Integer.valueOf(length).intValue()); 349 } 350 if (values > 0) { 351 List valueList = new ArrayList(); 352 for (int j = 1; j <= values; j++) { 353 String value = configuration.getValue( 354 xpath + "[" + i + "]/" + XPATH_VALUE + "[" + j + "]", 355 m_locale).getStringValue(m_cms); 356 if (CmsStringUtil.isNotEmpty(value)) { 357 valueList.add(value); 358 } 359 } 360 propObject.setValue(valueList); 361 } 362 363 if (LOG.isDebugEnabled()) { 364 LOG.debug( 365 Messages.get().getBundle().key( 366 Messages.LOG_DEBUG_PROPERTY_CONFIG_PROPERTY_3, 367 Integer.valueOf(i), 368 Integer.valueOf(size), 369 propObject)); 370 } 371 372 checks.add(propObject); 373 } 374 return checks; 375 } 376 377 /** 378 * Processes a list of CmsContentCheckProperetyObject and runs all available tests on them.<p> 379 * 380 * All errors or warnings found are collected in a list returned to the calling method. 381 * 382 * @param properties list of CmsContentCheckProperetyObject to process 383 * @param testResource the CmsContentCheckResource to run all tests on 384 * @return list of Strings containing either errors or warinings 385 */ 386 private List processProperties(List properties, CmsContentCheckResource testResource) { 387 388 List results = new ArrayList(); 389 390 if (LOG.isDebugEnabled()) { 391 LOG.debug( 392 Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_RESOURCE_1, testResource.getResourceName())); 393 } 394 395 //loop through all property tests 396 for (int i = 0; i < properties.size(); i++) { 397 try { 398 CmsContentCheckPropertyObject propObject = (CmsContentCheckPropertyObject)properties.get(i); 399 400 if (LOG.isDebugEnabled()) { 401 LOG.debug( 402 Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_PROPERTY_1, propObject.toString())); 403 } 404 405 // check if this test must be done for thies kind of resource 406 if ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_BOTH)) 407 || ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_FILE) 408 && (testResource.getResource().isFile()))) 409 || ((propObject.getType().equals(CmsContentCheckPropertyObject.TYPE_FOLDER) 410 && (testResource.getResource().isFolder()))) 411 412 ) { 413 414 // read the property 415 String prop = m_cms.readPropertyObject( 416 testResource.getResource(), 417 propObject.getPropertyname(), 418 false).getValue(); 419 420 if (LOG.isDebugEnabled()) { 421 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_PROPERTY_VALUE_1, prop)); 422 } 423 424 if (LOG.isDebugEnabled()) { 425 LOG.debug( 426 Messages.get().getBundle().key( 427 Messages.LOG_DEBUG_PROPERTY_ISEMPTYCHECK_1, 428 Boolean.valueOf(propObject.isEmpty()))); 429 } 430 431 // test if the property is empty 432 if (propObject.isEmpty() && CmsStringUtil.isEmpty(prop)) { 433 results.add( 434 Messages.get().getBundle().key( 435 Messages.ERR_CHECK_NO_PROPERTYNAME_1, 436 propObject.getPropertyname())); 437 if (LOG.isDebugEnabled()) { 438 LOG.debug( 439 Messages.get().getBundle().key( 440 Messages.ERR_CHECK_NO_PROPERTYNAME_1, 441 propObject.getPropertyname())); 442 } 443 } 444 445 if (LOG.isDebugEnabled()) { 446 LOG.debug( 447 Messages.get().getBundle().key( 448 Messages.LOG_DEBUG_PROPERTY_ISFILENAME_1, 449 Boolean.valueOf(propObject.isFilename()))); 450 } 451 452 // test if the property does not start with the filename 453 if (!CmsStringUtil.isEmpty(prop)) { 454 if (propObject.isFilename() 455 && testResource.getResource().getName().toLowerCase().startsWith(prop.toLowerCase())) { 456 results.add( 457 Messages.get().getBundle().key( 458 Messages.ERR_CHECK_CONTAINS_FILENAME_2, 459 propObject.getPropertyname(), 460 prop)); 461 if (LOG.isDebugEnabled()) { 462 LOG.debug( 463 Messages.get().getBundle().key( 464 Messages.ERR_CHECK_CONTAINS_FILENAME_2, 465 propObject.getPropertyname(), 466 prop)); 467 } 468 } 469 470 if (LOG.isDebugEnabled()) { 471 LOG.debug( 472 Messages.get().getBundle().key( 473 Messages.LOG_DEBUG_PROPERTY_CHECKLENGTH_2, 474 Integer.valueOf(propObject.getLength()), 475 Integer.valueOf(prop.length()))); 476 } 477 // test if the minmal property length is valid 478 if (propObject.getLength() > -1) { 479 if (prop.length() < propObject.getLength()) { 480 results.add( 481 Messages.get().getBundle().key( 482 Messages.ERR_CHECK_TOO_SHORT_3, 483 propObject.getPropertyname(), 484 prop, 485 Integer.valueOf(prop.length()))); 486 if (LOG.isDebugEnabled()) { 487 LOG.debug( 488 Messages.get().getBundle().key( 489 Messages.ERR_CHECK_TOO_SHORT_3, 490 propObject.getPropertyname(), 491 prop, 492 Integer.valueOf(prop.length()))); 493 } 494 } 495 } 496 497 // test if the value matches a regex 498 if (propObject.getValue().size() > 0) { 499 for (int j = 0; j < propObject.getValue().size(); j++) { 500 501 String regex = (String)propObject.getValue().get(j); 502 503 boolean matchResult = true; 504 if (regex.charAt(0) == '!') { 505 // negate the pattern 506 matchResult = false; 507 regex = regex.substring(1); 508 } 509 String matchValue = prop; 510 boolean match = Pattern.matches(regex, matchValue); 511 512 if (LOG.isDebugEnabled()) { 513 LOG.debug( 514 Messages.get().getBundle().key( 515 Messages.LOG_DEBUG_PROPERTY_MATCHPATTERN_2, 516 regex, 517 matchValue)); 518 } 519 520 if (matchResult != match) { 521 results.add( 522 Messages.get().getBundle().key( 523 Messages.ERR_CHECK_MATCH_3, 524 propObject.getPropertyname(), 525 prop, 526 propObject.getValue().get(j))); 527 if (LOG.isDebugEnabled()) { 528 LOG.debug( 529 Messages.get().getBundle().key( 530 Messages.ERR_CHECK_MATCH_3, 531 propObject.getPropertyname(), 532 prop, 533 propObject.getValue().get(j))); 534 } 535 } 536 } 537 } 538 } 539 } 540 541 } catch (CmsException e) { 542 LOG.error( 543 Messages.get().getBundle().key( 544 Messages.LOG_ERROR_PROCESSING_PROPERTIES_2, 545 testResource.getResourceName(), 546 e)); 547 } 548 } 549 550 return results; 551 } 552}