org.pesc.cds.web.TranscriptRequestController.java Source code

Java tutorial

Introduction

Here is the source code for org.pesc.cds.web.TranscriptRequestController.java

Source

/*
 * Copyright (c) 2017. California Community Colleges Technology Center
 *
 * 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 org.pesc.cds.web;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pesc.cds.domain.Transaction;
import org.pesc.cds.model.*;
import org.pesc.cds.repository.TransactionService;
import org.pesc.cds.service.OrganizationService;
import org.pesc.cds.service.PKIService;
import org.pesc.cds.utils.ErrorUtils;
import org.pesc.sdk.core.coremain.v1_14.DocumentTypeCodeType;
import org.pesc.sdk.core.coremain.v1_14.StateProvinceCodeType;
import org.pesc.sdk.core.coremain.v1_14.TransmissionTypeType;
import org.pesc.sdk.message.documentinfo.v1_0.DocumentTypeCode;
import org.pesc.sdk.message.transcriptrequest.v1_4.TranscriptRequest;
import org.pesc.sdk.sector.academicrecord.v1_9.PhoneType;
import org.pesc.sdk.sector.academicrecord.v1_9.ReleaseAuthorizedMethodType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.oxm.Marshaller;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.ResourceAccessException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by james on 9/12/16.
 */
@RestController
@RequestMapping(value = "api/v1/transcript-requests")
@Api(tags = "Transcript Requests", description = "Manage transcript requests.")
public class TranscriptRequestController {

    private static final Log log = LogFactory.getLog(TranscriptRequestController.class);

    @Autowired
    private PKIService pkiService;

    @Resource(name = "transcriptRequestMarshaller")
    private Marshaller transcriptRequestMarshaller;

    @Resource(name = "documentInfoMarshaller")
    private Marshaller documentInfoMarshaller;

    @Value("${networkServer.outbox.path}")
    private String localServerOutboxPath;

    @Value("${networkServer.id}")
    private String localServerId;

    @Value("${networkServer.webServiceURL}")
    private String localServerWebServiceURL;

    @Autowired
    private TransactionService transactionService;

    @Autowired
    private OrganizationService organizationService;

    @Autowired
    @Qualifier("myRestTemplate")
    private OAuth2RestTemplate restTemplate;

    private String getEmailAddress(JSONObject edexOrganization) {
        JSONArray contacts = edexOrganization.getJSONArray("contacts");
        if (contacts.length() > 0) {
            JSONObject contact = contacts.getJSONObject(0);
            return contact.getString("email");
        }

        return null;
    }

    /**
     * The source school indicates the school that is requesting the transcript.
     * @param builder
     * @param schoolDTO
     */
    private void constructTranscriptRequestSource(TranscriptRequestBuilder builder, RequestingSchoolDTO schoolDTO,
            JSONObject edexOrganization) {

        Map<SchoolCodeType, String> schoolCodes = Maps.newHashMap();

        SchoolCodeType srcSchoolCodeType = SchoolCodeType.valueOf(schoolDTO.getSchoolCodeType());
        schoolCodes.put(srcSchoolCodeType, schoolDTO.getSchoolCode());

        List<String> organizationNames = Arrays.asList(edexOrganization.getString("name"));
        List<String> addresses = Arrays.asList(edexOrganization.getString("street"));
        String city = edexOrganization.getString("city");
        StateProvinceCodeType stateProvinceCode = StateProvinceCodeType
                .valueOf(edexOrganization.getString("state"));
        String postalCode = edexOrganization.getString("zip");
        org.pesc.sdk.sector.academicrecord.v1_9.ObjectFactory academicRecordObjectFactory = new org.pesc.sdk.sector.academicrecord.v1_9.ObjectFactory();
        PhoneType phone = createPhone(edexOrganization.optString("telephone", ""));

        builder.sourceSchoolCodes(schoolCodes).sendersEmail(getEmailAddress(edexOrganization)).sendersPhone(phone)
                .sourceOrganizationNames(organizationNames).sourceOrganizationAddressLines(addresses)
                .sourceOrganizationCity(city).sourceOrganizationStateProvinceCode(stateProvinceCode)
                .sourceOrganizationPostalCode(postalCode);

    }

