All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.client.postgresql.Database Maven / Gradle / Ivy

There is a newer version: 1.92.0
Show newest version
/*
 * aoserv-client - Java client for the AOServ Platform.
 * Copyright (C) 2000-2013, 2016, 2017, 2018, 2019, 2020  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-client.
 *
 * aoserv-client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aoserv-client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aoserv-client.  If not, see .
 */
package com.aoindustries.aoserv.client.postgresql;

import com.aoindustries.aoserv.client.AOServConnector;
import com.aoindustries.aoserv.client.CachedObjectIntegerKey;
import com.aoindustries.aoserv.client.CannotRemoveReason;
import com.aoindustries.aoserv.client.Dumpable;
import com.aoindustries.aoserv.client.JdbcProvider;
import com.aoindustries.aoserv.client.NestedInputStream;
import com.aoindustries.aoserv.client.Removable;
import com.aoindustries.aoserv.client.StreamHandler;
import com.aoindustries.aoserv.client.net.Bind;
import com.aoindustries.aoserv.client.net.IpAddress;
import static com.aoindustries.aoserv.client.postgresql.ApplicationResources.accessor;
import com.aoindustries.aoserv.client.schema.AoservProtocol;
import com.aoindustries.aoserv.client.schema.Table;
import com.aoindustries.dto.DtoFactory;
import com.aoindustries.io.ByteCountInputStream;
import com.aoindustries.io.IoUtils;
import com.aoindustries.io.stream.StreamableInput;
import com.aoindustries.io.stream.StreamableOutput;
import com.aoindustries.lang.Strings;
import com.aoindustries.net.InetAddress;
import com.aoindustries.net.Port;
import com.aoindustries.net.URIEncoder;
import com.aoindustries.util.Internable;
import com.aoindustries.validation.InvalidResult;
import com.aoindustries.validation.ValidResult;
import com.aoindustries.validation.ValidationException;
import com.aoindustries.validation.ValidationResult;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A PostgresDatabase corresponds to a unique PostgreSQL table
 * space on one server.  The database name must be unique per server
 * and, to aid in account portability, will typically be unique
 * across the entire system.
 *
 * @see  Encoding
 * @see  UserServer
 *
 * @author  AO Industries, Inc.
 */
final public class Database extends CachedObjectIntegerKey implements Dumpable, Removable, JdbcProvider {

