diff --git a/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java b/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java index bd7a321..aaf177f 100644 --- a/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java +++ b/src/main/java/com/smart/watchboard/common/filter/JwtAuthenticationProcessingFilter.java @@ -9,6 +9,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseCookie; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; @@ -18,6 +19,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Optional; @RequiredArgsConstructor @@ -67,7 +69,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse * reIssueRefreshToken()로 리프레시 토큰 재발급 & DB에 리프레시 토큰 업데이트 메소드 호출 * 그 후 JwtService.sendAccessTokenAndRefreshToken()으로 응답 헤더에 보내기 */ - public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) { + public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) throws UnsupportedEncodingException { if (jwtService.isTokenValid(refreshToken)) { String reIssuedRefreshToken = reIssueRefreshToken(jwtService.extractUserId(refreshToken).get()); jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(jwtService.extractUserId(refreshToken).get()), @@ -77,8 +79,12 @@ public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, userRepository.findById(userId) .ifPresent(user -> { String reIssuedRefreshToken = reIssueRefreshToken(user.getId()); - jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(user.getId()), - reIssuedRefreshToken); + try { + jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(user.getId()), + reIssuedRefreshToken); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } }); } @@ -104,8 +110,10 @@ public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpSe FilterChain filterChain) throws ServletException, IOException { log.info("checkAccessTokenAndAuthentication() 호출"); jwtService.extractAccessToken(request) - .filter(jwtService::isTokenValid); - + .filter(jwtService::isTokenValid) + .ifPresent(accessToken -> jwtService.extractUserId(accessToken) + .ifPresent(userId -> userRepository.findById(userId) + .ifPresent(this::saveAuthentication))); filterChain.doFilter(request, response); } @@ -126,7 +134,6 @@ public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpSe */ public void saveAuthentication(User myUser) { String password = "asd"; - UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder() .username(myUser.getEmail()) .password(password) @@ -136,9 +143,7 @@ public void saveAuthentication(User myUser) { Authentication authentication = new UsernamePasswordAuthenticationToken(userDetailsUser, null, authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities())); - + log.info("saveAuthentication"); SecurityContextHolder.getContext().setAuthentication(authentication); } - - } diff --git a/src/main/java/com/smart/watchboard/config/SecurityConfig.java b/src/main/java/com/smart/watchboard/config/SecurityConfig.java index 740e766..b601867 100644 --- a/src/main/java/com/smart/watchboard/config/SecurityConfig.java +++ b/src/main/java/com/smart/watchboard/config/SecurityConfig.java @@ -7,21 +7,27 @@ import com.smart.watchboard.service.CustomOAuth2UserService; import com.smart.watchboard.service.JwtService; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; -@EnableWebSecurity @Configuration +@EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { + @Value("${front-url}") + private String frontUrl; + private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; private final CustomOAuth2UserService customOAuth2UserService; @@ -31,15 +37,19 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.formLogin().disable(); http.httpBasic().disable(); - http.cors().configurationSource(corsConfigurationSource()).and().authorizeHttpRequests(authorize -> authorize.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/**").permitAll() - .anyRequest().authenticated()).csrf().disable(); + http.csrf().disable(); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.cors().configurationSource(corsConfigurationSource()).and().authorizeHttpRequests(authorize -> authorize.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/users/token").permitAll() + .anyRequest().authenticated()); http.oauth2Login() .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler) .userInfoEndpoint() .userService(customOAuth2UserService); + http.addFilterAfter(jwtAuthenticationProcessingFilter(), LogoutFilter.class); return http.build(); } @@ -50,7 +60,7 @@ CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setExposedHeaders(Arrays.asList("Authorization")); - configuration.setAllowedOrigins(Arrays.asList("https://4988-221-148-248-129.ngrok-free.app")); // 추후 수정 + configuration.setAllowedOrigins(Arrays.asList(frontUrl)); // 추후 수정 configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); diff --git a/src/main/java/com/smart/watchboard/controller/GraphController.java b/src/main/java/com/smart/watchboard/controller/GraphController.java index 39a63ea..a5eb7dc 100644 --- a/src/main/java/com/smart/watchboard/controller/GraphController.java +++ b/src/main/java/com/smart/watchboard/controller/GraphController.java @@ -16,6 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.parameters.P; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @@ -39,6 +40,7 @@ public class GraphController { private final KeywordService keywordService; private final JwtService jwtService; private final QuestionService questionService; + private final SummaryService summaryService; @PostMapping("/graph/{documentID}") @Operation(summary = "마인드맵 생성", description = "ai 서버에 마인드맵 요청한다.") public void createMindmap(@PathVariable(value = "documentID") long documentId, @RequestBody KeywordsBodyDto keywordsBodyDto, @RequestHeader("Authorization") String accessToken) throws JsonProcessingException { @@ -70,8 +72,8 @@ public ResponseEntity getMindmap(@PathVariable(value = "documentID") long doc if (document.getDataType().equals("pdf")) { String pdfUrl = fileService.getPath(documentId); if (mindmapDto == null) { - sseService.notifyKeywords(documentId, pdfUrl); sseService.notifySummary(documentId, pdfUrl); + sseService.notifyKeywords(documentId, pdfUrl); return new ResponseEntity<>(HttpStatus.ACCEPTED); } } else { @@ -98,10 +100,16 @@ public ResponseEntity updateKeywords(@PathVariable(value = "documentID") long @Operation(summary = "키워드 질문", description = "키워드 AI에 질문") public ResponseEntity getAnswer(@PathVariable(value = "documentID") long documentId, @PathVariable String keywordLabel, @RequestHeader("Authorization") String accessToken) throws JsonProcessingException { AnswerDto answerDto = questionService.getAnswer(documentId, keywordLabel); + if (!summaryService.checkSummary(documentId)) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } if (answerDto == null) { sseService.notifyAnswer(documentId, keywordLabel); return new ResponseEntity<>(HttpStatus.ACCEPTED); } + if (answerDto.getText().equals("processing")) { + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } return new ResponseEntity<>(answerDto, HttpStatus.OK); } diff --git a/src/main/java/com/smart/watchboard/controller/UserController.java b/src/main/java/com/smart/watchboard/controller/UserController.java index 6a8abff..c5cdeb8 100644 --- a/src/main/java/com/smart/watchboard/controller/UserController.java +++ b/src/main/java/com/smart/watchboard/controller/UserController.java @@ -24,7 +24,7 @@ public class UserController { @GetMapping("/info") @Operation(summary = "사용자 정보 조회", description = "요청받은 사용자의 정보를 조회한다.") public ResponseEntity getUserInformation(@RequestHeader("Authorization") String accessToken) { - UserInformationDto userInformationDto = new UserInformationDto(1L, "권민석", "noparamin@naver.com"); + UserInformationDto userInformationDto = jwtService.getUserInformation(accessToken); return new ResponseEntity<>(userInformationDto, HttpStatus.OK); } diff --git a/src/main/java/com/smart/watchboard/controller/WhiteboardController.java b/src/main/java/com/smart/watchboard/controller/WhiteboardController.java index 6cc0028..c7e272e 100644 --- a/src/main/java/com/smart/watchboard/controller/WhiteboardController.java +++ b/src/main/java/com/smart/watchboard/controller/WhiteboardController.java @@ -2,6 +2,7 @@ import com.smart.watchboard.domain.WhiteboardData; import com.smart.watchboard.dto.*; +import com.smart.watchboard.service.JwtService; import com.smart.watchboard.service.WhiteboardService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,6 +20,7 @@ public class WhiteboardController { private final WhiteboardService whiteboardService; + private final JwtService jwtService; @GetMapping() @Operation(summary = "문서 목록 조회", description = "사용자가 속해 있는 모든 문서 목록을 조회한다.") @@ -31,6 +33,11 @@ public ResponseEntity getAllDocuments(@RequestHeader("Authorization") String @GetMapping("/{documentID}") @Operation(summary = "문서 데이터 조회", description = "특정 문서의 데이터를 조회한다.") public ResponseEntity getDocument(@PathVariable(value = "documentID") long documentId, @RequestHeader("Authorization") String accessToken) { + String extractedAccessToken = jwtService.extractAccessToken(accessToken); + Long userId = jwtService.extractUserId(extractedAccessToken).orElse(null); + if (!whiteboardService.checkAuthorization(documentId, userId)) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } DocumentResponseDto response = whiteboardService.findDocument(documentId, accessToken); return new ResponseEntity<>(response, HttpStatus.OK); diff --git a/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java index 69687b4..58a37d7 100644 --- a/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/com/smart/watchboard/handler/OAuth2AuthenticationSuccessHandler.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; @@ -21,6 +22,8 @@ @Slf4j @RequiredArgsConstructor public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler { + @Value("${front-url}") + private String frontUrl; private final JwtService jwtService; private final UserRepository userRepository; @@ -30,12 +33,12 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("OAuth2 Login 성공!"); try { CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal(); - if (oAuth2User.getRole() == Role.USER) { String refreshToken = jwtService.createRefreshToken(oAuth2User.getUserId()); ResponseCookie cookie = jwtService.setCookieRefreshToken(refreshToken); response.setHeader("Set-Cookie", cookie.toString()); - response.sendRedirect("https://4988-221-148-248-129.ngrok-free.app"); // 추후 수정 + response.sendRedirect(frontUrl); // 추후 수정 +// response.sendRedirect("http//localhost:8081"); User findUser = userRepository.findByEmail(oAuth2User.getEmail()) .orElseThrow(() -> new IllegalArgumentException("이메일에 해당하는 유저가 없습니다.")); findUser.authorizeUser(); diff --git a/src/main/java/com/smart/watchboard/service/JwtService.java b/src/main/java/com/smart/watchboard/service/JwtService.java index 30a2378..f614c23 100644 --- a/src/main/java/com/smart/watchboard/service/JwtService.java +++ b/src/main/java/com/smart/watchboard/service/JwtService.java @@ -3,6 +3,10 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; +import com.smart.watchboard.domain.User; +import com.smart.watchboard.dto.UserInformationDto; +import com.smart.watchboard.repository.UserRepository; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.Getter; @@ -45,6 +49,8 @@ public class JwtService { private static final String ISSUER_CLAIM_VALUE = "wb"; private static final String BEARER = "Bearer "; + private final UserRepository userRepository; + /** * AccessToken 생성 메소드 */ @@ -85,7 +91,7 @@ public void sendAccessToken(HttpServletResponse response, String accessToken) { /** * AccessToken + RefreshToken 헤더에 실어서 보내기 */ - public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) { + public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) throws UnsupportedEncodingException { response.setStatus(HttpServletResponse.SC_OK); setAccessTokenHeader(response, accessToken); @@ -100,7 +106,18 @@ public void sendAccessAndRefreshToken(HttpServletResponse response, String acces * 헤더를 가져온 후 "Bearer"를 삭제(""로 replace) */ public Optional extractRefreshToken(HttpServletRequest request) { - return Optional.ofNullable(request.getHeader(refreshHeader)) + Cookie[] cookies = request.getCookies(); + String bearerRefreshToken = ""; + if (cookies != null) { + for (int i = 0; i < cookies.length && cookies[i] != null; i++) { + if (cookies[i].getName().equals("refreshToken")) { + bearerRefreshToken = cookies[i].getValue(); + break; + } + } + } + + return Optional.ofNullable(bearerRefreshToken) .filter(refreshToken -> refreshToken.startsWith(BEARER)) .map(refreshToken -> refreshToken.replace(BEARER, "")); } @@ -119,6 +136,7 @@ public Optional extractAccessToken(HttpServletRequest request) { public String extractAccessToken(String accessToken) { try { isAccessTokenFormatValid(accessToken); + return accessToken.substring(7); } catch (IllegalArgumentException e) { log.info("Error: " + e); } @@ -157,14 +175,17 @@ public Optional extractUserId(String token) { * AccessToken 헤더 설정 */ public void setAccessTokenHeader(HttpServletResponse response, String accessToken) { - response.setHeader(accessHeader, accessToken); + String bearerAccessToken = "Bearer " + accessToken; + response.setHeader(accessHeader, bearerAccessToken); } /** * RefreshToken 헤더 설정 */ - public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) { - response.setHeader(refreshHeader, refreshToken); + public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) throws UnsupportedEncodingException { + ResponseCookie cookie = setCookieRefreshToken(refreshToken); + response.setHeader("Set-Cookie", cookie.toString()); + //response.setHeader(refreshHeader, refreshToken); } @@ -237,4 +258,13 @@ public HttpHeaders createHeaderWithDeletedCookie(String accessToken) { return headers; } + + public UserInformationDto getUserInformation(String accessToken) { + String extractedToken = extractDecodedToken(accessToken); + Long userId = extractUserId(extractedToken).orElse(null); + Optional user = userRepository.findById(userId); + UserInformationDto userInformationDto = new UserInformationDto(userId, user.get().getNickname(), user.get().getEmail()); + + return userInformationDto; + } } diff --git a/src/main/java/com/smart/watchboard/service/QuestionService.java b/src/main/java/com/smart/watchboard/service/QuestionService.java index 7d5b264..bba16f7 100644 --- a/src/main/java/com/smart/watchboard/service/QuestionService.java +++ b/src/main/java/com/smart/watchboard/service/QuestionService.java @@ -17,18 +17,21 @@ public class QuestionService { private final AnswerRepository answerRepository; public void createAnswer(Long documentId, String keyword, ResponseEntity responseEntity) { - AnswerDto answerDto = getAnswer(documentId, keyword); - if (answerDto.getText().equals("processing")) { - Answer answer = getAnswerForCreate(documentId, keyword); - answer.setText(responseEntity.getBody().getText()); - answerRepository.save(answer); - } else if(answerDto == null) { + String text = responseEntity.getBody().getText(); + if (text.equals("init")) { Answer answer = Answer.builder() .documentId(documentId) .keyword(keyword) - .text(responseEntity.getBody().getText()) + .text("processing") .build(); answerRepository.save(answer); + } else { + AnswerDto answerDto = getAnswer(documentId, keyword); + if (answerDto.getText().equals("processing")) { + Answer answer = getAnswerForCreate(documentId, keyword); + answer.setText(responseEntity.getBody().getText()); + answerRepository.save(answer); + } } } diff --git a/src/main/java/com/smart/watchboard/service/SseService.java b/src/main/java/com/smart/watchboard/service/SseService.java index 55f60e7..94cd721 100644 --- a/src/main/java/com/smart/watchboard/service/SseService.java +++ b/src/main/java/com/smart/watchboard/service/SseService.java @@ -1,5 +1,6 @@ package com.smart.watchboard.service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.itextpdf.text.DocumentException; import com.smart.watchboard.common.support.AwsS3Uploader; import com.smart.watchboard.domain.Document; @@ -69,10 +70,16 @@ private void sendToClient(Long documentId, List keywords) { if (whiteboardService.isPdfType(documentId)) { String path = fileService.getPath(documentId); ResponseEntity body = requestService.requestPdfMindmap(path, documentId, keywords); + KeywordsBodyDto keywordsDto = new KeywordsBodyDto(body.getBody().getKeywords()); + ResponseEntity keywordsDtoResponseEntity = new ResponseEntity<>(keywordsDto, HttpStatus.OK); + keywordService.renewKeywords(keywordsDtoResponseEntity, documentId); emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("mindmap").data("mindmap")); } else if (whiteboardService.isAudioType(documentId)) { Note note = noteService.findByDocument(documentId); ResponseEntity body = requestService.requestSTTMindmap(note.getPath(), documentId, keywords); + KeywordsBodyDto keywordsDto = new KeywordsBodyDto(body.getBody().getKeywords()); + ResponseEntity keywordsDtoResponseEntity = new ResponseEntity<>(keywordsDto, HttpStatus.OK); + keywordService.renewKeywords(keywordsDtoResponseEntity, documentId); emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("mindmap").data("mindmap")); } } catch (IOException exception) { @@ -90,7 +97,7 @@ public void notifySummary(Long documentId, String path) { sendSummary(documentId, path); } - public void notifyAnswer(Long documentId, String keywordLabel) { + public void notifyAnswer(Long documentId, String keywordLabel) throws JsonProcessingException { sendAnswer(documentId, keywordLabel); } @@ -153,19 +160,19 @@ private void sendSummary(Long documentId, String path) { } } - private void sendAnswer(Long documentId, String keyword) { + private void sendAnswer(Long documentId, String keyword) throws JsonProcessingException { AnswerDto answerDto = questionService.getAnswer(documentId, keyword); if (answerDto == null) { - answerDto = new AnswerDto("processing"); + answerDto = new AnswerDto("init"); ResponseEntity temp = new ResponseEntity<>(answerDto, HttpStatus.OK); questionService.createAnswer(documentId, keyword, temp); } + ResponseEntity responseEntity = requestService.requestAnswer(documentId, keyword); + questionService.createAnswer(documentId, keyword, responseEntity); SseEmitter emitter = emitterRepository.get(documentId); if (emitter != null) { try { - ResponseEntity responseEntity = requestService.requestAnswer(documentId, keyword); - questionService.createAnswer(documentId, keyword, responseEntity); emitter.send(SseEmitter.event().id(String.valueOf(documentId)).name("answer").data(keyword)); } catch (IOException exception) { emitterRepository.deleteById(documentId); diff --git a/src/main/java/com/smart/watchboard/service/SummaryService.java b/src/main/java/com/smart/watchboard/service/SummaryService.java index 9098ac3..b3c1192 100644 --- a/src/main/java/com/smart/watchboard/service/SummaryService.java +++ b/src/main/java/com/smart/watchboard/service/SummaryService.java @@ -52,5 +52,12 @@ public Summary findSummary(Long documentId) { return summary; } + public boolean checkSummary(Long documentId) { + Summary summary = findSummary(documentId); + if (summary == null) { + return false; + } + return true; + } } diff --git a/src/main/java/com/smart/watchboard/service/WhiteboardService.java b/src/main/java/com/smart/watchboard/service/WhiteboardService.java index dc77a15..f25dd88 100644 --- a/src/main/java/com/smart/watchboard/service/WhiteboardService.java +++ b/src/main/java/com/smart/watchboard/service/WhiteboardService.java @@ -6,6 +6,7 @@ import com.smart.watchboard.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.time.Instant; @@ -67,6 +68,14 @@ public void deleteDocument(long documentId, String accessToken) { } + public boolean checkAuthorization(Long documentId, Long userId) { + Document document = findDoc(documentId); + if (userId != document.getUser().getId()) { + return false; + } + return true; + } + // public void createWhiteboardData(Map documentData, long documentId, String accessToken) { // String extractedAccessToken = jwtService.extractAccessToken(accessToken); // Optional userId = jwtService.extractUserId(extractedAccessToken); @@ -159,7 +168,6 @@ public void setDataType(Long documentId, String dataType) { public boolean isPdfType(Long documentId) { Document document = findDoc(documentId); - System.out.println(document.getDataType()); if (document.getDataType().equals("pdf")) { return true; }