    private PhoneType createPhone(String phoneNumber) {
        org.pesc.sdk.sector.academicrecord.v1_9.ObjectFactory academicRecordObjectFactory = new org.pesc.sdk.sector.academicrecord.v1_9.ObjectFactory();
        PhoneType phone = academicRecordObjectFactory.createPhoneType();//Provided by Source Institution - optional
        if (StringUtils.isNotBlank(phoneNumber)) {
            int extensionIndex = StringUtils.indexOfIgnoreCase(phoneNumber, "x");//has extension?
            if (extensionIndex != -1) {
                if ((phoneNumber.length() - 1) > extensionIndex) {
                    String extension = phoneNumber.substring(extensionIndex + 1);
                    phone.setPhoneNumberExtension(extension);
                }
                phoneNumber = phoneNumber.substring(0, extensionIndex - 1);
            }
            phoneNumber = phoneNumber.replaceAll("\\D", "");
            if (phoneNumber.length() > 7) {
                String basePhoneNumber = phoneNumber.substring(phoneNumber.length() - 7);
                phone.setPhoneNumber(basePhoneNumber);
                String areaCode = phoneNumber.length() > 10
                        ? phoneNumber.substring(phoneNumber.length() - 10, phoneNumber.length() - 7)
                        : phoneNumber.substring(0, phoneNumber.length() - 7);
                phone.setAreaCityCode(areaCode);
                if (phoneNumber.length() > 10) {
                    String countryCode = phoneNumber.substring(0, phoneNumber.length() - 10);
                    phone.setCountryPrefixCode(countryCode);
                }
            }
        }
        return phone;
    }

    private TranscriptRequestBuilder constructStudent(TranscriptRequestBuilder builder,
            TranscriptRequestDTO transcriptRequestDTO, RecordHolderDTO schoolDTO, JSONObject edexOrganization) {
        Map<SchoolCodeType, String> currentlyAttendedSchoolCodes = Maps.newHashMap();

        Boolean trStudentRelease = transcriptRequestDTO.isStudentRelease();
        String trStudentReleasedMethod = transcriptRequestDTO.getStudentReleasedMethod();
        String studentBirthDate = transcriptRequestDTO.getStudentBirthDate();
        String trStudentFirstName = transcriptRequestDTO.getStudentFirstName();
        String trStudentLastName = transcriptRequestDTO.getStudentLastName();
        String trStudentMiddleName = transcriptRequestDTO.getStudentMiddleName();
        String trStudentEmail = transcriptRequestDTO.getStudentEmail();
        String trStudentPartialSsn = transcriptRequestDTO.getStudentPartialSSN();

        DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH);
        Date trStudentDOB = null;
        try {
            if (studentBirthDate != null) {
                trStudentDOB = dateFormat.parse(studentBirthDate);
            }
        } catch (Exception e) {
            log.error(e);

        }

        SchoolCodeType srcSchoolCodeType = SchoolCodeType.valueOf(schoolDTO.getSchoolCodeType());
        if (schoolDTO.isStudentCurrentlyEnrolled() == true) {
            currentlyAttendedSchoolCodes.put(srcSchoolCodeType, schoolDTO.getSchoolCode());
        }

        builder.studentRelease(trStudentRelease)
                .studentReleasedMethod(ReleaseAuthorizedMethodType.valueOf(trStudentReleasedMethod))
                .studentBirthDate(trStudentDOB).studentFirstName(trStudentFirstName)
                .studentLastName(trStudentLastName).studentEmail(trStudentEmail)
                .studentPartialSsn(trStudentPartialSsn).studentSchoolCodes(currentlyAttendedSchoolCodes)
                .studentSchoolName(edexOrganization.optString("name", ""))
                .studentCurrentlyEnrolled(schoolDTO.isStudentCurrentlyEnrolled());

        if (StringUtils.isNotEmpty(trStudentMiddleName)) {
            builder.studentMiddleNames(Arrays.asList(trStudentMiddleName));
        }

