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.cocoon;
017
018import java.io.File;
019import java.io.IOException;
020import java.util.Map;
021import java.util.MissingResourceException;
022
023import org.apache.avalon.framework.component.ComponentException;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.configuration.DefaultConfiguration;
027import org.apache.avalon.framework.parameters.Parameters;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.cocoon.ProcessingException;
031import org.apache.cocoon.environment.ObjectModelHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.environment.SourceResolver;
034import org.apache.cocoon.xml.ParamSaxBuffer;
035import org.apache.commons.lang.StringUtils;
036import org.xml.sax.SAXException;
037
038import org.ametys.core.cocoon.XMLResourceBundle;
039import org.ametys.web.skin.SkinsManager;
040
041/**
042 * CMS version of the I18nTransformer, dealing with skin catalogues.<br>
043 * It is also able to reconfigure itself dynamically, to reflect changes.
044 */
045public class I18nTransformer extends org.ametys.core.cocoon.I18nTransformer
046{
047    // The last time, "needsReload" was called
048    private static long _needsReloadTime;
049
050    /** the avalon service manager */
051    protected ServiceManager _manager;
052    
053    private Configuration _conf;
054    
055    /** The time the configure method was lastly called on this instance */
056    private long _lastConfigureTime;
057
058    private SkinsManager _skinsManager;
059    
060    @Override
061    public void configure(Configuration conf) throws ConfigurationException
062    {
063        _conf = conf;
064        _configure();
065    }
066    
067    @Override
068    public void service(ServiceManager smanager) throws ServiceException
069    {
070        super.service(smanager);
071        _manager = smanager;
072        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
073    }
074    
075    private void _configure() throws ConfigurationException
076    {
077        _lastConfigureTime = System.currentTimeMillis();
078        
079        // Modification de la configuration pour faire apparaitre les sites
080        DefaultConfiguration newConf = new DefaultConfiguration("i18n");
081
082        Configuration cataloguesConf = _conf.getChild("catalogues", false);
083        DefaultConfiguration catalogues = new DefaultConfiguration("catalogues");
084        newConf.addChild(catalogues);
085
086        if (cataloguesConf != null)
087        {
088            catalogues.addAll(cataloguesConf);
089        }
090
091        _configureCatalogue(catalogues, "skins", "skin");
092        _configureCatalogue(catalogues, "models", "model");
093        
094        super.configure(newConf);
095    }
096
097    private void _configureCatalogue(DefaultConfiguration catalogues, String dirName, String catalogName)
098    {
099        File skinsBaseDir = new File(_context.getRealPath("/" + dirName));
100
101        if (skinsBaseDir.exists())
102        {
103            File[] skinsDir = skinsBaseDir.listFiles();
104
105            for (int i = 0; i < skinsDir.length; i++)
106            {
107                File skinDir = skinsDir[i];
108
109                // On filtre les non-repertoires, non-plugins, et les plugins sans i18n
110                if (skinDir.isDirectory() && new File(skinDir, "i18n/messages.xml").exists())
111                {
112                    String name = skinDir.getName();
113                    String id = catalogName + "." + name;
114
115                    DefaultConfiguration catalogue = new DefaultConfiguration("catalogue");
116                    catalogue.setAttribute("id", id);
117                    catalogue.setAttribute("name", "messages");
118                    catalogue.setAttribute("location", "context://" + dirName + "/" + name + "/i18n");
119
120                    catalogues.addChild(catalogue);
121                }
122            }
123        }
124    }
125    
126    @Override
127    protected ParamSaxBuffer getMessage(String catalogueID, String key)
128    {
129        Request request = ObjectModelHelper.getRequest(objectModel);
130        
131        String skinLocation = (String) request.getAttribute("skin-location");
132        if (skinLocation != null && catalogueID != null && catalogueID.startsWith("skin"))
133        {
134            ParamSaxBuffer message = null;
135            
136            String skinName = _skinsManager.getSkinNameFromRequest (request);
137            
138            if (StringUtils.isNotEmpty(skinName))
139            {
140                message = _getExternalMessage ("ametys-home://" + skinLocation + "/" + skinName + "/i18n", key);
141            }
142            
143            if (message == null)
144            {
145                message = getUntranslatedMessage (catalogueID, key);
146            }
147            
148            return message;
149        }
150        else
151        {
152            return super.getMessage(catalogueID, key);
153        }
154    }
155    
156    private ParamSaxBuffer _getExternalMessage (String externalLocation, String key)
157    {
158        try
159        {
160            XMLResourceBundle bundle = (XMLResourceBundle) factory.select(externalLocation, "messages", locale);
161            if (bundle == null) 
162            {
163                // Can't translate
164                if (getLogger().isDebugEnabled())
165                {
166                    getLogger().debug("No bundle found for catalogue location '" + externalLocation + "'");
167                }
168                return null;
169            }
170            
171            try 
172            {
173                return (ParamSaxBuffer) bundle.getObject(key);
174            } 
175            catch (MissingResourceException e)  
176            {
177                if (getLogger().isDebugEnabled())
178                {
179                    getLogger().debug("Untranslated key: '" + key + "'");
180                }
181            }
182        }
183        catch (ComponentException e)
184        {
185            if (getLogger().isDebugEnabled())
186            {
187                getLogger().debug("Untranslated key: '" + key + "'");
188            }
189        }
190        
191        return null;
192        
193    }
194
195    /**
196     * Call this method when you change the list of i18n catalogues <br> by adding a new skin for example.
197     */
198    public static void needsReload()
199    {
200        _needsReloadTime = System.currentTimeMillis();
201    }
202    
203    @Override
204    public void setup(SourceResolver resolver, Map objModel, String source, Parameters parameters) throws ProcessingException, SAXException, IOException
205    {
206        try
207        {
208            if (_needsReloadTime > _lastConfigureTime)
209            {
210                _configure();
211            }
212        }
213        catch (ConfigurationException e)
214        {
215            String errorMessage = "Error with i18n on work dir";
216            getLogger().error(errorMessage, e);
217            throw new SAXException(errorMessage, e);
218        }
219        
220        super.setup(resolver, objModel, source, parameters);
221    }
222}