Skip to content

Commit

Permalink
Driver to return actual major and minor version
Browse files Browse the repository at this point in the history
UcanaccessDriver did not yet implement the methods
java.sql.Driver#getMajorVersion and #getMinorVersion.
These methods will now return the current driver version
as found in the project's Maven POM.
Further, hard-coded version information and assumptions were
reviewed and updated throughout the codebase.
  • Loading branch information
spannm committed Oct 29, 2024
1 parent b2087e9 commit c4279bf
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.ucanaccess.exception;

import net.ucanaccess.jdbc.IUcanaccessErrorCodes;
import net.ucanaccess.util.VersionInfo;
import org.hsqldb.error.ErrorCode;

import java.sql.SQLException;
Expand Down Expand Up @@ -75,11 +76,13 @@ String addVersionInfo(String _message) {
if (_message != null && _message.startsWith(MSG_PREFIX)) {
return _message;
}
return MSG_PREFIX

String ver = VersionInfo.find(getClass()).getVersion();
return (MSG_PREFIX
+ "::"
+ Optional.ofNullable(getClass().getPackage().getImplementationVersion()).orElse("5.1.0")
+ Optional.ofNullable(ver).orElse("x.y.z")
+ " "
+ (_message == null || _message.isBlank() ? "(n/a)" : _message.trim());
+ (_message == null || _message.isBlank() ? "(n/a)" : _message)).trim();
}

@Override
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/net/ucanaccess/jdbc/UcanaccessDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import net.ucanaccess.exception.UcanaccessSQLException;
import net.ucanaccess.type.ColumnOrder;
import net.ucanaccess.util.Try;
import net.ucanaccess.util.VersionInfo;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -252,12 +253,12 @@ private Integer validateLobScale(String _property) {

@Override
public int getMajorVersion() {
return 0;
return VersionInfo.find(getClass()).getMajorVersion();
}

@Override
public int getMinorVersion() {
return 0;
return VersionInfo.find(getClass()).getMinorVersion();
}

@Override
Expand Down
155 changes: 155 additions & 0 deletions src/main/java/net/ucanaccess/util/VersionInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package net.ucanaccess.util;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
* The purpose of this class is to find the project's version information by all means possible,
* <ul>
* <li>while developing the project in an IDE</li>
* <li>while running Maven lifecycle goals or</li>
* <li>while executing or referencing the project at run-time, whether packaged or not.</li>
* </ul>.
*
* First this class attempts to read version information from key {@code Attributes.Name.IMPLEMENTATION_VERSION}
* in the Java manifest by using the Java api.
* <p>
* Should this operation fail, it looks for a {@code MANIFEST.MF} in the file system in order to
* read version information from the first file found.
* <p>
* In case this also fails, it looks for the project's Maven POM file to extraxct version information from the xml structure.
*
* @author Markus Spann
* @since v5.1.2
*/
public final class VersionInfo {

/** Cash of version information by class. */
private static final Map<Class<?>, VersionInfo> CACHE = new ConcurrentHashMap<>();

private final Logger logger;

private String version;
private int majorVersion;
private int minorVersion;

public static VersionInfo find(Class<?> _class) {
return CACHE.computeIfAbsent(_class, VersionInfo::new);
}

private VersionInfo(Class<?> _class) {
logger = System.getLogger(getClass().getName());
version = Objects.requireNonNullElse(_class, getClass()).getPackage().getImplementationVersion();
if (version != null) {
logger.log(Level.DEBUG, "Found version ''{0}'' in package", version);
} else {
version = readFromManifest();
if (version == null) {
version = readFromMavenPom();
}
}

if (version != null) {
Matcher matcher = Pattern.compile("^([0-9]+)\\.([0-9]+).*").matcher(version);
if (matcher.matches()) {
majorVersion = Integer.valueOf(matcher.group(1));
minorVersion = Integer.valueOf(matcher.group(2));
} else {
logger.log(Level.WARNING, "Unparsable implementation version: {0}", version);
}
}
}

String readFromManifest() {
String filename = "MANIFEST.MF";
Path currDir = Paths.get("").toAbsolutePath();
logger.log(Level.DEBUG, "Searching manifest under current directory ''{0}''", currDir);
try (Stream<Path> walk = Files.walk(currDir)) {
Path manifest = walk.filter(p -> p.getFileName().toString().equals(filename)).findFirst().orElse(null);
if (manifest == null) {
logger.log(Level.DEBUG, "Could not find ''{0}'' in path ''{1}''", filename, currDir);
} else {
try (InputStream is = Files.newInputStream(manifest, StandardOpenOption.READ)) {
String ver = new Manifest(is).getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
logger.log(Level.DEBUG, "Found version ''{0}'' in manifest", ver);
return ver;
}
}
} catch (IOException _ex) {
logger.log(Level.WARNING, "Failed to find version info in manifest: {0}", _ex.toString());
}
return null;
}

String readFromMavenPom() {
String filename = "pom.xml";
Path currDir = Paths.get("").toAbsolutePath();
logger.log(Level.DEBUG, "Searching maven pom under current directory ''{0}''", currDir);
try (Stream<Path> walk = Files.walk(currDir)) {
Path pom = walk.filter(p -> p.getFileName().toString().equals(filename)).findFirst().orElse(null);
if (pom == null) {
logger.log(Level.WARNING, "Failed to find ''{0}'' in path ''{1}''", filename, currDir);
} else {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pom.toFile());

Element docElem = document.getDocumentElement();
docElem.normalize();

NodeList nodeList = docElem.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element && "version".equals(node.getNodeName())) {
String ver = node.getTextContent();
logger.log(Level.DEBUG, "Found version ''{0}'' in maven pom", ver);
return ver;
}
}
}
} catch (IOException | ParserConfigurationException | SAXException _ex) {
logger.log(Level.WARNING, "Failed to find version info in maven pom: {0}", _ex.toString());
}
return null;
}

