001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2025 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.pyranid; 018 019import javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.ThreadSafe; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.Optional; 025 026/** 027 * Enables per-column {@link ResultSet} mapping customization via {@link ResultSetMapper}. 028 * 029 * @author <a href="https://www.revetkn.com">Mark Allen</a> 030 * @since 3.0.0 031 */ 032public interface CustomColumnMapper { 033 /** 034 * Perform custom mapping of a {@link ResultSet} column: given a {@code resultSetValue}, optionally return an instance of {@code targetType} instead. 035 * <p> 036 * This method is only invoked when {@code resultSetValue} is non-null. 037 * 038 * @param statementContext current SQL context 039 * @param resultSet the {@link ResultSet} from which data was read 040 * @param resultSetValue the already-read value from the {@link ResultSet}, to be optionally converted to an instance of {@code targetType} 041 * @param targetType the type to which the {@code resultSetValue} should be converted 042 * @param columnIndex 1-based column index 043 * @param columnLabel normalized column label, if available 044 * @param instanceProvider instance-creation factory, may be used to instantiate values 045 * @return the result of the custom column mapping operation - either {@link MappingResult#of(Object)} to indicate a successfully-mapped value or {@link MappingResult#fallback()} if Pyranid should fall back to the registered {@link ResultSetMapper} mapping behavior. 046 * @throws SQLException if an error occurs during mapping 047 */ 048 @Nonnull 049 MappingResult map(@Nonnull StatementContext<?> statementContext, 050 @Nonnull ResultSet resultSet, 051 @Nonnull Object resultSetValue, 052 @Nonnull TargetType targetType, 053 @Nonnull Integer columnIndex, 054 @Nullable String columnLabel, 055 @Nonnull InstanceProvider instanceProvider) throws SQLException; 056 057 /** 058 * Specifies which types this mapper should handle. 059 * <p> 060 * For example, if this mapper should apply when marshaling to {@code MyCustomType}, this method could return {@code targetType.matchesClass(MyCustomType.class)}. 061 * <p> 062 * For parameterized types like {@code List<UUID>}, this method could return {@code targetType.matchesParameterizedType(List.class, UUID.class)}. 063 * 064 * @param targetType the target type to evaluate - should this mapper handle it or not? 065 * @return {@code true} if this mapper should handle the type, {@code false} otherwise. 066 */ 067 @Nonnull 068 Boolean appliesTo(@Nonnull TargetType targetType); 069 070 /** 071 * Result of a custom column mapping attempt. 072 * <p> 073 * Use {@link #of(Object)} to indicate a successfully mapped value or {@link #fallback()} to indicate "didn't map; fall back to the registered {@link ResultSetMapper} behavior".</p> 074 */ 075 @ThreadSafe 076 sealed abstract class MappingResult permits MappingResult.CustomMapping, MappingResult.Fallback { 077 private MappingResult() {} 078 079 /** 080 * Indicates a successfully-mapped custom value. 081 * 082 * @param value the custom value, may be {@code null} 083 * @return a result which indicates a successfully-mapped custom value 084 */ 085 @Nonnull 086 public static MappingResult of(@Nullable Object value) { 087 return new CustomMapping(value); 088 } 089 090 /** 091 * Indicates that this mapper did not map a custom value and prefers to fall back to the behavior of the registered {@link ResultSetMapper}. 092 * 093 * @return a result which indicates that this mapper did not map a custom value 094 */ 095 @Nonnull 096 public static MappingResult fallback() { 097 return Fallback.INSTANCE; 098 } 099 100 @ThreadSafe 101 static final class CustomMapping extends MappingResult { 102 @Nullable 103 private final Object value; 104 105 private CustomMapping(@Nullable Object value) { 106 this.value = value; 107 } 108 109 @Nonnull 110 public Optional<Object> getValue() { 111 return Optional.ofNullable(value); 112 } 113 } 114 115 @ThreadSafe 116 static final class Fallback extends MappingResult { 117 static final Fallback INSTANCE = new Fallback(); 118 119 private Fallback() {} 120 } 121 } 122}