content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You 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
   *
   *      https://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 org.apache.ivy.core.cache;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.FilenameFilter;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.text.ParseException;
  import java.util.Map;
  import java.util.Properties;
  
  import org.apache.ivy.core.IvyPatternHelper;
  import org.apache.ivy.core.RelativeUrlResolver;
  import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
  import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
  import org.apache.ivy.core.module.id.ModuleId;
  import org.apache.ivy.core.module.id.ModuleRevisionId;
  import org.apache.ivy.core.module.status.StatusManager;
  import org.apache.ivy.core.settings.IvySettings;
  import org.apache.ivy.core.settings.TimeoutConstraint;
  import org.apache.ivy.plugins.IvySettingsAware;
  import org.apache.ivy.plugins.conflict.ConflictManager;
  import org.apache.ivy.plugins.matcher.PatternMatcher;
  import org.apache.ivy.plugins.namespace.Namespace;
  import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
  import org.apache.ivy.plugins.parser.ParserSettings;
  import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
  import org.apache.ivy.plugins.resolver.DependencyResolver;
  import org.apache.ivy.util.FileUtil;
  
  public class DefaultResolutionCacheManager implements ResolutionCacheManager, IvySettingsAware {
  
      private static final String DEFAULT_CACHE_RESOLVED_IVY_PATTERN = "resolved-[organisation]-[module]-[revision].xml";
  
      private static final String DEFAULT_CACHE_RESOLVED_IVY_PROPERTIES_PATTERN = "resolved-[organisation]-[module]-[revision].properties";
  
      private String resolvedIvyPattern = DEFAULT_CACHE_RESOLVED_IVY_PATTERN;
  
      private String resolvedIvyPropertiesPattern = DEFAULT_CACHE_RESOLVED_IVY_PROPERTIES_PATTERN;
  
      private File basedir;
  
      private String name = "resolution-cache";
  
      private IvySettings settings;
  
      public DefaultResolutionCacheManager() {
      }
  
      public DefaultResolutionCacheManager(File basedir) {
          setBasedir(basedir);
      }
  
      public void setSettings(IvySettings settings) {
          this.settings = settings;
      }
  
      public File getResolutionCacheRoot() {
          if (basedir == null) {
              if (settings == null) {
                  throw new IllegalStateException("The 'basedir' or 'IvySettings' has not been set on the ResolutionCacheManager");
              }
              basedir = settings.getDefaultResolutionCacheBasedir();
          }
          return basedir;
      }
  
      public File getBasedir() {
          return basedir;
      }
  
      public void setBasedir(File basedir) {
          this.basedir = basedir;
      }
  
      public String getResolvedIvyPattern() {
          return resolvedIvyPattern;
      }
  
      public void setResolvedIvyPattern(String cacheResolvedIvyPattern) {
          this.resolvedIvyPattern = cacheResolvedIvyPattern;
      }
  
      public String getResolvedIvyPropertiesPattern() {
          return resolvedIvyPropertiesPattern;
      }
  
      public void setResolvedIvyPropertiesPattern(String cacheResolvedIvyPropertiesPattern) {
          this.resolvedIvyPropertiesPattern = cacheResolvedIvyPropertiesPattern;
      }
  
      public String getName() {
          return name;
      }
  
      public void setName(String name) {
          this.name = name;
      }
  
      public File getResolvedIvyFileInCache(ModuleRevisionId mrid) {
          String file = IvyPatternHelper.substitute(getResolvedIvyPattern(), mrid.getOrganisation(),
              mrid.getName(), mrid.getRevision(), "ivy", "ivy", "xml");
          return new File(getResolutionCacheRoot(), file);
      }
  
      public File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid) {
          String file = IvyPatternHelper.substitute(getResolvedIvyPropertiesPattern(),
              mrid.getOrganisation(), mrid.getName(), mrid.getRevision(), "ivy", "ivy", "xml");
          return new File(getResolutionCacheRoot(), file);
      }
  
      public File getConfigurationResolveReportInCache(String resolveId, String conf) {
          return new File(getResolutionCacheRoot(), resolveId + "-" + conf + ".xml");
      }
  
      public File[] getConfigurationResolveReportsInCache(final String resolveId) {
          final String prefix = resolveId + "-";
          final String suffix = ".xml";
          return getResolutionCacheRoot().listFiles(new FilenameFilter() {
              public boolean accept(File dir, String name) {
                  return name.startsWith(prefix) && name.endsWith(suffix);
              }
          });
      }
  
      public ModuleDescriptor getResolvedModuleDescriptor(ModuleRevisionId mrid)
              throws ParseException, IOException {
          File ivyFile = getResolvedIvyFileInCache(mrid);
          if (!ivyFile.exists()) {
              throw new IllegalStateException("Ivy file not found in cache for " + mrid + "!");
          }
  
          Properties paths = new Properties();
  
          File parentsFile = getResolvedIvyPropertiesInCache(ModuleRevisionId.newInstance(mrid,
              mrid.getRevision() + "-parents"));
          if (parentsFile.exists()) {
              FileInputStream in = new FileInputStream(parentsFile);
              paths.load(in);
              in.close();
          }
  
          ParserSettings pSettings = new CacheParserSettings(settings, paths);
  
          URL ivyFileURL = ivyFile.toURI().toURL();
          return getModuleDescriptorParser(ivyFile).parseDescriptor(pSettings, ivyFileURL, false);
      }
  
      /**
       * Choose write module descriptor parser for a given moduleDescriptor
       *
       * @param moduleDescriptorFile
       *            a given module descriptor
       * @return ModuleDescriptorParser
       */
      protected ModuleDescriptorParser getModuleDescriptorParser(File moduleDescriptorFile) {
          return XmlModuleDescriptorParser.getInstance();
      }
  
      public void saveResolvedModuleDescriptor(ModuleDescriptor md) throws ParseException,
              IOException {
          ModuleRevisionId mrevId = md.getResolvedModuleRevisionId();
          File ivyFileInCache = getResolvedIvyFileInCache(mrevId);
          assertInsideCache(ivyFileInCache);
          md.toIvyFile(ivyFileInCache);
  
          Properties paths = new Properties();
          saveLocalParents(mrevId, md, ivyFileInCache, paths);
  
          if (!paths.isEmpty()) {
              File parentsFile = getResolvedIvyPropertiesInCache(ModuleRevisionId.newInstance(mrevId,
                  mrevId.getRevision() + "-parents"));
              assertInsideCache(parentsFile);
              FileOutputStream out = new FileOutputStream(parentsFile);
              paths.store(out, null);
              out.close();
          }
      }
  
      /**
       * @throws IllegalArgumentException if the given path points outside of the cache.
       */
      public final void assertInsideCache(File fileInCache) {
          if (!FileUtil.isLeadingPath(getResolutionCacheRoot(), fileInCache)) {
              throw new IllegalArgumentException(fileInCache + " is outside of the cache");
          }
      }
  
      private void saveLocalParents(ModuleRevisionId baseMrevId, ModuleDescriptor md, File mdFile,
              Properties paths) throws ParseException, IOException {
          for (ExtendsDescriptor parent : md.getInheritedDescriptors()) {
              if (!parent.isLocal()) {
                  // we store only local parents in the cache!
                  continue;
              }
  
              ModuleDescriptor parentMd = parent.getParentMd();
              ModuleRevisionId pRevId = ModuleRevisionId.newInstance(baseMrevId,
                  baseMrevId.getRevision() + "-parent." + paths.size());
              File parentFile = getResolvedIvyFileInCache(pRevId);
              assertInsideCache(parentFile);
              parentMd.toIvyFile(parentFile);
  
              paths.setProperty(mdFile.getName() + "|" + parent.getLocation(),
                  parentFile.getAbsolutePath());
              saveLocalParents(baseMrevId, parentMd, parentFile, paths);
          }
      }
  
      public String toString() {
          return name;
      }
  
      public void clean() {
          FileUtil.forceDelete(getResolutionCacheRoot());
      }
  
      private static class CacheParserSettings implements ParserSettings {
  
          private ParserSettings delegate;
  
          private Map<Object, Object> parentPaths;
  
          public CacheParserSettings(ParserSettings delegate, Map<Object, Object> parentPaths) {
              this.delegate = delegate;
              this.parentPaths = parentPaths;
          }
  
          public String substitute(String value) {
              return delegate.substitute(value);
          }
  
          public Map<String, String> substitute(Map<String, String> strings) {
              return delegate.substitute(strings);
          }
  
          public ResolutionCacheManager getResolutionCacheManager() {
              return delegate.getResolutionCacheManager();
          }
  
          public ConflictManager getConflictManager(String name) {
              return delegate.getConflictManager(name);
          }
  
          public PatternMatcher getMatcher(String matcherName) {
              return delegate.getMatcher(matcherName);
          }
  
          public Namespace getNamespace(String namespace) {
              return delegate.getNamespace(namespace);
          }
  
          public StatusManager getStatusManager() {
              return delegate.getStatusManager();
          }
  
          public RelativeUrlResolver getRelativeUrlResolver() {
              return new MapURLResolver(parentPaths, delegate.getRelativeUrlResolver());
          }
  
          public DependencyResolver getResolver(ModuleRevisionId mRevId) {
              return delegate.getResolver(mRevId);
          }
  
          public File resolveFile(String filename) {
              return delegate.resolveFile(filename);
          }
  
          public String getDefaultBranch(ModuleId moduleId) {
              return delegate.getDefaultBranch(moduleId);
          }
  
          public Namespace getContextNamespace() {
              return delegate.getContextNamespace();
          }
  
          public String getVariable(String value) {
              return delegate.getVariable(value);
          }
  
          @Override
          public TimeoutConstraint getTimeoutConstraint(final String name) {
              return this.delegate.getTimeoutConstraint(name);
          }
      }
  
      private static final class MapURLResolver extends RelativeUrlResolver {
  
          private Map<Object, Object> paths;
  
          private RelativeUrlResolver delegate;
  
          private MapURLResolver(Map<Object, Object> paths, RelativeUrlResolver delegate) {
              this.paths = paths;
              this.delegate = delegate;
          }
  
          public URL getURL(URL context, String url) throws MalformedURLException {
              String path = context.getPath();
              if (path.contains("/")) {
                  String file = path.substring(path.lastIndexOf('/') + 1);
  
                  if (paths.containsKey(file + "|" + url)) {
                      File result = new File(paths.get(file + "|" + url).toString());
                      return result.toURI().toURL();
                  }
              }
  
              return delegate.getURL(context, url);
          }
      }
  }
  