/**
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed 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 foo.domaintest.http;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Iterables.getFirst;
import static foo.domaintest.util.QueryStringHelper.parseQuery;
import static java.util.UUID.randomUUID;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.common.io.CharStreams;
import foo.domaintest.action.Action;
import foo.domaintest.action.RequestModule;
import foo.domaintest.action.annotation.RequestData;
import foo.domaintest.config.ConfigModule.EasterEggs;
import foo.domaintest.metrics.Metrics;
import foo.domaintest.metrics.impl.MetricsModule;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Documented;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletResponse;
/** Dagger module for creating the HTTP API actions. */
@Module(
addsTo = RequestModule.class,
includes = MetricsModule.class,
injects = {
EchoAction.class,
StashAction.class,
TempAction.class,
TokenAction.class })
public class HttpApiModule {
/** Binding annotation for a random token for memcache. */
@Qualifier
@Documented
@interface RandomToken {}
/** Binding annotation for request parameters. */
@Qualifier
@Documented
@interface Param {
String value();
}
public static final Set<Class<? extends Action>> ACTIONS =
ImmutableSet.<Class<? extends Action>>of(
EchoAction.class,
StashAction.class,
TempAction.class,
TokenAction.class);
@Provides
@Singleton
Multimap<String, String> provideParameterMap(
@RequestData("queryString") String queryString,
@RequestData("postBody") Lazy<String> lazyPostBody,
@RequestData("charset") String requestCharset,
FileItemIterator multipartIterator) {
// Calling request.getParameter() or request.getParameterMap() etc. consumes the POST body. If
// we got the "postpayload" param we don't want to parse the body, so use only the query params.
// Note that specifying both "payload" and "postpayload" will result in the "payload" param
// being honored and the POST body being completely ignored.
ImmutableMultimap.Builder<String, String> params = new ImmutableMultimap.Builder<>();
Multimap<String, String> getParams = parseQuery(queryString);
params.putAll(getParams);
if (getParams.containsKey("postpayload")) {
// Treat the POST body as if it was the "payload" param.
return params.put("payload", nullToEmpty(lazyPostBody.get())).build();
}
// No "postpayload" so it's safe to consume the POST body and look for params there.
if (multipartIterator == null) { // Handle GETs and form-urlencoded POST requests.
params.putAll(parseQuery(nullToEmpty(lazyPostBody.get())));
} else { // Handle multipart/form-data requests.
try {
while (multipartIterator != null && multipartIterator.hasNext()) {
FileItemStream item = multipartIterator.next();
try (InputStream stream = item.openStream()) {
params.put(
item.isFormField() ? item.getFieldName() : item.getName(),
CharStreams.toString(new InputStreamReader(stream, requestCharset)));
}
}
} catch (FileUploadException | IOException e) {
// Ignore the failure and fall through to return whatever params we managed to parse.
}
}
return params.build();
}
@Provides
@Singleton
@EasterEggs
String provideEasterEggUrl(
Multimap<String, String> params, @EasterEggs Table<String, String, String> easterEggs) {
for (Entry<String, String> param : params.entries()) {
String easterEggUrl = easterEggs.get(param.getKey(), param.getValue());
if (easterEggUrl != null) {
return easterEggUrl;
}
}
return null;
}
@Provides
@Param("status")
Integer provideStatus(Multimap<String, String> params, @EasterEggs String easterEggUrl) {
if (easterEggUrl == null) {
String statusString = getFirst(params.get("status"), null);
return statusString == null ? null : parseInt(statusString);
}
return HttpServletResponse.SC_FOUND;
}
@Provides
@Param("sleep")
Integer provideSleep(Multimap<String, String> params) {
String sleepString = getFirst(params.get("sleep"), null);
return sleepString == null ? null : parseInt(sleepString);
}
@Provides
@Param("mime")
String provideMime(Multimap<String, String> params) {
return getFirst(params.get("mime"), null);
}
@Provides
@Param("payload")
String providePayload(Multimap<String, String> params, @EasterEggs String easterEggUrl) {
return easterEggUrl == null ? getFirst(params.get("payload"), null) : easterEggUrl;
}
@Provides
@Param("token")
String provideToken(Multimap<String, String> params) {
return getFirst(params.get("token"), null);
}
@Provides
@Param("delcookie")
List<String> provideDelCookie(Multimap<String, String> params) {
return ImmutableList.copyOf(params.get("delcookie"));
}
@Provides
@Param("addcookie")
Map<String, String> provideAddCookie(Multimap<String, String> params) {
return parseMap(params.get("addcookie"));
}
@Provides
@Param("header")
Map<String, String> provideHeader(Multimap<String, String> params) {
return parseMap(params.get("header"));
}
@Provides
@RandomToken
String provideRandomToken(Metrics metrics) {
metrics.addActivity("random_token");
return randomUUID().toString();
}
private Integer parseInt(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return null;
}
}
private Map<String, String> parseMap(Collection<String> values) {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
for (String value : values) {
List<String> parts = Splitter.on('=').limit(2).splitToList(value);
// Note that this puts "" in the map if there's no '=' in the value.
builder.put(parts.get(0), Iterables.get(parts, 1, ""));
}
return builder.build();
}
}