        return builder;
    }

    /**
     * The destination is the record holder's school.
     * @param builder
     * @param schoolDTO
     */
    private void constructTranscriptRequestDestination(TranscriptRequestBuilder builder, RecordHolderDTO schoolDTO,
            JSONObject edexOrganization) {

        Map<SchoolCodeType, String> schoolCodes = Maps.newHashMap();

        SchoolCodeType srcSchoolCodeType = SchoolCodeType.valueOf(schoolDTO.getSchoolCodeType());
        schoolCodes.put(srcSchoolCodeType, schoolDTO.getSchoolCode());

        List<String> organizationNames = Arrays.asList(edexOrganization.getString("name"));
        List<String> addresses = Arrays.asList(edexOrganization.getString("street"));
        String city = edexOrganization.getString("city");
        StateProvinceCodeType stateProvinceCode = StateProvinceCodeType
                .valueOf(edexOrganization.getString("state"));
        String postalCode = edexOrganization.getString("zip");
        PhoneType phone = createPhone(edexOrganization.optString("telephone", ""));

        builder.destinationSchoolCodes(schoolCodes).receiversEmail(getEmailAddress(edexOrganization))
                .receiversPhone(phone).destinationOrganizationNames(organizationNames)
                .destinationOrganizationAddressLines(addresses).destinationCity(city)
                .destinationOrganizationStateProvinceCode(stateProvinceCode)
                .destinationOrganizationPostalCode(postalCode);

    }

    private JSONObject getEDExOrg(SchoolDTO schoolDTO) {
        //get the EDExchange organization object from the directory server...
        Preconditions.checkArgument(StringUtils.isNotBlank(schoolDTO.getSchoolCode()), "School Code is required");
        Preconditions.checkArgument(StringUtils.isNotBlank(schoolDTO.getSchoolCodeType()),
                "School Code Type is required");

        if (schoolDTO.getSchoolCodeType().equals(SchoolCodeType.EDEXCHANGE.name())) {
            return organizationService.getOrganization(Integer.valueOf(schoolDTO.getSchoolCode()));
        }
        return organizationService.getOrganization(schoolDTO.getSchoolCode(), schoolDTO.getSchoolCodeType());

    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public List<Transaction> transcriptRequest(@RequestBody TranscriptRequestDTO transcriptRequestDTO) {

        Preconditions.checkArgument(transcriptRequestDTO.getDestinationInstitutions().size() == 1);
        Preconditions.checkArgument(transcriptRequestDTO.getSourceInstitutions().size() == 1);

        String fileFormat = DocumentFormat.PESCXML.getFormatName();
        String documentType = DocumentType.TRANSCRIPT_REQUEST.getDocumentName();
        String department = "Administration";

        List<Transaction> transactions = new ArrayList<Transaction>();

        //The receiving institution information in terms of the transcript.  For the transcript request,
        //this will be the opposite---it will be the sending/originating institution.
        RequestingSchoolDTO transcriptRequestor = transcriptRequestDTO.getDestinationInstitutions().get(0);
        RecordHolderDTO recordHolderInstitution = transcriptRequestDTO.getSourceInstitutions().get(0);

        List<String> transcriptRequestRecordHolderNames = Lists.newArrayList();//Provided by Source Institution

        //Create a transaction to track the transcript request.

        Transaction tx = new Transaction();

        //Get the endpoint for the record holder.  This is where the transcript request will be sent.
        String endpointURI = organizationService.getEndpointURIForSchool(recordHolderInstitution.getSchoolCode(),
                recordHolderInstitution.getSchoolCodeType(), fileFormat, documentType, department, tx,
                transcriptRequestRecordHolderNames, transcriptRequestDTO.getMode());

        if (endpointURI == null) {
            String error = ErrorUtils.getNoEndpointFoundMessage(tx.getRecipientId(), fileFormat, documentType,
                    department);
            log.warn(error);
            throw new IllegalArgumentException(error);
        }

        JSONObject requestorOrg = getEDExOrg(transcriptRequestor);
        JSONObject recordHolderOrg = getEDExOrg(recordHolderInstitution);
        //Create
        File outboxDirectory = new File(localServerOutboxPath);
        outboxDirectory.mkdirs();
        UUID uuid = UUID.randomUUID();
        String ext = "xml";
        String trFileName = uuid.toString() + "_document." + ext;
        File requestFile = new File(outboxDirectory, trFileName);

        tx.setSenderId(requestorOrg.getInt("id"));
        tx.setRecipientId(recordHolderOrg.getInt("id"));
        tx.setFileFormat(fileFormat);
        tx.setFilePath(requestFile.getAbsolutePath());
        tx.setDocumentType(documentType);
        tx.setDepartment(department);
        tx.setAckURL(localServerWebServiceURL);
        tx.setSignerId(Integer.valueOf(localServerId));
        tx.setOperation("SEND");
        tx.setOccurredAt(new Timestamp(Calendar.getInstance().getTimeInMillis()));

        tx = transactionService.create(tx);
        String trDocumentID = tx.getId() + "";
        DocumentTypeCodeType trDocumentTypeCode = DocumentTypeCodeType.STUDENT_REQUEST;
        TransmissionTypeType trTransmissionType = TransmissionTypeType.ORIGINAL;
        String trRequestTrackingID = tx.getId() + "";

        //destination

        //trDestinationSchoolCodes.put(SchoolCodeType.MutuallyDefined, String.valueOf(tx.getSenderId()));
        //document
        DocumentTypeCode trDocumentType = DocumentTypeCode.TRANSCRIPT;

        //student

        TranscriptRequestBuilder builder = new TranscriptRequestBuilder()
                .documentInfoMarshaller(documentInfoMarshaller).documentID(trDocumentID)
                .documentTypeCode(trDocumentTypeCode).documentFormat(transcriptRequestDTO.getFileFormat())
                .transmissionType(trTransmissionType).requestTrackingID(trRequestTrackingID)
                .parchmentDocumentTypeCode(trDocumentType).fileName(trFileName);

        constructStudent(builder, transcriptRequestDTO, recordHolderInstitution, recordHolderOrg);

        constructTranscriptRequestDestination(builder, recordHolderInstitution, recordHolderOrg);

        constructTranscriptRequestSource(builder, transcriptRequestor, requestorOrg);

        TranscriptRequest transcriptRequest = builder.build();

        try {
            if (!requestFile.createNewFile()) {
                String message = tx.getError() != null ? tx.getError() : "";
                tx.setError(message + ". " + String.format("file %s already exists", trFileName));
            } else {
                //File gets saved to file system here.
                transcriptRequestMarshaller.marshal(transcriptRequest, new StreamResult(requestFile));
            }
        } catch (Exception e) {
            log.error("Failed to save transcript request document.", e);

            tx.setError(e.getLocalizedMessage());
            tx.setStatus(TransactionStatus.FAILURE);
        }

        tx.setFileSize(requestFile.length());

        transactionService.update(tx);

        transactions.add(tx);

        try {
            if (StringUtils.isEmpty(tx.getError())) {
                sendDocument(requestFile, endpointURI, tx, fileFormat, documentType, department);
            }
        } catch (Exception e) {

            tx.setError(e.getMessage());
            transactionService.update(tx);

            log.error("Failed to send transcript request.", e);

            throw new IllegalArgumentException(e.getMessage());
        }

        //Now send the transcript request document to the destination school

        return transactions;
    }

    private void sendDocument(File outboxFile, String endpointURI, Transaction tx, String fileFormat,
            String documentType, String department) throws IOException {

        byte[] fileSignature = pkiService.createDigitalSignature(new FileInputStream(outboxFile),
                pkiService.getSigningKeys().getPrivate());
        try {

            LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
            map.add("recipient_id", tx.getRecipientId());
            map.add("sender_id", tx.getSenderId());
            map.add("signer_id", localServerId);
            map.add("file_format", fileFormat);
            map.add("document_type", documentType);
            map.add("department", department);
            map.add("transaction_id", tx.getId());
            map.add("ack_url", localServerWebServiceURL);
            map.add("file", new FileSystemResource(outboxFile));
            map.add("signature", new ByteArrayResource(fileSignature) {
                @Override
                public String getFilename() {
                    return "signature.dat";
                }
            });

            org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
            headers.setContentType(org.springframework.http.MediaType.MULTIPART_FORM_DATA);

            ResponseEntity<String> response = restTemplate.exchange(endpointURI, HttpMethod.POST,
                    new org.springframework.http.HttpEntity<Object>(map, headers), String.class);

            if (response.getStatusCode() != HttpStatus.OK) {
                throw new IllegalArgumentException(
                        "Failed to send document.  Reason: " + response.getStatusCode().getReasonPhrase());
            }

            log.info(response.getStatusCode().getReasonPhrase());

        } catch (ResourceAccessException e) {

            //Force the OAuth client to retrieve the token again whenever it is used again.

            restTemplate.getOAuth2ClientContext().setAccessToken(null);

            tx.setError(e.getMessage());
            transactionService.update(tx);

            log.error(e);
            throw new IllegalArgumentException(e);

        } catch (Exception e) {

            tx.setError(e.getMessage());
            transactionService.update(tx);

            log.error(e);

            throw new IllegalArgumentException(e);

        }

    }

    @ExceptionHandler
    void handleIllegalArgumentException(IllegalArgumentException e, HttpServletResponse response)
            throws IOException {
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }

}