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.alias;
017
018import java.util.Calendar;
019import java.util.GregorianCalendar;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import org.apache.avalon.framework.parameters.Parameters;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.thread.ThreadSafe;
029import org.apache.cocoon.acting.ServiceableAction;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.PermanentRedirector;
032import org.apache.cocoon.environment.Redirector;
033import org.apache.cocoon.environment.Response;
034import org.apache.cocoon.environment.SourceResolver;
035import org.apache.commons.lang.StringUtils;
036
037import org.ametys.cms.transformation.xslt.ResolveURIComponent;
038import org.ametys.plugins.repository.AmetysObjectIterable;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.UnknownAmetysObjectException;
041import org.ametys.web.URIPrefixHandler;
042import org.ametys.web.alias.Alias.TargetType;
043import org.ametys.web.renderingcontext.RenderingContext;
044import org.ametys.web.renderingcontext.RenderingContextHandler;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.repository.site.Site;
047import org.ametys.web.repository.site.SiteManager;
048
049/**
050 * Redirect URL to alias URL if exists
051 */
052public class AliasRedirectAction extends ServiceableAction implements ThreadSafe
053{
054    private static Pattern PAGE_PATTERN = Pattern.compile("^/([^?#]+)\\.html([;?#].*)?$");
055
056    // Associate already created pattern to corresponding alias url
057    // Sitename, url, pattern
058    private static Map<String, Map<String, Pattern>> PATTERN_CACHE = new ConcurrentHashMap<>();
059
060    private AmetysObjectResolver _resolver;
061
062    private SiteManager _siteManager;
063
064    private RenderingContextHandler _renderingContextHandler;
065
066    private URIPrefixHandler _prefixHandler;
067
068    @Override
069    public void service(ServiceManager smanager) throws ServiceException
070    {
071        super.service(smanager);
072        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
073        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
074        _renderingContextHandler = (RenderingContextHandler) smanager.lookup(RenderingContextHandler.ROLE);
075        _prefixHandler = (URIPrefixHandler) smanager.lookup(URIPrefixHandler.ROLE);
076    }
077
078    @Override
079    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
080    {
081        String siteName = parameters.getParameter("siteName", null);
082        String uri = parameters.getParameter("uri", null);
083        String queryString = parameters.getParameter("queryString", null);
084        String initialRequest = parameters.getParameter("initialRequest", null);
085        String contextPath = parameters.getParameter("contextPath", null);
086
087        if (queryString != null && queryString.length() > 0)
088        {
089            uri += "?" + queryString;
090        }
091
092        Calendar cal = new GregorianCalendar();
093        cal.set(Calendar.HOUR_OF_DAY, 0);
094        cal.set(Calendar.MINUTE, 0);
095        cal.set(Calendar.SECOND, 0);
096        cal.set(Calendar.MILLISECOND, 0);
097
098        Response response = ObjectModelHelper.getResponse(objectModel);
099
100        String xpathQuery = AliasHelper.getXPath(siteName, cal.getTime());
101        try (AmetysObjectIterable<DefaultAlias> it = _resolver.query(xpathQuery);)
102        {
103            // Check if the asked url is NOT an existing page in sitemap
104            if (!_isPageURL(siteName, StringUtils.isEmpty(initialRequest) ? uri : initialRequest))
105            {
106                Map<String, Pattern> cache = _getCache(siteName);
107                
108                for (Alias alias : it)
109                {
110                    // Convert alias
111                    Pattern urlPattern = cache.get(alias.getUrl());
112                    if (urlPattern == null)
113                    {
114                        urlPattern = _fillCache(cache, alias);
115                    }
116    
117                    Matcher matcher = urlPattern.matcher(StringUtils.isEmpty(initialRequest) ? uri : initialRequest);
118    
119                    if (matcher.matches())
120                    {
121                        String target = alias.getTarget();
122    
123                        if (TargetType.PAGE.equals(alias.getType()))
124                        {
125                            try
126                            {
127                                Page page = _resolver.resolveById(target);
128                                
129                                String redirectURL;
130                                if (_renderingContextHandler.getRenderingContext() == RenderingContext.FRONT)
131                                {
132                                    redirectURL = ResolveURIComponent.resolve("page", page.getId());
133                                }
134                                else
135                                {
136                                    redirectURL = _prefixHandler.getAbsoluteUriPrefix(page.getSiteName()) + "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html";
137                                }
138                                
139                                if (redirector instanceof PermanentRedirector)
140                                {
141                                    ((PermanentRedirector) redirector).permanentRedirect(false, redirectURL);
142                                    response.setHeader("Cache-Control", "no-cache");
143                                }
144                                else
145                                {
146                                    redirector.redirect(false, redirectURL);
147                                }
148                            }
149                            catch (UnknownAmetysObjectException e)
150                            {
151                                // No redirection
152                                getLogger().warn("Can not redirect to non-existing page of id '" + target + "'");
153                                return EMPTY_MAP;
154                            }
155                        }
156                        else
157                        {
158                            int groupCount = matcher.groupCount();
159                            for (int i = 0; i < groupCount; i++)
160                            {
161                                String group = matcher.group(i + 1);
162                                target = target.replaceFirst("\\{" + (i + 1) + "\\}", group);
163                            }
164    
165                            String redirectURL = target.startsWith("http") ? target : contextPath + target;
166    
167                            if (redirector instanceof PermanentRedirector)
168                            {
169                                ((PermanentRedirector) redirector).permanentRedirect(false, redirectURL);
170                                response.setHeader("Cache-Control", "no-cache");
171                            }
172                            else
173                            {
174                                redirector.redirect(false, redirectURL);
175                            }
176                        }
177    
178                        // The first url to match is the url to redirect to
179                        return EMPTY_MAP;
180                    }
181                }
182            }
183        }
184        // No redirection
185        return EMPTY_MAP;
186    }
187
188    private Map<String, Pattern> _getCache(String siteName)
189    {
190        Map<String, Pattern> cache = PATTERN_CACHE.get(siteName);
191        if (cache == null)
192        {
193            cache = new ConcurrentHashMap<>();
194            PATTERN_CACHE.put(siteName, cache);
195        }
196        return cache;
197    }
198
199    private Pattern _fillCache(Map<String, Pattern> cache, Alias alias)
200    {
201        String[] urlParts = StringUtils.splitByWholeSeparatorPreserveAllTokens(alias.getUrl(), "**");
202        for (int i = 0; i < urlParts.length; i++)
203        {
204            urlParts[i] = urlParts[i].replaceAll("\\*", "([^/]*)").replaceAll("\\.", "\\\\.").replaceAll("\\?", "\\\\?");
205        }
206        String urlASRegex = StringUtils.join(urlParts, "(.*)");
207
208        Pattern urlPattern = Pattern.compile(urlASRegex);
209        cache.put(alias.getUrl(), urlPattern);
210
211        return urlPattern;
212    }
213
214    private boolean _isPageURL(String siteName, String initialRequest)
215    {
216        Matcher matcher = PAGE_PATTERN.matcher(initialRequest);
217        if (matcher.matches())
218        {
219            Site site = _siteManager.getSite(siteName);
220            if (site != null)
221            {
222                try
223                {
224                    if (site.getChild("ametys-internal:sitemaps/" + matcher.group(1)) != null)
225                    {
226                        return true;
227                    }
228                }
229                catch (UnknownAmetysObjectException e)
230                {
231                    // The page does not exist
232                }
233            }
234        }
235
236        return false;
237    }
238}