001/*
002 *  Copyright 2012 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.cocoon.source;
017
018import java.io.IOException;
019import java.net.MalformedURLException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.apache.avalon.framework.configuration.Configurable;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.logger.AbstractLogEnabled;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.avalon.framework.thread.ThreadSafe;
034import org.apache.commons.lang.StringUtils;
035import org.apache.excalibur.source.Source;
036import org.apache.excalibur.source.SourceFactory;
037import org.apache.excalibur.source.SourceNotFoundException;
038import org.apache.excalibur.source.SourceResolver;
039
040/**
041 * Factory for reading files in one protocol and switch to others if not found.
042 * Configuration should be like:<br><pre><code>
043 *       &lt;match&gt;kernel://(.*)&lt;/match&gt;
044 *       &lt;protocols&gt;
045 *         &lt;protocol&gt;resource://org/ametys/runtime/kernel/{1}&lt;/protocol&gt;
046 *         &lt;protocol&gt;context://kernel/{1}&lt;/protocol&gt;
047 *       &lt;/protocols&gt;</code></pre>
048 */
049public class ProxySourceFactory extends AbstractLogEnabled implements SourceFactory, ThreadSafe, Serviceable, Configurable
050{
051    /** URI matcher */
052    protected Pattern _matcher;
053    
054    /** Proxied sources */
055    protected List<String> _protocols;
056
057    private SourceResolver _sourceResolver;
058    private ServiceManager _manager;
059    
060    @Override
061    public void configure(Configuration configuration) throws ConfigurationException
062    {
063        _matcher = Pattern.compile(configuration.getChild("match").getValue());
064        
065        _protocols = new ArrayList<>();
066        for (Configuration protocolConf : configuration.getChild("protocols").getChildren("protocol"))
067        {
068            String protocol = protocolConf.getValue();
069            if (!StringUtils.isBlank(protocol))
070            {
071                _protocols.add(protocol);
072            }
073            else
074            {
075                getLogger().warn("Trying to configure a blank protocol. Ignoring this line.");
076            }
077        }
078        
079        if (_protocols.size() == 0)
080        {
081            throw new ConfigurationException("Cannot configure this protocol with no redirecting protocols. Add <protocol>...</protocol> items in this component configuration.", configuration);
082        }
083    }
084    
085    @Override
086    public void service(ServiceManager manager) throws ServiceException
087    {
088        _manager = manager;
089    }
090    
091    @Override
092    public Source getSource(String location, Map parameters) throws IOException, MalformedURLException
093    {
094        if (_sourceResolver == null)
095        {
096            try
097            {
098                _sourceResolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE);
099            }
100            catch (ServiceException e)
101            {
102                throw new IOException(e);
103            }
104        }
105
106        Matcher matcher = _matcher.matcher(location);
107        if (!matcher.matches())
108        {
109            throw new MalformedURLException("Invalid format for source : " + location);
110        }
111
112        Source source = null;
113        for (String protocol : _protocols)
114        {
115            String uri = protocol;
116            try
117            {
118                for (int groupIndex = 0; groupIndex <= matcher.groupCount(); groupIndex++)
119                {
120                    uri = uri.replaceAll("\\{" + groupIndex + "\\}", matcher.group(groupIndex));
121                }
122                
123                source = _sourceResolver.resolveURI(uri, null, parameters);
124                if (!source.exists())
125                {
126                    _sourceResolver.release(source);
127                    source = null;
128                }
129                else
130                {
131                    return source;
132                }
133            }
134            catch (IOException e)
135            {
136                // nothing to do...
137            }
138        }
139        
140        throw new SourceNotFoundException("Source not found '" + location + "'. (tried successively in " + _protocols + ")");
141    }
142    
143    @Override
144    public void release(Source source)
145    {
146        // Should never be called
147    }
148}