	/**
	 * Represents a name that may be used for a PostgreSQL database.  Database names must:
	 * 
    *
  • Be non-null
  • *
  • Be non-empty
  • *
  • Be between 1 and 31 characters
  • *
  • Characters may contain [a-z,A-Z,0-9,_,-,.,(space)]
  • *
* * @author AO Industries, Inc. */ final static public class Name implements Comparable, Serializable, ObjectInputValidation, DtoFactory, Internable { private static final long serialVersionUID = 5843440870677129701L; /** * The name of a database is limited by the internal data type of * the pg_database table. The type is name * which has a maximum length of 31 characters. */ public static final int MAX_LENGTH = 31; /** * Validates a PostgreSQL database name. */ public static ValidationResult validate(String name) { if(name==null) return new InvalidResult(accessor, "Database.Name.validate.isNull"); int len = name.length(); if(len==0) return new InvalidResult(accessor, "Database.Name.validate.isEmpty"); if(len > MAX_LENGTH) return new InvalidResult(accessor, "Database.Name.validate.tooLong", MAX_LENGTH, len); // Characters may contain [a-z,A-Z,0-9,_,-,.] for (int c = 0; c < len; c++) { char ch = name.charAt(c); if ( (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9') && ch != '_' && ch != '-' && ch != '.' && ch != ' ' ) return new InvalidResult(accessor, "Database.Name.validate.illegalCharacter"); } return ValidResult.getInstance(); } private static final ConcurrentMap interned = new ConcurrentHashMap<>(); /** * @param name when {@code null}, returns {@code null} */ public static Name valueOf(String name) throws ValidationException { if(name == null) return null; //Name existing = interned.get(name); //return existing!=null ? existing : new Name(name); return new Name(name, true); } final private String name; private Name(String name, boolean validate) throws ValidationException { this.name = name; if(validate) validate(); } /** * @param name Does not validate, should only be used with a known valid value. */ private Name(String name) { ValidationResult result; assert (result = validate(name)).isValid() : result.toString(); this.name = name; } private void validate() throws ValidationException { ValidationResult result = validate(name); if(!result.isValid()) throw new ValidationException(result); } /** * Perform same validation as constructor on readObject. */ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); validateObject(); } @Override public void validateObject() throws InvalidObjectException { try { validate(); } catch(ValidationException err) { InvalidObjectException newErr = new InvalidObjectException(err.getMessage()); newErr.initCause(err); throw newErr; } } @Override public boolean equals(Object O) { return O!=null && O instanceof Name && name.equals(((Name)O).name) ; } @Override public int hashCode() { return name.hashCode(); } @Override public int compareTo(Name other) { return this==other ? 0 : name.compareTo(other.name); } @Override public String toString() { return name; } /** * Interns this name much in the same fashion as String.intern(). * * @see String#intern() */ @Override public Name intern() { Name existing = interned.get(name); if(existing==null) { String internedName = name.intern(); Name addMe = (name == internedName) ? this : new Name(internedName); existing = interned.putIfAbsent(internedName, addMe); if(existing==null) existing = addMe; } return existing; } @Override public com.aoindustries.aoserv.client.dto.PostgresDatabaseName getDto() { return new com.aoindustries.aoserv.client.dto.PostgresDatabaseName(name); } } static final int COLUMN_PKEY=0, COLUMN_POSTGRES_SERVER=2, COLUMN_DATDBA=3 ; static final String COLUMN_NAME_name = "name"; static final String COLUMN_POSTGRES_SERVER_name = "postgres_server"; /** * The classname of the JDBC driver used for the PostgresDatabase. */ public static final String JDBC_DRIVER="org.postgresql.Driver"; public static final Name /** Templates */ TEMPLATE0, TEMPLATE1, /** Monitoring */ POSTGRESMON, /** AO Platform Components */ AOINDUSTRIES, // TODO: Remove once renamed to "aoserv-master" AOSERV, AOSERV_MASTER, AOWEB; static { try { TEMPLATE0 = Name.valueOf("template0"); TEMPLATE1 = Name.valueOf("template1"); POSTGRESMON = Name.valueOf("postgresmon"); AOINDUSTRIES = Name.valueOf("aoindustries"); AOSERV = Name.valueOf("aoserv"); AOSERV_MASTER = Name.valueOf("aoserv-master"); AOWEB = Name.valueOf("aoweb"); } catch(ValidationException e) { throw new AssertionError("These hard-coded values are valid", e); } } /** * Special PostgreSQL databases may not be added or removed. */ public static boolean isSpecial(Name name) { return // Templates name.equals(TEMPLATE0) || name.equals(TEMPLATE1) // Monitoring || name.equals(POSTGRESMON) // AO Platform Components || name.equals(AOINDUSTRIES) || name.equals(AOSERV) || name.equals(AOSERV_MASTER) || name.equals(AOWEB); } private Name name; private int postgres_server; private int datdba; private int encoding; private boolean is_template; private boolean allow_conn; private boolean enable_postgis; public boolean allowsConnections() { return allow_conn; } /** * @see #dump(java.io.Writer) */ @Override public void dump(PrintWriter out) throws IOException, SQLException { dump((Writer)out); } /** * The character set used by the dumps. */ public static final Charset DUMP_ENCODING = StandardCharsets.ISO_8859_1; /** * Dumps the database into textual representation, not gzipped. */ public void dump(final Writer out) throws IOException, SQLException { table.getConnector().requestUpdate( false, AoservProtocol.CommandID.DUMP_POSTGRES_DATABASE, new AOServConnector.UpdateRequest() { @Override public void writeRequest(StreamableOutput masterOut) throws IOException { masterOut.writeCompressedInt(pkey); masterOut.writeBoolean(false); } @Override public void readResponse(StreamableInput masterIn) throws IOException, SQLException { long dumpSize = masterIn.readLong(); if(dumpSize < 0) throw new IOException("dumpSize < 0: " + dumpSize); long bytesRead; try ( ByteCountInputStream byteCountIn = new ByteCountInputStream(new NestedInputStream(masterIn)); Reader nestedIn = new InputStreamReader(byteCountIn, DUMP_ENCODING) ) { IoUtils.copy(nestedIn, out); bytesRead = byteCountIn.getCount(); } if(bytesRead < dumpSize) throw new IOException("Too few bytes read: " + bytesRead + " < " + dumpSize); if(bytesRead > dumpSize) throw new IOException("Too many bytes read: " + bytesRead + " > " + dumpSize); } @Override public void afterRelease() { } } ); } /** * Dumps the database in {@link #DUMP_ENCODING} encoding into binary form, optionally gzipped. */ public void dump( final boolean gzip, final StreamHandler streamHandler ) throws IOException, SQLException { table.getConnector().requestUpdate( false, AoservProtocol.CommandID.DUMP_POSTGRES_DATABASE, new AOServConnector.UpdateRequest() { @Override public void writeRequest(StreamableOutput masterOut) throws IOException { masterOut.writeCompressedInt(pkey); masterOut.writeBoolean(gzip); } @Override public void readResponse(StreamableInput masterIn) throws IOException, SQLException { long dumpSize = masterIn.readLong(); if(dumpSize < -1) throw new IOException("dumpSize < -1: " + dumpSize); streamHandler.onDumpSize(dumpSize); long bytesRead; try (InputStream nestedIn = new NestedInputStream(masterIn)) { bytesRead = IoUtils.copy(nestedIn, streamHandler.getOut()); } if(dumpSize != -1) { if(bytesRead < dumpSize) throw new IOException("Too few bytes read: " + bytesRead + " < " + dumpSize); if(bytesRead > dumpSize) throw new IOException("Too many bytes read: " + bytesRead + " > " + dumpSize); } } @Override public void afterRelease() { } } ); } /** * Indicates that PostGIS should be enabled for this database. */ public boolean getEnablePostgis() { return enable_postgis; } @Override protected Object getColumnImpl(int i) { switch(i) { case COLUMN_PKEY: return pkey; case 1: return name; case COLUMN_POSTGRES_SERVER: return postgres_server; case COLUMN_DATDBA: return datdba; case 4: return encoding; case 5: return is_template; case 6: return allow_conn; case 7: return enable_postgis; default: throw new IllegalArgumentException("Invalid index: " + i); } } public int getDatdba_id() { return datdba; } public UserServer getDatDBA() throws SQLException, IOException { UserServer obj=table.getConnector().getPostgresql().getUserServer().get(datdba); if(obj==null) throw new SQLException("Unable to find PostgresServerUser: "+datdba); return obj; } @Override public String getJdbcDriver() { return JDBC_DRIVER; } @Override public String getJdbcUrl(boolean ipOnly) throws SQLException, IOException { Server ps = getPostgresServer(); com.aoindustries.aoserv.client.linux.Server ao = ps.getLinuxServer(); StringBuilder jdbcUrl = new StringBuilder(); jdbcUrl.append("jdbc:postgresql://"); Bind nb = ps.getBind(); IpAddress ip = nb.getIpAddress(); InetAddress ia = ip.getInetAddress(); if(ipOnly) { if(ia.isUnspecified()) { jdbcUrl.append(ao.getHost().getNetDevice(ao.getDaemonDeviceId().getName()).getPrimaryIPAddress().getInetAddress().toBracketedString()); } else { jdbcUrl.append(ia.toBracketedString()); } } else { if(ia.isUnspecified()) { jdbcUrl.append(ao.getHostname()); } else if(ia.isLoopback()) { // Loopback as IP addresses to avoid ambiguity about which stack (IPv4/IPv6) used for "localhost" in Java jdbcUrl.append(ia.toBracketedString()); } else { jdbcUrl.append(ip.getHostname()); } } Port port = nb.getPort(); if(!port.equals(Server.DEFAULT_PORT)) { jdbcUrl .append(':') .append(port.getPort()); } jdbcUrl.append('/'); URIEncoder.encodeURIComponent(getName().toString(), jdbcUrl); return jdbcUrl.toString(); } @Override public String getJdbcDocumentationUrl() throws SQLException, IOException { final String LIST = "https://jdbc.postgresql.org/documentation/documentation.html"; final String HEAD = "https://jdbc.postgresql.org/documentation/head/index.html"; String version = getPostgresServer().getVersion().getTechnologyVersion(table.getConnector()).getVersion(); List split = Strings.split(version, '.'); if(split.size() < 2) { return LIST; } else { String major = split.get(0); String minor = split.get(1); if(major.equals("7")) { return "https://www.postgresql.org/docs/" + URIEncoder.encodeURIComponent(major) + "." + URIEncoder.encodeURIComponent(minor) + "/jdbc.html"; } if( major.equals("8") || ( major.equals("9") && ( minor.equals("0") || minor.equals("1") || minor.equals("2") || minor.equals("3") || minor.equals("4") ) ) ) { return "https://jdbc.postgresql.org/documentation/" + URIEncoder.encodeURIComponent(major) + URIEncoder.encodeURIComponent(minor) + "/index.html"; } return HEAD; } } public Name getName() { return name; } public boolean isSpecial() { return isSpecial(name); } public Encoding getPostgresEncoding() throws SQLException, IOException { Encoding obj=table.getConnector().getPostgresql().getEncoding().get(encoding); if(obj==null) throw new SQLException("Unable to find PostgresEncoding: "+encoding); // Make sure the postgres encoding postgresql version matches the server this database is part of if( obj.getPostgresVersion(table.getConnector()).getPkey() != getPostgresServer().getVersion().getPkey() ) { throw new SQLException("encoding/postgres server version mismatch on PostgresDatabase: #"+pkey); } return obj; } public int getPostgresServer_bind_id() { return postgres_server; } public Server getPostgresServer() throws SQLException, IOException { Server obj=table.getConnector().getPostgresql().getServer().get(postgres_server); if(obj==null) throw new SQLException("Unable to find PostgresServer: "+postgres_server); return obj; } @Override public Table.TableID getTableID() { return Table.TableID.POSTGRES_DATABASES; } @Override public void init(ResultSet result) throws SQLException { try { pkey=result.getInt(1); name = Name.valueOf(result.getString(2)); postgres_server=result.getInt(3); datdba=result.getInt(4); encoding=result.getInt(5); is_template=result.getBoolean(6); allow_conn=result.getBoolean(7); enable_postgis=result.getBoolean(8); } catch(ValidationException e) { throw new SQLException(e); } } public boolean isTemplate() { return is_template; } @Override public void read(StreamableInput in, AoservProtocol.Version protocolVersion) throws IOException { try { pkey=in.readCompressedInt(); name = Name.valueOf(in.readUTF()); postgres_server=in.readCompressedInt(); datdba=in.readCompressedInt(); encoding=in.readCompressedInt(); is_template=in.readBoolean(); allow_conn=in.readBoolean(); enable_postgis=in.readBoolean(); } catch(ValidationException e) { throw new IOException(e); } } @Override public List> getCannotRemoveReasons() throws SQLException, IOException { List> reasons=new ArrayList<>(); Server ps=getPostgresServer(); if(!allow_conn) reasons.add(new CannotRemoveReason<>("Not allowed to drop a PostgreSQL database that does not allow connections: "+name+" on "+ps.getName()+" on "+ps.getLinuxServer().getHostname(), this)); if(is_template) reasons.add(new CannotRemoveReason<>("Not allowed to drop a template PostgreSQL database: "+name+" on "+ps.getName()+" on "+ps.getLinuxServer().getHostname(), this)); if(isSpecial()) { reasons.add( new CannotRemoveReason<>( "Not allowed to drop a special PostgreSQL database: " + name + " on " + ps.getName() + " on " + ps.getLinuxServer().getHostname(), this ) ); } return reasons; } @Override public void remove() throws IOException, SQLException { if(isSpecial()) throw new SQLException("Refusing to remove special PostgreSQL database: " + this); table.getConnector().requestUpdateIL( true, AoservProtocol.CommandID.REMOVE, Table.TableID.POSTGRES_DATABASES, pkey ); } @Override public String toStringImpl() { return name.toString(); } @Override public void write(StreamableOutput out, AoservProtocol.Version protocolVersion) throws IOException { out.writeCompressedInt(pkey); out.writeUTF(name.toString()); out.writeCompressedInt(postgres_server); out.writeCompressedInt(datdba); out.writeCompressedInt(encoding); out.writeBoolean(is_template); out.writeBoolean(allow_conn); if(protocolVersion.compareTo(AoservProtocol.Version.VERSION_1_30)<=0) { out.writeShort(0); out.writeShort(7); } if(protocolVersion.compareTo(AoservProtocol.Version.VERSION_1_27)>=0) out.writeBoolean(enable_postgis); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy