Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/configure
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ configure.start "$1"
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
. .github/github-tools/base.configure --tag "$GITHUB_REF"
else
set -x
. .github/github-tools/base.configure "$GH_PR_TITLE" "$GH_PR_NUMBER"
set +x
fi

configure.complete
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
runs-on: ubuntu-24.04

steps:
- name: Force DNS resolution for Nexus
run: echo "162.19.239.148 nexus-jx.apps.serv.run" | sudo tee -a /etc/hosts

- uses: actions/checkout@v5

- name: Set up JDK 17
Expand Down Expand Up @@ -150,6 +153,8 @@ jobs:
needs: [build, test-and-scan]

steps:
- name: Force DNS resolution for Nexus
run: echo "162.19.239.148 nexus-jx.apps.serv.run" | sudo tee -a /etc/hosts
- uses: actions/checkout@v5

- name: Restore the build output
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.agiletec.aps.system.common.dao;

import java.sql.SQLException;

public final class DuplicateKeyDetector {

private DuplicateKeyDetector() {
// utility class
}

public static boolean isDuplicateKey(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
if (current instanceof SQLException && isDuplicateKey((SQLException) current)) {

Check warning on line 27 in engine/src/main/java/com/agiletec/aps/system/common/dao/DuplicateKeyDetector.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof SQLException sqlexception'

See more on https://sonarcloud.io/project/issues?id=entando_app-engine&issues=AaAZVCq-XV8hH96q165E&open=AaAZVCq-XV8hH96q165E&pullRequest=369
return true;
}
current = current.getCause();
}
return false;
}

