/* * 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.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.Region; import com.amazonaws.util.StringUtils; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.springframework.core.env.PropertyResolver; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * Backing store for {@link ShortLinkRepository} that uses an AWS S3 bucket */ public class S3ShortLinkRepository implements ShortLinkRepository { static final Logger log = Logger.getLogger(S3ShortLinkRepository.class); // Start all shortlink objects with a prefix private static final String OBJECT_PREFIX = "shortlinks/"; private boolean enabled; private AmazonS3Client client; private String bucket; public S3ShortLinkRepository(final PropertyResolver props) { String awsRegion; String awsKey; String awsSecret; enabled = true; try { bucket = props.getProperty("shortlink.s3.bucket", String.class); log.info("Short linking will use S3 bucket: " + bucket); awsKey = props.getProperty("shortlink.s3.s3key", String.class); awsSecret = props.getProperty("shortlink.s3.s3secret", String.class); if (awsKey == null || awsSecret == null) { log.warn("No AWS key or Secret found. Using Anonymous access."); client = new AmazonS3Client(); } else { client = new AmazonS3Client(new BasicAWSCredentials(awsKey, awsSecret)); } boolean exists = client.doesBucketExist(bucket); if (! exists) { awsRegion = props.getProperty("aws.s3.region", String.class, Region.US_Standard.toString()); client.createBucket(bucket, awsRegion); } log.info("S3ShortLinkRepository initialized"); } catch (Exception e) { log.info("Failed to initialize the S3 client. Shortlinking disabled.", e); enabled = false; } } @Override public boolean mapShortCode(String code, String paramString) throws IOException { if(!enabled) { throw new IllegalStateException("Shortlink feature disabled"); } try { // does object exist? client.getObjectMetadata(bucket, OBJECT_PREFIX + code); } catch (AmazonS3Exception e) { if (e.getStatusCode() == 404) { return false; } throw e; } byte[] paramStringBytes = paramString.getBytes(StringUtils.UTF8); InputStream is = new ByteArrayInputStream(paramStringBytes); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("text/plain"); metadata.setContentLength(paramStringBytes.length); client.putObject(new PutObjectRequest(bucket, OBJECT_PREFIX + code, is, metadata)); return true; } @Override public String resolveShortCode(String shortCode) throws IOException { if(!enabled) { throw new IllegalStateException("Shortlink feature disabled"); } final InputStream contents = client.getObject(bucket, OBJECT_PREFIX + shortCode).getObjectContent(); final String query = IOUtils.toString(contents, "UTF-8"); contents.close(); return query; } @Override public boolean isEnabled() { return enabled; } }