001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 * http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package de.softwareforge.testing.postgres.embedded;
016
017import static com.google.common.base.Preconditions.checkState;
018import static java.lang.String.format;
019
020import jakarta.annotation.Nonnull;
021import java.sql.SQLException;
022import java.util.Optional;
023import java.util.stream.Collectors;
024import javax.sql.DataSource;
025
026import com.google.auto.value.AutoValue;
027import com.google.common.annotations.VisibleForTesting;
028import com.google.common.collect.ImmutableMap;
029
030/**
031 * Information about a database located on a PostgreSQL server connected to an {@link EmbeddedPostgres} instance.
032 */
033@AutoValue
034public abstract class DatabaseInfo {
035
036    private static final String JDBC_FORMAT = "jdbc:postgresql://localhost:%d/%s";
037
038    DatabaseInfo() {
039    }
040
041    /**
042     * The default user used for databases.
043     */
044    static final String PG_DEFAULT_USER = "postgres";
045
046    /**
047     * The default database name.
048     */
049    static final String PG_DEFAULT_DB = "postgres";
050
051    /**
052     * Returns the name of the database.
053     *
054     * @return Name of the database. Is never null.
055     */
056    @Nonnull
057    public abstract String dbName();
058
059    /**
060     * Returns the TCP port for the database server.
061     *
062     * @return A port number. May be -1 if this objects represents an error connection.
063     */
064    public abstract int port();
065
066    /**
067     * Returns the user that can connect to this database.
068     *
069     * @return The user name. Is never null.
070     */
071    @Nonnull
072    public abstract String user();
073
074    /**
075     * Returns all properties that are be applied to a new data source connection to this database. See
076     * <a href="https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters">the
077     * PostgreSQL JDBC driver documentation</a> for a comprehensive list.
078     *
079     * @return Map of key-value pairs representing data source connection properties.
080     * @since 3.0
081     */
082    @Nonnull
083    public abstract ImmutableMap<String, String> connectionProperties();
084
085    @Nonnull
086    abstract Optional<SQLException> exception();
087
088    @Nonnull
089    static Builder builder() {
090        return new AutoValue_DatabaseInfo.Builder()
091                .dbName(PG_DEFAULT_DB)
092                .user(PG_DEFAULT_USER);
093    }
094
095    @Nonnull
096    static DatabaseInfo forException(SQLException e) {
097        return builder().exception(e).port(-1).build();
098    }
099
100    @VisibleForTesting
101    String getBaseUrl() {
102        return format(JDBC_FORMAT, port(), dbName());
103    }
104
105    @VisibleForTesting
106    String getAdditionalParameters() {
107        return connectionProperties().entrySet().stream()
108                .map(e -> format("%s=%s", e.getKey(), e.getValue()))
109                .collect(Collectors.joining("&"));
110    }
111
112    /**
113     * Returns a JDBC url to connect to the described database.
114     *
115     * @return A JDBC url that can be used to connect to the database. Never null.
116     */
117    @Nonnull
118    public String asJdbcUrl() {
119        checkState(exception().isEmpty(), "DatabaseInfo contains SQLException: %s", exception());
120        var parameters = getAdditionalParameters();
121        return parameters.isEmpty() ? getBaseUrl() : getBaseUrl() + "?" + parameters;
122    }
123
124    /**
125     * Returns a {@link DataSource} instance connected to the described database.
126     *
127     * @return An initialized {@link DataSource} object. Never null.
128     * @throws SQLException A problem occurred trying to connect to the database.
129     */
130    @Nonnull
131    public DataSource asDataSource() throws SQLException {
132        if (exception().isPresent()) {
133            throw exception().get();
134        }
135
136        return EmbeddedPostgres.createDataSource(user(), dbName(), port(), connectionProperties());
137    }
138
139    @AutoValue.Builder
140    abstract static class Builder {
141
142        abstract Builder dbName(String dbName);
143
144        abstract Builder port(int port);
145
146        abstract Builder user(String user);
147
148        abstract Builder exception(SQLException exception);
149
150        abstract ImmutableMap.Builder<String, String> connectionPropertiesBuilder();
151
152        final Builder addConnectionProperty(String key, String value) {
153            connectionPropertiesBuilder().put(key, value);
154            return this;
155        }
156
157        abstract Builder connectionProperties(ImmutableMap<String, String> connectionProperties);
158
159        abstract DatabaseInfo build();
160
161    }
162}