View Javadoc
1   /**
2    * This file Copyright (c) 2003-2017 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.virtualuri.mapping;
35  
36  import info.magnolia.context.MgnlContext;
37  import info.magnolia.context.WebContext;
38  
39  import java.net.URI;
40  import java.util.ArrayList;
41  import java.util.Collections;
42  import java.util.Comparator;
43  import java.util.List;
44  import java.util.Optional;
45  
46  import org.apache.commons.lang3.StringUtils;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  /**
51   * Virtual Uri mapping that can forward to a different url depending on the request host name. The "mappings" node
52   * contains a list of nodes with "host" and "toUri" properties.
53   * See below for a sample configuration:
54   *
55   * <pre>
56   * # [module]/virtualUriMappings/[mapping-name].yaml
57   * ---
58   * class: info.magnolia.virtualuri.mapping.HostBasedVirtualUriMapping
59   * fromUri: /
60   * toUri: redirect:/.magnolia/admincentral
61   * mappings:
62   *   - host: www.acme.com
63   *     toUri: forward:/acme/en/index.html
64   *   - host: www.acme.de
65   *     toUri: forward:/acme/de/index.html
66   * </pre>
67   */
68  public class HostBasedVirtualUriMapping extends DefaultVirtualUriMapping {
69      private static final Logger log = LoggerFactory.getLogger(HostBasedVirtualUriMapping.class);
70  
71      private List<HostUriMapping> hostMappings = new ArrayList<>();
72  
73      public List<HostUriMapping> getMappings() {
74          return Collections.unmodifiableList(hostMappings);
75      }
76  
77      public void setMappings(List<HostUriMapping> hostMappings) {
78          this.hostMappings = hostMappings;
79      }
80  
81      /**
82       * Choose shortest HostMapping matching request host, with the least number of additional characters.
83       *
84       * @return The URI to map to.
85       */
86      @Override
87      public Optional<Result> mapUri(URI uri) {
88          Optional<Result> fallbackResult = super.mapUri(uri);
89          if (!fallbackResult.isPresent()) {
90              // fromUri pattern didn't match
91              return Optional.empty();
92          }
93  
94          Optional<String> hostResult = tryToMapHost();
95          if (!hostResult.isPresent()) {
96              return fallbackResult;
97          }
98  
99          return Optional.of(new Result(hostResult.get(), getPattern().getLength(), this));
100     }
101 
102     @Override
103     public boolean isValid() {
104         return StringUtils.isNotEmpty(getFromUri()) && hostMappings.stream()
105                 .anyMatch(HostUriMapping::isValid);
106     }
107 
108     /**
109      * Choose the longest host-mapping matching the request host from its end (most specific).
110      */
111     protected Optional<String> tryToMapHost() {
112         if (hostMappings == null || hostMappings.isEmpty()) {
113             log.warn("No configured hosts to evaluate");
114             return Optional.empty();
115         }
116 
117         // URI mappings consume a relativized URI, so we historically resort to other means to get the host name.
118         // This may be revisited if we generalize relative URIs into a domain abstraction of a magnolia request (superseding most of agg-state).
119         String requestHost = ((WebContext) MgnlContext.getInstance()).getRequest().getServerName();
120         Comparator<HostUriMapping> hostLengthComparator = (hm1, hm2) -> Integer.compare(hm1.getHost().length(), hm2.getHost().length());
121 
122         return hostMappings.stream()
123                 .filter(hostMapping -> hostMapping.isValid() && requestHost.endsWith(hostMapping.getHost()))
124                 .max(hostLengthComparator)
125                 .map(HostUriMapping::getToUri);
126     }
127 
128     @Override
129     public String toString() {
130         return "[" + super.toString() + "[hosts:" + hostMappings.toString() + "]]";
131     }
132 
133     /**
134      * HostUriMapping object to receive "mapping" nodes in configuration.
135      */
136     public static class HostUriMapping {
137         private String host;
138         private String toUri;
139 
140         public String getHost() {
141             return host;
142         }
143 
144         public void setHost(String host) {
145             this.host = host;
146         }
147 
148         public String getToUri() {
149             return toUri;
150         }
151 
152         public void setToUri(String toUri) {
153             this.toUri = toUri;
154         }
155 
156         public boolean isValid() {
157             return StringUtils.isNotEmpty(host) && StringUtils.isNotEmpty(toUri);
158         }
159 
160         @Override
161         public String toString() {
162             return String.format("[%s --> %s]", host, toUri);
163         }
164     }
165 }