001package io.ebean.docker.commands;
002
003import java.net.MalformedURLException;
004import java.net.URL;
005import java.nio.file.Files;
006import java.nio.file.Paths;
007import java.util.Properties;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * SAP HANA configuration.
014 *
015 * For more information about the HANA docker configuration see the tutorial
016 * <a href=
017 * "https://developers.sap.com/tutorials/hxe-ua-install-using-docker.html">Installing
018 * SAP HANA, express edition with Docker</a>
019 */
020public class HanaConfig extends DbConfig {
021
022  private static final Logger log = LoggerFactory.getLogger(HanaConfig.class);
023
024  private String mountsDirectory;
025  private URL passwordsUrl;
026  private String instanceNumber;
027  private boolean agreeToSapLicense;
028
029  public HanaConfig(String version, Properties properties) {
030    this(version);
031    setProperties(properties);
032    if (!Integer.toString(this.port).matches("\\d{5}")) {
033      throw new IllegalArgumentException("Invalid port: " + this.port + ". The port must consist of exactly 5 digits.");
034    }
035    this.mountsDirectory = prop(properties, "mountsDirectory", "/data/dockermounts");
036    if (!Files.isDirectory(Paths.get(this.mountsDirectory))) {
037      throw new IllegalArgumentException(
038          "The given mounts directory \"" + this.mountsDirectory + "\" doesn't exist or is not a directory");
039    }
040    try {
041      this.passwordsUrl = new URL(prop(properties, "passwordsUrl", "file:///hana/mounts/passwords.json"));
042    } catch (MalformedURLException e) {
043      log.warn("Invalid passwords URL. Using default.", e);
044      try {
045        this.passwordsUrl = new URL("file:///hana/mounts/passwords.json");
046      } catch (MalformedURLException e1) {
047        log.debug("Invalid passwords URL. Can't happen.");
048      }
049    }
050    this.instanceNumber = prop(properties, "instanceNumber", "90");
051    if (!this.instanceNumber.matches("\\d{2}")) {
052      throw new IllegalArgumentException("Invalid instance number: " + this.instanceNumber
053          + ". The instance number must consist of exactly two digits.");
054    }
055    if (!"90".equals(this.instanceNumber)) {
056      String portStr = Integer.toString(this.port);
057      this.port = Integer.parseInt(portStr.substring(0, 1) + this.instanceNumber + portStr.substring(3));
058    }
059    this.agreeToSapLicense = checkLicenseAgreement(properties);
060  }
061
062  public HanaConfig(String version) {
063    super("hana", 39017, 39017, version);
064    this.image = "store/saplabs/hanaexpress:" + version;
065    this.mountsDirectory = "/data/dockermounts";
066    try {
067      this.passwordsUrl = new URL("file:///hana/mounts/passwords.json");
068    } catch (MalformedURLException e1) {
069      log.debug("Invalid passwords URL. Can't happen.");
070    }
071    this.instanceNumber = "90";
072    this.agreeToSapLicense = checkLicenseAgreement();
073    setAdminUser("SYSTEM");
074    setAdminPassword("HXEHana1");
075    setPassword("HXEHana1");
076    setDbName("HXE");
077    setMaxReadyAttempts(3000);
078  }
079
080  /**
081   * Return the JDBC URL for connecting to the database
082   */
083  public String jdbcUrl() {
084    return "jdbc:sap://localhost:" + getPort() + "/?databaseName=" + getDbName();
085  }
086
087  /**
088   * Return the path to the container-external mounts directory that can be used
089   * by the HANA docker container to store its data.
090   * <p>
091   * The directory must be created before starting the docker container, for
092   * example, like this:
093   *
094   * <pre>
095   * sudo mkdir -p /data/&lt;directory_name&gt;
096   * sudo chown 12000:79 /data/&lt;directory_name&gt;
097   * </pre>
098   *
099   * @return The path to the external directory
100   */
101  public String getMountsDirectory() {
102    return mountsDirectory;
103  }
104
105  /**
106   * Set the path to the container-external mounts directory that can be used by
107   * the HANA docker image to store its data.
108   *
109   * @param mountsDirectory The path to the external directory
110   */
111  public void setMountsDirectory(String mountsDirectory) {
112    this.mountsDirectory = mountsDirectory;
113  }
114
115  /**
116   * Return the URL of the file containing the default password(s) for the HANA
117   * database users.
118   * <p>
119   * The file must contain passwords in a JSON format, for example:
120   *
121   * <pre>
122   * {
123   *   "master_password" : "HXEHana1"
124   * }
125   * </pre>
126   *
127   * If the file is located in the container-external mounts directory (see
128   * {@link #getMountsDirectory()}), the URL should be
129   * {@code file:///hana/mounts/<file_name>.json}
130   *
131   * @return The URL of the file containing the default password(s) for the HANA
132   *         database users.
133   */
134  public URL getPasswordsUrl() {
135    return passwordsUrl;
136  }
137
138  /**
139   * Set the URL of the file containing the default password(s) for the HANA
140   * database users.
141   *
142   * @param passwordsUrl The URL of the file containing the default password(s)
143   *                     for the HANA database users.
144   */
145  public void setPasswordsUrl(URL passwordsUrl) {
146    this.passwordsUrl = passwordsUrl;
147  }
148
149  /**
150   * Return the container-external instance number of the HANA database.
151   *
152   * A different instance number is necessary when running more than one instance
153   * of HANA on one host. The instance number can range from 00 to 99. The default
154   * instance number is 90.
155   *
156   * @return The container-external instance number of the HANA database.
157   */
158  public String getInstanceNumber() {
159    return instanceNumber;
160  }
161
162  /**
163   * Set the container-external instance number of the HANA database.
164   *
165   * @param instanceNumber The container-external instance number of the HANA
166   *                       database.
167   */
168  public void setInstanceNumber(String instanceNumber) {
169    this.instanceNumber = instanceNumber;
170  }
171
172  /**
173   * Returns whether the user agrees to the <a href=
174   * "https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf">SAP
175   * license</a> for the HANA docker image.
176   *
177   * @return {@code true} if the user agrees to the license, {@code false}
178   *         otherwise.
179   */
180  public boolean isAgreeToSapLicense() {
181    return agreeToSapLicense;
182  }
183
184  /**
185   * Set whether the user agrees to the <a href=
186   * "https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf">SAP
187   * license</a> for the HANA docker image.
188   *
189   * @param agreeToSapLicense Whether the user agrees to the license or not
190   */
191  public void setAgreeToSapLicense(boolean agreeToSapLicense) {
192    this.agreeToSapLicense = agreeToSapLicense;
193  }
194
195  /**
196   * Check if the user has agreed to the <a href=
197   * "https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf">SAP
198   * license</a>
199   *
200   * @param properties The properties to check
201   * @return {@code true} if the user has agreed to the license, {@code false}
202   *         otherwise
203   */
204  public boolean checkLicenseAgreement(Properties properties) {
205    String propertyValue = null;
206    if (properties != null) {
207      propertyValue = prop(properties, "agreeToSapLicense", null);
208      if (propertyValue != null) {
209        return Boolean.parseBoolean(propertyValue);
210      }
211    }
212
213    return checkLicenseAgreement();
214  }
215
216  /**
217   * Check if the user has agreed to the <a href=
218   * "https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf">SAP
219   * license</a>
220   *
221   * @return {@code true} if the user has agreed to the license, {@code false}
222   *         otherwise
223   */
224  public static boolean checkLicenseAgreement() {
225    String propertyValue = System.getProperty("hana.agreeToSapLicense");
226    if (propertyValue != null) {
227      return Boolean.parseBoolean(propertyValue);
228    }
229
230    propertyValue = System.getenv("hana.agreeToSapLicense");
231    if (propertyValue != null) {
232      return Boolean.parseBoolean(propertyValue);
233    }
234
235    return false;
236  }
237}