This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch 3_0_X in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push: new 78e28a7ddc [SYNCOPE-1662] Introducing JSON support for MariaDB (#797) 78e28a7ddc is described below commit 78e28a7ddcc4126352363f2d02a1e39d6d6cabaa Author: Francesco Chicchiriccò <ilgro...@users.noreply.github.com> AuthorDate: Fri Jul 26 13:09:45 2024 +0200 [SYNCOPE-1662] Introducing JSON support for MariaDB (#797) --- .github/workflows/mariadb.yml | 28 +++ core/persistence-jpa-json/pom.xml | 116 ++++++++- .../jpa/MaJPAJSONPersistenceContext.java | 100 ++++++++ .../persistence/jpa/dao/AbstractJPAJSONAnyDAO.java | 89 +++---- .../core/persistence/jpa/dao/MaJPAJSONAnyDAO.java | 80 +++++++ .../persistence/jpa/dao/MaJPAJSONAnySearchDAO.java | 138 +++++++++++ .../persistence/jpa/dao/MaJPAJSONAuditConfDAO.java | 62 +++++ .../jpa/dao/MaJPAJSONPlainSchemaDAO.java | 46 ++++ .../core/persistence/jpa/dao/MyJPAJSONAnyDAO.java | 9 +- .../core/persistence/jpa/dao/PGJPAJSONAnyDAO.java | 9 +- .../jpa/entity/MaJPAJSONEntityFactory.java | 30 +++ .../main/resources/META-INF/spring-orm-majson.xml | 137 +++++++++++ .../src/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} | 17 +- .../core/persistence/jpa/dao/AbstractAnyDAO.java | 7 +- .../core/persistence/jpa/dao/JPAAnySearchDAO.java | 22 +- .../core/persistence/jpa/inner/UserTest.java | 4 +- .../core/src/main/resources/core-majson.properties | 20 +- ...mpose-mariadb.yml => docker-compose-majson.yml} | 9 +- .../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, 1446 insertions(+), 89 deletions(-) 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. <!-- possible values: sql | schema --> <action>sql</action> - <!-- possible values: pgjsonb | myjson | ojson --> + <!-- possible values: pgjsonb | myjson | majson | ojson --> <orm>ojson</orm> <skipTests>true</skipTests> @@ -422,6 +422,120 @@ under the License. </build> </profile> + <profile> + <id>majson</id> + + <dependencies> + <dependency> + <groupId>org.mariadb.jdbc</groupId> + <artifactId>mariadb-java-client</artifactId> + <version>${jdbc.mariadb.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <defaultGoal>clean verify</defaultGoal> + + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>add-test-source</id> + <phase>generate-test-sources</phase> + <goals> + <goal>add-test-source</goal> + </goals> + <configuration> + <sources> + <source>${basedir}/../persistence-jpa/src/test/java</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <configuration> + <includes> + <include>**/*Test.java</include> + </includes> + <excludedGroups>multitenancy,plainAttrTable</excludedGroups> + <systemPropertyVariables> + <profileId>${project.activeProfiles[0].id}</profileId> + <CORE_PROPERTIES>classpath:core-majson.properties,classpath:core-majson-test.properties</CORE_PROPERTIES> + <DB_CONTAINER_IP>${docker.container.mariadb.ip}</DB_CONTAINER_IP> + <syncope.connid.location>file:${bundles.directory}/</syncope.connid.location> + </systemPropertyVariables> + </configuration> + </plugin> + + <plugin> + <groupId>io.fabric8</groupId> + <artifactId>docker-maven-plugin</artifactId> + <configuration> + <images> + <image> + <alias>mariadb</alias> + <name>mariadb:${docker.mariadb.version}</name> + <run> + <env> + <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD> + <MYSQL_DATABASE>syncope</MYSQL_DATABASE> + <MYSQL_USER>syncope</MYSQL_USER> + <MYSQL_PASSWORD>syncope</MYSQL_PASSWORD> + </env> + <tmpfs> + <mount>/var/lib/mysql:rw</mount> + </tmpfs> + <wait> + <log>MariaDB init process done. Ready for start up.</log> + <time>30000</time> + </wait> + </run> + </image> + </images> + </configuration> + <executions> + <execution> + <id>start-mariadb</id> + <phase>pre-integration-test</phase> + <goals> + <goal>start</goal> + </goals> + </execution> + <execution> + <id>stop-mariadb</id> + <phase>post-integration-test</phase> + <goals> + <goal>stop</goal> + <goal>remove</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + + <testResources> + <testResource> + <directory>src/test/resources</directory> + <filtering>true</filtering> + </testResource> + </testResources> + </build> + </profile> + <profile> <id>ojson</id> 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 @@ import org.springframework.transaction.annotation.Transactional; abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> 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<String> split(final String attrValue, final List<String> literals) { + List<String> 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 @@ abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> impleme return result; } + protected String plainAttrQuery( + final String table, + final AnyUtils anyUtils, + final PlainSchema schema, + final PlainAttrValue attrValue, + final boolean ignoreCaseMatch, + final List<Object> 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 @@ abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> impleme return List.of(); } + List<Object> 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 @@ abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> impleme : 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<String> split(final String attrValue, final List<String> literals) { - List<String> 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<Object> findByDerAttrValue( + private List<Object> findByDerAttrValue( final String table, final Map<String, List<Object>> clauses) { @@ -302,26 +317,18 @@ abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> impleme // 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<Object> 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<Object> 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<Object> 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<Object> parameters, + final SearchSupport svs) { + + Pair<PlainSchema, PlainAttrValue> 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<ObjectNode> 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 <T extends PlainAttr<?>> boolean hasAttrs(final PlainSchema schema, final Class<T> 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 @@ public class MyJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO { Pair<String, Boolean> 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 @@ public class PGJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO { Pair<String, Boolean> 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<? extends AnySearchDAO> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm + http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" + version="2.0"> + + <persistence-unit-metadata> + <persistence-unit-defaults> + <entity-listeners> + <entity-listener class="org.apache.syncope.core.persistence.jpa.validation.entity.EntityValidationListener"> + <pre-persist method-name="validate"/> + <pre-update method-name="validate"/> + </entity-listener> + </entity-listeners> + </persistence-unit-defaults> + </persistence-unit-metadata> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject"> + <attributes> + <basic name="plainAttrs"> + <column column-definition="json"/> + <lob/> + </basic> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"> + <attributes> + <basic name="plainAttrs"> + <column column-definition="json"/> + <lob/> + </basic> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser"> + <attributes> + <basic name="plainAttrs"> + <column column-definition="json"/> + <lob/> + </basic> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONLinkedAccount"> + <attributes> + <basic name="plainAttrs"> + <column column-definition="json"/> + <lob/> + </basic> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup"> + <attributes> + <many-to-one name="userOwner" target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser"/> + <many-to-one name="groupOwner" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension"> + <attributes> + <many-to-one name="group" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership"> + <attributes> + <many-to-one name="leftEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser"> + <join-column name="user_id"/> + </many-to-one> + <many-to-one name="rightEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"> + <join-column name="group_id"/> + </many-to-one> + </attributes> + </entity> + <entity class="org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership"> + <attributes> + <one-to-one name="group" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/> + </attributes> + </entity> + <entity class="org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship"> + <attributes> + <many-to-one name="leftEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser"> + <join-column name="user_id"/> + </many-to-one> + <many-to-one name="rightEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject"> + <join-column name="anyObject_id"/> + </many-to-one> + </attributes> + </entity> + + <entity class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership"> + <attributes> + <many-to-one name="leftEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject"> + <join-column name="anyObject_id"/> + </many-to-one> + <many-to-one name="rightEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"> + <join-column name="group_id"/> + </many-to-one> + </attributes> + </entity> + <entity class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership"> + <attributes> + <one-to-one name="group" target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/> + </attributes> + </entity> + <entity class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship"> + <attributes> + <many-to-one name="leftEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject"> + <join-column name="left_anyObject_id"/> + </many-to-one> + <many-to-one name="rightEnd" target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject"> + <join-column name="right_anyObject_id"/> + </many-to-one> + </attributes> + </entity> +</entity-mappings> 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- +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. +--> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> + <comment>Additional indexes (in respect to JPA's)</comment> + + <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON Realm(parent_id)</entry> + + <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON SyncopeUser(realm_id)</entry> + <entry key="SyncopeUser_username">CREATE UNIQUE INDEX SyncopeUser_username ON SyncopeUser(username)</entry> + + <entry key="SyncopeGroup_realm_id">CREATE INDEX SyncopeGroup_realm_id ON SyncopeGroup(realm_id)</entry> + <entry key="SyncopeGroup_name">CREATE UNIQUE INDEX SyncopeGroup_name ON SyncopeGroup(name)</entry> + + <entry key="AnyObject_realm_id">CREATE INDEX AnyObject_realm_id ON AnyObject(realm_id)</entry> + <entry key="AnyObject_name">CREATE UNIQUE INDEX AnyObject_name ON AnyObject(type_id,name)</entry> + + <entry key="UDynGroupMembers_any_id">CREATE INDEX UDynGroupMembers_any_id ON UDynGroupMembers(any_id)</entry> + <entry key="UDynGroupMembers_group_id">CREATE INDEX UDynGroupMembers_group_id ON UDynGroupMembers(group_id)</entry> + <entry key="ADynGroupMembers_any_id">CREATE INDEX ADynGroupMembers_any_id ON ADynGroupMembers(any_id)</entry> + <entry key="ADynGroupMembers_group_id">CREATE INDEX ADynGroupMembers_group_id ON ADynGroupMembers(group_id)</entry> + + <entry key="DynRoleMembers_any_id">CREATE INDEX DynRoleMembers_any_id ON DynRoleMembers(any_id)</entry> + <entry key="DynRoleMembers_role_id">CREATE INDEX DynRoleMembers_role_id ON DynRoleMembers(role_id)</entry> + + <entry key="DynRealmMembers_any_id">CREATE INDEX DynRealmMembers_any_id ON DynRealmMembers(any_id)</entry> + <entry key="DynRealmMembers_realm_id">CREATE INDEX DynRealmMembers_dynRealm_id ON DynRealmMembers(dynRealm_id)</entry> + + <entry key="UMembership_GroupIndex">CREATE INDEX UMembership_GroupIndex ON UMembership(group_id)</entry> + <entry key="UMembership_UserIndex">CREATE INDEX UMembership_UserIndex ON UMembership(user_id)</entry> + <entry key="AMembership_GroupIndex">CREATE INDEX AMembership_GroupIndex ON AMembership(group_id)</entry> + <entry key="AMembership_AnyObjectIndex">CREATE INDEX AMembership_AnyObjectIndex ON AMembership(anyObject_id)</entry> + + <entry key="URelationship_RightIndex">CREATE INDEX URelationship_RightIndex ON URelationship(anyObject_id)</entry> + <entry key="URelationship_LeftIndex">CREATE INDEX URelationship_LeftIndex ON URelationship(user_id)</entry> + <entry key="ARelationship_RightIndex">CREATE INDEX ARelationship_RightIndex ON ARelationship(right_anyObject_id)</entry> + <entry key="ARelationship_AnyObjectIndex">CREATE INDEX ARelationship_AnyObjectIndex ON ARelationship(left_anyObject_id)</entry> + + <entry key="Task_executedIndex">CREATE INDEX Task_executedIndex ON NotificationTask(executed)</entry> + <entry key="TaskExec1_TaskIdIndex">CREATE INDEX TaskExec1_TaskIdIndex ON PropagationTaskExec(task_id)</entry> + <entry key="TaskExec2_TaskIdIndex">CREATE INDEX TaskExec2_TaskIdIndex ON PullTaskExec(task_id)</entry> + <entry key="TaskExec3_TaskIdIndex">CREATE INDEX TaskExec3_TaskIdIndex ON PushTaskExec(task_id)</entry> + <entry key="TaskExec4_TaskIdIndex">CREATE INDEX TaskExec4_TaskIdIndex ON NotificationTaskExec(task_id)</entry> + <entry key="TaskExec5_TaskIdIndex">CREATE INDEX TaskExec5_TaskIdIndex ON SchedTaskExec(task_id)</entry> + <entry key="ATPullTask_PullTaskIndex">CREATE INDEX ATPullTask_PullTaskIndex ON AnyTemplatePullTask(pullTask_id)</entry> +</properties> 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- +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. +--> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> + + <entry key="UDynGroupMembers"> + CREATE TABLE UDynGroupMembers( + any_id CHAR(36), + group_id CHAR(36), + UNIQUE(any_id, group_id)) + </entry> + <entry key="ADynGroupMembers"> + CREATE TABLE ADynGroupMembers( + anyType_id VARCHAR(255), + any_id CHAR(36), + group_id CHAR(36), + UNIQUE(anyType_id, any_id, group_id)) + </entry> + <entry key="DynRoleMembers"> + CREATE TABLE DynRoleMembers( + any_id CHAR(36), + role_id VARCHAR(255), + UNIQUE(any_id, role_id)) + </entry> + <entry key="DynRealmMembers"> + CREATE TABLE DynRealmMembers( + any_id CHAR(36), + dynRealm_id VARCHAR(255), + UNIQUE(any_id, dynRealm_id)) + </entry> + + <!-- user --> + <entry key="user_search"> + CREATE VIEW user_search AS + + SELECT u.id as any_id, u.* FROM SyncopeUser u + </entry> + <entry key="user_search_unique_attr"> + 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) + </entry> + <entry key="user_search_attr"> + 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) + </entry> + <entry key="user_search_urelationship"> + 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 + </entry> + <entry key="user_search_umembership"> + 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 + </entry> + <entry key="user_search_role"> + CREATE VIEW user_search_role AS + + SELECT ss.user_id AS any_id, ss.role_id AS role_id + FROM SyncopeUser_SyncopeRole ss + </entry> + <entry key="user_search_priv"> + 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 + </entry> + <entry key="user_search_dynpriv"> + CREATE VIEW user_search_dynpriv AS + + SELECT any_id, privilege_id + FROM DynRoleMembers drm, SyncopeRole_Privilege rp + WHERE drm.role_id = rp.role_id + </entry> + <entry key="user_search_auxClass"> + CREATE VIEW user_search_auxClass AS + + SELECT st.user_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM SyncopeUser_AnyTypeClass st + </entry> + <entry key="user_search_resource"> + CREATE VIEW user_search_resource AS + + SELECT st.user_id AS any_id, st.resource_id AS resource_id + FROM SyncopeUser_ExternalResource st + </entry> + <entry key="user_search_group_res"> + 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 + </entry> + + <!-- anyObject --> + <entry key="anyObject_search"> + CREATE VIEW anyObject_search AS + + SELECT a.id as any_id, a.* FROM AnyObject a + </entry> + <entry key="anyObject_search_unique_attr"> + 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) + </entry> + <entry key="anyObject_search_attr"> + 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) + </entry> + <entry key="anyObject_search_arelationship"> + 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 + </entry> + <entry key="anyObject_search_amembership"> + 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 + </entry> + <entry key="anyObject_search_auxClass"> + CREATE VIEW anyObject_search_auxClass AS + + SELECT st.anyObject_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM AnyObject_AnyTypeClass st + </entry> + <entry key="anyObject_search_resource"> + CREATE VIEW anyObject_search_resource AS + + SELECT st.anyObject_id AS any_id, st.resource_id AS resource_id + FROM AnyObject_ExternalResource st + </entry> + <entry key="anyObject_search_group_res"> + 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 + </entry> + + <!-- group --> + <entry key="group_search"> + CREATE VIEW group_search AS + + SELECT r.id as any_id, r.* FROM SyncopeGroup r + </entry> + <entry key="group_search_unique_attr"> + 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) + </entry> + <entry key="group_search_attr"> + 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) + </entry> + <entry key="group_search_auxClass"> + CREATE VIEW group_search_auxClass AS + + SELECT st.group_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id + FROM SyncopeGroup_AnyTypeClass st + </entry> + <entry key="group_search_resource"> + CREATE VIEW group_search_resource AS + + SELECT st.group_id AS any_id, st.resource_id AS resource_id + FROM SyncopeGroup_ExternalResource st + </entry> + +</properties> 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 class JPAJSONTestContextCustomizer implements ContextCustomizer { "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 class JPAJSONTestContextCustomizer implements ContextCustomizer { 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/main/resources/META-INF/spring.factories b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties similarity index 59% copy from core/persistence-jpa-json/src/main/resources/META-INF/spring.factories copy to core/persistence-jpa-json/src/test/resources/core-majson-test.properties index 3eea832444..9f235bece1 100644 --- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories +++ b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties @@ -15,7 +15,16 @@ # specific language governing permissions and limitations # under the License. -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.OJPAJSONPersistenceContext +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 abstract class AbstractAnyDAO<A extends Any<?>> extends AbstractDAO<A> im 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 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO { && !(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 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO { 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 class UserTest extends AbstractTest { 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/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories b/docker/core/src/main/resources/core-majson.properties similarity index 50% copy from core/persistence-jpa-json/src/main/resources/META-INF/spring.factories copy to docker/core/src/main/resources/core-majson.properties index 3eea832444..dd203b6253 100644 --- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories +++ b/docker/core/src/main/resources/core-majson.properties @@ -15,7 +15,19 @@ # specific language governing permissions and limitations # under the License. -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.OJPAJSONPersistenceContext +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-mariadb.yml b/docker/src/main/resources/docker-compose/docker-compose-majson.yml similarity index 91% copy from docker/src/main/resources/docker-compose/docker-compose-mariadb.yml copy to docker/src/main/resources/docker-compose/docker-compose-majson.yml index 15cb1c5651..9331524fb0 100644 --- a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml +++ b/docker/src/main/resources/docker-compose/docker-compose-majson.yml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -# Full deployment (Core, Console, Enduser) on MariaDB +# Full deployment (Core, Console, Enduser) on MariaDB with JSON_TABLE support services: db: @@ -30,15 +30,16 @@ 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" restart: always environment: - SPRING_PROFILES_ACTIVE: docker,mariadb,saml2 + SPRING_PROFILES_ACTIVE: docker,majson,saml2 DB_URL: jdbc:mariadb://db:3306/syncope?characterEncoding=UTF-8&relaxAutoCommit=true&useSSL=false - DB_USER: syncope - DB_PASSWORD: syncope + DB_USER: root + DB_PASSWORD: password DB_POOL_MAX: 20 DB_POOL_MIN: 5 OPENJPA_REMOTE_COMMIT: sjvm 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. </plugins> </build> </profile> + + <profile> + <id>majson-it</id> + + <properties> + <jdbcdriver.groupId>org.mariadb.jdbc</jdbcdriver.groupId> + <jdbcdriver.artifactId>mariadb-java-client</jdbcdriver.artifactId> + + <spring.profiles.active>embedded,majson</spring.profiles.active> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.syncope.core</groupId> + <artifactId>syncope-core-persistence-jpa-json</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.mariadb.jdbc</groupId> + <artifactId>mariadb-java-client</artifactId> + <version>${jdbc.mariadb.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <defaultGoal>clean verify</defaultGoal> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <configuration> + <packagingExcludes>WEB-INF/classes/domains/Two*</packagingExcludes> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <inherited>true</inherited> + <configuration> + <includes> + <include>**/org/apache/syncope/fit/core/*ITCase.java</include> + </includes> + </configuration> + </plugin> + + <plugin> + <groupId>io.fabric8</groupId> + <artifactId>docker-maven-plugin</artifactId> + <configuration> + <images> + <image> + <alias>mariadb</alias> + <name>mariadb:${docker.mariadb.version}</name> + <run> + <env> + <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD> + <MYSQL_DATABASE>syncope</MYSQL_DATABASE> + <MYSQL_USER>syncope</MYSQL_USER> + <MYSQL_PASSWORD>syncope</MYSQL_PASSWORD> + </env> + <tmpfs> + <mount>/var/lib/mysql:rw</mount> + </tmpfs> + <wait> + <log>MariaDB init process done. Ready for start up.</log> + <time>30000</time> + </wait> + </run> + </image> + </images> + </configuration> + <executions> + <execution> + <id>start-mariadb</id> + <phase>pre-integration-test</phase> + <goals> + <goal>start</goal> + </goals> + </execution> + <execution> + <id>stop-mariadb</id> + <phase>post-integration-test</phase> + <goals> + <goal>stop</goal> + <goal>remove</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.codehaus.cargo</groupId> + <artifactId>cargo-maven3-plugin</artifactId> + <inherited>true</inherited> + <configuration> + <container> + <systemProperties> + <DB_CONTAINER_IP>${docker.container.mariadb.ip}</DB_CONTAINER_IP> + </systemProperties> + </container> + </configuration> + </plugin> + </plugins> + + <resources> + <resource> + <directory>${basedir}/../../core/persistence-jpa-json/src/test/resources/domains</directory> + <targetPath>${project.build.outputDirectory}/domains</targetPath> + <filtering>true</filtering> + </resource> + </resources> + </build> + </profile> + <profile> <id>mariadb-it</id> 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"] +---- +<dependency> + <groupId>org.apache.syncope.core</groupId> + <artifactId>syncope-core-persistence-jpa-json</artifactId> + <version>${syncope.version}</version> +</dependency> +---- + +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. <p>This build profile requires <a href="https://www.docker.com/">Docker</a> to work.</p> </div> Perform the full test suite against a real <a href="https://mariadb.org/">MariaDB</a> database via - <source>$ mvn -Pmariadb-it</source> + <source>$ mvn -Pmariadb-it</source> or <source>$ mvn -Pmajson-it</source> (for JSON support) <h5>Oracle database</h5> <div class="alert alert-warning">