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.skin;
017
018import java.io.File;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.CascadingRuntimeException;
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
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.cocoon.Constants;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.io.FileUtils;
038import org.apache.commons.lang.StringUtils;
039import org.apache.excalibur.source.SourceResolver;
040
041import org.ametys.runtime.servlet.RuntimeConfig;
042import org.ametys.web.repository.site.SiteManager;
043
044/**
045 * Manages the templates
046 */
047public class SkinsManager extends AbstractLogEnabled implements ThreadSafe, Serviceable, Component, Contextualizable
048{
049    /** The avalon role name */
050    public static final String ROLE = SkinsManager.class.getName();
051
052    /** The set of templates classified by skins */
053    protected Map<String, Skin> _skins = new HashMap<>();
054    
055    /** The avalon service manager */
056    protected ServiceManager _manager;
057    /** The excalibur source resolver */
058    protected SourceResolver _sourceResolver;
059    /** Avalon context */
060    protected Context _context;
061    /** Cocoon context */
062    protected org.apache.cocoon.environment.Context _cocoonContext;
063    /** The site manager */
064    protected SiteManager _siteManager;
065    
066    @Override
067    public void service(ServiceManager manager) throws ServiceException
068    {
069        _manager = manager;
070        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
071    }
072    
073    @Override
074    public void contextualize(Context context) throws ContextException
075    {
076        _context = context;
077        _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
078    }
079    
080    /**
081     * Get the list of existing skins
082     * @return A set of skin names. Can be null if there is an error.
083     */
084    public Set<String> getSkins()
085    {
086        try
087        {
088            Set<String> skins = new HashSet<>();
089
090            File skinsDir = new File(getSkinsLocation());
091            if (skinsDir.exists() && skinsDir.isDirectory())
092            {
093                for (File child : skinsDir.listFiles())
094                {
095                    if (_skinExists(child))
096                    {
097                        skins.add(child.getName());
098                    }
099                }
100            }
101            
102            return skins;
103        }
104        catch (Exception e)
105        {
106            getLogger().error("Can not determine the list of skins available", e);
107            return null;
108        }
109    }
110    
111    /**
112     * Get a skin
113     * @param id The id of the skin
114     * @return The skin or null if the skin does not exist
115     */
116    @SuppressWarnings("null")
117    public Skin getSkin(String id)
118    {
119        try
120        {
121            File skinsDir = new File (_getSkinLocation(id));
122            
123            boolean skinDirExists = _skinExists(skinsDir);
124            Skin skin = _skins.get(skinsDir.getAbsolutePath());
125            if (skin == null && skinDirExists)
126            {
127                skin = new Skin(id, new File (_getSkinLocation(id)));
128                _skins.put(skinsDir.getAbsolutePath(), skin);
129            }
130            else if (!skinDirExists)
131            {
132                _skins.put(skinsDir.getAbsolutePath(), null);
133                return null;
134            }
135            
136            skin.refreshValues();
137            return skin;
138        }
139        catch (Exception e)
140        {
141            throw new IllegalStateException("Can not create the skin DAO for skin '" + id + "'", e);
142        }
143    }
144    
145    /**
146     * Get the skins location
147     * @return the skin location
148     */
149    public String getSkinsLocation ()
150    {
151        try
152        {
153            Request request = ContextHelper.getRequest(_context);
154            String skinLocation = (String) request.getAttribute("skin-location");
155            if (skinLocation != null)
156            {
157                return new File(RuntimeConfig.getInstance().getAmetysHome(), skinLocation).getPath();
158            }
159        }
160        catch (CascadingRuntimeException e)
161        {
162            // Ignore
163        }
164        
165        return _cocoonContext.getRealPath("/skins");
166    }
167    
168    /**
169     * Get the skin name from request or <code>null</code> if not found
170     * @param request The request
171     * @return The skin name or <code>null</code>
172     */
173    public String getSkinNameFromRequest (Request request)
174    {
175        if (_siteManager == null)
176        {
177            try
178            {
179                _siteManager = (SiteManager) _manager.lookup(SiteManager.ROLE);
180            }
181            catch (ServiceException e)
182            {
183                throw new IllegalStateException(e);
184            }
185        }
186        
187        // First, search the skin name in the request attributes.
188        String skinName = (String) request.getAttribute("skin");
189        
190        // Then, test if the site name is present as a request attribute to deduce the skin name.
191        if (StringUtils.isEmpty(skinName))
192        {
193            String siteName = (String) request.getAttribute("site");
194            if (StringUtils.isEmpty(siteName))
195            {
196                siteName = (String) request.getAttribute("siteName");
197            }
198            
199            if (StringUtils.isNotEmpty(siteName))
200            {
201                skinName = _siteManager.getSite(siteName).getSkinId();
202            }
203        }
204        
205        return skinName;
206    }
207    
208    /**
209     * Get the skin location
210     * @param id The id of the skin
211     * @return the skin location
212     */
213    private String _getSkinLocation (String id)
214    {
215        Request request = ContextHelper.getRequest(_context);
216        String skinLocation = (String) request.getAttribute("skin-location");
217        if (skinLocation != null)
218        {
219            return FileUtils.getFile(RuntimeConfig.getInstance().getAmetysHome(), skinLocation, id).getPath();
220        }
221        
222        return _cocoonContext.getRealPath("/skins/" + id);
223    }
224
225    private boolean _skinExists(File skinsDir)
226    {
227        if (!skinsDir.exists() || !skinsDir.isDirectory())
228        {
229            return false;
230        }
231        
232        File templatesDir = new File(skinsDir, "templates");
233        if (!templatesDir.exists() || !templatesDir.isDirectory())
234        {
235            return false;
236        }
237        
238        return true;
239    }
240}