001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2014 ForgeRock AS.
026 */
027package org.forgerock.opendj.config.dsconfig;
028
029import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_MISMATCH;
030import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_MALFORMED;
031import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_NOT_FOUND;
032import static com.forgerock.opendj.ldap.ConfigMessages.ERR_CONFIGVERSION_NOT_FOUND;
033import static org.forgerock.util.Utils.closeSilently;
034
035import java.io.BufferedReader;
036import java.io.File;
037import java.io.FileReader;
038import java.io.IOException;
039import java.util.Arrays;
040
041import org.forgerock.opendj.config.ConfigurationFramework;
042import org.forgerock.opendj.config.server.ConfigException;
043import org.forgerock.opendj.ldap.Connection;
044import org.forgerock.opendj.ldap.LdapException;
045import org.forgerock.opendj.ldap.responses.SearchResultEntry;
046
047/**
048 * Represents a particular version of OpenDJ useful for making comparisons between versions. FIXME TODO Move this file
049 * in ? package.
050 */
051public class BuildVersion implements Comparable<BuildVersion> {
052
053    private final int major;
054    private final int minor;
055    private final int point;
056    private final long rev;
057
058    /**
059     * Creates a new build version using the provided version information.
060     *
061     * @param major
062     *            Major release version number.
063     * @param minor
064     *            Minor release version number.
065     * @param point
066     *            Point release version number.
067     * @param rev
068     *            VCS revision number.
069     */
070    public BuildVersion(final int major, final int minor, final int point, final long rev) {
071        this.major = major;
072        this.minor = minor;
073        this.point = point;
074        this.rev = rev;
075    }
076
077    /**
078     * Returns the build version as specified in the entry "cn=Version,cn=monitor".
079     *
080     * @param connection
081     *            The connection to use to read the entry.
082     * @return The build version as specified in the current installation configuration.
083     * @throws ConfigException
084     *             Sends an exception if it is impossible to retrieve the version configuration entry.
085     */
086    public static BuildVersion binaryVersion(final Connection connection) throws ConfigException {
087        try {
088            final SearchResultEntry entry = connection.readEntry("", "fullVendorVersion");
089            return valueOf(entry.getAttribute("fullVendorVersion").firstValueAsString());
090        } catch (LdapException e) {
091            throw new ConfigException(ERR_CONFIGVERSION_NOT_FOUND.get());
092        }
093    }
094
095    /**
096     * Checks if the binary version is the same than the instance version. If not, a configuration exception is thrown.
097     *
098     * @param connection
099     *            The connection to use to read the configuration entry.
100     * @throws ConfigException
101     *             Sends an exception if the version mismatch.
102     */
103    public static void checkVersionMismatch(final Connection connection) throws ConfigException {
104        final BuildVersion binaryVersion = BuildVersion.binaryVersion(connection);
105        final BuildVersion instanceVersion = BuildVersion.instanceVersion();
106        if (!binaryVersion.toString().equals(instanceVersion.toString())) {
107            throw new ConfigException(ERR_BUILDVERSION_MISMATCH.get(binaryVersion, instanceVersion));
108        }
109    }
110
111    /**
112     * Reads the instance version from config/buildinfo.
113     *
114     * @return The instance version from config/buildinfo.
115     * @throws ConfigException
116     *             If an error occurred while reading or parsing the version.
117     */
118    public static BuildVersion instanceVersion() throws ConfigException {
119        final String buildInfo = ConfigurationFramework.getInstance().getInstancePath() + File.separator + "config"
120                + File.separator + "buildinfo";
121        BufferedReader reader = null;
122        try {
123            reader = new BufferedReader(new FileReader(buildInfo));
124            final String s = reader.readLine();
125            if (s != null) {
126                return valueOf(s);
127            } else {
128                throw new ConfigException(ERR_BUILDVERSION_MALFORMED.get(buildInfo));
129            }
130        } catch (IOException e) {
131            throw new ConfigException(ERR_BUILDVERSION_NOT_FOUND.get(buildInfo));
132        } catch (final IllegalArgumentException e) {
133            throw new ConfigException(ERR_BUILDVERSION_MALFORMED.get(buildInfo));
134        } finally {
135            closeSilently(reader);
136        }
137    }
138
139    /**
140     * Parses the string argument as a build version. The string must be of the form:
141     *
142     * <pre>
143     * major.minor.point.rev
144     * </pre>
145     *
146     * @param s
147     *            The string to be parsed as a build version.
148     * @return The parsed build version.
149     * @throws IllegalArgumentException
150     *             If the string does not contain a parsable build version.
151     */
152    public static BuildVersion valueOf(final String s) {
153        final String[] fields = s.split("\\.");
154        if (fields.length != 4) {
155            throw new IllegalArgumentException("Invalid version string " + s);
156        }
157        final int major = Integer.parseInt(fields[0]);
158        final int minor = Integer.parseInt(fields[1]);
159        final int point = Integer.parseInt(fields[2]);
160        final long rev = Long.parseLong(fields[3]);
161        return new BuildVersion(major, minor, point, rev);
162    }
163
164    /**
165     * Returns the major release version number.
166     *
167     * @return The major release version number.
168     */
169    public int getMajorVersion() {
170        return major;
171    }
172
173    /**
174     * Returns the minor release version number.
175     *
176     * @return The minor release version number.
177     */
178    public int getMinorVersion() {
179        return minor;
180    }
181
182    /**
183     * Returns the point release version number.
184     *
185     * @return The point release version number.
186     */
187    public int getPointVersion() {
188        return point;
189    }
190
191    /** {@inheritDoc} */
192    public boolean equals(final Object obj) {
193        if (this == obj) {
194            return true;
195        } else if (obj instanceof BuildVersion) {
196            final BuildVersion other = (BuildVersion) obj;
197            return major == other.major && minor == other.minor && point == other.point && rev == other.rev;
198        } else {
199            return false;
200        }
201    }
202
203    /** {@inheritDoc} */
204    public int compareTo(final BuildVersion version) {
205        if (major == version.major) {
206            if (minor == version.minor) {
207                if (point == version.point) {
208                    if (rev == version.rev) {
209                        return 0;
210                    } else if (rev < version.rev) {
211                        return -1;
212                    }
213                } else if (point < version.point) {
214                    return -1;
215                }
216            } else if (minor < version.minor) {
217                return -1;
218            }
219        } else if (major < version.major) {
220            return -1;
221        }
222        return 1;
223    }
224
225    /**
226     * Returns the VCS revision number.
227     *
228     * @return The VCS revision number.
229     */
230    public long getRevisionNumber() {
231        return rev;
232    }
233
234    /** {@inheritDoc} */
235    public int hashCode() {
236        return Arrays.hashCode(new int[] { major, minor, point, (int) (rev >>> 32), (int) (rev & 0xFFFFL) });
237    }
238
239    /**
240     * Returns the string representation of the version. E.g:
241     *
242     * <pre>
243     * version : 2.8.0.1022
244     * </pre>
245     *
246     * @return The string representation of the version.
247     */
248    public String toString() {
249        final StringBuilder builder = new StringBuilder();
250        builder.append(major);
251        builder.append('.');
252        builder.append(minor);
253        builder.append('.');
254        builder.append(point);
255        builder.append('.');
256        builder.append(rev);
257        return builder.toString();
258    }
259}