/*
* Copyright (C) 2017 Indeed Inc.
*
* 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 com.indeed.imhotep.shortlink;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Handles the HTTP requests for creating or retrieving short links
*/
@Controller
public class ShortLinkController {
private static final Logger log = Logger.getLogger(ShortLinkController.class);
private final ShortLinkRepository shortLinkRepository;
private final boolean enabled;
@Autowired
public ShortLinkController(final ShortLinkRepository shortLinkRepository) {
this.shortLinkRepository = shortLinkRepository;
this.enabled = shortLinkRepository.isEnabled();
log.info("Short linking enabled? " + enabled);
}
@RequestMapping(value="/shortlink", method={RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public Object create(
final HttpServletRequest request,
final HttpServletResponse response,
final UriComponentsBuilder uriBuilder,
@RequestParam("p") final String paramString) {
if (!enabled) {
return ImmutableMap.of("status", "disabled");
}
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
String shortCode = null;
for (int retries = 50; retries > 0; retries--) {
try {
final String allowed = "acdefghkmnprtwxyz2346789";
final String attempt = RandomStringUtils.random(6, allowed).toUpperCase();
if (shortLinkRepository.mapShortCode(attempt, paramString)) {
shortCode = attempt;
break;
}
} catch(IOException e) {
if (retries == 50) {
log.warn("First attempt to map short code failed", e);
}
}
}
if (shortCode == null) {
log.error("Failed to map a short code for " + paramString);
throw new RuntimeException("Failed to create shortcode for hash");
}
return ImmutableMap.of(
"status", "ok",
"url", uriBuilder.path("/q/"+shortCode).build().toUriString());
}
@RequestMapping(value="/q/{shortcode}", method=RequestMethod.GET)
public View redirect(@PathVariable("shortcode") final String shortCode) {
if (!enabled) {
return new RedirectView("/iql/");
}
try {
final String paramString = shortLinkRepository.resolveShortCode(shortCode);
log.info(shortCode + " resolved to " + paramString);
return new RedirectView("/iql/#" + paramString);
} catch (Exception e) {
log.error("Failed to handle /q/" + shortCode, e);
return new RedirectView("/iql/");
}
}
@RequestMapping(value="/shortlink-enabled", method=RequestMethod.GET)
@ResponseBody
public Object checkEnabled() {
return ImmutableMap.of("enabled", enabled);
}
}