-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
540 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
dina-base-api/src/main/java/ca/gc/aafc/dina/crkn/PatchedCrnkErrorController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package ca.gc.aafc.dina.crkn; | ||
|
||
import io.crnk.core.engine.document.Document; | ||
import io.crnk.core.engine.document.ErrorData; | ||
import io.crnk.core.engine.document.ErrorDataBuilder; | ||
import io.crnk.core.engine.http.HttpHeaders; | ||
import org.springframework.boot.autoconfigure.web.ErrorProperties; | ||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; | ||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; | ||
import org.springframework.boot.web.servlet.error.ErrorAttributes; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.ResponseBody; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* CrnkErrorController but patched for SpringBoot >= 2.5 | ||
* Source: https://github.com/crnk-project/crnk-framework/commit/0ed1721159943f6ffc5260ac502252efbbcc39c8 | ||
* | ||
* We can't upgrade Crnk at this point since it creates other (more complex) issues and the project is not maintained anymore. | ||
* | ||
*/ | ||
public class PatchedCrnkErrorController extends BasicErrorController { | ||
|
||
public PatchedCrnkErrorController(ErrorAttributes errorAttributes, | ||
ErrorProperties errorProperties) { | ||
super(errorAttributes, errorProperties); | ||
} | ||
|
||
public PatchedCrnkErrorController(ErrorAttributes errorAttributes, | ||
ErrorProperties errorProperties, | ||
List<ErrorViewResolver> errorViewResolvers) { | ||
super(errorAttributes, errorProperties, errorViewResolvers); | ||
} | ||
|
||
// TODO for whatever reason this is not called directly | ||
@RequestMapping(produces = HttpHeaders.JSONAPI_CONTENT_TYPE) | ||
@ResponseBody | ||
public ResponseEntity<Document> errorToJsonApi(HttpServletRequest request) { | ||
Map<String, Object> body = getErrorAttributes(request, | ||
getErrorAttributeOptions(request, MediaType.ALL)); | ||
HttpStatus status = getStatus(request); | ||
|
||
ErrorDataBuilder errorDataBuilder = ErrorData.builder(); | ||
for (Map.Entry<String, Object> attribute : body.entrySet()) { | ||
if (attribute.getKey().equals("status")) { | ||
errorDataBuilder.setStatus(attribute.getValue().toString()); | ||
} else if (attribute.getKey().equals("error")) { | ||
errorDataBuilder.setTitle(attribute.getValue().toString()); | ||
} else if (attribute.getKey().equals("message")) { | ||
errorDataBuilder.setDetail(attribute.getValue().toString()); | ||
} else { | ||
errorDataBuilder.addMetaField(attribute.getKey(), attribute.getValue()); | ||
} | ||
} | ||
Document document = new Document(); | ||
document.setErrors(Arrays.asList(errorDataBuilder.build())); | ||
return new ResponseEntity<>(document, status); | ||
} | ||
|
||
|
||
@RequestMapping | ||
@ResponseBody | ||
public ResponseEntity error(HttpServletRequest request) { | ||
return errorToJsonApi(request); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...-base-api/src/main/java/ca/gc/aafc/dina/exceptionmapping/IllegalStateExceptionMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package ca.gc.aafc.dina.exceptionmapping; | ||
|
||
import io.crnk.core.engine.document.ErrorData; | ||
import io.crnk.core.engine.error.ErrorResponse; | ||
import io.crnk.core.engine.error.ExceptionMapper; | ||
import io.crnk.core.engine.http.HttpStatus; | ||
import java.util.Collections; | ||
import javax.inject.Named; | ||
|
||
@Named | ||
public class IllegalStateExceptionMapper implements ExceptionMapper<IllegalStateException> { | ||
|
||
private static final Integer STATUS_ON_ERROR = HttpStatus.BAD_REQUEST_400; | ||
|
||
@Override | ||
public ErrorResponse toErrorResponse(IllegalStateException exception) { | ||
return new ErrorResponse( | ||
Collections.singletonList( | ||
ErrorData.builder() | ||
.setStatus(STATUS_ON_ERROR.toString()) | ||
.setTitle("BAD_REQUEST") | ||
.setDetail(exception.getMessage()) | ||
.build() | ||
), | ||
STATUS_ON_ERROR | ||
); | ||
} | ||
|
||
@Override | ||
public IllegalStateException fromErrorResponse(ErrorResponse errorResponse) { | ||
throw new UnsupportedOperationException("Crnk client not supported"); | ||
} | ||
|
||
@Override | ||
public boolean accepts(ErrorResponse errorResponse) { | ||
throw new UnsupportedOperationException("Crnk client not supported"); | ||
} | ||
} |
155 changes: 155 additions & 0 deletions
155
dina-base-api/src/main/java/ca/gc/aafc/dina/file/FileCleaner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package ca.gc.aafc.dina.file; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.LinkOption; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.FileTime; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.time.temporal.TemporalUnit; | ||
import java.util.EnumSet; | ||
import java.util.Objects; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Stream; | ||
import java.util.stream.StreamSupport; | ||
|
||
import org.apache.commons.io.FilenameUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import lombok.SneakyThrows; | ||
|
||
/** | ||
* Deletes file, recursively, from a root path if the provided predicate returns true. | ||
* By default, only files will be checked (no folders/no symlinks). | ||
* | ||
* Some checks are also included to avoid file system root or non-existing folder. | ||
*/ | ||
public final class FileCleaner { | ||
|
||
public enum Options { ALLOW_NON_TMP } | ||
|
||
private static final String TMP_DIR_PROPERTY = "java.io.tmpdir"; | ||
|
||
private final Path rootPath; | ||
private final Predicate<Path> predicate; | ||
|
||
/** | ||
* Creates a default instance where the provided predicate will be combined with the buildFileOnlyPredicate. | ||
* Folders and symlinks will be ignored. | ||
* @param rootPath | ||
* @param predicate | ||
* @return | ||
*/ | ||
public static FileCleaner newInstance(Path rootPath, Predicate<Path> predicate) { | ||
return new FileCleaner(rootPath, buildFileOnlyPredicate().and(predicate), null); | ||
} | ||
|
||
/** | ||
* Creates an instance with specific options. | ||
* Use carefully, options gives more flexibility but requires the caller to do more checks to avoid | ||
* unwanted destructive (file delete) operations. | ||
* @param rootPath | ||
* @param predicate | ||
* @param options | ||
* @return | ||
*/ | ||
public static FileCleaner newInstance(Path rootPath, Predicate<Path> predicate, EnumSet<Options> options) { | ||
Objects.requireNonNull(options); | ||
return new FileCleaner(rootPath, buildFileOnlyPredicate().and(predicate), options); | ||
} | ||
|
||
/** | ||
* Private constructor to avoid misuse of always true predicate. | ||
* @param rootPath | ||
* @param predicate | ||
*/ | ||
private FileCleaner(Path rootPath, Predicate<Path> predicate, EnumSet<Options> options) { | ||
// sanity checks | ||
Objects.requireNonNull(rootPath); | ||
Objects.requireNonNull(predicate); | ||
|
||
Path normalizedRootPath = rootPath.normalize(); | ||
if (!normalizedRootPath.toFile().isDirectory() || !normalizedRootPath.toFile().exists()) { | ||
throw new IllegalArgumentException( | ||
"FileCleaner can only be initialized on an existing directory"); | ||
} | ||
|
||
// by default (no options provided) we restrict to tmp directory | ||
boolean restrictToTmpDirectory = options == null || !options.contains(Options.ALLOW_NON_TMP); | ||
|
||
if (restrictToTmpDirectory && !normalizedRootPath.startsWith(System.getProperty(TMP_DIR_PROPERTY))) { | ||
throw new IllegalArgumentException( | ||
"FileCleaner can only be initialized on a directory under " + | ||
System.getProperty(TMP_DIR_PROPERTY)); | ||
} | ||
|
||
if (StreamSupport.stream(normalizedRootPath.getFileSystem().getRootDirectories().spliterator(), false) | ||
.anyMatch(p -> p.equals(normalizedRootPath))) { | ||
throw new IllegalArgumentException("can't initialize FileCleaner on a root directory"); | ||
} | ||
|
||
this.rootPath = normalizedRootPath; | ||
this.predicate = predicate; | ||
} | ||
|
||
/** | ||
* Build a predicate that is checking for the maximum age of a file based on its lastModifiedTime. | ||
* @param unit | ||
* @param maxAge | ||
* @return | ||
*/ | ||
public static Predicate<Path> buildMaxAgePredicate(TemporalUnit unit, long maxAge) { | ||
return path -> { | ||
Duration interval = Duration.between(getLastModifiedTime(path).toInstant(), Instant.now()); | ||
return interval.get(unit) > maxAge; | ||
}; | ||
} | ||
|
||
/** | ||
* Build a predicate for checking for a specific file extension of a file. | ||
* | ||
* The check for the extension is case-insensitive. | ||
* | ||
* @param extension the extension without the leading "." (eg. "txt", "md"). | ||
* @return predicate checking the extension based on the file extension | ||
* provided. | ||
*/ | ||
public static Predicate<Path> buildFileExtensionPredicate(String extension) { | ||
if (StringUtils.isBlank(extension)) { | ||
throw new IllegalArgumentException("Extension must not be null or empty."); | ||
} | ||
|
||
return path -> FilenameUtils.isExtension(path.getFileName().toString().toLowerCase(), extension.toLowerCase()); | ||
} | ||
|
||
/** | ||
* Excludes folder and symlinks | ||
* @return | ||
*/ | ||
public static Predicate<Path> buildFileOnlyPredicate() { | ||
return path -> Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS); | ||
} | ||
|
||
/** | ||
* Clean folder recursively by deleting all files that are matching the predicate. | ||
* @throws IOException | ||
*/ | ||
public void clean() throws IOException { | ||
try (Stream<Path> p = Files.walk(rootPath)) { | ||
p.filter(predicate) | ||
.forEach(FileCleaner::delete); | ||
} | ||
} | ||
|
||
@SneakyThrows | ||
public static FileTime getLastModifiedTime(Path path) { | ||
return Files.getLastModifiedTime(path); | ||
} | ||
|
||
@SneakyThrows | ||
public static void delete(Path path) { | ||
Files.delete(path); | ||
} | ||
|
||
} |
Oops, something went wrong.