001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.lenya.cms.linking;
019:
020: import java.net.URI;
021: import java.net.URISyntaxException;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.HashMap;
025: import java.util.List;
026: import java.util.Map;
027:
028: import org.apache.avalon.framework.service.ServiceException;
029: import org.apache.avalon.framework.service.ServiceManager;
030: import org.apache.avalon.framework.service.ServiceSelector;
031: import org.apache.lenya.ac.AccessController;
032: import org.apache.lenya.ac.AccessControllerResolver;
033: import org.apache.lenya.ac.AccreditableManager;
034: import org.apache.lenya.ac.Policy;
035: import org.apache.lenya.ac.PolicyManager;
036: import org.apache.lenya.cms.publication.DocumentFactory;
037: import org.apache.lenya.cms.publication.DocumentUtil;
038: import org.apache.lenya.cms.publication.Proxy;
039: import org.apache.lenya.cms.publication.Publication;
040: import org.apache.lenya.cms.publication.PublicationException;
041: import org.apache.lenya.cms.publication.URLInformation;
042: import org.apache.lenya.cms.repository.Session;
043: import org.apache.lenya.util.StringUtil;
044:
045: /**
046: * <p>
047: * Converts web application links to links which will be sent to the browser by using the
048: * publication's proxy settings. If the current request is SSL-encrypted, all link URLs will use the
049: * SSL proxy.
050: * </p>
051: * <p>
052: * Objects of this class are not thread-safe.
053: * </p>
054: */
055: public class OutgoingLinkRewriter extends ServletLinkRewriter {
056:
057: private boolean relativeUrls;
058: private PolicyManager policyManager;
059: private AccreditableManager accreditableManager;
060: private DocumentFactory factory;
061: private boolean ssl;
062: private GlobalProxies globalProxies;
063: private boolean considerSslPolicies;
064:
065: /**
066: * @param manager The service manager to use.
067: * @param session The current session.
068: * @param requestUrl The request URL where the links should be rewritten.
069: * @param ssl If the current page is SSL-encrypted.
070: * @param considerSslPolicies If the SSL protection of policies should be considered when
071: * resolving the corresponding proxy. Setting this to <code>true</code> leads to a
072: * substantial performance overhead.
073: * @param relativeUrls If relative URLs should be created.
074: */
075: public OutgoingLinkRewriter(ServiceManager manager,
076: Session session, String requestUrl, boolean ssl,
077: boolean considerSslPolicies, boolean relativeUrls) {
078:
079: super (manager);
080: this .requestUrl = requestUrl;
081: this .relativeUrls = relativeUrls;
082: this .ssl = ssl;
083: this .considerSslPolicies = considerSslPolicies;
084:
085: ServiceSelector serviceSelector = null;
086: AccessControllerResolver acResolver = null;
087:
088: try {
089: this .factory = DocumentUtil.createDocumentFactory(
090: this .manager, session);
091:
092: if (this .considerSslPolicies) {
093: serviceSelector = (ServiceSelector) this .manager
094: .lookup(AccessControllerResolver.ROLE
095: + "Selector");
096: acResolver = (AccessControllerResolver) serviceSelector
097: .select(AccessControllerResolver.DEFAULT_RESOLVER);
098: AccessController accessController = acResolver
099: .resolveAccessController(requestUrl);
100: if (accessController != null) {
101: this .accreditableManager = accessController
102: .getAccreditableManager();
103: this .policyManager = accessController
104: .getPolicyManager();
105: }
106: }
107:
108: Publication[] pubs = this .factory.getPublications();
109: for (int i = 0; i < pubs.length; i++) {
110: this .publicationCache.put(pubs[i].getId(), pubs[i]);
111: }
112:
113: } catch (final Exception e) {
114: throw new RuntimeException(e);
115: } finally {
116: if (serviceSelector != null) {
117: if (acResolver != null) {
118: serviceSelector.release(acResolver);
119: }
120: this .manager.release(serviceSelector);
121: }
122: }
123: }
124:
125: protected GlobalProxies getGlobalProxies() {
126: if (this .globalProxies == null) {
127: try {
128: this .globalProxies = (GlobalProxies) this .manager
129: .lookup(GlobalProxies.ROLE);
130: } catch (ServiceException e) {
131: throw new RuntimeException(e);
132: }
133: }
134: return this .globalProxies;
135: }
136:
137: public boolean matches(String url) {
138: return url.startsWith("/");
139: }
140:
141: private Map publicationCache = new HashMap();
142:
143: protected Publication getPublication(String pubId)
144: throws PublicationException {
145: return (Publication) this .publicationCache.get(pubId);
146: }
147:
148: public String rewrite(final String url) {
149:
150: String rewrittenUrl = "";
151:
152: try {
153: String normalizedUrl = normalizeUrl(url);
154: if (this .relativeUrls) {
155: rewrittenUrl = getRelativeUrlTo(normalizedUrl);
156: } else {
157: boolean useSsl = this .ssl;
158: if (!useSsl && this .policyManager != null) {
159: Policy policy = this .policyManager.getPolicy(
160: this .accreditableManager, normalizedUrl);
161: useSsl = policy.isSSLProtected();
162: }
163:
164: URLInformation info = new URLInformation(normalizedUrl);
165: String pubId = info.getPublicationId();
166:
167: Publication pub = null;
168: if (pubId != null) {
169: pub = getPublication(pubId);
170: }
171:
172: // link points to publication
173: if (pub != null) {
174: rewrittenUrl = rewriteLink(normalizedUrl, pub,
175: useSsl);
176: }
177:
178: // link doesn't point to publication
179: else {
180: Proxy proxy = getGlobalProxies().getProxy(ssl);
181: rewrittenUrl = proxy.getUrl() + normalizedUrl;
182: }
183: }
184: } catch (Exception e) {
185: throw new RuntimeException(e);
186: }
187: return rewrittenUrl;
188: }
189:
190: protected String normalizeUrl(final String url)
191: throws URISyntaxException {
192: String normalizedUrl;
193: if (url.indexOf("..") > -1) {
194: normalizedUrl = new URI(url).normalize().toString();
195: } else {
196: normalizedUrl = url;
197: }
198: return normalizedUrl;
199: }
200:
201: private String requestUrl;
202:
203: private Map pubId2areaList = new HashMap();
204:
205: /**
206: * Checks if a publication has an area by using a cache for performance reasons.
207: * @param pub The publication.
208: * @param area The area name.
209: * @return if the publication contains the area.
210: */
211: protected boolean hasArea(Publication pub, String area) {
212: String pubId = pub.getId();
213: List areas = (List) this .pubId2areaList.get(pubId);
214: if (areas == null) {
215: areas = Arrays.asList(pub.getAreaNames());
216: this .pubId2areaList.put(pubId, areas);
217: }
218: return areas.contains(area);
219: }
220:
221: /**
222: * @param linkUrl The original link URL.
223: * @param pub The publication to use for proxy resolving.
224: * @param ssl If the URL uses SSL.
225: * @return A link URL.
226: */
227: protected String rewriteLink(String linkUrl, Publication pub,
228: boolean ssl) {
229: URLInformation info = new URLInformation(linkUrl);
230: String rewrittenUrl;
231: String areaName = info.getArea();
232:
233: // valid area
234: if (areaName != null && hasArea(pub, areaName)) {
235: Proxy proxy = pub.getProxy(areaName, ssl);
236: rewrittenUrl = proxy.getUrl() + info.getDocumentUrl();
237: }
238:
239: // invalid area
240: else {
241: Proxy proxy = getGlobalProxies().getProxy(ssl);
242: rewrittenUrl = proxy.getUrl() + linkUrl;
243: }
244: return rewrittenUrl;
245: }
246:
247: protected String getRelativeUrlTo(String webappUrl) {
248: String relativeUrl;
249: if (this .requestUrl.equals(webappUrl)) {
250: relativeUrl = getLastStep(webappUrl);
251: } else {
252: List sourceSteps = toList(this .requestUrl);
253: List targetSteps = toList(webappUrl);
254:
255: while (!sourceSteps.isEmpty() && !targetSteps.isEmpty()
256: && sourceSteps.get(0).equals(targetSteps.get(0))) {
257: sourceSteps.remove(0);
258: targetSteps.remove(0);
259: }
260:
261: String prefix = "";
262: if (targetSteps.isEmpty()) {
263: prefix = generateUpDots(sourceSteps.size());
264: } else if (sourceSteps.isEmpty()) {
265: prefix = getLastStep(this .requestUrl) + "/";
266: } else if (sourceSteps.size() > 1) {
267: prefix = generateUpDots(sourceSteps.size() - 1) + "/";
268: }
269:
270: String[] targetArray = (String[]) targetSteps
271: .toArray(new String[targetSteps.size()]);
272: String targetPath = StringUtil.join(targetArray, "/");
273: relativeUrl = prefix + targetPath;
274: }
275: return relativeUrl;
276: }
277:
278: protected String getLastStep(String url) {
279: return url.substring(url.lastIndexOf("/") + 1);
280: }
281:
282: protected String generateUpDots(int length) {
283: String upDots;
284: String[] upDotsArray = new String[length];
285: Arrays.fill(upDotsArray, "..");
286: upDots = StringUtil.join(upDotsArray, "/");
287: return upDots;
288: }
289:
290: protected List toList(String url) {
291: return new ArrayList(Arrays.asList(url.substring(1).split("/",
292: -1)));
293: }
294:
295: }
|