CoursesCSVController.java

1
package edu.ucsb.cs156.courses.controllers;
2
3
import com.opencsv.bean.StatefulBeanToCsv;
4
import com.opencsv.exceptions.CsvDataTypeMismatchException;
5
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
6
import edu.ucsb.cs156.courses.collections.ConvertedSectionCollection;
7
import edu.ucsb.cs156.courses.documents.ConvertedSection;
8
import edu.ucsb.cs156.courses.models.SectionCSVLine;
9
import edu.ucsb.cs156.courses.services.SectionCSVLineService;
10
import io.swagger.v3.oas.annotations.Operation;
11
import io.swagger.v3.oas.annotations.Parameter;
12
import io.swagger.v3.oas.annotations.media.Content;
13
import io.swagger.v3.oas.annotations.media.Schema;
14
import io.swagger.v3.oas.annotations.responses.ApiResponse;
15
import io.swagger.v3.oas.annotations.tags.Tag;
16
import java.io.IOException;
17
import java.io.OutputStreamWriter;
18
import java.io.Writer;
19
import java.nio.charset.StandardCharsets;
20
import java.util.List;
21
import java.util.stream.Collectors;
22
import lombok.extern.slf4j.Slf4j;
23
import org.springframework.beans.factory.annotation.Autowired;
24
import org.springframework.data.util.Streamable;
25
import org.springframework.http.HttpHeaders;
26
import org.springframework.http.MediaType;
27
import org.springframework.http.ResponseEntity;
28
import org.springframework.web.bind.annotation.GetMapping;
29
import org.springframework.web.bind.annotation.RequestMapping;
30
import org.springframework.web.bind.annotation.RequestParam;
31
import org.springframework.web.bind.annotation.RestController;
32
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
33
34
@Slf4j
35
@Tag(name = "API for course data as CSV downloads")
36
@RequestMapping("/api/courses/csv")
37
@RestController
38
public class CoursesCSVController extends ApiController {
39
40
  @Autowired ConvertedSectionCollection convertedSectionCollection;
41
42
  @Autowired private SectionCSVLineService sectionCsvLineService;
43
44
  @Operation(
45
      summary = "Download Complete Course List for a quarter as a CSV File",
46
      description = "Returns a CSV file as a response",
47
      responses = {
48
        @ApiResponse(
49
            responseCode = "200",
50
            description = "CSV file",
51
            content =
52
                @Content(
53
                    mediaType = "text/csv",
54
                    schema = @Schema(type = "string", format = "binary"))),
55
        @ApiResponse(responseCode = "500", description = "Internal Server Error")
56
      })
57
  @GetMapping(value = "/quarter", produces = "text/csv")
58
  public ResponseEntity<StreamingResponseBody> csvForCourses(
59
      @Parameter(name = "yyyyq", description = "quarter in yyyyq format", example = "20252")
60
          @RequestParam
61
          String yyyyq)
62
      throws Exception, IOException {
63
    StreamingResponseBody stream =
64
        (outputStream) -> {
65
          Iterable<ConvertedSection> iterable = convertedSectionCollection.findByQuarter(yyyyq);
66
67
          List<SectionCSVLine> list =
68
              Streamable.of(iterable).toList().stream()
69
                  .map(
70
                      section -> {
71 1 1. lambda$csvForCourses$0 : replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::lambda$csvForCourses$0 → KILLED
                        return SectionCSVLine.toSectionCSVLine(section);
72
                      })
73
                  .collect(Collectors.toList());
74
75
          try (Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
76
            try {
77
              StatefulBeanToCsv<SectionCSVLine> beanToCsvWriter =
78
                  sectionCsvLineService.getStatefulBeanToCSV(writer);
79 1 1. lambda$csvForCourses$1 : removed call to com/opencsv/bean/StatefulBeanToCsv::write → KILLED
              beanToCsvWriter.write(list);
80
            } catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
81
              log.error("Error writing CSV file", e);
82
              throw new IOException("Error writing CSV file: " + e.getMessage());
83
            }
84
          }
85
        };
86
87 1 1. csvForCourses : replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::csvForCourses → KILLED
    return ResponseEntity.ok()
88
        .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
89
        .header(
90
            HttpHeaders.CONTENT_DISPOSITION,
91
            String.format("attachment;filename=courses_%s.csv", yyyyq))
92
        .header(HttpHeaders.CONTENT_TYPE, "text/csv; charset=UTF-8")
93
        .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
94
        .body(stream);
95
  }
96
97
  @Operation(
98
      summary = "Download Course List for a subject area and quarter as a CSV File",
99
      description = "Returns a CSV file as a response",
100
      responses = {
101
        @ApiResponse(
102
            responseCode = "200",
103
            description = "CSV file",
104
            content =
105
                @Content(
106
                    mediaType = "text/csv",
107
                    schema = @Schema(type = "string", format = "binary"))),
108
        @ApiResponse(responseCode = "500", description = "Internal Server Error")
109
      })
110
  @GetMapping(value = "/byQuarterAndSubjectArea", produces = "text/csv")
