From f5b65813b6dff5b5c786704c49b5a4dc6c2c9036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Fri, 26 Jul 2024 11:16:35 +0200 Subject: [PATCH] [SYNCOPE-1662] Introducing JSON support for MariaDB --- .github/workflows/mariadb.yml | 28 ++ core/persistence-jpa-json/pom.xml | 116 +++++++- .../jpa/MaJPAJSONPersistenceContext.java | 100 +++++++ .../jpa/dao/AbstractJPAJSONAnyDAO.java | 89 +++--- .../persistence/jpa/dao/MaJPAJSONAnyDAO.java | 80 ++++++ .../jpa/dao/MaJPAJSONAnySearchDAO.java | 138 +++++++++ .../jpa/dao/MaJPAJSONAuditConfDAO.java | 62 +++++ .../jpa/dao/MaJPAJSONPlainSchemaDAO.java | 46 +++ .../persistence/jpa/dao/MyJPAJSONAnyDAO.java | 9 +- .../persistence/jpa/dao/PGJPAJSONAnyDAO.java | 9 +- .../jpa/entity/MaJPAJSONEntityFactory.java | 30 ++ .../resources/META-INF/spring-orm-majson.xml | 137 +++++++++ .../main/resources/META-INF/spring.factories | 1 + .../src/main/resources/audit/audit_majson.sql | 24 ++ .../src/main/resources/core-majson.properties | 33 +++ .../src/main/resources/majson/indexes.xml | 63 +++++ .../src/main/resources/majson/views.xml | 262 ++++++++++++++++++ .../jpa/JPAJSONTestContextCustomizer.java | 7 + .../resources/core-majson-test.properties | 30 ++ .../persistence/jpa/dao/AbstractAnyDAO.java | 7 +- .../persistence/jpa/dao/JPAAnySearchDAO.java | 22 +- .../core/persistence/jpa/inner/UserTest.java | 4 +- .../src/main/resources/core-majson.properties | 33 +++ .../docker-compose/docker-compose-majson.yml | 83 ++++++ .../docker-compose/docker-compose-mariadb.yml | 1 + fit/core-reference/pom.xml | 117 ++++++++ .../src/main/resources/core-majson.properties | 35 +++ .../reference-guide/configuration/dbms.adoc | 67 +++++ src/site/xdoc/building.xml | 2 +- 29 files changed, 1558 insertions(+), 77 deletions(-) create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java create mode 100644 core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java create mode 100644 core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml create mode 100644 core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql create mode 100644 core/persistence-jpa-json/src/main/resources/core-majson.properties create mode 100644 core/persistence-jpa-json/src/main/resources/majson/indexes.xml create mode 100644 core/persistence-jpa-json/src/main/resources/majson/views.xml create mode 100644 core/persistence-jpa-json/src/test/resources/core-majson-test.properties create mode 100644 docker/core/src/main/resources/core-majson.properties create mode 100644 docker/src/main/resources/docker-compose/docker-compose-majson.yml create mode 100644 fit/core-reference/src/main/resources/core-majson.properties diff --git a/.github/workflows/mariadb.yml b/.github/workflows/mariadb.yml index 2a1066eb66..536b5b7291 100644 --- a/.github/workflows/mariadb.yml +++ b/.github/workflows/mariadb.yml @@ -51,3 +51,31 @@ jobs: run: mvn -U -T 1C -P 'skipTests,all' - name: 'Integration Tests: MariaDB' run: mvn -f fit/core-reference/pom.xml -P mariadb-it -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true + + majson: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 11 + - name: Setup Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 + - uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build + run: mvn -U -T 1C -P 'skipTests,all' + - name: 'Unit Tests: MariaDB JPA JSON' + run: mvn -f core/persistence-jpa-json/pom.xml -P majson -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.phase=none -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true + - name: 'Integration Tests: MariaDB JPA JSON' + run: mvn -f fit/core-reference/pom.xml -P majson-it -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true diff --git a/core/persistence-jpa-json/pom.xml b/core/persistence-jpa-json/pom.xml index c06b335993..50c0ee5981 100644 --- a/core/persistence-jpa-json/pom.xml +++ b/core/persistence-jpa-json/pom.xml @@ -165,7 +165,7 @@ under the License. sql - + ojson true @@ -422,6 +422,120 @@ under the License. + + majson + + + + org.mariadb.jdbc + mariadb-java-client + ${jdbc.mariadb.version} + test + + + + + clean verify + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + ${basedir}/../persistence-jpa/src/test/java + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*Test.java + + multitenancy,plainAttrTable + + ${project.activeProfiles[0].id} + classpath:core-majson.properties,classpath:core-majson-test.properties + ${docker.container.mariadb.ip} + file:${bundles.directory}/ + + + + + + io.fabric8 + docker-maven-plugin + + + + mariadb + mariadb:${docker.mariadb.version} + + + password + syncope + syncope + syncope + + + /var/lib/mysql:rw + + + MariaDB init process done. Ready for start up. + + + + + + + + + start-mariadb + pre-integration-test + + start + + + + stop-mariadb + post-integration-test + + stop + remove + + + + + + + + + src/test/resources + true + + + + + ojson diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java new file mode 100644 index 0000000000..a35d2a289f --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa; + +import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +import org.apache.syncope.core.persistence.api.dao.AnySearchDAO; +import org.apache.syncope.core.persistence.api.dao.AuditConfDAO; +import org.apache.syncope.core.persistence.api.dao.DynRealmDAO; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.JPAJSONAnyDAO; +import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.RealmDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnyDAO; +import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnySearchDAO; +import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAuditConfDAO; +import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONPlainSchemaDAO; +import org.apache.syncope.core.persistence.jpa.entity.MaJPAJSONEntityFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; + +@ConditionalOnExpression("#{'${provisioning.quartz.sql}' matches '.*mariadb.*'}") +public class MaJPAJSONPersistenceContext extends JPAJSONPersistenceContext { + + @ConditionalOnMissingBean(name = "maJPAJSONEntityFactory") + @Bean + public EntityFactory entityFactory() { + return new MaJPAJSONEntityFactory(); + } + + @ConditionalOnMissingBean(name = "maJPAJSONAnyDAO") + @Bean + public JPAJSONAnyDAO anyDAO(final @Lazy PlainSchemaDAO plainSchemaDAO) { + return new MaJPAJSONAnyDAO(plainSchemaDAO); + } + + @ConditionalOnMissingBean(name = "maJPAJSONAnySearchDAO") + @Bean + public AnySearchDAO anySearchDAO( + final @Lazy RealmDAO realmDAO, + final @Lazy DynRealmDAO dynRealmDAO, + final @Lazy UserDAO userDAO, + final @Lazy GroupDAO groupDAO, + final @Lazy AnyObjectDAO anyObjectDAO, + final @Lazy PlainSchemaDAO schemaDAO, + final @Lazy EntityFactory entityFactory, + final AnyUtilsFactory anyUtilsFactory, + final PlainAttrValidationManager validator) { + + return new MaJPAJSONAnySearchDAO( + realmDAO, + dynRealmDAO, + userDAO, + groupDAO, + anyObjectDAO, + schemaDAO, + entityFactory, + anyUtilsFactory, + validator); + } + + @ConditionalOnMissingBean(name = "maJPAJSONAuditConfDAO") + @Bean + public AuditConfDAO auditConfDAO() { + return new MaJPAJSONAuditConfDAO(); + } + + @ConditionalOnMissingBean(name = "maJPAJSONPlainSchemaDAO") + @Bean + public PlainSchemaDAO plainSchemaDAO( + final AnyUtilsFactory anyUtilsFactory, + final @Lazy PlainAttrDAO plainAttrDAO, + final @Lazy ExternalResourceDAO resourceDAO) { + + return new MaJPAJSONPlainSchemaDAO(anyUtilsFactory, plainAttrDAO, resourceDAO); + } +} diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java index 1101148d7c..c06f89ca11 100644 --- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java @@ -53,6 +53,29 @@ abstract class AbstractJPAJSONAnyDAO extends AbstractDAO implements JPAJSONAnyDAO { + /** + * Split an attribute value recurring on provided literals/tokens. + * + * @param attrValue value to be split + * @param literals literals/tokens + * @return split value + */ + private static List split(final String attrValue, final List literals) { + List attrValues = new ArrayList<>(); + + if (literals.isEmpty()) { + attrValues.add(attrValue); + } else { + for (String token : attrValue.split(Pattern.quote(literals.get(0)))) { + if (!token.isEmpty()) { + attrValues.addAll(split(token, literals.subList(1, literals.size()))); + } + } + } + + return attrValues; + } + protected final PlainSchemaDAO plainSchemaDAO; protected AbstractJPAJSONAnyDAO(final PlainSchemaDAO plainSchemaDAO) { @@ -129,6 +152,20 @@ protected > List buildResult(final AnyUtils anyUtils, final return result; } + protected String plainAttrQuery( + final String table, + final AnyUtils anyUtils, + final PlainSchema schema, + final PlainAttrValue attrValue, + final boolean ignoreCaseMatch, + final List queryParams) { + + queryParams.add(schema.getKey()); + queryParams.add(getAttrValue(schema, attrValue, ignoreCaseMatch)); + + return queryBegin(table) + "WHERE " + attrValueMatch(anyUtils, schema, attrValue, ignoreCaseMatch); + } + @SuppressWarnings("unchecked") @Transactional(readOnly = true) @Override @@ -144,11 +181,12 @@ public > List findByPlainAttrValue( return List.of(); } + List queryParams = new ArrayList<>(); Query query = entityManager().createNativeQuery( - queryBegin(table) - + "WHERE " + attrValueMatch(anyUtils, schema, attrValue, ignoreCaseMatch)); - query.setParameter(1, schema.getKey()); - query.setParameter(2, getAttrValue(schema, attrValue, ignoreCaseMatch)); + plainAttrQuery(table, anyUtils, schema, attrValue, ignoreCaseMatch, queryParams)); + for (int i = 0; i < queryParams.size(); i++) { + query.setParameter(i + 1, queryParams.get(i)); + } return buildResult(anyUtils, query.getResultList()); } @@ -177,31 +215,8 @@ public > Optional findByPlainAttrUniqueValue( : Optional.of(result.get(0)); } - /** - * Split an attribute value recurring on provided literals/tokens. - * - * @param attrValue value to be split - * @param literals literals/tokens - * @return split value - */ - protected List split(final String attrValue, final List literals) { - List attrValues = new ArrayList<>(); - - if (literals.isEmpty()) { - attrValues.add(attrValue); - } else { - for (String token : attrValue.split(Pattern.quote(literals.get(0)))) { - if (!token.isEmpty()) { - attrValues.addAll(split(token, literals.subList(1, literals.size()))); - } - } - } - - return attrValues; - } - @SuppressWarnings("unchecked") - protected List findByDerAttrValue( + private List findByDerAttrValue( final String table, final Map> clauses) { @@ -302,26 +317,18 @@ public > List findByDerAttrValue( // clear builder bld.delete(0, bld.length()); - PlainAttrValue attrValue; - if (schema.isUniqueConstraint()) { - attrValue = anyUtils.newPlainAttrUniqueValue(); - } else { - attrValue = anyUtils.newPlainAttrValue(); - } + PlainAttrValue attrValue = schema.isUniqueConstraint() + ? anyUtils.newPlainAttrUniqueValue() + : anyUtils.newPlainAttrValue(); attrValue.setStringValue(attrValues.get(i)); + List queryParams = new ArrayList<>(); bld.append('('). - append(queryBegin(table)). - append("WHERE "). - append(attrValueMatch(anyUtils, schema, attrValue, ignoreCaseMatch)). + append(plainAttrQuery(table, anyUtils, schema, attrValue, ignoreCaseMatch, queryParams)). append(')'); used.add(identifiers.get(i)); - List queryParams = new ArrayList<>(); - queryParams.add(schema.getKey()); - queryParams.add(getAttrValue(schema, attrValue, ignoreCaseMatch)); - clauses.put(bld.toString(), queryParams); } } diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java new file mode 100644 index 0000000000..72a5466869 --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; + +public class MaJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO { + + public MaJPAJSONAnyDAO(final PlainSchemaDAO plainSchemaDAO) { + super(plainSchemaDAO); + } + + @Override + protected String queryBegin(final String table) { + throw new UnsupportedOperationException("This method shall never be called"); + } + + @Override + protected String attrValueMatch( + final AnyUtils anyUtils, + final PlainSchema schema, + final PlainAttrValue attrValue, + final boolean ignoreCaseMatch) { + + throw new UnsupportedOperationException("This method shall never be called"); + } + + @Override + protected String plainAttrQuery( + final String table, + final AnyUtils anyUtils, + final PlainSchema schema, + final PlainAttrValue attrValue, + final boolean ignoreCaseMatch, + final List queryParams) { + + queryParams.add(schema.getKey()); + queryParams.add(attrValue.getStringValue()); + queryParams.add(attrValue.getBooleanValue()); + queryParams.add(Optional.ofNullable(attrValue.getDateValue()). + map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).orElse(null)); + queryParams.add(attrValue.getLongValue()); + queryParams.add(attrValue.getDoubleValue()); + + SearchViewSupport svs = new SearchViewSupport(anyUtils.anyTypeKind()); + return "SELECT DISTINCT any_id FROM " + + (schema.isUniqueConstraint() ? svs.uniqueAttr().name : svs.attr().name) + + " WHERE schema_id = ? AND ((stringValue IS NOT NULL" + + " AND " + + (ignoreCaseMatch ? "LOWER(" : "BINARY ") + "stringValue" + (ignoreCaseMatch ? ")" : "") + + " = " + + (ignoreCaseMatch ? "LOWER(" : "") + "?" + (ignoreCaseMatch ? ")" : "") + ')' + + " OR (booleanValue IS NOT NULL AND booleanValue = ?)" + + " OR (dateValue IS NOT NULL AND dateValue = ?)" + + " OR (longValue IS NOT NULL AND longValue = ?)" + + " OR (doubleValue IS NOT NULL AND doubleValue = ?))"; + } +} diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java new file mode 100644 index 0000000000..8ec7e90070 --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +import org.apache.syncope.core.persistence.api.dao.DynRealmDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.RealmDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.AnyCond; +import org.apache.syncope.core.persistence.api.dao.search.AttrCond; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue; +import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; + +public class MaJPAJSONAnySearchDAO extends JPAAnySearchDAO { + + public MaJPAJSONAnySearchDAO( + final RealmDAO realmDAO, + final DynRealmDAO dynRealmDAO, + final UserDAO userDAO, + final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, + final PlainSchemaDAO schemaDAO, + final EntityFactory entityFactory, + final AnyUtilsFactory anyUtilsFactory, + final PlainAttrValidationManager validator) { + + super( + realmDAO, + dynRealmDAO, + userDAO, + groupDAO, + anyObjectDAO, + schemaDAO, + entityFactory, + anyUtilsFactory, + validator); + } + + @Override + protected String getQuery( + final AttrCond cond, + final boolean not, + final List parameters, + final SearchSupport svs) { + + Pair checked = check(cond, svs.anyTypeKind); + + // normalize NULL / NOT NULL checks + if (not) { + if (cond.getType() == AttrCond.Type.ISNULL) { + cond.setType(AttrCond.Type.ISNOTNULL); + } else if (cond.getType() == AttrCond.Type.ISNOTNULL) { + cond.setType(AttrCond.Type.ISNULL); + } + } + + StringBuilder query = + new StringBuilder("SELECT DISTINCT any_id FROM ").append(svs.field().name).append(" WHERE "); + switch (cond.getType()) { + case ISNOTNULL: + query.append("JSON_SEARCH(plainAttrs, 'one', '"). + append(checked.getLeft().getKey()). + append("', NULL, '$[*].schema') IS NOT NULL"); + break; + + case ISNULL: + query.append("JSON_SEARCH(plainAttrs, 'one', '"). + append(checked.getLeft().getKey()). + append("', NULL, '$[*].schema') IS NULL"); + break; + + default: + if (!not && cond.getType() == AttrCond.Type.EQ) { + PlainAttr container = anyUtilsFactory.getInstance(svs.anyTypeKind).newPlainAttr(); + container.setSchema(checked.getLeft()); + if (checked.getRight() instanceof PlainAttrUniqueValue) { + container.setUniqueValue((PlainAttrUniqueValue) checked.getRight()); + } else { + ((JSONPlainAttr) container).add(checked.getRight()); + } + + query.append("JSON_CONTAINS(plainAttrs, '"). + append(POJOHelper.serialize(List.of(container)).replace("'", "''")). + append("')"); + } else { + query = new StringBuilder("SELECT DISTINCT any_id FROM "); + if (not && !(cond instanceof AnyCond) && checked.getLeft().isMultivalue()) { + query.append(svs.field().name).append(" WHERE "); + } else { + query.append((checked.getLeft().isUniqueConstraint() + ? svs.asSearchViewSupport().uniqueAttr().name + : svs.asSearchViewSupport().attr().name)). + append(" WHERE schema_id='").append(checked.getLeft().getKey()); + } + + Optional.ofNullable(checked.getRight().getDateValue()). + map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format). + ifPresent(formatted -> { + checked.getRight().setDateValue(null); + checked.getRight().setStringValue(formatted); + }); + + fillAttrQuery(query, checked.getRight(), checked.getLeft(), cond, not, parameters, svs); + } + } + + return query.toString(); + } +} diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java new file mode 100644 index 0000000000..dbc1bd7423 --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; + +public class MaJPAJSONAuditConfDAO extends AbstractJPAJSONLoggerDAO { + + protected static class MaMessageCriteriaBuilder extends JSONMessageCriteriaBuilder { + + @Override + protected String doBuild(final List containers) { + if (entityKey != null) { + query.append(andIfNeeded()).append('('). + append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.before') LIKE '%"). + append(entityKey). + append("%' OR "). + append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.input') LIKE '%"). + append(entityKey). + append("%' OR "). + append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.output') LIKE '%"). + append(entityKey). + append("%')"); + } + + if (!containers.isEmpty()) { + query.append(andIfNeeded()).append('('). + append(containers.stream(). + map(container -> "JSON_CONTAINS(" + AUDIT_ENTRY_MESSAGE_COLUMN + ", '" + + POJOHelper.serialize(container).replace("'", "''") + + "')").collect(Collectors.joining(" OR "))). + append(')'); + } + + return query.toString(); + } + } + + @Override + protected MessageCriteriaBuilder messageCriteriaBuilder(final String entityKey) { + return new MaMessageCriteriaBuilder().entityKey(entityKey); + } +} diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java new file mode 100644 index 0000000000..0a1f5c157b --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.dao; + +import javax.persistence.Query; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO; +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; + +public class MaJPAJSONPlainSchemaDAO extends AbstractJPAJSONPlainSchemaDAO { + + public MaJPAJSONPlainSchemaDAO( + final AnyUtilsFactory anyUtilsFactory, + final PlainAttrDAO plainAttrDAO, + final ExternalResourceDAO resourceDAO) { + + super(anyUtilsFactory, plainAttrDAO, resourceDAO); + } + + @Override + public > boolean hasAttrs(final PlainSchema schema, final Class reference) { + Query query = entityManager().createNativeQuery( + "SELECT COUNT(id) FROM " + new SearchSupport(getAnyTypeKind(reference)).field().name + + " WHERE JSON_CONTAINS(plainAttrs, '[{\"schema\":\"" + schema.getKey() + "\"}]')"); + + return ((Number) query.getSingleResult()).intValue() > 0; + } +} diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java index 06ad3692c1..ebae9b2d35 100644 --- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java @@ -50,16 +50,11 @@ protected String attrValueMatch( Pair schemaInfo = schemaInfo(schema.getType(), ignoreCaseMatch); if (schemaInfo.getRight()) { return "plainSchema = ? " - + "AND " - + (schemaInfo.getRight() ? "LOWER(" : "") + + "AND LOWER(" + (schema.isUniqueConstraint() ? "attrUniqueValue ->> '$." + schemaInfo.getLeft() + '\'' : schemaInfo.getLeft()) - + (schemaInfo.getRight() ? ")" : "") - + " = " - + (schemaInfo.getRight() ? "LOWER(" : "") - + '?' - + (schemaInfo.getRight() ? ")" : ""); + + ") = LOWER(?)"; } else { PlainAttr container = anyUtils.newPlainAttr(); container.setSchema(schema); diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java index 2bc9ad4e2a..ecce397ad8 100644 --- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java @@ -52,15 +52,10 @@ protected String attrValueMatch( Pair schemaInfo = schemaInfo(schema.getType(), ignoreCaseMatch); if (schemaInfo.getRight()) { return "attrs ->> 'schema' = ? " - + "AND " - + (schemaInfo.getRight() ? "LOWER(" : "") + + "AND LOWER(" + (schema.isUniqueConstraint() ? "attrs -> 'uniqueValue'" : "attrValues") + " ->> '" + schemaInfo.getLeft() - + '\'' + (schemaInfo.getRight() ? ")" : "") - + " = " - + (schemaInfo.getRight() ? "LOWER(" : "") - + '?' - + (schemaInfo.getRight() ? ")" : ""); + + "') = LOWER(?)"; } PlainAttr container = anyUtils.newPlainAttr(); diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java new file mode 100644 index 0000000000..c509826cf3 --- /dev/null +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.persistence.jpa.entity; + +import org.apache.syncope.core.persistence.api.dao.AnySearchDAO; +import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnySearchDAO; + +public class MaJPAJSONEntityFactory extends JPAJSONEntityFactory { + + @Override + public Class anySearchDAOClass() { + return MaJPAJSONAnySearchDAO.class; + } +} diff --git a/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml b/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml new file mode 100644 index 0000000000..ec73b4b348 --- /dev/null +++ b/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories b/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories index 3eea832444..1176397e32 100644 --- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories +++ b/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories @@ -18,4 +18,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.syncope.core.persistence.jpa.PGJPAJSONPersistenceContext,\ org.apache.syncope.core.persistence.jpa.MyJPAJSONPersistenceContext,\ + org.apache.syncope.core.persistence.jpa.MaJPAJSONPersistenceContext,\ org.apache.syncope.core.persistence.jpa.OJPAJSONPersistenceContext diff --git a/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql b/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql new file mode 100644 index 0000000000..13aae9f9a5 --- /dev/null +++ b/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql @@ -0,0 +1,24 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +CREATE TABLE IF NOT EXISTS AuditEntry ( + EVENT_DATE TIMESTAMP, + LOGGER_LEVEL VARCHAR(255) NOT NULL, + LOGGER VARCHAR(255) NOT NULL, + MESSAGE JSON NOT NULL, + THROWABLE TEXT +); diff --git a/core/persistence-jpa-json/src/main/resources/core-majson.properties b/core/persistence-jpa-json/src/main/resources/core-majson.properties new file mode 100644 index 0000000000..e29b704fe7 --- /dev/null +++ b/core/persistence-jpa-json/src/main/resources/core-majson.properties @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +persistence.indexesXML=classpath:majson/indexes.xml +persistence.viewsXML=classpath:majson/views.xml + +persistence.domain[0].key=Master +persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver +persistence.domain[0].jdbcURL=jdbc:mariadb://localhost:3306/syncope?characterEncoding=UTF-8 +persistence.domain[0].dbUsername=syncope +persistence.domain[0].dbPassword=syncope +persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3) +persistence.domain[0].orm=META-INF/spring-orm-majson.xml +persistence.domain[0].auditSql=audit_majson.sql +persistence.domain[0].poolMaxActive=10 +persistence.domain[0].poolMinIdle=2 + +provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +provisioning.quartz.sql=tables_mariadb.sql diff --git a/core/persistence-jpa-json/src/main/resources/majson/indexes.xml b/core/persistence-jpa-json/src/main/resources/majson/indexes.xml new file mode 100644 index 0000000000..8bc611e0ae --- /dev/null +++ b/core/persistence-jpa-json/src/main/resources/majson/indexes.xml @@ -0,0 +1,63 @@ + + + + + Additional indexes (in respect to JPA's) + + CREATE INDEX Realm_parent_id ON Realm(parent_id) + + CREATE INDEX SyncopeUser_realm_id ON SyncopeUser(realm_id) + CREATE UNIQUE INDEX SyncopeUser_username ON SyncopeUser(username) + + CREATE INDEX SyncopeGroup_realm_id ON SyncopeGroup(realm_id) + CREATE UNIQUE INDEX SyncopeGroup_name ON SyncopeGroup(name) + + CREATE INDEX AnyObject_realm_id ON AnyObject(realm_id) + CREATE UNIQUE INDEX AnyObject_name ON AnyObject(type_id,name) + + CREATE INDEX UDynGroupMembers_any_id ON UDynGroupMembers(any_id) + CREATE INDEX UDynGroupMembers_group_id ON UDynGroupMembers(group_id) + CREATE INDEX ADynGroupMembers_any_id ON ADynGroupMembers(any_id) + CREATE INDEX ADynGroupMembers_group_id ON ADynGroupMembers(group_id) + + CREATE INDEX DynRoleMembers_any_id ON DynRoleMembers(any_id) + CREATE INDEX DynRoleMembers_role_id ON DynRoleMembers(role_id) + + CREATE INDEX DynRealmMembers_any_id ON DynRealmMembers(any_id) + CREATE INDEX DynRealmMembers_dynRealm_id ON DynRealmMembers(dynRealm_id) + + CREATE INDEX UMembership_GroupIndex ON UMembership(group_id) + CREATE INDEX UMembership_UserIndex ON UMembership(user_id) + CREATE INDEX AMembership_GroupIndex ON AMembership(group_id) + CREATE INDEX AMembership_AnyObjectIndex ON AMembership(anyObject_id) + + CREATE INDEX URelationship_RightIndex ON URelationship(anyObject_id) + CREATE INDEX URelationship_LeftIndex ON URelationship(user_id) + CREATE INDEX ARelationship_RightIndex ON ARelationship(right_anyObject_id) + CREATE INDEX ARelationship_AnyObjectIndex ON ARelationship(left_anyObject_id) + + CREATE INDEX Task_executedIndex ON NotificationTask(executed) + CREATE INDEX TaskExec1_TaskIdIndex ON PropagationTaskExec(task_id) + CREATE INDEX TaskExec2_TaskIdIndex ON PullTaskExec(task_id) + CREATE INDEX TaskExec3_TaskIdIndex ON PushTaskExec(task_id) + CREATE INDEX TaskExec4_TaskIdIndex ON NotificationTaskExec(task_id) + CREATE INDEX TaskExec5_TaskIdIndex ON SchedTaskExec(task_id) + CREATE INDEX ATPullTask_PullTaskIndex ON AnyTemplatePullTask(pullTask_id) + diff --git a/core/persistence-jpa-json/src/main/resources/majson/views.xml b/core/persistence-jpa-json/src/main/resources/majson/views.xml new file mode 100644 index 0000000000..ca89fe82ae --- /dev/null +++ b/core/persistence-jpa-json/src/main/resources/majson/views.xml @@ -0,0 +1,262 @@ + + + + + + + CREATE TABLE UDynGroupMembers( + any_id CHAR(36), + group_id CHAR(36), + UNIQUE(any_id, group_id)) + + + CREATE TABLE ADynGroupMembers( + anyType_id VARCHAR(255), + any_id CHAR(36), + group_id CHAR(36), + UNIQUE(anyType_id, any_id, group_id)) + + + CREATE TABLE DynRoleMembers( + any_id CHAR(36), + role_id VARCHAR(255), + UNIQUE(any_id, role_id)) + + + CREATE TABLE DynRealmMembers( + any_id CHAR(36), + dynRealm_id VARCHAR(255), + UNIQUE(any_id, dynRealm_id)) + + + + + CREATE VIEW user_search AS + + SELECT u.id as any_id, u.* FROM SyncopeUser u + + + CREATE VIEW user_search_unique_attr AS + + SELECT u.id as any_id, attrs.* + FROM SyncopeUser u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.uniqueValue' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW user_search_attr AS + + SELECT u.id as any_id, attrs.* + FROM SyncopeUser u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.values[*]' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW user_search_urelationship AS + + SELECT m.user_id AS any_id, m.anyObject_id AS right_any_id, m.type_id AS type + FROM URelationship m + + + CREATE VIEW user_search_umembership AS + + SELECT m.user_id AS any_id, g.id AS group_id, g.name AS group_name + FROM UMembership m, SyncopeGroup g + WHERE m.group_id = g.id + + + CREATE VIEW user_search_role AS + + SELECT ss.user_id AS any_id, ss.role_id AS role_id + FROM SyncopeUser_SyncopeRole ss + + + CREATE VIEW user_search_priv AS + + SELECT ss.user_id AS any_id, sp.privilege_id AS privilege_id + FROM SyncopeUser_SyncopeRole ss, SyncopeRole_Privilege sp + WHERE ss.role_id = sp.role_id + + + CREATE VIEW user_search_dynpriv AS + + SELECT any_id, privilege_id + FROM DynRoleMembers drm, SyncopeRole_Privilege rp + WHERE drm.role_id = rp.role_id + + + CREATE VIEW user_search_auxClass AS + + SELECT st.user_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM SyncopeUser_AnyTypeClass st + + + CREATE VIEW user_search_resource AS + + SELECT st.user_id AS any_id, st.resource_id AS resource_id + FROM SyncopeUser_ExternalResource st + + + CREATE VIEW user_search_group_res AS + + SELECT m.user_id AS any_id, st.resource_id AS resource_id + FROM UMembership m, SyncopeGroup r, SyncopeGroup_ExternalResource st + WHERE m.group_id = r.id AND st.group_id = r.id + + + + + CREATE VIEW anyObject_search AS + + SELECT a.id as any_id, a.* FROM AnyObject a + + + CREATE VIEW anyObject_search_unique_attr AS + + SELECT u.id as any_id, attrs.* + FROM AnyObject u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.uniqueValue' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW anyObject_search_attr AS + + SELECT u.id as any_id, attrs.* + FROM AnyObject u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.values[*]' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW anyObject_search_arelationship AS + + SELECT m.left_anyObject_id AS any_id, m.right_anyObject_id AS right_any_id, m.type_id AS type + FROM ARelationship m + + + CREATE VIEW anyObject_search_amembership AS + + SELECT m.anyObject_id AS any_id, g.id AS group_id, g.name AS group_name + FROM AMembership m, SyncopeGroup g + WHERE m.group_id = g.id + + + CREATE VIEW anyObject_search_auxClass AS + + SELECT st.anyObject_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM AnyObject_AnyTypeClass st + + + CREATE VIEW anyObject_search_resource AS + + SELECT st.anyObject_id AS any_id, st.resource_id AS resource_id + FROM AnyObject_ExternalResource st + + + CREATE VIEW anyObject_search_group_res AS + + SELECT m.anyObject_id AS any_id, st.resource_id AS resource_id + FROM AMembership m, SyncopeGroup r, SyncopeGroup_ExternalResource st + WHERE m.group_id = r.id AND st.group_id = r.id + + + + + CREATE VIEW group_search AS + + SELECT r.id as any_id, r.* FROM SyncopeGroup r + + + CREATE VIEW group_search_unique_attr AS + + SELECT u.id as any_id, attrs.* + FROM SyncopeGroup u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.uniqueValue' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW group_search_attr AS + + SELECT u.id as any_id, attrs.* + FROM SyncopeGroup u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), '$[*]' COLUMNS ( + schema_id VARCHAR(255) PATH '$.schema', + NESTED PATH '$.values[*]' COLUMNS ( + booleanvalue INT PATH '$.booleanValue', + datevalue VARCHAR(32) PATH '$.dateValue', + doublevalue DOUBLE PATH '$.doubleValue', + longvalue BIGINT(20) PATH '$.longValue', + stringvalue VARCHAR(255) PATH '$.stringValue')) + ) AS attrs ON 1=1 + WHERE schema_id IS NOT NULL + AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL) + + + CREATE VIEW group_search_auxClass AS + + SELECT st.group_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM SyncopeGroup_AnyTypeClass st + + + CREATE VIEW group_search_resource AS + + SELECT st.group_id AS any_id, st.resource_id AS resource_id + FROM SyncopeGroup_ExternalResource st + + + diff --git a/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java b/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java index 8a73334730..798400fcdd 100644 --- a/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java +++ b/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java @@ -53,6 +53,12 @@ public void customizeContext(final ConfigurableApplicationContext ctx, final Mer "provisioning.quartz.sql=tables_mysql_innodb.sql"); break; + case "majson": + TestPropertySourceUtils.addInlinedPropertiesToEnvironment( + ctx, + "provisioning.quartz.sql=tables_mariadb.sql"); + break; + case "ojson": TestPropertySourceUtils.addInlinedPropertiesToEnvironment( ctx, @@ -65,6 +71,7 @@ public void customizeContext(final ConfigurableApplicationContext ctx, final Mer AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(getBeanDefinitionRegistry(ctx)); reader.registerBean(PGJPAJSONPersistenceContext.class, "PGJPAJSONPersistenceContext"); reader.registerBean(MyJPAJSONPersistenceContext.class, "MyJPAJSONPersistenceContext"); + reader.registerBean(MaJPAJSONPersistenceContext.class, "MaJPAJSONPersistenceContext"); reader.registerBean(OJPAJSONPersistenceContext.class, "OJPAJSONPersistenceContext"); } } diff --git a/core/persistence-jpa-json/src/test/resources/core-majson-test.properties b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties new file mode 100644 index 0000000000..9f235bece1 --- /dev/null +++ b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +security.adminUser=${adminUser} +security.anonymousUser=${anonymousUser} +security.jwsKey=${jwsKey} +security.secretKey=${secretKey} + +persistence.domain[0].jdbcURL=jdbc:mariadb://${DB_CONTAINER_IP}:3306/syncope?characterEncoding=UTF-8 +persistence.domain[0].poolMaxActive=10 +persistence.domain[0].poolMinIdle=2 +# keep the next two lines until https://jira.mariadb.org/browse/MDEV-27898 is fixed +persistence.domain[0].dbUsername=root +persistence.domain[0].dbPassword=password + +provisioning.connIdLocation=${syncope.connid.location} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java index 2cea7b3837..3362317b5a 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java @@ -208,11 +208,8 @@ public List findByPlainAttrValue( query.setParameter("schemaKey", schema.getKey()); query.setParameter("stringValue", attrValue.getStringValue()); query.setParameter("booleanValue", attrValue.getBooleanValue()); - if (attrValue.getDateValue() == null) { - query.setParameter("dateValue", null); - } else { - query.setParameter("dateValue", attrValue.getDateValue().toInstant()); - } + query.setParameter("dateValue", Optional.ofNullable(attrValue.getDateValue()). + map(OffsetDateTime::toInstant).orElse(null)); query.setParameter("longValue", attrValue.getLongValue()); query.setParameter("doubleValue", attrValue.getDoubleValue()); diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java index 8fc9582573..55f9c2ef50 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java @@ -948,13 +948,11 @@ protected void fillAttrQuery( && !(cond instanceof AnyCond) && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != AttrCond.Type.ISNOTNULL) { - query.append("any_id NOT IN (SELECT DISTINCT any_id FROM "); - if (schema.isUniqueConstraint()) { - query.append(svs.asSearchViewSupport().uniqueAttr().name); - } else { - query.append(svs.asSearchViewSupport().attr().name); - } - query.append(" WHERE schema_id='").append(schema.getKey()); + query.append("any_id NOT IN (SELECT DISTINCT any_id FROM "). + append((schema.isUniqueConstraint() + ? svs.asSearchViewSupport().uniqueAttr().name + : svs.asSearchViewSupport().attr().name)). + append(" WHERE schema_id='").append(schema.getKey()); fillAttrQuery(query, attrValue, schema, cond, false, parameters, svs); query.append(')'); } else { @@ -1104,12 +1102,10 @@ protected String getQuery( if (not && !(cond instanceof AnyCond) && checked.getLeft().isMultivalue()) { query.append(svs.field().name).append(" WHERE "); } else { - if (checked.getLeft().isUniqueConstraint()) { - query.append(svs.asSearchViewSupport().uniqueAttr().name); - } else { - query.append(svs.asSearchViewSupport().attr().name); - } - query.append(" WHERE schema_id='").append(checked.getLeft().getKey()); + query.append((checked.getLeft().isUniqueConstraint() + ? svs.asSearchViewSupport().uniqueAttr().name + : svs.asSearchViewSupport().attr().name)). + append(" WHERE schema_id='").append(checked.getLeft().getKey()); } fillAttrQuery(query, checked.getRight(), checked.getLeft(), cond, not, parameters, svs); } diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java index 21ca8564b0..9fc2e5e759 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java @@ -85,8 +85,8 @@ public void find() { assertNull(user.getSecurityQuestion()); assertNull(user.getSecurityAnswer()); assertEquals("admin", user.getCreator()); - assertEquals("Giacomo", user.getPlainAttr("firstname").get().getValuesAsStrings().get(0)); - assertEquals("Puccini", user.getPlainAttr("surname").get().getValuesAsStrings().get(0)); + assertEquals("Giacomo", user.getPlainAttr("firstname").orElseThrow().getValuesAsStrings().get(0)); + assertEquals("Puccini", user.getPlainAttr("surname").orElseThrow().getValuesAsStrings().get(0)); } @Test diff --git a/docker/core/src/main/resources/core-majson.properties b/docker/core/src/main/resources/core-majson.properties new file mode 100644 index 0000000000..dd203b6253 --- /dev/null +++ b/docker/core/src/main/resources/core-majson.properties @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +persistence.indexesXML=classpath:majson/indexes.xml +persistence.viewsXML=classpath:majson/views.xml + +persistence.domain[0].key=Master +persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver +persistence.domain[0].jdbcURL=${DB_URL} +persistence.domain[0].dbUsername=${DB_USER} +persistence.domain[0].dbPassword=${DB_PASSWORD} +persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3) +persistence.domain[0].orm=META-INF/spring-orm-majson.xml +persistence.domain[0].auditSql=audit_majson.sql +persistence.domain[0].poolMaxActive=${DB_POOL_MAX} +persistence.domain[0].poolMinIdle=${DB_POOL_MIN} + +provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +provisioning.quartz.sql=tables_mariadb.sql diff --git a/docker/src/main/resources/docker-compose/docker-compose-majson.yml b/docker/src/main/resources/docker-compose/docker-compose-majson.yml new file mode 100644 index 0000000000..9331524fb0 --- /dev/null +++ b/docker/src/main/resources/docker-compose/docker-compose-majson.yml @@ -0,0 +1,83 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Full deployment (Core, Console, Enduser) on MariaDB with JSON_TABLE support + +services: + db: + image: mariadb:11 + restart: always + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: syncope + MYSQL_USER: syncope + MYSQL_PASSWORD: syncope + + syncope: + depends_on: + - db + command: ["wait-for-it", "db:3306", "-t", "60", "--", "/opt/syncope/bin/startup.sh"] + image: apache/syncope:${SYNCOPE_VERSION} + ports: + - "18080:8080" + restart: always + environment: + SPRING_PROFILES_ACTIVE: docker,majson,saml2 + DB_URL: jdbc:mariadb://db:3306/syncope?characterEncoding=UTF-8&relaxAutoCommit=true&useSSL=false + DB_USER: root + DB_PASSWORD: password + DB_POOL_MAX: 20 + DB_POOL_MIN: 5 + OPENJPA_REMOTE_COMMIT: sjvm + KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster + KEYMASTER_USERNAME: ${KEYMASTER_USERNAME} + KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD} + SERVICE_DISCOVERY_ADDRESS: http://syncope:8080/syncope/rest/ + ANONYMOUS_USER: ${ANONYMOUS_USER} + ANONYMOUS_KEY: ${ANONYMOUS_KEY} + + syncope-console: + depends_on: + - syncope + image: apache/syncope-console:${SYNCOPE_VERSION} + ports: + - "28080:8080" + restart: always + environment: + SPRING_PROFILES_ACTIVE: docker + KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster + KEYMASTER_USERNAME: ${KEYMASTER_USERNAME} + KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD} + SERVICE_DISCOVERY_ADDRESS: http://syncope-console:8080/syncope-console/ + ANONYMOUS_USER: ${ANONYMOUS_USER} + ANONYMOUS_KEY: ${ANONYMOUS_KEY} + + syncope-enduser: + depends_on: + - syncope + image: apache/syncope-enduser:${SYNCOPE_VERSION} + ports: + - "38080:8080" + restart: always + environment: + SPRING_PROFILES_ACTIVE: docker + KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster + KEYMASTER_USERNAME: ${KEYMASTER_USERNAME} + KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD} + SERVICE_DISCOVERY_ADDRESS: http://syncope-enduser:8080/syncope-enduser/ + ANONYMOUS_USER: ${ANONYMOUS_USER} + ANONYMOUS_KEY: ${ANONYMOUS_KEY} diff --git a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml index 15cb1c5651..17447baf60 100644 --- a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml +++ b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml @@ -30,6 +30,7 @@ services: syncope: depends_on: - db + command: ["wait-for-it", "db:3306", "-t", "60", "--", "/opt/syncope/bin/startup.sh"] image: apache/syncope:${SYNCOPE_VERSION} ports: - "18080:8080" diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml index f9d70266df..5ca79aae3a 100644 --- a/fit/core-reference/pom.xml +++ b/fit/core-reference/pom.xml @@ -1176,7 +1176,124 @@ under the License. + + + majson-it + + + org.mariadb.jdbc + mariadb-java-client + + embedded,majson + + + + + org.apache.syncope.core + syncope-core-persistence-jpa-json + ${project.version} + + + org.mariadb.jdbc + mariadb-java-client + ${jdbc.mariadb.version} + test + + + + + clean verify + + + + org.apache.maven.plugins + maven-war-plugin + + WEB-INF/classes/domains/Two* + + + + + org.apache.maven.plugins + maven-failsafe-plugin + true + + + **/org/apache/syncope/fit/core/*ITCase.java + + + + + + io.fabric8 + docker-maven-plugin + + + + mariadb + mariadb:${docker.mariadb.version} + + + password + syncope + syncope + syncope + + + /var/lib/mysql:rw + + + MariaDB init process done. Ready for start up. + + + + + + + + + start-mariadb + pre-integration-test + + start + + + + stop-mariadb + post-integration-test + + stop + remove + + + + + + + org.codehaus.cargo + cargo-maven3-plugin + true + + + + ${docker.container.mariadb.ip} + + + + + + + + + ${basedir}/../../core/persistence-jpa-json/src/test/resources/domains + ${project.build.outputDirectory}/domains + true + + + + + mariadb-it diff --git a/fit/core-reference/src/main/resources/core-majson.properties b/fit/core-reference/src/main/resources/core-majson.properties new file mode 100644 index 0000000000..62efd56e14 --- /dev/null +++ b/fit/core-reference/src/main/resources/core-majson.properties @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +persistence.indexesXML=classpath:majson/indexes.xml +persistence.viewsXML=classpath:majson/views.xml + +persistence.domain[0].key=Master +persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver +persistence.domain[0].jdbcURL=jdbc:mariadb://${DB_CONTAINER_IP}:3306/syncope?characterEncoding=UTF-8 +# keep the next two lines until https://jira.mariadb.org/browse/MDEV-27898 is fixed +persistence.domain[0].dbUsername=root +persistence.domain[0].dbPassword=password +persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3) +persistence.domain[0].orm=META-INF/spring-orm-majson.xml +persistence.domain[0].auditSql=audit_majson.sql +persistence.domain[0].poolMaxActive=10 +persistence.domain[0].poolMinIdle=2 + +provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +provisioning.quartz.sql=tables_mariadb.sql + diff --git a/src/main/asciidoc/reference-guide/configuration/dbms.adoc b/src/main/asciidoc/reference-guide/configuration/dbms.adoc index f2b803e099..9817fa17e9 100644 --- a/src/main/asciidoc/reference-guide/configuration/dbms.adoc +++ b/src/main/asciidoc/reference-guide/configuration/dbms.adoc @@ -248,6 +248,73 @@ https://mariadb.com/kb/en/configuring-mariadb-with-option-files/[option file^]. This assumes that you have a MariaDB instance running on localhost, listening on its default port 3306 with a database `syncope` fully accessible by user `syncope` with password `syncope`. +==== MariaDB (JSON) + +[NOTE] +With the configurations reported below, Apache Syncope will leverage the +https://mariadb.com/kb/en/json_table/^] function. + +[NOTE] +Apache Syncope {docVersion} is verified with MariaDB server >= {mariadb} and JDBC driver >= {mariadbJDBC}. + +Add the following dependency to `core/pom.xml`: + +[source,xml,subs="verbatim,attributes"] +---- + + org.apache.syncope.core + syncope-core-persistence-jpa-json + ${syncope.version} + +---- + +Create + +[source] +.... +persistence.indexesXML=classpath:majson/indexes.xml +persistence.viewsXML=classpath:majson/views.xml + +persistence.domain[0].key=Master +persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver +persistence.domain[0].jdbcURL=jdbc:mariadb://localhost:3306/syncope?characterEncoding=UTF-8 +persistence.domain[0].dbUsername=root +persistence.domain[0].dbPassword=password +persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3) +persistence.domain[0].orm=META-INF/spring-orm-majson.xml +persistence.domain[0].auditSql=audit_majson.sql +persistence.domain[0].poolMaxActive=10 +persistence.domain[0].poolMinIdle=2 + +provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +provisioning.quartz.sql=tables_mariadb.sql +.... + +as `core/src/main/resources/core-majson.properties`. + +Do not forget to include `majson` as +https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/features.html#features.profiles.adding-active-profiles[Spring Boot profile^] +for the Core application. + +[CAUTION] +It is important to set the collation to `utf8_general_ci` after creation of `syncope` database. + +[WARNING] +==== +It is necessary to use `utf8mb4_unicode_ci` instead of `utf8mb4_general_ci` if case-sensitive queries are required. +In this case, set +.... +init_connect = "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci" +.... +under either the `[mysqld]` section or the `[mariadb]` section of your +https://mariadb.com/kb/en/configuring-mariadb-with-option-files/[option file^]. +==== + +[WARNING] +This assumes that you have a MariaDB instance running on localhost, listening on its default port 3306 with a database +`syncope` and super-admin user `root` with password `password`. +Super-admin user is required until https://jira.mariadb.org/browse/MDEV-27898[this bug^] is fixed. + ==== Oracle Database [NOTE] diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml index b4b8386ca7..a98276d2e4 100644 --- a/src/site/xdoc/building.xml +++ b/src/site/xdoc/building.xml @@ -171,7 +171,7 @@ under the License.

This build profile requires Docker to work.

Perform the full test suite against a real MariaDB database via - $ mvn -Pmariadb-it + $ mvn -Pmariadb-it or $ mvn -Pmajson-it (for JSON support)
Oracle database