001/*
002 *  Copyright 2010 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.web.source;
017
018import java.io.IOException;
019import java.net.MalformedURLException;
020import java.nio.file.Path;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Map;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027import org.apache.avalon.framework.configuration.Configurable;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.commons.lang.StringUtils;
034import org.apache.excalibur.source.Source;
035import org.apache.excalibur.source.SourceFactory;
036import org.apache.excalibur.source.SourceNotFoundException;
037
038import org.ametys.runtime.plugin.component.AbstractLogEnabled;
039import org.ametys.web.repository.site.SiteManager;
040import org.ametys.web.skin.Skin;
041import org.ametys.web.skin.SkinsManager;
042
043/**
044 * Resolve the protocol skin:// seeking file in skins/{siteConfiguration:skin}/
045 */
046public class SkinSourceFactory extends AbstractLogEnabled implements SourceFactory, Serviceable, Configurable
047{
048    /** The regexp for a protocol */
049    protected static final Pattern __SOURCE_PATTERN = Pattern.compile("^[\\w-]+(:([^:#]+)?(#([^:]+))?)?://([^?]*)(?:\\?.*)?$");
050    /** Scheme for skin */
051    protected static final String __SKIN_SCHEME = "skin";
052    
053    /** The skins manager */
054    protected SkinsManager _skinsManager;
055    /** The site manager */
056    protected SiteManager _siteManager;
057    /** Cocoon context */
058    protected org.apache.cocoon.environment.Context _cocoonContext;
059    
060    private ServiceManager _manager;
061    
062    private boolean _supportInheritance;
063
064    
065    @Override
066    public void service(ServiceManager manager) throws ServiceException
067    {
068        _manager = manager;
069    }
070    
071    public void configure(Configuration configuration) throws ConfigurationException
072    {
073        _supportInheritance = configuration.getChild("inheritance").getValueAsBoolean(true);
074    }
075    
076    /**
077     * Lookup {@link SiteManager} and {@link SkinsManager} for lazy initialization.
078     */
079    protected void initializeComponents()
080    {
081        try
082        {
083            if (_siteManager == null)
084            {
085                _siteManager = (SiteManager) _manager.lookup(SiteManager.ROLE);
086            }
087            
088            if (_skinsManager == null)
089            {
090                _skinsManager = (SkinsManager) _manager.lookup(SkinsManager.ROLE);
091            }
092        }
093        catch (ServiceException e)
094        {
095            throw new IllegalStateException("Exception while getting components", e);
096        }
097    }
098
099    /**
100     * Return the current skin
101     * @param name the skin name. Can be null or empty
102     * @param defaultValue switch to this value if skin cannot be determined, or if the determined skin does not exist. Can be null
103     * @return the skin. Cannot be null.
104     * @throws SourceNotFoundException If the skin do not exist
105     */
106    protected Skin getSkin(String name, String defaultValue) throws SourceNotFoundException
107    {
108        String skinName = name;
109        
110        if (StringUtils.isEmpty(skinName))
111        {
112            skinName = _skinsManager.getSkinNameFromRequest();
113        }
114        
115        // Then, try the default value.
116        if (StringUtils.isEmpty(skinName))
117        {
118            skinName = defaultValue;
119        }
120        
121        if (StringUtils.isEmpty(skinName))
122        {
123            String message = "The current skin name has to be set in current request to use this protocol.";
124            getLogger().info(message);
125            throw new SourceNotFoundException(message);
126        }
127        
128        Skin skin = _skinsManager.getSkin(skinName); 
129        if (skin == null && defaultValue != null)
130        {
131            skin = _skinsManager.getSkin(defaultValue);
132        }
133        
134        if (skin == null)
135        {
136            String message = "The skin '" + skinName + "' currently setup for this site does not exist.";
137            getLogger().info(message);
138            throw new SourceNotFoundException(message);
139        }
140        
141        return skin;
142    }
143
144    @Override
145    public Source getSource(String location, Map parameters) throws IOException
146    {
147        // lazy initialization to prevent chicken/egg scenario on startup
148        initializeComponents();
149        
150        Matcher m = __SOURCE_PATTERN.matcher(location);
151        if (!m.matches())
152        {
153            throw new MalformedURLException("URI must be like protocol:<name>://path/to/resource. Location was '" + location + "'");
154        }
155        
156        Skin skin = getSkin(m.group(2), m.group(4));
157        
158        String fileLocation = m.group(5);
159
160        
161        List<Path> paths = new ArrayList<>();
162        if (_supportInheritance)
163        {
164            for (Skin s :  _skinsManager.getSkinAndParents(skin))
165            {
166                final Path resource = s.getRawPath().resolve(fileLocation);
167                paths.add(resource);
168            }
169        }
170        else
171        {
172            final Path resource = skin.getRawPath().resolve(fileLocation);
173            paths.add(resource);
174        }
175        return new SkinSource(__SKIN_SCHEME, location, paths);
176    }
177    
178    public void release(Source source)
179    {
180        // Nothing to do
181    }
182}