111
  public ResponseEntity<StreamingResponseBody> csvForCoursesQuarterAndSubjectArea(
112
      @Parameter(name = "yyyyq", description = "quarter in yyyyq format", example = "20261")
113
          @RequestParam
114
          String yyyyq,
115
      @Parameter(name = "subjectArea", description = "subject area", example = "CMPSC")
116
          @RequestParam
117
          String subjectArea,
118
      @Parameter(
119
              name = "level",
120
              description = "U for undergrad, G for graduate, A for all (default: U)",
121
              example = "",
122
              schema =
123
                  @Schema(
124
                      type = "string",
125
                      allowableValues = {"A", "U", "G"}))
126
          @RequestParam(defaultValue = "U")
127
          String level,
128
      @Parameter(name = "omitSections", description = "omit sections", example = "true")
129
          @RequestParam(defaultValue = "true")
130
          boolean omitSections,
131
      @Parameter(
132
              name = "withTimeLocations",
133
              description =
134
                  "return only courses that have times and/or locations assigned (e.g. no independent study type courses)",
135
              example = "true")
136
          @RequestParam(defaultValue = "true")
137
          boolean withTimeLocations)
138
      throws Exception, IOException {
139
140
    StreamingResponseBody stream =
141
        (outputStream) -> {
142 1 1. lambda$csvForCoursesQuarterAndSubjectArea$3 : negated conditional → KILLED
          String sectionRegex = omitSections ? "00$" : ".*";
143
          String courseLevelRegex =
144 2 1. lambda$csvForCoursesQuarterAndSubjectArea$3 : negated conditional → KILLED
2. lambda$csvForCoursesQuarterAndSubjectArea$3 : negated conditional → KILLED
              level.equals("A") ? "" : level.equals("G") ? "[23456789]" : "[ 1]";
145
          String courseIdRegex = String.format("^%-8s", subjectArea) + courseLevelRegex;
146
          Iterable<ConvertedSection> iterable =
147
              convertedSectionCollection.findByQuarterAndSubjectArea(
148 1 1. lambda$csvForCoursesQuarterAndSubjectArea$3 : negated conditional → KILLED
                  yyyyq, courseIdRegex, sectionRegex, withTimeLocations ? 1 : 0);
149
150
          log.info(
151
              "Found {} sections for quarter {} and courseIdRegex <{}> and sectionRegex <{}>",
152
              Streamable.of(iterable).toList().size(),
153
              yyyyq,
154
              courseIdRegex,
155
              sectionRegex);
156
157
          List<SectionCSVLine> list =
158
              Streamable.of(iterable).toList().stream()
159
                  .map(
160
                      section -> {
161 1 1. lambda$csvForCoursesQuarterAndSubjectArea$2 : replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::lambda$csvForCoursesQuarterAndSubjectArea$2 → KILLED
                        return SectionCSVLine.toSectionCSVLine(section);
162
                      })
163
                  .collect(Collectors.toList());
164
165
          try (Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
166
            try {
167
              StatefulBeanToCsv<SectionCSVLine> beanToCsvWriter =
168
                  sectionCsvLineService.getStatefulBeanToCSV(writer);
169 1 1. lambda$csvForCoursesQuarterAndSubjectArea$3 : removed call to com/opencsv/bean/StatefulBeanToCsv::write → KILLED
              beanToCsvWriter.write(list);
170
            } catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
171
              log.error("Error writing CSV file", e);
172
              throw new IOException("Error writing CSV file: " + e.getMessage());
173
            }
174
          }
175
        };
176
177 1 1. csvForCoursesQuarterAndSubjectArea : replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::csvForCoursesQuarterAndSubjectArea → KILLED
    return ResponseEntity.ok()
178
        .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
179
        .header(
180
            HttpHeaders.CONTENT_DISPOSITION,
181
            String.format("attachment;filename=courses_%s.csv", yyyyq))
182
        .header(HttpHeaders.CONTENT_TYPE, "text/csv; charset=UTF-8")
183
        .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
184
        .body(stream);
185
  }
186
}

Mutations

71

1.1
Location : lambda$csvForCourses$0
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarter_success()]
replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::lambda$csvForCourses$0 → KILLED

79

1.1
Location : lambda$csvForCourses$1
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarter_success()]
removed call to com/opencsv/bean/StatefulBeanToCsv::write → KILLED

87

1.1
Location : csvForCourses
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarter_exception()]
replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::csvForCourses → KILLED

142

1.1
Location : lambda$csvForCoursesQuarterAndSubjectArea$3
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
negated conditional → KILLED

144

1.1
Location : lambda$csvForCoursesQuarterAndSubjectArea$3
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
negated conditional → KILLED

2.2
Location : lambda$csvForCoursesQuarterAndSubjectArea$3
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
negated conditional → KILLED

148

1.1
Location : lambda$csvForCoursesQuarterAndSubjectArea$3
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
negated conditional → KILLED

161

1.1
Location : lambda$csvForCoursesQuarterAndSubjectArea$2
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::lambda$csvForCoursesQuarterAndSubjectArea$2 → KILLED

169

1.1
Location : lambda$csvForCoursesQuarterAndSubjectArea$3
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_success_defaults()]
removed call to com/opencsv/bean/StatefulBeanToCsv::write → KILLED

177

1.1
Location : csvForCoursesQuarterAndSubjectArea
Killed by : edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.courses.controllers.CoursesCSVControllerTests]/[method:testCsvForQuarterAndSubjectArea_exception()]
replaced return value with null for edu/ucsb/cs156/courses/controllers/CoursesCSVController::csvForCoursesQuarterAndSubjectArea → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0