public static boolean isDuplicateKey(SQLException exception) {
if (exception == null) {
return false;
}
String sqlState = exception.getSQLState();
int errorCode = exception.getErrorCode();

return "23505".equals(sqlState)
|| errorCode == 1062
|| errorCode == 1586
|| errorCode == 1
|| errorCode == 2601
|| errorCode == 2627;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import com.agiletec.aps.system.common.AbstractSearcherDAO;
import com.agiletec.aps.system.common.FieldSearchFilter;
import com.agiletec.aps.system.common.dao.DuplicateKeyDetector;
import com.agiletec.aps.system.services.group.Group;
import com.agiletec.aps.system.services.role.Role;

Expand Down Expand Up @@ -340,24 +341,7 @@ private void updateIatAndAuthorizations(String username, Long iat, List<Authoriz
}

private boolean isDuplicateKey(SQLException e) {
final String sqlState = e.getSQLState();

if (StringUtils.isNotBlank(sqlState)) {
// 23505: unique_violation (PostgreSQL, Derby)
// 23000: integrity constraint violation (MySQL, Oracle - need to check error code)
if ("23505".equals(sqlState)) {
return true;
}
if ("23000".equals(sqlState)) {
int errorCode = e.getErrorCode();
// MySQL: 1062 (ER_DUP_ENTRY), 1586 (ER_DUP_ENTRY_WITH_KEY_NAME)
// Oracle: 1 (ORA-00001: unique constraint violated)
if (errorCode == 1062 || errorCode == 1586 || errorCode == 1) {
return true;
}
}
}
return false;
return DuplicateKeyDetector.isDuplicateKey(e);
}

private void deleteAuthorities(final Connection conn, final String username, final List<Authorization> list) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.agiletec.aps.system.services.group;

public class DuplicateGroupException extends RuntimeException {

public DuplicateGroupException(String message) {
super(message);
}

public DuplicateGroupException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.agiletec.aps.system.common.AbstractSearcherDAO;
import com.agiletec.aps.system.common.FieldSearchFilter;
import com.agiletec.aps.system.common.dao.DuplicateKeyDetector;
import org.entando.entando.ent.util.EntLogging.EntLogger;
import org.entando.entando.ent.util.EntLogging.EntLogFactory;

Expand Down Expand Up @@ -94,7 +95,12 @@ public void addGroup(Group group) {
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
logger.error("Error while adding a group", t);
if (DuplicateKeyDetector.isDuplicateKey(t)) {
logger.debug("Group '{}' already exists; treating duplicate key as a recoverable race",
group.getName());
throw new DuplicateGroupException("Group already exists: " + group.getName(), t);
}
logger.error("Error while adding a group", t);
throw new RuntimeException("Error while adding a group", t);
} finally {
closeDaoResources(null, stat, conn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ public void addGroup(Group group) throws EntException {
group.setDescr(LabelSanitizer.stripMarkup(group.getDescr()));
this.getGroupDAO().addGroup(group);
this.getCacheWrapper().addGroup(group);
} catch (DuplicateGroupException e) {
logger.debug("Group '{}' already present, skipping insert", group.getName());
throw new EntException("Group already exists: " + group.getName(), e);
} catch (Throwable t) {
logger.error("Error detected while adding a group", t);
throw new EntException("Error detected while adding a group", t);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.agiletec.aps.system.services.role;

public class DuplicateRoleException extends RuntimeException {

public DuplicateRoleException(String message) {
super(message);
}

public DuplicateRoleException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import org.entando.entando.ent.exception.EntException;
import com.agiletec.aps.system.common.AbstractDAO;
import com.agiletec.aps.system.common.dao.DuplicateKeyDetector;

/**
* Data Access Object per gli oggetti ruolo (Role).
Expand Down Expand Up @@ -164,6 +165,11 @@ public void addRole(Role role) {
conn.commit();
} catch (Throwable t) {
this.executeRollback(conn);
if (DuplicateKeyDetector.isDuplicateKey(t)) {
_logger.debug("Role '{}' already exists; treating duplicate key as a recoverable race",
role.getName());
throw new DuplicateRoleException("Role already exists: " + role.getName(), t);
}
_logger.error("Error while adding a role", t);
throw new RuntimeException("Error while adding a role", t);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ public void addRole(Role role) throws EntException {
try {
this.getRoleDAO().addRole(role);
this.getRoleCacheWrapper().addRole(role);
} catch (DuplicateRoleException e) {
logger.debug("Role '{}' already present, skipping insert", role.getName());
throw new EntException("Role already exists: " + role.getName(), e);
} catch (Throwable t) {
logger.error("Error while adding a role", t);
throw new EntException("Error while adding a role", t);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.agiletec.aps.system.common.dao;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.sql.SQLException;
import org.junit.jupiter.api.Test;

class DuplicateKeyDetectorTest {

@Test
void shouldDetectPostgreSqlDuplicateKey() {
SQLException ex = new SQLException("duplicate key value violates unique constraint", "23505");
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectDerbyDuplicateKey() {
SQLException ex = new SQLException("duplicate key value in unique index", "23505");
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectMySqlDuplicateKey() {
SQLException ex = new SQLException("Duplicate entry", "23000", 1062);
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectMySqlDuplicateKeyWithKeyName() {
SQLException ex = new SQLException("Duplicate entry with key name", "23000", 1586);
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectOracleDuplicateKey() {
SQLException ex = new SQLException("ORA-00001: unique constraint violated", "23000", 1);
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectSqlServerDuplicateKey2627() {
SQLException ex = new SQLException("Violation of PRIMARY KEY constraint", "23000", 2627);
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldDetectSqlServerDuplicateKey2601() {
SQLException ex = new SQLException("Cannot insert duplicate key row", "23000", 2601);
assertTrue(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldNotTreatPostgreSqlForeignKeyViolationAsDuplicateKey() {
SQLException ex = new SQLException("foreign key violation", "23503");
assertFalse(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldNotTreatNotNullViolationAsDuplicateKey() {
SQLException ex = new SQLException("null value in column violates not-null constraint", "23502");
assertFalse(DuplicateKeyDetector.isDuplicateKey(ex));
}

@Test
void shouldInspectNestedCauses() {
SQLException sql = new SQLException("duplicate key", "23505");
RuntimeException wrapped = new RuntimeException("DAO failure", sql);
assertTrue(DuplicateKeyDetector.isDuplicateKey(wrapped));
}

@Test
void shouldReturnFalseForNullOrNonSqlException() {
assertFalse(DuplicateKeyDetector.isDuplicateKey((Throwable) null));
assertFalse(DuplicateKeyDetector.isDuplicateKey((SQLException) null));
assertFalse(DuplicateKeyDetector.isDuplicateKey(new RuntimeException("general error")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ void testAddDeleteGroup() throws Throwable {
}
}

@Test
void testAddDuplicateGroupThrowsDuplicateGroupException() throws Throwable {
String groupCode = "grp_dup_test";
Group group = new Group();
group.setName(groupCode);
group.setDescription("descr_gruppo_duplicate");
try {
groupManager.addGroup(group);
EntException ex = org.junit.jupiter.api.Assertions.assertThrows(EntException.class, () -> {
groupManager.addGroup(group);
});
assertTrue(ex.getCause() instanceof DuplicateGroupException);
} finally {
groupManager.removeGroup(group);
}
}

@Test
void testUpdateGroup() throws Throwable {
int initSize = groupManager.getGroups().size();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.agiletec.aps.BaseTestCase;
import com.agiletec.aps.system.SystemConstants;
import org.entando.entando.ent.exception.EntException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -144,5 +145,21 @@ public void testGetRolesWithPemission() throws Throwable {
assertEquals("supervisor", role.getName());
}
}

@Test
void testAddDuplicateRoleThrowsDuplicateRoleException() throws Throwable {
Role role = new Role();
role.setName("temp_dup_role");
role.setDescription("descr_dup_role");
try {
roleManager.addRole(role);
EntException ex = org.junit.jupiter.api.Assertions.assertThrows(EntException.class, () -> {
roleManager.addRole(role);
});
assertTrue(ex.getCause() instanceof DuplicateRoleException);
} finally {
roleManager.removeRole(role);
}
}

}
Loading
Loading