/* * Copyright 2012 Joseph Spencer. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spencernetdevelopment; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; /** * * @author Joseph Spencer */ public class LinkValidator { private final Set<String> failedFragments = new HashSet<>(); private final Set<String> validatedFragments = new HashSet<>(); private final Set<String> failedPages = new HashSet<>(); private final Set<String> validatedPages = new HashSet<>(); private final Map<String, ExternalLinkValidator<Object>> validators; private DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); private XPathFactory xpathFactory = XPathFactory.newInstance(); private DefaultNamespaceContext defaultNamespaceContext; private StaticPagesConfiguration config; public LinkValidator( Map<String, ExternalLinkValidator<Object>> validators, StaticPagesConfiguration config, DefaultNamespaceContext defaultNamespaceContext ){ this.validators=validators; this.config=config; this.defaultNamespaceContext=defaultNamespaceContext; } public void validateExternalURL(String path) throws MalformedURLException { synchronized(validators){ if(!validators.containsKey(path)){ validators.put( path, new ExternalLinkValidator(config, new URL(path)) ); } } } public void validatePageReference(String path) throws IOException { if(hasBeenValidated(path, validatedPages, failedPages)){ return; } FilePath page = getPageFilePath(path); if(!page.toFile().isFile()){ synchronized(failedPages){ failedPages.add(path); } throw new IOException("The following page doesn't exist: "+page); } } /** * Validates the existence of a document fragment within an xml file. The * fragment must exist within the body element. * * @param path */ public void validateFragmentReference(String path, String fragment) throws IOException { String key = path+"#"+fragment; if(hasBeenValidated(key, validatedFragments, failedFragments)){ return; } validatePageReference(path); try { FilePath page = getPageFilePath(path); if(fragment != null && fragment.trim().isEmpty()){ throw new IllegalArgumentException( "Empty document fragment not allowed: "+page+"#"+fragment ); } File toFile = page.toFile(); if(!toFile.isFile()){ throw new IOException( "The following page doesn't exist: "+page.toString() ); } DocumentBuilder builder = factory.newDocumentBuilder(); Document xml = builder.parse(toFile); XPath xpath = xpathFactory.newXPath(); xpath.setNamespaceContext(defaultNamespaceContext); Node elementById = (Node)xpath .evaluate( "//d:body//d:*[@id='"+fragment+"']", xml, XPathConstants.NODE ); if(elementById == null){ throw new IllegalArgumentException( "Couldn't find the following fragment: "+page+"#"+fragment ); } } catch( ParserConfigurationException| SAXException| IllegalArgumentException| XPathExpressionException ex ){ synchronized(failedFragments){ failedFragments.add(key); } throw new IOException( "The following error occurred: "+ex.getMessage() ); } } public boolean hasBeenValidated( String key, Set<String> validatedSet, Set<String> failedSet ) throws IOException { synchronized(failedSet){ if(failedSet.contains(key)){ throw new IOException( "Validation for the following link has already failed: "+key ); } } synchronized(validatedSet){ if(validatedSet.contains(key)){ return true; } validatedSet.add(key); return false; } } private FilePath getPageFilePath(String path) throws IOException { return config.getPagesDirPath().resolve( path.endsWith("/")? path + "index.xml": path.endsWith(".xml")? path: path + ".xml" ); } }