001/*
002 *  Copyright 2024 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 */
016package org.ametys.core.source;
017
018import java.util.LinkedHashMap;
019import java.util.Map;
020import java.util.Map.Entry;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.excalibur.source.SourceUtil;
029
030import org.ametys.runtime.plugin.component.PluginAware;
031
032/**
033 * A configurable static fallback for optional sources
034 */
035public class StaticOptionalSourceFallback implements OptionalSourceFallback, Component, Configurable, PluginAware
036{
037    private Fallbacks _fallbacks;
038    private String _pluginName;
039
040    public void configure(Configuration configuration) throws ConfigurationException
041    {
042        _fallbacks = configureFallbacks(configuration, "plugin:" + _pluginName + "://");
043    }
044
045    public void setPluginInfo(String pluginName, String featureName, String id)
046    {
047        _pluginName = pluginName;
048    }
049
050    /**
051     * Configure fallbacks based upon a static xml configuration
052     * @param configuration The configuration
053     * @param baseUri The uri of the configuration. For example, if the configuration is in a plugin, should be the root of the plugin
054     * @return The fallbacks configured
055     * @throws ConfigurationException If the configuration has issues
056     */
057    public static Fallbacks configureFallbacks(Configuration configuration, String baseUri) throws ConfigurationException
058    {
059        Map<String, String> stringFallbacks = new LinkedHashMap<>();
060        Map<Pattern, String> patternFallbacks = new LinkedHashMap<>();
061        
062        for (Configuration fallback : configuration.getChildren("fallback"))
063        {
064            String uri = fallback.getChild("uri").getValue();
065            String value = fallback.getChild("fallback").getValue();
066            boolean isRegexp = fallback.getChild("uri").getAttributeAsBoolean("regexp", false);
067            
068            if (isRegexp)
069            {
070                patternFallbacks.put(Pattern.compile(uri), value);
071            }
072            else
073            {
074                stringFallbacks.put(uri, value);
075            }
076        }
077        
078        return new Fallbacks(baseUri, stringFallbacks, patternFallbacks);
079    }
080
081    public String fallback(String uri)
082    {
083        return applyFallbacks(uri, _fallbacks);
084    }
085    
086    /**
087     * Apply the given fallbacks to an uri
088     * @param uri The uri to consider
089     * @param fallbacks The fallbacks to applu
090     * @return The fallback or null if no one apply
091     */
092    public static String applyFallbacks(String uri, Fallbacks fallbacks)
093    {
094        if (fallbacks.stringFallbacks().containsKey(uri))
095        {
096            return fallbacks.stringFallbacks().get(uri);
097        }
098        else
099        {
100            for (Entry<Pattern, String> patternFallback : fallbacks.patternFallbacks().entrySet())
101            {
102                Matcher matcher = patternFallback.getKey().matcher(uri);
103                if (matcher.matches())
104                {
105                    String value = patternFallback.getValue();
106                    for (int g = 1; g <= matcher.groupCount(); g++)
107                    {
108                        value = value.replace("$" + g, matcher.group(g));
109                    }
110                    return SourceUtil.absolutize(fallbacks.baseUri(), value);
111                }
112            }
113            
114            return fallbacks.patternFallbacks().entrySet().stream()
115                .filter(e -> e.getKey().matcher(uri).matches())
116                .findFirst().map(e -> SourceUtil.absolutize(fallbacks.baseUri(), e.getValue()))
117                .orElse(null);
118        }
119    }
120    
121    /**
122     * A fallback static configuration
123     * @param baseUri The base uri for values
124     * @param stringFallbacks Simple string fallbacks
125     * @param patternFallbacks Fallbacks with pattern
126     */
127    public record Fallbacks(String baseUri, Map<String, String> stringFallbacks, Map<Pattern, String> patternFallbacks) { }
128}