public String getVersion() {
return version;
}

public int getMajorVersion() {
return majorVersion;
}

public int getMinorVersion() {
return minorVersion;
}

@Override
public String toString() {
return String.format("%s[version=%s, major=%d, minor=%d]",
getClass().getSimpleName(), version, majorVersion, minorVersion);
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package net.ucanaccess.exception;

import static org.assertj.core.api.Assertions.assertThat;

import net.ucanaccess.test.AbstractBaseTest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class UcanaccessSQLExceptionTest extends AbstractBaseTest {
class UcanaccessSQLExceptionTest extends AbstractBaseTest {

@ParameterizedTest(name = "[{index}] {0} => {1}")
@CsvSource(delimiter = ';', value = {
"UCAExc:; UCAExc:",
"bogus; UCAExc:::5.1.0 bogus",
"''; UCAExc:::5.1.0 (n/a)",
"; UCAExc:::5.1.0 (n/a)"
@CsvSource(delimiter = '|', value = {
"UCAExc:| UCAExc:",
"bogus| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? bogus",
"''| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? \\(n\\/a\\)",
"| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? \\(n\\/a\\)"
})
void testAddVersionInfo(String _message, CharSequence _expectedResult) {
assertEquals(_expectedResult, new UcanaccessSQLException().addVersionInfo(_message));
void testAddVersionInfo(String _message, CharSequence _expectedPattern) {
assertThat(new UcanaccessSQLException().addVersionInfo(_message))
.matches(_expectedPattern);
}

}
8 changes: 8 additions & 0 deletions src/test/java/net/ucanaccess/jdbc/UcanaccessDriverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ void testNormalizeProperties() {
.containsEntry("withoutVal2", null);
}

@Test
void testVersion() {
UcanaccessDriver driver = new UcanaccessDriver();

assertThat(driver.getMajorVersion()).isGreaterThanOrEqualTo(5);
assertThat(driver.getMinorVersion()).isGreaterThanOrEqualTo(1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void testExecute(AccessVersion _accessVersion) throws Exception {
"DELETE FROM t_noroman")) {
assertThatThrownBy(() -> st.executeQuery(sql))
.isInstanceOf(UcanaccessSQLException.class)
.hasMessageMatching("UCAExc:::[0-9\\.]+ General error");
.hasMessageMatching("UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? General error");
st.execute(sql);
}
}
Expand Down

0 comments on commit c4279bf

Please sign in to comment.