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.collect.ImmutableMap;
028
029/**
030 * Information about a database located on a PostgreSQL server connected to an {@link EmbeddedPostgres} instance.
031 */
032@AutoValue
033public abstract class DatabaseInfo {
034
035    private static final String JDBC_FORMAT = "jdbc:postgresql://localhost:%d/%s?user=%s";
036
037    DatabaseInfo() {
038    }
039
040    /**
041     * The default user used for databases.
042     */
043    static final String PG_DEFAULT_USER = "postgres";
044
045    /**
046     * The default database name.
047     */
048    static final String PG_DEFAULT_DB = "postgres";
049
050    /**
051     * Returns the name of the database.
052     *
053     * @return Name of the database. Is never null.
054     */
055    @Nonnull
056    public abstract String dbName();
057
058    /**
059     * Returns the TCP port for the database server.
060     *
061     * @return A port number. May be -1 if this objects represents an error connection.
062     */
063    public abstract int port();
064
065    /**
066     * Returns the user that can connect to this database.
067     *
068     * @return The user name. Is never null.
069     */
070    @Nonnull
071    public abstract String user();
072
073    /**
074     * Returns all properties that are be applied to a new data source connection to this database. See
075     * <a href="https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters">the
076     * PostgreSQL JDBC driver documentation</a> for a comprehensive list.
077     *
078     * @return Map of key-value pairs representing data source connection properties.
079     * @since 3.0
080     */
081    @Nonnull
082    public abstract ImmutableMap<String, String> connectionProperties();
083
084    @Nonnull
085    abstract Optional<SQLException> exception();
086
087    @Nonnull
088    static Builder builder() {
089        return new AutoValue_DatabaseInfo.Builder()
090                .dbName(PG_DEFAULT_DB)
091                .user(PG_DEFAULT_USER);
092    }
093
094    @Nonnull
095    static DatabaseInfo forException(SQLException e) {
096        return builder().exception(e).port(-1).build();
097    }
098
099    /**
100     * Returns a JDBC url to connect to the described database.
101     *
102     * @return A JDBC url that can be used to connect to the database. Never null.
103     */
104    @Nonnull
105    public String asJdbcUrl() {
106        checkState(exception().isEmpty(), "DatabaseInfo contains SQLException: %s", exception());
107
108        String additionalParameters = connectionProperties().entrySet().stream()
109                .map(e -> format("&%s=%s", e.getKey(), e.getValue()))
110                .collect(Collectors.joining());
111        return format(JDBC_FORMAT, port(), dbName(), user()) + additionalParameters;
112    }
113
114    /**
115     * Returns a {@link DataSource} instance connected to the described database.
116     *
117     * @return An initialized {@link DataSource} object. Never null.
118     * @throws SQLException A problem occurred trying to connect to the database.
119     */
120    @Nonnull
121    public DataSource asDataSource() throws SQLException {
122        if (exception().isPresent()) {
123            throw exception().get();
124        }
125
126        return EmbeddedPostgres.createDataSource(user(), dbName(), port(), connectionProperties());
127    }
128
129    @AutoValue.Builder
130    abstract static class Builder {
131
132        abstract Builder dbName(String dbName);
133
134        abstract Builder port(int port);
135
136        abstract Builder user(String user);
137
138        abstract Builder exception(SQLException exception);
139
140        abstract ImmutableMap.Builder<String, String> connectionPropertiesBuilder();
141
142        final Builder addConnectionProperty(String key, String value) {
143            connectionPropertiesBuilder().put(key, value);
144            return this;
145        }
146
147        abstract Builder connectionProperties(ImmutableMap<String, String> connectionProperties);
148
149        abstract DatabaseInfo build();
150
151    }
152}