View Javadoc
1   /*
2    * Copyright 2010 FatWire Corporation. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.fatwire.gst.foundation.url;
17  
18  import java.net.URI;
19  import java.net.URISyntaxException;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import com.fatwire.cs.core.uri.Assembler;
28  import com.fatwire.cs.core.uri.Definition;
29  import com.fatwire.cs.core.uri.QueryAssembler;
30  import com.fatwire.cs.core.uri.Simple;
31  import com.openmarket.xcelerate.publish.PubConstants;
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  import static COM.FutureTense.Interfaces.Utilities.goodString;
38  
39  /**
40   * Web-referenceable asset path assembler.
41   * 
42   * @author Tony Field
43   * @since Jul 20, 2010
44   */
45  public final class WraPathAssembler extends LightweightAbstractAssembler {
46      protected static final Log LOG = LogFactory.getLog(WraPathAssembler.class.getName());
47  
48      /**
49       * Name of query string parameter for virtual webroot
50       */
51      private static final String VIRTUAL_WEBROOT = "virtual-webroot";
52  
53      /**
54       * Name of query string parameter for url-path
55       */
56      private static final String URL_PATH = "url-path";
57  
58      /**
59       * Name of packedargs param
60       */
61      private static final String PACKEDARGS = "packedargs";
62  
63      /**
64       * The assembler to use in case the input does not support the WRAPath
65       * approach
66       */
67      private Assembler theBackupAssembler;
68  
69      /**
70       * The pagename that is used when disassembling URLs
71       */
72      private String[] pagename = { "GST/Dispatcher" };
73  
74      /**
75       * List of parameters that are effectively embedded in the URL. These
76       * parameters will not be relayed through the URL as query string
77       * parameters.
78       */
79      private static List<String> EMBEDDED_PARAMS = Arrays.asList(PubConstants.PAGENAME, PubConstants.CHILDPAGENAME,
80              VIRTUAL_WEBROOT, URL_PATH, PubConstants.c, PubConstants.cid);
81      /**
82       * Configuration property for overriding the default dispatcher pagename.
83       * The default is GST/Dispatcher.
84       */
85      public static final String DISPATCHER_PROPNAME = "com.fatwire.gst.foundation.url.wrapathassembler.dispatcher";
86  
87      /**
88       * Configuration property for overriding the backup assembler. URLs that
89       * can't be built using this assembler, that is, URLs for assets that are
90       * not WRAs (or for WRAs missing critical fields), are built using the
91       * backup assembler. The default backup assembler is the standard
92       * QueryAssembler.
93       */
94      public static final String BACKUP_ASSEMBLER_PROPNAME = "com.fatwire.gst.foundation.url.wrapathassembler.backup-assembler";
95  
96      /**
97       * Set properties, initializing the assembler
98       * 
99       * @param properties configuration properties
100      */
101     public void setProperties(Properties properties) {
102         super.setProperties(properties);
103         String backupAssemblerClass = getProperty(BACKUP_ASSEMBLER_PROPNAME, QueryAssembler.class.getName());
104         theBackupAssembler = _instantiateBackupAssembler(backupAssemblerClass);
105         theBackupAssembler.setProperties(properties);
106         pagename[0] = getProperty(DISPATCHER_PROPNAME, "GST/Dispatcher");
107         LOG.info("Initialized " + WraPathAssembler.class + " with backup assembler " + backupAssemblerClass
108                 + " using properties " + properties);
109     }
110 
111     private Assembler _instantiateBackupAssembler(String classname) {
112         try {
113             Class<?> c = Class.forName(classname);
114             Object o = c.newInstance();
115             return (Assembler) o;
116         } catch (ClassNotFoundException e) {
117             throw new IllegalArgumentException("Illegal class name for backup assembler: " + classname, e);
118         } catch (InstantiationException e) {
119             throw new IllegalStateException("Could not instantiate backup assembler: " + classname, e);
120         } catch (IllegalAccessException e) {
121             throw new IllegalStateException("Could not instantiate backup assembler: " + classname, e);
122         } catch (ClassCastException e) {
123             throw new IllegalArgumentException("Backup assembler class is not an instance of Assembler: " + classname,
124                     e);
125         }
126     }
127 
128     /**
129      * Looks for virtual-webroot and url-path. If found, concatenates
130      * virtual-webroot and url-path. Once core query params are suppressed, the
131      * remaining params are appended to the URL.
132      * 
133      * @param definition
134      * @return valid URI
135      * @throws URISyntaxException
136      */
137     public URI assemble(Definition definition) throws URISyntaxException {
138 
139         // get packedargs just in case we need them
140         Map<String, String[]> packedargs = getPackedargs(definition);
141 
142         String virtualWebroot = definition.getParameter(VIRTUAL_WEBROOT);
143         if (!goodString(virtualWebroot) && packedargs.containsKey(VIRTUAL_WEBROOT)) {
144             String[] s = packedargs.get(VIRTUAL_WEBROOT);
145             if (s != null && s.length > 0)
146                 virtualWebroot = s[0];
147         }
148         String urlPath = definition.getParameter(URL_PATH);
149         if (!goodString(urlPath) && packedargs.containsKey(URL_PATH)) {
150             String[] s = packedargs.get(URL_PATH);
151             if (s != null && s.length > 0)
152                 urlPath = s[0];
153         }
154         if (!goodString(virtualWebroot) || !goodString(urlPath)) {
155             if (LOG.isDebugEnabled()) {
156                 LOG.debug("WRAPathAssembler can't assemble definition due to missing '" + VIRTUAL_WEBROOT
157                         + "' and/or '" + URL_PATH + "' params. Definition: " + definition);
158             }
159             return theBackupAssembler.assemble(definition); // Can't assemble
160                                                             // this URL.
161         }
162         if (LOG.isDebugEnabled()) {
163             LOG.debug("WRAPathAssembler is assembling definition: " + definition);
164         }
165         String quotedQueryString = _getQuotedQueryString(definition);
166         return constructURI(virtualWebroot, urlPath, quotedQueryString, definition.getFragment());
167     }
168 
169     private String _getQuotedQueryString(Definition definition) {
170         Map<String, String[]> newQryParams = new HashMap<String, String[]>();
171 
172         // build the query string if there is one
173         for (Object o : definition.getParameterNames()) {
174             // Get the parameter name
175             String key = (String) o;
176 
177             // Don't add embedded params to the query string
178             if (!EMBEDDED_PARAMS.contains(key)) {
179                 String[] vals = definition.getParameters(key);
180                 if (key.equals(PACKEDARGS)) {
181                     vals = excludeFromPackedargs(vals, EMBEDDED_PARAMS);
182                 }
183                 newQryParams.put(key, vals);
184             }
185         }
186         return constructQueryString(newQryParams);
187     }
188 
189     @SuppressWarnings("unchecked")
190     private Map<String, String[]> getPackedargs(Definition definition) {
191         String[] packedargs = definition.getParameters(PACKEDARGS);
192         if (packedargs != null && packedargs.length > 0) {
193             return parseQueryString(packedargs[0]);
194         }
195         return Collections.EMPTY_MAP;
196     }
197 
198     /**
199      * Construct a query string using the required input for this assembler.
200      * 
201      * @param virtualWebroot as defined in the GST Site Foundation spec
202      * @param uriPath as defined in the GST Site Foundation spec
203      * @param quotedQueryString query string excluding embedded params, quoted
204      * @param fragment fragment from definition
205      * @return valid URL
206      * @throws URISyntaxException on bad input data
207      * @see LightweightAbstractAssembler#constructURI for inspiration if edits
208      *      are required
209      */
210     private static final URI constructURI(final String virtualWebroot, String uriPath, String quotedQueryString,
211             String fragment) throws URISyntaxException {
212         StringBuilder bf = new StringBuilder();
213         bf.append(virtualWebroot);
214         // Path needs quoting though, so let the URI object do it for us.
215         // Use the toASCIIString() method because we need the quoted values.
216         // (toString() is really just for readability and debugging, not
217         // programmatic use)
218         bf.append(new URI(null, null, uriPath, null, null).getRawPath());
219         if (goodString(quotedQueryString)) {
220             bf.append('?').append(quotedQueryString); // already quoted
221         }
222         // needs quoting
223         if (goodString(fragment)) {
224             bf.append(new URI(null, null, null, null, fragment).toASCIIString());
225         }
226         URI uri = new URI(bf.toString());
227 
228         if (LOG.isDebugEnabled()) {
229             StringBuilder msg = new StringBuilder();
230             if (LOG.isTraceEnabled()) {
231                 msg.append("Constructing new URI using the following components: \n\tvirtual-webroot=")
232                         .append(virtualWebroot).append("\n\turi-path=").append(uriPath)
233                         .append("\n\tquotedQueryString=").append(quotedQueryString).append("\n\tfragment=")
234                         .append(fragment);
235                 msg.append("\n");
236             }
237             msg.append("Assembled URI").append(uri.toASCIIString());
238             if (LOG.isTraceEnabled())
239                 LOG.trace(msg);
240             else
241                 LOG.debug(msg);
242         }
243         return uri;
244     }
245 
246     public Definition disassemble(URI uri, Definition.ContainerType containerType) throws URISyntaxException {
247         Map<String, String[]> params = parseQueryString(uri.getRawQuery());
248 
249         String[] virtualWebrootArr = params.get(VIRTUAL_WEBROOT);
250         String[] uriPathArr = params.get(URL_PATH);
251         if (virtualWebrootArr == null || virtualWebrootArr.length != 1) {
252             if (LOG.isTraceEnabled())
253                 LOG.trace("WRAPathAssembler cannot disassemble URI '" + uri + "' because the " + VIRTUAL_WEBROOT
254                         + " parameter is either missing or has more than one value");
255             return theBackupAssembler.disassemble(uri, containerType);
256         }
257         if (uriPathArr == null || uriPathArr.length != 1) {
258             if (LOG.isTraceEnabled())
259                 LOG.trace("WRAPathAssembler cannot disassemble URI '" + uri + "' because the " + URL_PATH
260                         + " parameter is either missing or has more than one value");
261             return theBackupAssembler.disassemble(uri, containerType);
262         }
263 
264         if (LOG.isDebugEnabled()) {
265             LOG.debug("Disassembling URI: " + uri);
266         }
267 
268         Simple result;
269         try {
270             String[] request_pagename = params.get(PubConstants.PAGENAME);
271             if (request_pagename == null || request_pagename.length == 0 || StringUtils.isBlank(request_pagename[0])) {
272                 params.put(PubConstants.PAGENAME, pagename);
273             }
274             final Definition.AppType appType = Definition.AppType.CONTENT_SERVER;
275             final Definition.SatelliteContext satelliteContext = Definition.SatelliteContext.SATELLITE_SERVER;
276             final boolean sessionEncode = false;
277             final String scheme = uri.getScheme();
278             final String authority = uri.getAuthority();
279             final String fragment = uri.getFragment();
280 
281             result = new Simple(sessionEncode, satelliteContext, containerType, scheme, authority, appType, fragment);
282             result.setQueryStringParameters(params);
283         } catch (IllegalArgumentException e) {
284             // Something bad happened
285             throw new URISyntaxException(uri.toString(), e.toString());
286         }
287         return result;
288     }
289 }