001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.model.xml; 028 029import gov.nist.secauto.metaschema.core.model.MetaschemaException; 030import gov.nist.secauto.metaschema.core.util.CollectionUtil; 031import gov.nist.secauto.metaschema.core.util.ObjectUtils; 032 033import org.apache.logging.log4j.LogManager; 034import org.apache.logging.log4j.Logger; 035 036import java.io.File; 037import java.io.IOException; 038import java.net.MalformedURLException; 039import java.net.URI; 040import java.net.URISyntaxException; 041import java.net.URL; 042import java.nio.file.Path; 043import java.util.Collection; 044import java.util.Deque; 045import java.util.LinkedHashMap; 046import java.util.LinkedList; 047import java.util.Map; 048import java.util.stream.Collectors; 049 050import edu.umd.cs.findbugs.annotations.NonNull; 051 052public abstract class AbstractLoader<T> { 053 private static final Logger LOGGER = LogManager.getLogger(ConstraintLoader.class); 054 055 @NonNull 056 private final Map<URI, T> cache = new LinkedHashMap<>(); // NOPMD - intentional 057 058 /** 059 * Retrieve the set of loaded resources. 060 * 061 * @return the set of loaded resources 062 */ 063 @NonNull 064 public Collection<T> getLoadedResources() { 065 return CollectionUtil.unmodifiableCollection(ObjectUtils.notNull(cache.values())); 066 } 067 068 /** 069 * Retrieve a mapping of resource URIs to the associated loaded resource. 070 * 071 * @return the mapping 072 */ 073 @NonNull 074 protected Map<URI, T> getCachedEntries() { 075 return CollectionUtil.unmodifiableMap(cache); 076 } 077 078 /** 079 * Load a resource from the specified URI. 080 * 081 * @param resource 082 * the resource to load 083 * @return the loaded instance for the specified resource 084 * @throws MetaschemaException 085 * if an error occurred while processing the resource 086 * @throws IOException 087 * if an error occurred parsing the resource 088 */ 089 @NonNull 090 public T load(@NonNull URI resource) throws MetaschemaException, IOException { 091 if (!resource.isAbsolute()) { 092 throw new IllegalArgumentException(String.format("The URI '%s' must be absolute.", resource.toString())); 093 } 094 return loadInternal(resource, new LinkedList<>()); 095 } 096 097 /** 098 * Load a resource from the specified path. 099 * 100 * @param path 101 * the resource to load 102 * @return the loaded instance for the specified resource 103 * @throws MetaschemaException 104 * if an error occurred while processing the resource 105 * @throws IOException 106 * if an error occurred parsing the resource 107 */ 108 @NonNull 109 public T load(@NonNull Path path) throws MetaschemaException, IOException { 110 return loadInternal(ObjectUtils.notNull(path.toAbsolutePath().normalize().toUri()), new LinkedList<>()); 111 } 112 113 /** 114 * Load a resource from the specified file. 115 * 116 * @param file 117 * the resource to load 118 * @return the loaded instance for the specified resource 119 * @throws MetaschemaException 120 * if an error occurred while processing the resource 121 * @throws IOException 122 * if an error occurred parsing the resource 123 */ 124 @NonNull 125 public T load(@NonNull File file) throws MetaschemaException, IOException { 126 return load(file.toPath()); 127 } 128 129 /** 130 * Loads a resource from the specified URL. 131 * 132 * @param url 133 * the URL to load the resource from 134 * @return the loaded instance for the specified resource 135 * @throws MetaschemaException 136 * if an error occurred while processing the resource 137 * @throws IOException 138 * if an error occurred parsing the resource 139 */ 140 @NonNull 141 public T load(@NonNull URL url) throws MetaschemaException, IOException { 142 try { 143 URI resource = url.toURI(); 144 return loadInternal(ObjectUtils.notNull(resource), new LinkedList<>()); 145 } catch (URISyntaxException ex) { 146 // this should not happen 147 LOGGER.error("Invalid url", ex); 148 throw new IOException(ex); 149 } 150 } 151 152 /** 153 * Loads a resource from the provided URI. 154 * <p> 155 * If the resource imports other resources, the provided 156 * {@code visitedResources} can be used to track circular imports. This is 157 * useful when this method recurses into included resources. 158 * <p> 159 * Previously loaded resources are provided by the cache. This method will add 160 * the resource to the cache after all imported resources have been loaded. 161 * 162 * @param resource 163 * the resource to load 164 * @param visitedResources 165 * a LIFO queue representing previously visited resources in an import 166 * chain 167 * @return the loaded resource 168 * @throws MetaschemaException 169 * if an error occurred while processing the resource 170 * @throws MalformedURLException 171 * if the provided URI is malformed 172 * @throws IOException 173 * if an error occurred parsing the resource 174 */ 175 @NonNull 176 protected T loadInternal(@NonNull URI resource, @NonNull Deque<URI> visitedResources) 177 throws MetaschemaException, MalformedURLException, IOException { 178 // first check if the current resource has been visited to prevent cycles 179 if (visitedResources.contains(resource)) { 180 throw new MetaschemaException("Cycle detected in metaschema includes for '" + resource + "'. Call stack: '" 181 + visitedResources.stream().map(n -> n.toString()).collect(Collectors.joining(","))); 182 } 183 184 T retval = cache.get(resource); 185 if (retval == null) { 186 LOGGER.info("Loading module '{}'", resource); 187 188 try { 189 visitedResources.push(resource); 190 retval = parseResource(resource, visitedResources); 191 } finally { 192 visitedResources.pop(); 193 } 194 cache.put(resource, retval); 195 } else { 196 if (LOGGER.isDebugEnabled()) { 197 LOGGER.debug("Found metaschema in cache '{}'", resource); 198 } 199 } 200 return ObjectUtils.notNull(retval); 201 } 202 203 /** 204 * Parse the provided {@code resource}. 205 * 206 * @param resource 207 * the resource to parse 208 * @param visitedResources 209 * a stack representing previously parsed resources imported by the 210 * provided {@code resource} 211 * @return the parsed resource 212 * @throws IOException 213 * if an error occurred while parsing the resource 214 */ 215 protected abstract T parseResource(@NonNull URI resource, @NonNull Deque<URI> visitedResources) 216 throws IOException; 217 218}