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}