Java tutorial
/** * Copyright 2016, RadiantBlue Technologies, 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 gateway.controller.util; import java.io.IOException; import java.security.Principal; import java.util.concurrent.ExecutionException; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.ObjectMetadata; import com.fasterxml.jackson.databind.ObjectMapper; import exception.PiazzaJobException; import messaging.job.KafkaClientFactory; import model.data.FileRepresentation; import model.data.location.FileLocation; import model.data.location.S3FileStore; import model.job.type.IngestJob; import model.request.PiazzaJobRequest; import model.response.ErrorResponse; import model.response.JobResponse; import model.response.PiazzaResponse; import util.PiazzaLogger; import util.UUIDFactory; /** * Utility class that defines common procedures for handling requests, responses, and brokered end points to internal * Piazza components. * * @author Patrick.Doody * */ @Component public class GatewayUtil { @Autowired private ObjectMapper objectMapper; @Autowired private UUIDFactory uuidFactory; @Autowired PiazzaLogger logger; @Autowired private RestTemplate restTemplate; @Value("${vcap.services.pz-kafka.credentials.host}") private String KAFKA_ADDRESS; @Value("${s3.domain}") private String AMAZONS3_DOMAIN; @Value("${vcap.services.pz-blobstore.credentials.access_key_id:}") private String AMAZONS3_ACCESS_KEY; @Value("${vcap.services.pz-blobstore.credentials.secret_access_key:}") private String AMAZONS3_PRIVATE_KEY; @Value("${vcap.services.pz-blobstore.credentials.bucket}") private String AMAZONS3_BUCKET_NAME; @Value("${jobmanager.url}") private String JOBMANAGER_URL; private final static Logger LOGGER = LoggerFactory.getLogger(GatewayUtil.class); private Producer<String, String> producer; private AmazonS3 s3Client; /** * Initializing the Kafka Producer on Controller startup. */ @PostConstruct public void init() { // Kafka Producer. producer = KafkaClientFactory.getProducer(KAFKA_ADDRESS.split(":")[0], KAFKA_ADDRESS.split(":")[1]); // Connect to S3 Bucket. Only apply credentials if they are present. if ((AMAZONS3_ACCESS_KEY.isEmpty()) && (AMAZONS3_PRIVATE_KEY.isEmpty())) { s3Client = new AmazonS3Client(); } else { BasicAWSCredentials credentials = new BasicAWSCredentials(AMAZONS3_ACCESS_KEY, AMAZONS3_PRIVATE_KEY); s3Client = new AmazonS3Client(credentials); } } @PreDestroy public void cleanup() { producer.close(); } /** * Sends a Job Request to the Job Manager. This will generate a Job Id and return it once the Job Manager has * indexed the Job into its database. * * @param request * The Job Request * @return The Job Id */ public String sendJobRequest(PiazzaJobRequest request, String jobId) throws PiazzaJobException { try { // Generate a Job Id if (jobId == null) { jobId = getUuid(); } // Send the message to Job Manager HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<PiazzaJobRequest> entity = new HttpEntity<PiazzaJobRequest>(request, headers); ResponseEntity<PiazzaResponse> jobResponse = restTemplate.postForEntity( String.format("%s/%s?jobId=%s", JOBMANAGER_URL, "requestJob", jobId), entity, PiazzaResponse.class); // Check if the response was an error. if (jobResponse.getBody() instanceof ErrorResponse) { throw new PiazzaJobException(((ErrorResponse) jobResponse.getBody()).message); } // Return the Job Id from the response. return ((JobResponse) jobResponse.getBody()).data.getJobId(); } catch (Exception exception) { String error = String.format("Error with Job Manager when Requesting New Piazza Job: %s", exception.getMessage()); LOGGER.error(error, exception); throw new PiazzaJobException(error); } } /** * Sends a message to Kafka. This will additionally invoke .get() on the message sent, which will block until the * acknowledgement from Kafka has been received that the message entered the Kafka queue. * * @param message * The message to send. * @throws Exception * Any exceptions encountered with the send. */ public void sendKafkaMessage(ProducerRecord<String, String> message) throws InterruptedException, ExecutionException { producer.send(message).get(); } /** * Gets a UUID from the Piazza UUID Factory. * * @return UUID */ public String getUuid() throws PiazzaJobException { try { return uuidFactory.getUUID(); } catch (Exception exception) { String error = String.format("Could not connect to UUID Service for UUID: %s", exception.getMessage()); LOGGER.error(error, exception); throw new PiazzaJobException(error); } } /** * Safely returns the name of the user who has performed a request to a Gateway endpoint. * * @param user * The principal * @return The username. If the request was not authenticated, then that will be returned. */ public String getPrincipalName(Principal user) { return user != null ? user.getName() : "UNAUTHENTICATED"; } /** * Handles the uploaded file from the data/file endpoint. This will push the file to S3, and then modify the content * of the job to reference the new S3 location of the file. * * @param jobId * The Id of the Job, used for generating a unique S3 bucket file name. * @param job * The ingest job, containing the DataResource metadata * @param file * The file to be uploaded * @return The modified job, with the location of the S3 file added to the metadata */ public IngestJob pushS3File(String jobId, IngestJob job, MultipartFile file) throws AmazonServiceException, AmazonClientException, IOException { // The content length must be specified. ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(file.getSize()); // Send the file to S3. The key corresponds with the S3 file name. String fileKey = String.format("%s-%s", jobId, file.getOriginalFilename()); s3Client.putObject(AMAZONS3_BUCKET_NAME, fileKey, file.getInputStream(), metadata); // Note the S3 file path in the Ingest Job. // Attach the file to the FileLocation object FileLocation fileLocation = new S3FileStore(AMAZONS3_BUCKET_NAME, fileKey, file.getSize(), AMAZONS3_DOMAIN); ((FileRepresentation) job.getData().getDataType()).setLocation(fileLocation); logger.log(String.format("S3 File for Job %s Persisted to %s:%s", jobId, AMAZONS3_BUCKET_NAME, fileKey), PiazzaLogger.INFO); return job; } /** * Validates Pagination Inputs for List requests * * @param type * The key name of the query parameter * @param value * The value of the query parameter * @return Error description if errors occurred. Null if no errors occur. */ public String validateInput(String type, Object value) { switch (type) { case "order": if (!value.equals("asc") && !value.equals("desc")) { return "'order' parameter must be 'asc' or 'desc'"; } break; case "perPage": if (Integer.parseInt(value.toString()) < 0) { return "'perPage' parameter must be zero or greater."; } break; case "page": if (Integer.parseInt(value.toString()) < 0) { return "'page' parameter must be zero or greater."; } break; } return null; } /** * Attempts to deserialize JSON content into the ErrorResponse object, constructs a new object if it fails. * * @param String * errorBody The JSON content * @return ErrorResponse object to return to the client */ public ErrorResponse getErrorResponse(String errorBody) { try { return objectMapper.readValue(errorBody, ErrorResponse.class); } catch (Exception exception) { LOGGER.error(String.format("Error Serializing Error Body (%s) into ErrorResponse class.", errorBody), exception); return new ErrorResponse(errorBody, "Gateway"); } } }