/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shindig.gadgets.uri;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.shindig.common.servlet.ServletRequestContext;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.UriBuilder;
import org.apache.shindig.config.ContainerConfig;
import org.apache.shindig.gadgets.uri.UriCommon.Param;
// Temporary replacement of javax.annotation.Nullable
import org.apache.shindig.common.Nullable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Default implementation of a ConcatUriManager
*
* @since 2.0.0
*/
public class DefaultConcatUriManager implements ConcatUriManager {
public static final String CONCAT_HOST_PARAM = "gadgets.uri.concat.host";
public static final String CONCAT_PATH_PARAM = "gadgets.uri.concat.path";
public static final String CONCAT_JS_SPLIT_PARAM = "gadgets.uri.concat.js.splitToken";
public static final String CONCAT_JS_EVAL_TPL = "eval(%s['%s']);";
private static final ConcatUri BAD_URI =
new ConcatUri(UriStatus.BAD_URI, null, null, null, null);
private static final Integer START_INDEX = 1;
private final ContainerConfig config;
private final Versioner versioner;
private boolean strictParsing;
@Inject
public DefaultConcatUriManager(ContainerConfig config, @Nullable Versioner versioner) {
this.config = config;
this.versioner = versioner;
}
@Inject(optional = true)
public void setUseStrictParsing(
@Named("shindig.uri.concat.use-strict-parsing") boolean useStrict) {
this.strictParsing = useStrict;
}
public List<ConcatData> make(List<ConcatUri> resourceUris,
boolean isAdjacent) {
List<ConcatData> concatUris = Lists.newArrayListWithCapacity(resourceUris.size());
if (resourceUris.isEmpty()) {
return concatUris;
}
ConcatUri exemplar = resourceUris.get(0);
String container = exemplar.getContainer();
List<String> versions = null;
List<List<Uri>> batches = Lists.newArrayListWithCapacity(resourceUris.size());
for (ConcatUri ctx : resourceUris) {
batches.add(ctx.getBatch());
}
if (versioner != null) {
versions = versioner.version(batches, container);
}
Iterator<String> versionIt = versions != null ? versions.iterator() : null;
for (ConcatUri ctx : resourceUris) {
String version = versionIt != null ? versionIt.next() : null;
concatUris.add(
makeConcatUri(ctx, isAdjacent, version));
}
return concatUris;
}
private ConcatData makeConcatUri(ConcatUri ctx, boolean isAdjacent, String version) {
// TODO: Consider per-bundle isAdjacent plus first-bundle direct evaluation
if (!isAdjacent && ctx.getType() != Type.JS) {
// Split-concat is only supported for JS at the moment.
// This situation should never occur due to ConcatLinkRewriter's implementation.
throw new UnsupportedOperationException("Split concatenation only supported for JS");
}
UriBuilder uriBuilder = ctx.makeQueryParams(null, version);
String concatHost = getReqVal(ctx.getContainer(), CONCAT_HOST_PARAM);
String concatPath = getReqVal(ctx.getContainer(), CONCAT_PATH_PARAM);
uriBuilder.setAuthority(concatHost);
uriBuilder.setPath(concatPath);
uriBuilder.addQueryParameter(Param.TYPE.getKey(), ctx.getType().getType());
List<Uri> resourceUris = ctx.getBatch();
Map<Uri, String> snippets = Maps.newHashMapWithExpectedSize(resourceUris.size());
String splitParam = config.getString(ctx.getContainer(), CONCAT_JS_SPLIT_PARAM);
boolean doSplit = false;
if (!isAdjacent && splitParam != null && !"false".equalsIgnoreCase(splitParam)) {
uriBuilder.addQueryParameter(Param.JSON.getKey(), splitParam);
doSplit = true;
}
Integer i = Integer.valueOf(START_INDEX);
for (Uri resource : resourceUris) {
uriBuilder.addQueryParameter(i.toString(), resource.toString());
i++;
if (doSplit) {
snippets.put(resource, getJsSnippet(splitParam, resource));
}
}
return new ConcatData(uriBuilder.toUri(), snippets);
}
static String getJsSnippet(String splitParam, Uri resource) {
return String.format(CONCAT_JS_EVAL_TPL, splitParam,
StringEscapeUtils.escapeJavaScript(resource.toString()));
}
private String getReqVal(String container, String key) {
String val = config.getString(container, key);
if (val == null) {
throw new RuntimeException(
"Missing required config '" + key + "' for container: " + container);
}
val = val.replace("%host%", ServletRequestContext.getAuthority());
return val;
}
public ConcatUri process(Uri uri) {
String container = uri.getQueryParameter(Param.CONTAINER.getKey());
if (strictParsing && container == null) {
return BAD_URI;
}
if (strictParsing) {
String concatHost = getReqVal(container, CONCAT_HOST_PARAM);
String concatPath = getReqVal(container, CONCAT_PATH_PARAM);
if (!uri.getAuthority().equalsIgnoreCase(concatHost) ||
!uri.getPath().equals(concatPath)) {
return BAD_URI;
}
}
// At this point the Uri is at least concat.
UriStatus status = UriStatus.VALID_UNVERSIONED;
List<Uri> uris = Lists.newLinkedList();
Type type = Type.fromType(uri.getQueryParameter(Param.TYPE.getKey()));
if (type == null) {
// try "legacy" method
type = Type.fromMime(uri.getQueryParameter("rewriteMime"));
if (type == null) {
return BAD_URI;
}
}
String splitParam = type == Type.JS ? uri.getQueryParameter(Param.JSON.getKey()) : null;
Integer i = Integer.valueOf(START_INDEX);
String uriStr = null;
while ((uriStr = uri.getQueryParameter(i.toString())) != null) {
try {
uris.add(Uri.parse(uriStr));
} catch (IllegalArgumentException e) {
// Malformed inbound Uri. Don't process.
return BAD_URI;
}
i++;
}
if (versioner != null) {
String version = uri.getQueryParameter(Param.VERSION.getKey());
if (version != null) {
status = versioner.validate(uris, container, version);
}
}
return new ConcatUri(status, uris, splitParam, type, uri);
}
}