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