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.concurrent.NotThreadSafe; 021import java.sql.ResultSet; 022import java.sql.SQLException; 023import java.util.List; 024import java.util.Locale; 025import java.util.Optional; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Contract for mapping a {@link ResultSet} row to the specified type. 031 * <p> 032 * A production-ready concrete implementation is available via the following static methods: 033 * <ul> 034 * <li>{@link #withDefaultConfiguration()}</li> 035 * <li>{@link #withPlanCachingEnabled(Boolean)} (builder)</li> 036 * <li>{@link #withCustomColumnMappers(List)} (builder)</li> 037 * <li>{@link #withNormalizationLocale(Locale)} (builder)</li> 038 * </ul> 039 * <p> 040 * How to acquire an instance: 041 * <pre>{@code // With out-of-the-box defaults 042 * ResultSetMapper default = ResultSetMapper.withDefaultConfiguration(); 043 * 044 * // Customized 045 * ResultSetMapper custom = ResultSetMapper.withPlanCachingEnabled(false) 046 * .customColumnMappers(List.of(...)) 047 * .normalizationLocale(Locale.forLanguageTag("pt-BR")) 048 * .build();}</pre> Or, implement your own: <pre>{@code ResultSetMapper myImpl = new ResultSetMapper() { 049 * @Nonnull 050 * @Override 051 * public <T> Optional<T> map( 052 * @Nonnull StatementContext<T> statementContext, 053 * @Nonnull ResultSet resultSet, 054 * @Nonnull Class<T> resultSetRowType, 055 * @Nonnull InstanceProvider instanceProvider 056 * ) throws SQLException { 057 * // TODO: pull data from resultSet and apply to a new instance of T 058 * return Optional.empty(); 059 * } 060 * };}</pre> 061 * 062 * @author <a href="https://www.revetkn.com">Mark Allen</a> 063 * @since 1.0.0 064 */ 065@FunctionalInterface 066public interface ResultSetMapper { 067 /** 068 * Maps the current row of {@code resultSet} to the result class indicated by {@code statementContext}. 069 * 070 * @param <T> result instance type token 071 * @param statementContext current SQL context 072 * @param resultSet provides raw row data to pull from 073 * @param resultSetRowType the type to which the {@link ResultSet} row should be marshaled 074 * @param instanceProvider instance-creation factory, used to instantiate {@code resultSetRowType} row objects 075 * @return an {@link Optional} containing an instance of the given {@code resultClass}, or {@link Optional#empty()} to indicate a {@code null} value 076 * @throws SQLException if an error occurs during mapping 077 */ 078 @Nonnull 079 <T> Optional<T> map(@Nonnull StatementContext<T> statementContext, 080 @Nonnull ResultSet resultSet, 081 @Nonnull Class<T> resultSetRowType, 082 @Nonnull InstanceProvider instanceProvider) throws SQLException; 083 084 /** 085 * Acquires a builder for a concrete implementation of this interface, specifying the locale to use when massaging JDBC column names for matching against Java property names. 086 * 087 * @param normalizationLocale the locale to use when massaging JDBC column names for matching against Java property names 088 * @return a {@code Builder} for a concrete implementation 089 */ 090 @Nonnull 091 static Builder withNormalizationLocale(@Nonnull Locale normalizationLocale) { 092 requireNonNull(normalizationLocale); 093 094 new ResultSetMapper() { 095 @Nonnull 096 @Override 097 public <T> Optional<T> map( 098 @Nonnull StatementContext<T> statementContext, 099 @Nonnull ResultSet resultSet, 100 @Nonnull Class<T> resultSetRowType, 101 @Nonnull InstanceProvider instanceProvider) { 102 return Optional.empty(); 103 } 104 }; 105 106 return new Builder().normalizationLocale(normalizationLocale); 107 } 108 109 /** 110 * Acquires a builder for a concrete implementation of this interface, specifying a {@link List} of custom column-specific mapping logic to apply, in priority order. 111 * 112 * @param customColumnMappers a {@link List} of custom column-specific mapping logic to apply, in priority order 113 * @return a {@code Builder} for a concrete implementation 114 */ 115 @Nonnull 116 static Builder withCustomColumnMappers(@Nonnull List<CustomColumnMapper> customColumnMappers) { 117 requireNonNull(customColumnMappers); 118 return new Builder().customColumnMappers(customColumnMappers); 119 } 120 121 /** 122 * Acquires a builder for a concrete implementation of this interface, specifying whether an internal "mapping plan" cache should be used to speed up {@link ResultSet} mapping. 123 * 124 * @param planCachingEnabled whether an internal "mapping plan" cache should be used to speed up {@link ResultSet} mapping 125 * @return a {@code Builder} for a concrete implementation 126 */ 127 @Nonnull 128 static Builder withPlanCachingEnabled(@Nonnull Boolean planCachingEnabled) { 129 requireNonNull(planCachingEnabled); 130 return new Builder().planCachingEnabled(planCachingEnabled); 131 } 132 133 /** 134 * Acquires a concrete implementation of this interface with out-of-the-box defaults. 135 * <p> 136 * The returned instance is thread-safe. 137 * 138 * @return a concrete implementation of this interface with out-of-the-box defaults 139 */ 140 @Nonnull 141 static ResultSetMapper withDefaultConfiguration() { 142 return new Builder().build(); 143 } 144 145 /** 146 * Builder used to construct a standard implementation of {@link ResultSetMapper}. 147 * <p> 148 * This class is intended for use by a single thread. 149 * 150 * @author <a href="https://www.revetkn.com">Mark Allen</a> 151 * @since 3.0.0 152 */ 153 @NotThreadSafe 154 class Builder { 155 @Nonnull 156 Locale normalizationLocale; 157 @Nonnull 158 List<CustomColumnMapper> customColumnMappers; 159 @Nonnull 160 Boolean planCachingEnabled; 161 162 private Builder() { 163 this.normalizationLocale = Locale.getDefault(); 164 this.customColumnMappers = List.of(); 165 this.planCachingEnabled = true; 166 } 167 168 /** 169 * Specifies the locale to use when massaging JDBC column names for matching against Java property names. 170 * 171 * @param normalizationLocale the locale to use when massaging JDBC column names for matching against Java property names 172 * @return this {@code Builder}, for chaining 173 */ 174 @Nonnull 175 public Builder normalizationLocale(@Nonnull Locale normalizationLocale) { 176 requireNonNull(normalizationLocale); 177 this.normalizationLocale = normalizationLocale; 178 return this; 179 } 180 181 /** 182 * Specifies a {@link List} of custom column-specific mapping logic to apply, in priority order. 183 * 184 * @param customColumnMappers a {@link List} of custom column-specific mapping logic to apply, in priority order 185 * @return this {@code Builder}, for chaining 186 */ 187 @Nonnull 188 public Builder customColumnMappers(@Nonnull List<CustomColumnMapper> customColumnMappers) { 189 requireNonNull(customColumnMappers); 190 this.customColumnMappers = customColumnMappers; 191 return this; 192 } 193 194 /** 195 * Specifies whether an internal "mapping plan" cache should be used to speed up {@link ResultSet} mapping. 196 * 197 * @param planCachingEnabled whether an internal "mapping plan" cache should be used to speed up {@link ResultSet} mapping 198 * @return this {@code Builder}, for chaining 199 */ 200 @Nonnull 201 public Builder planCachingEnabled(@Nonnull Boolean planCachingEnabled) { 202 requireNonNull(planCachingEnabled); 203 this.planCachingEnabled = planCachingEnabled; 204 return this; 205 } 206 207 /** 208 * Constructs a default {@code ResultSetMapper} instance. 209 * <p> 210 * The constructed instance is thread-safe. 211 * 212 * @return a {@code ResultSetMapper} instance 213 */ 214 @Nonnull 215 public ResultSetMapper build() { 216 return new DefaultResultSetMapper(this); 217 } 218 } 219}