diff --git a/.github/workflows/crosschecks.yml b/.github/workflows/crosschecks.yml new file mode 100644 index 00000000000..b211476044c --- /dev/null +++ b/.github/workflows/crosschecks.yml @@ -0,0 +1,60 @@ +# 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 +name: "Cross Checks" + +on: + push: + branches: [2_1_X] + pull_request: + # The branches below must be a subset of the branches above + branches: [2_1_X] + schedule: + - cron: '0 13 * * 4' + +jobs: + crosschecks: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + language: ['java'] + java: [ '8' ] + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java JDK + uses: actions/setup-java@v1.4.3 + with: + java-version: ${{ matrix.java }} + - name: Setup Maven + uses: stCarolas/setup-maven@v4 + with: + maven-version: 3.6.3 + - uses: actions/cache@v2.1.1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build + run: mvn -U -T 1C -P 'skipTests,all,without-eclipse' + - name: Validate + run: mvn -T 1C -P 'all,without-eclipse' checkstyle:check tools:verify-legal-files modernizer:modernizer apache-rat:check + - name: Test + run: mvn -T 1C -P 'all,without-eclipse' clean test '-Dinvoker.streamLogs=true' '-Dmodernizer.skip=true' '-Dianal.phase=none' '-Drat.skip=true' '-Dcheckstyle.skip=true' '-Dsass.skip=true' diff --git a/.github/workflows/fit_Elasticsearch.yml b/.github/workflows/fit_Elasticsearch.yml new file mode 100644 index 00000000000..6015b0d4247 --- /dev/null +++ b/.github/workflows/fit_Elasticsearch.yml @@ -0,0 +1,47 @@ +# 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 +name: "FIT Elasticsearch" + +on: + push: + branches: [2_1_X] + pull_request: + # The branches below must be a subset of the branches above + branches: [2_1_X] + schedule: + - cron: '0 13 * * 4' + +jobs: + fit_Elasticsearch: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java JDK + uses: actions/setup-java@v1.4.3 + with: + java-version: 8 + - uses: actions/cache@v2.1.1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build + run: mvn -U -T 1C -P 'skipTests,all,without-eclipse' + - name: 'Elasticsearch / H2 / JSON' + run: mvn -f fit/core-reference/pom.xml -P elasticsearch-it -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true diff --git a/.github/workflows/fit_Tomcat_H2_JSON.yml b/.github/workflows/fit_Tomcat_H2_JSON.yml new file mode 100644 index 00000000000..08a5e05663e --- /dev/null +++ b/.github/workflows/fit_Tomcat_H2_JSON.yml @@ -0,0 +1,51 @@ +# 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 +name: "FIT Tomcat H2 JSON" + +on: + push: + branches: [2_1_X] + pull_request: + # The branches below must be a subset of the branches above + branches: [2_1_X] + schedule: + - cron: '0 13 * * 4' + +jobs: + fit_Tomcat_H2_JSON: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java JDK + uses: actions/setup-java@v1.4.3 + with: + java-version: 8 + - name: Setup Maven + uses: stCarolas/setup-maven@v4 + with: + maven-version: 3.6.3 + - uses: actions/cache@v2.1.1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build + run: mvn -U -T 1C -P 'skipTests,all,without-eclipse' + - name: 'Tomcat / H2 / JSON' + run: mvn -f fit/core-reference/pom.xml verify -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true diff --git a/.github/workflows/jpajson.yml b/.github/workflows/jpajson.yml new file mode 100644 index 00000000000..48da9c466dd --- /dev/null +++ b/.github/workflows/jpajson.yml @@ -0,0 +1,49 @@ +# 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 +name: "JPA JSON" + +on: + push: + branches: [2_1_X] + pull_request: + # The branches below must be a subset of the branches above + branches: [2_1_X] + schedule: + - cron: '0 13 * * 4' + +jobs: + JPAJSON: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java JDK + uses: actions/setup-java@v1.4.3 + with: + java-version: 8 + - uses: actions/cache@v2.1.1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build + run: mvn -U -T 1C -P 'skipTests,all,without-eclipse' + - name: 'Unit Tests: PostgreSQL JPA JSON' + run: mvn -f core/persistence-jpa-json/pom.xml -P pgjsonb -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.phase=none -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true + - name: 'Unit Tests: MySQL JPA JSON' + run: mvn -f core/persistence-jpa-json/pom.xml -P myjson -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.phase=none -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true diff --git a/.gitignore b/.gitignore index e21571ffb16..d479649a1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/bin/ ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/lib/ ide/eclipse/bundles/org.apache.syncope.ide.eclipse.tests/bin/ ide/eclipse/bundles/org.apache.syncope.ide.eclipse.tests/screenshots/ -fit/core-reference/nb-configuration.xml +*nb-configuration.xml +node_modules diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6e008aa5eb2..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -# 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. - -sudo: false -addons: - apt: - packages: - - oracle-java8-installer -language: java -jdk: - - oraclejdk8 -before_install: - - echo 'MAVEN_OPTS="-Xms512m -Xmx1024m -XX:+TieredCompilation -XX:TieredStopAtLevel=1"' >~/.mavenrc -# default install is mvn install --quiet -DskipTests=true -install: travis_wait mvn --show-version --quiet -T 1C -P all,skipTests -before_script: - - cd fit/core-reference -script: - # invoker.streamLogs: we cannot access to log files through Travis web ui, so display everything in the console - - mvn --show-version verify -Dinvoker.streamLogs=true -after_failure: - - cat target/log/* - - cat target/failsafe-reports/org.apache.syncope.fit.*-output.txt -notifications: - email: - - dev@syncope.apache.org diff --git a/CHANGES b/CHANGES index 7babeeaef0e..ac54ebf8b26 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,387 @@ Apache Syncope - CHANGES Licensed under Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- +Release Notes - Syncope - Version 2.1.14 +================================================================================ + +** Bug + * [SYNCOPE-1731] - Performance issue with multiple any type classes + * [SYNCOPE-1734] - Elasticsearch not updated for uidOnCreate + * [SYNCOPE-1749] - Incorrect Dynamic Group Membership Condition save from Console + * [SYNCOPE-1750] - Password policy not enforced if password is not stored in Syncope + * [SYNCOPE-1755] - NullPointer exception during PULL delete operation in case of NO_MATCH + * [SYNCOPE-1756] - Add an id_token_hint parameter to the logout provider url for OIDC + +** Improvement + * [SYNCOPE-1720] - Switch persistence identifiers to UUID version 7 + +Release Notes - Syncope - Version 2.1.13 +================================================================================ + +** Bug + * [SYNCOPE-1693] - Must change password submit on console leads to errors + * [SYNCOPE-1704] - Policy update not affecting External Resources + * [SYNCOPE-1706] - Notification task not created with event category PROPAGATION + +** Improvement + * [SYNCOPE-1694] - Optimize creation of Implementation instances + * [SYNCOPE-1696] - Audit Elasticsearch persistence + * [SYNCOPE-1709] - Persist Jobs' current status in the database to support multi-node deployments + * [SYNCOPE-1713] - Support time-based conditions for Audit, Exec and Remediation queries + +Release Notes - Syncope - Version 2.1.12 +================================================================================ + +** Bug + * [SYNCOPE-1671] - Wrong JobDelegate column name in scheduled task table + * [SYNCOPE-1683] - Show connector overridden properties in resource wizard in tabular topology during create + * [SYNCOPE-1691] - Schema labels not used for attribute column headers + +** Improvement + * [SYNCOPE-1669] - Create pull results for remediations + * [SYNCOPE-1670] - Support Graceful shutdown + * [SYNCOPE-1674] - Optimize User, Group and Any Object lifecycle events management + +Release Notes - Syncope - Version 2.1.11 +================================================================================ + +** Bug + * [SYNCOPE-1646] - Linked Account status set to wrong value on propagation + * [SYNCOPE-1648] - Search with PostgreSQL JSONB fails for FIQL like 'username!=value' + * [SYNCOPE-1649] - Reports: XML character escaping applied to CSV output + * [SYNCOPE-1650] - Default Account Rule: pattern is ignored + * [SYNCOPE-1651] - Invalid users can be specified in X-Syncope-Delegated-By + * [SYNCOPE-1654] - Inconsistent Realm search FIQL expressions between JPA and Elasticsearch engines + * [SYNCOPE-1656] - Remediations are not created on update while pulling + * [SYNCOPE-1657] - Unable to define a new name for a cloned resource + * [SYNCOPE-1659] - Read-only flag not working in console on virtual attributes + * [SYNCOPE-1660] - Anonymous requests does not store domain and delegatedBy information in the auth context + * [SYNCOPE-1663] - Value errors in FIQL expressions lead to empty result rather than error messages + * [SYNCOPE-1664] - JSONB: Inconsistent search query when is used a pull correlation rule + +** Improvement + * [SYNCOPE-1658] - Allow to view the topology in table format + * [SYNCOPE-1666] - Security Answer encryption + * [SYNCOPE-1667] - Propagation Policy + * [SYNCOPE-1668] - Provide Entity Cache report and management + +Release Notes - Syncope - Version 2.1.10 +================================================================================ + +** Bug + * [SYNCOPE-1628] - Console goes NPE when Connector fails to initialize + * [SYNCOPE-1629] - JPA JSON: Date conversion pattern including slashes leads to incorrect search results + * [SYNCOPE-1634] - Group Owner update/delete action doesn't trigger propagation action + * [SYNCOPE-1635] - Create Rules with configurations for each domain, make creation thread safe + * [SYNCOPE-1640] - Uncaught exception when creating Enum schema + * [SYNCOPE-1643] - Update of Realm doesn't trigger provisioning for users + * [SYNCOPE-1644] - Task run failure with multi-node deployments + * [SYNCOPE-1645] - Case insensitive search with Elasticsearch extension returns wrong results + +** New Feature + * [SYNCOPE-129] - Delegation + +** Improvement + * [SYNCOPE-1630] - Use Group owners to extend Delegated Administration + * [SYNCOPE-1631] - Pass ConnId ObjectClass to ReconFilterBuilder + * [SYNCOPE-1633] - Give the possibility to add a custom message to the confirm dialog + * [SYNCOPE-1639] - Provide ordering of attributes in the diff view on the history management + * [SYNCOPE-1641] - Allow to purge Propagation Tasks + +Release Notes - Syncope - Version 2.1.9 +================================================================================ + +** Bug + * [SYNCOPE-1606] - Syncope returns an exception when doing two sequential operations for the same user from the toggle panel + * [SYNCOPE-1607] - Console Page preferences not working + * [SYNCOPE-1613] - startAt date is set to start field for SCHEDULED, PULL and PUSH TaskTOs + * [SYNCOPE-1616] - CSV and single push / pull concurrency issues + * [SYNCOPE-1619] - SearchPanel should display the input field based on the type of the selected property + * [SYNCOPE-1620] - JWT validation requires exp and nbf claims + * [SYNCOPE-1622] - ConnId Connectors not pooled with Resource override + +** Improvement + * [SYNCOPE-1608] - Allow wildcard group membership search + * [SYNCOPE-1609] - Reduce the number of table joins into PostgreSQL JSONB persistence implementation + * [SYNCOPE-1610] - Set Reconciliation to work with Pull and Push Correlation Rules if available + * [SYNCOPE-1611] - Caffeine Cache for Virtual Attribute Cache + * [SYNCOPE-1624] - Toggle panel improvements + +Release Notes - Syncope - Version 2.1.8 +================================================================================ + +** Bug + * [SYNCOPE-1590] - Error when adding i18n labels to schemas + * [SYNCOPE-1596] - Console: read-only attributes not rendered as disabled + * [SYNCOPE-1598] - Create or update user with two+ memberships for the same group are not prevented + * [SYNCOPE-1601] - Propagation not always triggered after form submit in User Requests + * [SYNCOPE-1602] - ConnObjectKey attribute values not included with DefaultPushCorrelationRule + * [SYNCOPE-1603] - PushCorrelationRule not used for DELETE on External Resources + * [SYNCOPE-1604] - AjaxDateTimePicker doesn't handle some 1900 dates the right way + * [SYNCOPE-1605] - Propagation task not generated if update involves only ConnObjectLink + +** Improvement + * [SYNCOPE-1591] - Support fetching data from internal storage for XML content loader + * [SYNCOPE-1594] - Allow to filter user requests and forms by username + * [SYNCOPE-1597] - Enable default customization of console layout + * [SYNCOPE-1600] - Flowable: support password form property type + +Release Notes - Syncope - Version 2.1.7 +================================================================================ + +** Bug + * [SYNCOPE-1549] - Groups select opens a popup when removing a group + * [SYNCOPE-1560] - File upload component: missing translations + * [SYNCOPE-1561] - Batch: missing support for custom TLSClientParameters + * [SYNCOPE-1563] - User approval update should send the password only when requested + * [SYNCOPE-1564] - Integration tests run with YAML payloads are failing + * [SYNCOPE-1565] - Integration tests run with XML payloads are failing + * [SYNCOPE-1567] - Mapping does not allow relationships + * [SYNCOPE-1569] - Console: cannot save Reportlet search conditions + * [SYNCOPE-1573] - Logout forced from Console when editing user with many memberships + * [SYNCOPE-1581] - When enabling Swagger UI or Flowable extensions Installer generates invalid POM files + * [SYNCOPE-1583] - For members part of a Dynamic Group, but cannot access group attributes in member mapping + * [SYNCOPE-1586] - Startup failures with sample docker-compose and MySQL + +** Improvement + * [SYNCOPE-1568] - Render custom wizard on user request + * [SYNCOPE-1575] - Provide the ability to specify on which resources the user's status should be propagated + +Release Notes - Syncope - Version 2.1.6 +================================================================================ + +** Bug + * [SYNCOPE-1461] - MySQL: Segmentation fault with query using JSON_TABLE + * [SYNCOPE-1503] - Cannot remove provisioning information from Resource in Admin Console + * [SYNCOPE-1504] - Error setting uidOnCreate attribute during a pull task with multiple provisions + * [SYNCOPE-1505] - Changes to "AjaxPalettePanel" components in Console are not saved when the previous step button is pressed before submitting the wizard form + * [SYNCOPE-1512] - Error while saving a unique plain attribute value with single quote when using JPA JSON + * [SYNCOPE-1520] - Exception when updating Group unique attribute with JPA JSON + * [SYNCOPE-1524] - Social registration does not redirect to self registration page + * [SYNCOPE-1525] - Documentation indicates sharing private key, hiding public key + * [SYNCOPE-1526] - Broken link to issues from reference documentation + * [SYNCOPE-1533] - Broken backward compatibilty because of changes in Equals and HashCode methods in TOs + * [SYNCOPE-1536] - Enduser UI does not clean up file alteration monitors on shutdown + * [SYNCOPE-1537] - Password of LinkedAccounts not saved properly from Admin Console + * [SYNCOPE-1538] - Admin Console: users / groups management slow to access with high number of realms + * [SYNCOPE-1539] - AjaxPalettePanel does not support setRequired + * [SYNCOPE-1542] - Search panel issues in Admin Console + * [SYNCOPE-1544] - 'Override?' flag not properly set for password and username fields of LinkedAccounts + * [SYNCOPE-1546] - PriorityPropagationTaskExecutor: rejected tasks not stored + * [SYNCOPE-1554] - Generated default admin role layout doesn't work + +** New Feature + * [SYNCOPE-957] - Multiaccount + * [SYNCOPE-1506] - Merge Users + * [SYNCOPE-1511] - Configure audit events create/update/etc of users, groups, etc + * [SYNCOPE-1529] - French (CA) translation for Admin Console + +** Improvement + * [SYNCOPE-1498] - Allow variable resolution in Content.xml + * [SYNCOPE-1499] - Add support for READ correlation rule + * [SYNCOPE-1500] - Allow single import from External Resource + * [SYNCOPE-1501] - Allow filtering for explore resource + * [SYNCOPE-1502] - Find Anys using FIQL: SQL improvements + * [SYNCOPE-1508] - Allow to extend the set of attributes requested from External Resources + * [SYNCOPE-1509] - Auto-select language from Accept-Language HTTP header + * [SYNCOPE-1510] - Allow to store encrypted schema's secret key externally + * [SYNCOPE-1513] - Allow to customize security headers + * [SYNCOPE-1515] - Adapt realm selector to actual number of realms + * [SYNCOPE-1517] - Audit appender should be configurable + * [SYNCOPE-1518] - Allow X-Forwarded-For and X-Forwarded-Proto HTTP headers integration + * [SYNCOPE-1521] - Allow filtering for Role assignment + * [SYNCOPE-1522] - Realm behaviors for Delegated Administration + * [SYNCOPE-1523] - JPAConnInstanceDAO should be marked as Transactional + * [SYNCOPE-1527] - Allow for custom search conditions + * [SYNCOPE-1530] - Add parameters at User Requests start + * [SYNCOPE-1531] - Easier bulk upload from / download to CSV + * [SYNCOPE-1532] - Allow tilde for key values and Realms name + * [SYNCOPE-1534] - Display friendly error messages in Admin Console + * [SYNCOPE-1535] - Customize the order of the provisions of a resource according to the object classes + * [SYNCOPE-1540] - Make internal storage export DBMS independent + * [SYNCOPE-1541] - XML response message timestamps missing millisecs component if "0 msecs" + * [SYNCOPE-1547] - Allow the possibility to customize the roles to be displayed + * [SYNCOPE-1548] - Allow the possibility to customize the Groups wizard step + * [SYNCOPE-1551] - Allow for info notifications in Admin Console + +** Task + * [SYNCOPE-1514] - Report CLI deprecation + +Release Notes - Syncope - Version 2.1.5 +================================================================================ + +** Bug + * [SYNCOPE-1462] - Requests list is not refreshed if empty + * [SYNCOPE-1467] - RDN not allowed when an attribute of the group present also in the DN is changed + * [SYNCOPE-1469] - MySQL with JSON support: errors during startup + * [SYNCOPE-1470] - Flowable extension not working with MariaDB + * [SYNCOPE-1472] - Resource association is duplicated on database after update, assign or link operations + * [SYNCOPE-1474] - Resource is duplicated after batch operation + * [SYNCOPE-1476] - Error while creating Enum schema from Admin Console + * [SYNCOPE-1477] - jQuery UI's spinner not rendered + * [SYNCOPE-1478] - Unable to remove value of "Schema to hold values for identifiers generated upon Create by the external Identity Store" from provisioning + * [SYNCOPE-1480] - Elasticsearch:dynrealm assignment not updated on condition change + * [SYNCOPE-1481] - Invalid values when saving a membership attribute of type date + * [SYNCOPE-1484] - syncope-ide-netbeans submodule fails to find netbeans dependency + * [SYNCOPE-1485] - Reindex of elasticsearch ends with memory error in case of huge amount of data + * [SYNCOPE-1487] - Build Instructions do not say that the "patch" program is needed + * [SYNCOPE-1488] - Change to MVM Env for JDK > 8.00 + * [SYNCOPE-1492] - Build Instructions are missing an EVN (DOCKER_HOST) needed for mvn -Ppostgres-it + * [SYNCOPE-1493] - Mapping unique schema as remote key never matches internal objects + +** Improvement + * [SYNCOPE-1463] - Improve UX enduser User Request management + * [SYNCOPE-1468] - Allow for configurable org.quartz.jobStore.misfireThreshold + * [SYNCOPE-1473] - Provide a PropagationActions to maintain a conservative membership policy management + +** Task + * [SYNCOPE-1464] - Upgrade to Apache Netbeans Maven dependencies + +Release Notes - Syncope - Version 2.1.4 +================================================================================ + +** Bug + * [SYNCOPE-1428] - APIs to read by key return 404 instead of 401 for not authenticated calls + * [SYNCOPE-1429] - Wildcard case-insesitive queries do not work with Elasticsearch + * [SYNCOPE-1430] - ItemTransformer for Date schemas throws NPE + * [SYNCOPE-1431] - Connector and Resource history compare does not work + * [SYNCOPE-1432] - After creating new connector / resource, Topology does not show it + * [SYNCOPE-1437] - Error while searching for users / groups / any objects with Elasticsearch when no data are present + * [SYNCOPE-1438] - "changePwdDate" field is not initialized when create a new user with the specified password + * [SYNCOPE-1439] - User membership attributes not updated + * [SYNCOPE-1440] - Pagination of Users/Groups doesn't work as expected with Elasticsearch + * [SYNCOPE-1442] - Inactive Job with cron expression set is executed anyway + * [SYNCOPE-1443] - Changing Display Rows number in Reconciliation Resource Panel doesn't work + * [SYNCOPE-1446] - Persistence exception on PostgreSQL when AUDIT is enabled on propagation tasks + * [SYNCOPE-1447] - NPE while deleting a privilege from admin console + * [SYNCOPE-1448] - Bean loading/register section not threadsafe + * [SYNCOPE-1450] - Audit: sensitive information not masked by default during update + * [SYNCOPE-1452] - Notification about is not deleted after update + * [SYNCOPE-1453] - MappingItem with "mustChangePassword" field cannot be provisioned and updated during import + * [SYNCOPE-1454] - Avoid duplicated Propagation Tasks + * [SYNCOPE-1457] - NonAlphaNumeric policy pattern matches the "Not word" character class + +** New Feature + * [SYNCOPE-1401] - Leverage MySQL JSON type + +** Improvement + * [SYNCOPE-1433] - Unflag/flag uniqueness shouldn't be permitted + * [SYNCOPE-1436] - Remove pullPolicy EAGER fetchType from JPAExternalResource + * [SYNCOPE-1441] - Perform in-memory match for dynamic conditions + * [SYNCOPE-1444] - Pull correlation rules: allow to discriminate ongoing event + * [SYNCOPE-1445] - Docker: support pgjsonb as DBMS option + * [SYNCOPE-1449] - Support multi-value attributes in JEXL expressions + +** Task + * [SYNCOPE-1400] - Support MySQL 8 + +Release Notes - Syncope - Version 2.1.3 +================================================================================ + +** Bug + * [SYNCOPE-1391] - Check template for confirmPasswordReset and mustChangePassword + * [SYNCOPE-1393] - jexl function fullPath2Dn return invalid value for ROOT realm + * [SYNCOPE-1399] - Error while executing the custom task to initialize indices with Elasticsearch v6.x + * [SYNCOPE-1404] - Dialog not closing in Netbeans ide plugin when creating a new element + * [SYNCOPE-1405] - Error during db initialization: views.xml always set for PostgreSQL + * [SYNCOPE-1406] - Error during startup because of missing property 'historyLevel' + * [SYNCOPE-1407] - Date pattern ignored by widget + * [SYNCOPE-1408] - Partial user edit via Role layout implies removing all unmanaged attributes + * [SYNCOPE-1411] - User/Any object updates generate attributes with null owner in case of patches involving membership attributes + * [SYNCOPE-1417] - Search with order by two plain attributes gives no results + * [SYNCOPE-1419] - User and AnyObject search fails in case of not leaf conditions given on multivalue fields + * [SYNCOPE-1420] - Expired Access Tokens might impede successful authentication + * [SYNCOPE-1425] - Mapping item transformers do not work for non-string values + +** New Feature + * [SYNCOPE-1368] - Add some accessibility features to Console + * [SYNCOPE-1395] - Leverage PostgreSQL's jsonb type + +** Improvement + * [SYNCOPE-1392] - Reduce usage of Reflection to improve overall performance + * [SYNCOPE-1394] - Add un-claim capability for requests + * [SYNCOPE-1396] - Give the possibility to configure TLS client parameters + * [SYNCOPE-1397] - No Such element exception while editing USER update approval + * [SYNCOPE-1409] - Avoid double round-trip to External Resource during Push + * [SYNCOPE-1412] - Serch for identities with null attributes can be improved + * [SYNCOPE-1416] - remove user_search_null_attr view + * [SYNCOPE-1422] - Permit to provide custom implementation of NotificationManager and AuditManager + * [SYNCOPE-1424] - Improve Propagation task ordered search + +** Task + * [SYNCOPE-1381] - Support user request from Enduser UI + * [SYNCOPE-1402] - Upgrade to Validation API 2.0 + +Release Notes - Syncope - Version 2.1.2 +================================================================================ + +** Bug + * [SYNCOPE-1360] - Delegated administration to Dynamic Realms not possible + * [SYNCOPE-1361] - Custom audit appender does not work after a restart + * [SYNCOPE-1362] - Sorting users by creation date raises RuntimeException + * [SYNCOPE-1363] - Deleting multiple users at once reports "Operation delete not supported" + * [SYNCOPE-1364] - Upgrade tool from 2.0 script error + * [SYNCOPE-1365] - Erorr during retrieve candidate groups for approval process + * [SYNCOPE-1366] - Audit events ownership always set to admin user + * [SYNCOPE-1370] - Password reset succeeds also on wrong captcha + * [SYNCOPE-1371] - After upgrade from 2.0, error when updating Realm: ClassCastException: Expected LOGIC_ACTIONS, got PULL_ACTIONS + * [SYNCOPE-1372] - Password history checks not effective + * [SYNCOPE-1373] - Custom task schedule is reset after update + * [SYNCOPE-1374] - Concurrent propagation tasks for non-Master domains not saved + * [SYNCOPE-1375] - The existence of a membership attribute mapping implies membership creation during pull + * [SYNCOPE-1376] - swagger-ui server URL incorrect behind ssl reverse proxy + * [SYNCOPE-1377] - Wrong X-Syncope-Domain header does not throw an error + * [SYNCOPE-1380] - During Push or Pull, if policy with conflict resolution IGNORE is set, the process is interrupted as soon as such setting applies + * [SYNCOPE-1383] - Exception during "getObject" from external resource + * [SYNCOPE-1387] - ClassCast exception when pull realms + * [SYNCOPE-1388] - mustChangePassword flag does not prevent user from invoking actions + * [SYNCOPE-1389] - In case of virtual attribute mapping, propagation is always set as UPDATE also in case of CREATE + * [SYNCOPE-1390] - Pull Realms: pull task with Unmatching Rules: PROVISION shouldn't create propagation task + +** New Feature + * [SYNCOPE-1019] - Template mechanism for Enduser UI + * [SYNCOPE-1220] - Support Groovy implementations in the Netbeans IDE plugin + * [SYNCOPE-1367] - Add some accessibility features to Enduser + * [SYNCOPE-1369] - User requests + +** Improvement + * [SYNCOPE-1379] - Make configurable resource check timeout + * [SYNCOPE-1382] - Failure specifying push task filters including db column mapped as integer + * [SYNCOPE-1384] - SAML 2.0: Allow to customize RequestedAuthnContext for a given Service Provider + * [SYNCOPE-1385] - Priority propagation timeout hard coded into PriorityPropagationTaskExecutor + +Release Notes - Syncope - Version 2.1.1 +================================================================================ + +** Bug + * [SYNCOPE-1331] - ExternalResourcePropagationAction is too long name for a table in Oracle DB + * [SYNCOPE-1333] - Missing virtual attribute value in case of type extension + * [SYNCOPE-1334] - Maven install problem with Apache Syncope 2.1.0 + * [SYNCOPE-1335] - Missing SQL statements when upgrading from 2.0 Jazz + * [SYNCOPE-1337] - Password history policy is not enforced on salted passwords + * [SYNCOPE-1338] - Double type conversion applied during pull leads to errors + * [SYNCOPE-1339] - Enduser spinner does not apply to the whole page + * [SYNCOPE-1340] - Cannot update membership attribute + * [SYNCOPE-1342] - console UI login form ignores Domain selection + * [SYNCOPE-1343] - Attributes are not reset after pull of null values + * [SYNCOPE-1344] - CORE_SCHEME not being updated in enduser.properties + * [SYNCOPE-1346] - Adding a new task while re-executing a propagation task + * [SYNCOPE-1347] - Invocation Problem calling org.apache.syncope.installer.processes.ArchetypeProcess + * [SYNCOPE-1350] - Date values not formatted according to the conversion pattern + * [SYNCOPE-1352] - Group wizard doesn't update the plain attributes + * [SYNCOPE-1353] - DBPasswordPropagationActions link in the reference guide is wrong + * [SYNCOPE-1354] - Push Tasks do not send status onto External Resources + * [SYNCOPE-1356] - LDAPMembershipPullActions does not remove memberships + * [SYNCOPE-1357] - MemoryVirAttrCache not working + * [SYNCOPE-1358] - Search by boolean value does not work from Admin Console + +** New Feature + * [SYNCOPE-1348] - REST: replace bulk operations with batch requests + +** Improvement + * [SYNCOPE-1336] - Add pagination for approvals forms + * [SYNCOPE-1341] - Domain should be configurable parameter for syncope-enduser docker image + Release Notes - Syncope - Version 2.1.0 ================================================================================ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7bc7e4b1c36..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,22 +0,0 @@ - -**Apache Syncope** is an Open Source system for managing digital identities in enterprise environments, -implemented in Java EE technology and released under Apache 2.0 license. - -Would you like to contribute? Visit http://syncope.apache.org/contributing.html diff --git a/NOTICE b/NOTICE index f9f015bf388..d7aa1399613 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Syncope -Copyright 2012-2018 The Apache Software Foundation +Copyright 2012-2023 The Apache Software Foundation This product includes software developed by: The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md deleted file mode 100644 index c4468af4354..00000000000 --- a/README.md +++ /dev/null @@ -1,33 +0,0 @@ - -**Apache Syncope** is an Open Source system for managing digital identities in enterprise environments, -implemented in Java EE technology and released under Apache 2.0 license. - -More information at http://syncope.apache.org - - - - - - - - - - - diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5fc0c02eb89..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -# 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. -version: '{build}' -os: Windows Server 2012 -init: - - git config --global core.autocrlf true -install: - - cinst git - - ps: | - Add-Type -AssemblyName System.IO.Compression.FileSystem - if (!(Test-Path -Path "C:\maven" )) { - (new-object System.Net.WebClient).DownloadFile('https://www.apache.org/dist/maven/maven-3/3.5.3/binaries/apache-maven-3.5.3-bin.zip', 'C:\maven-bin.zip') - [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven") - } - - cmd: SET PATH=C:\maven\apache-maven-3.5.3\bin;%JAVA_HOME%\bin;=%; - - cmd: SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0 - - cmd: SET M2_HOME=C:\maven\apache-maven-3.5.3 - - cmd: SET MAVEN_OPTS=-Xmx4g - - cmd: SET JAVA_OPTS=-Xmx4g -build_script: - - mvn --show-version --quiet -T 1C -PskipTests,all --batch-mode -test_script: - - mvn -T 1C clean install --batch-mode -cache: - - C:\maven\ - - C:\Users\appveyor\.m2 diff --git a/archetype/pom.xml b/archetype/pom.xml index 0c41b566524..66921a00df2 100644 --- a/archetype/pom.xml +++ b/archetype/pom.xml @@ -24,7 +24,7 @@ under the License. org.apache.syncope syncope - 2.1.1-SNAPSHOT + 2.1.15-SNAPSHOT Apache Syncope Archetype @@ -42,7 +42,7 @@ under the License. org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.2.0 @@ -51,7 +51,7 @@ under the License. org.apache.maven.plugins maven-archetype-plugin - 3.0.1 + 3.2.0 true @@ -142,6 +142,14 @@ under the License. ${project.build.outputDirectory}/archetype-resources/core/src/main/resources + + ../core/persistence-jpa-json/src/main/resources/ + + META-INF/* + audit/* + + ${project.build.outputDirectory}/archetype-resources/core/src/main/resources + ../core/persistence-jpa/src/test/resources/domains ${project.build.outputDirectory}/archetype-resources/core/src/test/resources/domains @@ -182,6 +190,13 @@ under the License. provisioning.properties + + ../fit/core-reference/src/test/resources + ${project.build.outputDirectory}/archetype-resources/core/src/test/resources + + mail.properties + + ../fit/core-reference/src/test/resources/scriptedsql ${project.build.outputDirectory}/archetype-resources/core/src/test/resources/scriptedsql @@ -213,7 +228,6 @@ under the License. ../fit/core-reference/src/test/resources ${project.build.outputDirectory}/archetype-resources/core/src/test/resources - addFlowableToContent.xsl keystore @@ -244,10 +258,6 @@ under the License. ${project.build.outputDirectory}/archetype-resources/console/src/main/resources log4j2.xml - save-model.html - app-cfg.js - url-config.js - toolbar.js @@ -276,7 +286,8 @@ under the License. ${project.build.outputDirectory}/archetype-resources/enduser/src/main/resources enduser.properties - customForm.json + customFormAttributes.json + customTemplate.json @@ -309,17 +320,10 @@ under the License. ${project.build.outputDirectory}/archetype-resources/enduser/src/test/resources enduser.properties + customFormAttributes.json + customTemplate.json saml2sp-agent.properties - customForm.json - - - - ../fit/enduser-reference/src/main/resources - ${project.build.outputDirectory}/archetype-resources/enduser/src/test/resources - - enduser.properties oidcclient-agent.properties - customForm.json diff --git a/archetype/src/main/resources/archetype-resources/console/pom.xml b/archetype/src/main/resources/archetype-resources/console/pom.xml index 479126c6925..9f472c65d32 100644 --- a/archetype/src/main/resources/archetype-resources/console/pom.xml +++ b/archetype/src/main/resources/archetype-resources/console/pom.xml @@ -43,8 +43,8 @@ under the License. javax.servlet.jsp-api - javax.servlet - jstl + org.apache.taglibs + taglibs-standard-impl @@ -62,22 +62,6 @@ under the License. org.slf4j slf4j-api - - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-core - - - com.lmax - disruptor - - - org.apache.logging.log4j - log4j-slf4j-impl - commons-logging commons-logging @@ -96,13 +80,6 @@ under the License. org.webjars codemirror - - - - junit - junit - test - @@ -128,6 +105,12 @@ under the License. all + + org.apache.syncope.ext.flowable + syncope-ext-flowable-client-console + ${syncope.version} + + org.apache.syncope.ext.camel syncope-ext-camel-client-console @@ -151,90 +134,9 @@ under the License. syncope-ext-scimv2-client-console ${syncope.version} - - - org.flowable - flowable-ui-modeler-app - war - test - - - - org.apache.maven.plugins - maven-antrun-plugin - true - - - setupFlowableModeler - process-resources - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - run - - - - - - src/main/resources diff --git a/archetype/src/main/resources/archetype-resources/core/pom.xml b/archetype/src/main/resources/archetype-resources/core/pom.xml index f248d9a79a2..ed72c53dba5 100644 --- a/archetype/src/main/resources/archetype-resources/core/pom.xml +++ b/archetype/src/main/resources/archetype-resources/core/pom.xml @@ -42,8 +42,8 @@ under the License. javax.servlet.jsp-api - javax.servlet - jstl + org.apache.taglibs + taglibs-standard-impl @@ -71,20 +71,16 @@ under the License. org.apache.logging.log4j - log4j-api + log4j-core org.apache.logging.log4j - log4j-core + log4j-slf4j-impl com.lmax disruptor - - org.apache.logging.log4j - log4j-slf4j-impl - commons-logging commons-logging @@ -160,8 +156,8 @@ under the License. - org.apache.syncope.core - syncope-core-workflow-flowable + org.apache.syncope.ext.flowable + syncope-ext-flowable-rest-cxf ${syncope.version} diff --git a/archetype/src/main/resources/archetype-resources/core/src/test/resources/addFlowableToContent.xsl b/archetype/src/main/resources/archetype-resources/core/src/test/resources/addFlowableToContent.xsl new file mode 100644 index 00000000000..70aa21bcbb8 --- /dev/null +++ b/archetype/src/main/resources/archetype-resources/core/src/test/resources/addFlowableToContent.xsl @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archetype/src/main/resources/archetype-resources/enduser/pom.xml b/archetype/src/main/resources/archetype-resources/enduser/pom.xml index 5f98d31f920..442a5a5fb03 100644 --- a/archetype/src/main/resources/archetype-resources/enduser/pom.xml +++ b/archetype/src/main/resources/archetype-resources/enduser/pom.xml @@ -43,8 +43,8 @@ under the License. javax.servlet.jsp-api - javax.servlet - jstl + org.apache.taglibs + taglibs-standard-impl @@ -64,20 +64,16 @@ under the License. org.apache.logging.log4j - log4j-api + log4j-core org.apache.logging.log4j - log4j-core + log4j-slf4j-impl com.lmax disruptor - - org.apache.logging.log4j - log4j-slf4j-impl - commons-logging commons-logging @@ -247,7 +243,11 @@ under the License. todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" overwrite="true"/> - + + @@ -338,6 +338,12 @@ under the License. syncope-ext-oidcclient-client-enduser ${syncope.version} + + + org.apache.syncope.ext.flowable + syncope-ext-flowable-client-enduser + ${syncope.version} + diff --git a/archetype/src/main/resources/meta-pom.xml b/archetype/src/main/resources/meta-pom.xml index 0b163aec703..af7861f758b 100644 --- a/archetype/src/main/resources/meta-pom.xml +++ b/archetype/src/main/resources/meta-pom.xml @@ -36,6 +36,7 @@ under the License. ${jwsKey} ${adminPassword} + none true true @@ -69,12 +70,6 @@ under the License. syncope-core-persistence-jpa ${syncope.version} - - - org.apache.syncope.core - syncope-core-workflow-flowable - ${syncope.version} - org.apache.syncope.client @@ -90,24 +85,6 @@ under the License. - - - - org.codehaus.mojo - ianal-maven-plugin - true - - - - verify-legal-files - - none - - - - - - common core diff --git a/client/cli/LICENSE b/client/cli/LICENSE index eeb76fe47f6..eb607cf5d97 100644 --- a/client/cli/LICENSE +++ b/client/cli/LICENSE @@ -213,7 +213,231 @@ This is licensed under the AL 2.0, see above. == -For JAX-B (http://jaxb.java.net/): +For Swagger (http://swagger.io/): +This is licensed under the AL 2.0, see above. + +== + +For Jakarta Activation (https://eclipse-ee4j.github.io/jaf/): +This is licensed under the EPL 1.0: + +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + +== + +For Eclipse Project for JAX-RS (https://projects.eclipse.org/projects/ee4j.jaxrs): +This is licensed under the EPL 1.0, see above. + +== + +For Jakarta XML Binding (https://eclipse-ee4j.github.io/jaxb-ri/): +This is licensed under the EPL 1.0, see above. + +== + +For javax.annotation-api (https://jcp.org/en/jsr/detail?id=250): This is licensed under the CDDL 1.0: COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 @@ -599,26 +823,11 @@ of liability. == -For javax.annotation-api (https://jcp.org/en/jsr/detail?id=250): -This is licensed under the CDDL 1.0, see above. - -== - For Bean Validation API (http://beanvalidation.org/): This is licensed under the AL 2.0, see above. == -For javax.ws.rs-api (https://jax-rs-spec.java.net/): -This is licensed under the CDDL 1.0, see above. - -== - -For Joda Time (http://www.joda.org/joda-time/): -This is licensed under the AL 2.0, see above. - -== - For StAX2 API (http://wiki.fasterxml.com/WoodstoxStax2): This is licensed under the BSD license: @@ -685,3 +894,8 @@ This is licensed under the MIT license: For Spring Framework (http://projects.spring.io/spring-framework/): This is licensed under the AL 2.0, see above. + +== + +For SnakeYAML (http://www.snakeyaml.org/): +This is licensed under the AL 2.0, see above. diff --git a/client/cli/NOTICE b/client/cli/NOTICE index 3171f8e46e5..45b43871349 100644 --- a/client/cli/NOTICE +++ b/client/cli/NOTICE @@ -1,5 +1,5 @@ Apache Syncope -Copyright 2012-2018 The Apache Software Foundation +Copyright 2012-2023 The Apache Software Foundation This product includes software developed by: The Apache Software Foundation (http://www.apache.org/). @@ -13,28 +13,34 @@ This product includes software developed by the Jackson project. == -This product includes software developed by the JAXB project. -Copyright (c) 2013-2016 The JAXB project. +This product includes software developed by the Swagger project. +Copyright 2016 SmartBear Software == -This product includs software developed by Oracle. -Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved. +This product includes software developed by the Eclipse Foundation. +Copyright (c) 2019, 2020 Eclipse Foundation. All rights reserved. == -This product includes software developed by the Bean Validation Project (http://beanvalidation.org). -Copyright (c) Red Hat, Inc., Emmanuel Bernard +This product includes software developed by the Eclipse Project for JAX-RS. +Eclipse Project for JAX-RS is a trademark of the Eclipse Foundation. +All content is the property of the respective authors or their employers. For more information regarding authorship of content, please consult the listed source code repository logs. + +== + +This product includes software developed by the Eclipse Foundation. +Copyright (c) 2019, 2020 Eclipse Foundation. All rights reserved. == -This product includes software developed by the JAX-RS project. -Copyright (c) 2014, Oracle Corporation and/or its affiliates. All rights reserved. +This product includs software developed by Oracle. +Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved. == -This product includes software developed by the Joda Time project. -Copyright (c) 2002-2016 Joda.org. All Rights Reserved. +This product includes software developed by the Bean Validation Project (http://beanvalidation.org). +Copyright (c) Red Hat, Inc., Emmanuel Bernard == @@ -59,3 +65,7 @@ Copyright (c) 2004-2016 QOS.ch. This product includes software developed by SpringSource. Copyright (c) 2004-2016 SpringSource All rights reserved. + +== + +This product includes software developed by the SnakeYAML project. diff --git a/client/cli/pom.xml b/client/cli/pom.xml index 0cfaedf3f24..bf308b536db 100644 --- a/client/cli/pom.xml +++ b/client/cli/pom.xml @@ -24,7 +24,7 @@ under the License. org.apache.syncope syncope-client - 2.1.1-SNAPSHOT + 2.1.15-SNAPSHOT Apache Syncope Client CLI @@ -98,7 +98,7 @@ under the License. org.junit.jupiter - junit-jupiter-engine + junit-jupiter test diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java index b2606e4e2e5..29c6feb6382 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java @@ -28,6 +28,7 @@ import org.apache.syncope.client.cli.util.JasyptUtils; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.rest.api.service.SyncopeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +39,7 @@ public final class SyncopeServices { private static String SYNCOPE_ADDRESS; - public static T get(final Class clazz) { + private static SyncopeClient client() { final Properties properties = new Properties(); try (InputStream is = Files.newInputStream(Paths.get(InstallConfigFileTemplate.configurationFilePath()))) { properties.load(is); @@ -49,13 +50,19 @@ public static T get(final Class clazz) { String syncopeAdminPassword = JasyptUtils.get().decrypt(properties.getProperty("syncope.admin.password")); SYNCOPE_ADDRESS = properties.getProperty("syncope.rest.services"); String useGZIPCompression = properties.getProperty("useGZIPCompression"); - SyncopeClient syncopeClient = new SyncopeClientFactoryBean(). + return new SyncopeClientFactoryBean(). setAddress(SYNCOPE_ADDRESS). setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)). create(properties.getProperty("syncope.admin.user"), syncopeAdminPassword); + } + public static T get(final Class clazz) { LOG.debug("Creating service for {}", clazz.getName()); - return syncopeClient.getService(clazz); + return client().getService(clazz); + } + + public static BatchRequest batch() { + return client().batch(); } public static String getAddress() { diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/Info.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/Info.java index 8167cc595a8..e277b9da02f 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/Info.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/Info.java @@ -73,21 +73,9 @@ public void selfRegistrationAllowed() { public void provisioningManager() { try { infoResultManager.printProvisioningManager( - platformInfo.getAnyObjectProvisioningManager(), - platformInfo.getUserProvisioningManager(), - platformInfo.getGroupProvisioningManager()); - } catch (final Exception ex) { - LOG.error("Information error", ex); - infoResultManager.genericError(ex.getMessage()); - } - } - - public void workflowAdapter() { - try { - infoResultManager.printWorkflowAdapter( - platformInfo.getAnyObjectWorkflowAdapter(), - platformInfo.getUserWorkflowAdapter(), - platformInfo.getGroupWorkflowAdapter()); + platformInfo.getProvisioningInfo().getAnyObjectProvisioningManager(), + platformInfo.getProvisioningInfo().getUserProvisioningManager(), + platformInfo.getProvisioningInfo().getGroupProvisioningManager()); } catch (final Exception ex) { LOG.error("Information error", ex); infoResultManager.genericError(ex.getMessage()); @@ -246,7 +234,7 @@ public void passwordGenerator() { public void virAttrCache() { try { - infoResultManager.printVirtualAttributeCacheClass(platformInfo.getVirAttrCache()); + infoResultManager.printVirtualAttributeCacheClass(platformInfo.getProvisioningInfo().getVirAttrCache()); } catch (final Exception ex) { LOG.error("Information error", ex); infoResultManager.genericError(ex.getMessage()); diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoCommand.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoCommand.java index 28559be44fe..460d54a3990 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoCommand.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoCommand.java @@ -52,9 +52,6 @@ public void execute(final Input input) { case PROVISIONING_MANAGER: info.provisioningManager(); break; - case WORKFLOW_ADAPTER: - info.workflowAdapter(); - break; case ACCOUNT_RULES: info.accountRules(); break; @@ -122,7 +119,6 @@ private enum Options { PWD_RESET_WITH_SECURITY_QUESTION("--pwd-reset-with-question"), SELF_REG_ALLOWED("--self-reg-allowed"), PROVISIONING_MANAGER("--provisioning-manager-classes"), - WORKFLOW_ADAPTER("--workflow-adapter-classes"), ACCOUNT_RULES("--account-rules-classes"), CONNID_LOCATION("--connid-locations"), RECON_FILTER_BUILDERS("--reconciliation-filter-builders"), diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoResultManager.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoResultManager.java index d19b0aeddae..65ba2063360 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoResultManager.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/info/InfoResultManager.java @@ -48,15 +48,6 @@ public void printProvisioningManager(final String anyObjectProvisioningManager, "Group provisioning manager class: " + getGroupProvisioningManager); } - public void printWorkflowAdapter(final String anyObjectWorkflowAdapter, - final String userWorkflowAdapter, - final String groupWorkflowAdapter) { - genericMessage( - "Any object workflow adapter class: " + anyObjectWorkflowAdapter, - "User workflow adapter class: " + userWorkflowAdapter, - "Group workflow adapter class: " + groupWorkflowAdapter); - } - public void printAccountRules(final Collection rules) { rules.forEach(accountRule -> { genericMessage("Account rule: " + accountRule); diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerResultManager.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerResultManager.java index 327f8c08209..2865b9209f6 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerResultManager.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerResultManager.java @@ -26,7 +26,7 @@ import java.util.Map; import org.apache.syncope.client.cli.commands.CommonsResultManager; import org.apache.syncope.client.cli.view.Table; -import org.apache.syncope.common.lib.log.LogStatementTO; +import org.apache.syncope.common.lib.log.LogStatement; import org.apache.syncope.common.lib.log.LoggerTO; public class LoggerResultManager extends CommonsResultManager { @@ -41,9 +41,9 @@ public void fromListMemoryAppenders(final List memoryAppenders) { tableBuilder.build().print(); } - public void fromGetLastLogStatements(final List statements) throws JsonProcessingException { + public void fromGetLastLogStatements(final List statements) throws JsonProcessingException { final Table.TableBuilder tableBuilder = new Table.TableBuilder("last statements").header("statement"); - for (final LogStatementTO statement : statements) { + for (final LogStatement statement : statements) { tableBuilder.rowValues(Collections.singletonList( MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(statement))); } diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerSyncopeOperations.java index b1d52bfb23f..8b44c37aa55 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerSyncopeOperations.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/logger/LoggerSyncopeOperations.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; import org.apache.syncope.client.cli.SyncopeServices; import org.apache.syncope.common.lib.log.LogAppender; -import org.apache.syncope.common.lib.log.LogStatementTO; +import org.apache.syncope.common.lib.log.LogStatement; import org.apache.syncope.common.lib.log.LoggerTO; import org.apache.syncope.common.lib.types.LoggerType; import org.apache.syncope.common.rest.api.service.LoggerService; @@ -35,7 +35,7 @@ public List listMemoryAppenders() { return loggerService.memoryAppenders().stream().map(LogAppender::getName).collect(Collectors.toList()); } - public List getLastLogStatements(final String appender) { + public List getLastLogStatements(final String appender) { return loggerService.getLastLogStatements(appender); } diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/realm/RealmSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/realm/RealmSyncopeOperations.java index 19af12f7709..75c07fec862 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/realm/RealmSyncopeOperations.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/realm/RealmSyncopeOperations.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.syncope.client.cli.SyncopeServices; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.rest.api.service.RealmService; @@ -28,7 +29,7 @@ public class RealmSyncopeOperations { private final RealmService realmService = SyncopeServices.get(RealmService.class); public List list() { - return realmService.list(); + return realmService.list(SyncopeConstants.ROOT_REALM); } public boolean exists(final String realm) { diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/report/ReportSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/report/ReportSyncopeOperations.java index 17c6efad48d..da1d267f4ad 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/report/ReportSyncopeOperations.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/report/ReportSyncopeOperations.java @@ -29,7 +29,7 @@ import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ReportTO; import org.apache.syncope.common.lib.types.ReportExecExportFormat; -import org.apache.syncope.common.rest.api.beans.ExecuteQuery; +import org.apache.syncope.common.rest.api.beans.ExecSpecs; import org.apache.syncope.common.rest.api.service.ReportService; public class ReportSyncopeOperations { @@ -98,7 +98,7 @@ public String exportExecutionResult(final String executionKey, final String repo } public void execute(final String reportKey) { - reportService.execute(new ExecuteQuery.Builder().key(reportKey).build()); + reportService.execute(new ExecSpecs.Builder().key(reportKey).build()); } public void deleteExecution(final String executionKey) { diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/AbstractWorkflowCommand.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/AbstractWorkflowCommand.java deleted file mode 100644 index b21abf39bcc..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/AbstractWorkflowCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.client.cli.commands.self; - -public abstract class AbstractWorkflowCommand { - - protected final WorkflowResultManager workflowResultManager = new WorkflowResultManager(); - - protected final WorkflowSyncopeOperations workflowSyncopeOperations = new WorkflowSyncopeOperations(); - -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowCommand.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowCommand.java deleted file mode 100644 index 215c8659132..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowCommand.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.client.cli.commands.self; - -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.cli.Command; -import org.apache.syncope.client.cli.Input; -import org.apache.syncope.client.cli.commands.AbstractCommand; - -@Command(name = "workflow") -public class WorkflowCommand extends AbstractCommand { - - private final WorkflowResultManager workflowResultManager = new WorkflowResultManager(); - - @Override - public void execute(final Input input) { - if (StringUtils.isBlank(input.getOption())) { - input.setOption(UserWorkflowOptions.HELP.getOptionName()); - } - - switch (UserWorkflowOptions.fromName(input.getOption())) { - case EXPORT_DIAGRAM: - new WorkflowExportDiagram(input).export(); - break; - case EXPORT_DEFINITION: - new WorkflowExportDefinition(input).export(); - break; - case HELP: - System.out.println(getHelpMessage()); - break; - default: - workflowResultManager.defaultOptionMessage(input.getOption(), getHelpMessage()); - } - } - - @Override - public String getHelpMessage() { - return workflowResultManager.commandHelpMessage(getClass()); - } - - private enum UserWorkflowOptions { - - HELP("--help"), - EXPORT_DIAGRAM("--export-diagram"), - EXPORT_DEFINITION("--export-definition"); - - private final String optionName; - - UserWorkflowOptions(final String optionName) { - this.optionName = optionName; - } - - public String getOptionName() { - return optionName; - } - - public boolean equalsOptionName(final String otherName) { - return (otherName == null) ? false : optionName.equals(otherName); - } - - public static UserWorkflowOptions fromName(final String name) { - UserWorkflowOptions optionToReturn = HELP; - for (final UserWorkflowOptions option : UserWorkflowOptions.values()) { - if (option.equalsOptionName(name)) { - optionToReturn = option; - } - } - return optionToReturn; - } - - public static List toList() { - final List options = new ArrayList<>(); - for (final UserWorkflowOptions value : values()) { - options.add(value.getOptionName()); - } - return options; - } - } -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDefinition.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDefinition.java deleted file mode 100644 index b855ae0c861..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDefinition.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.client.cli.commands.self; - -import javax.xml.ws.WebServiceException; -import org.apache.syncope.client.cli.Input; -import org.apache.syncope.client.cli.util.CommandUtils; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WorkflowExportDefinition extends AbstractWorkflowCommand { - - private static final Logger LOG = LoggerFactory.getLogger(WorkflowExportDefinition.class); - - private static final String EXPORT_HELP_MESSAGE = "workflow --export-definition {ANY-TYPE-KIND}\n"; - - private final Input input; - - public WorkflowExportDefinition(final Input input) { - this.input = input; - } - - public void export() { - if (input.parameterNumber() == 1) { - try { - workflowSyncopeOperations.exportDefinition(input.firstParameter()); - } catch (final SyncopeClientException | WebServiceException ex) { - LOG.error("Error", ex); - if (ex.getMessage().startsWith("NotFound")) { - workflowResultManager.notFoundError("Workflow", input.firstParameter()); - } else { - workflowResultManager.genericError(ex.getMessage()); - } - } catch (final IllegalArgumentException ex) { - LOG.error("Error", ex); - workflowResultManager.typeNotValidError( - "workflow", input.firstParameter(), CommandUtils.fromEnumToArray(AnyTypeKind.class)); - } - } else { - workflowResultManager.commandOptionError(EXPORT_HELP_MESSAGE); - } - } -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDiagram.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDiagram.java deleted file mode 100644 index 80b6e417875..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowExportDiagram.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.client.cli.commands.self; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import javax.ws.rs.core.Response; -import javax.xml.ws.WebServiceException; -import org.apache.cxf.helpers.IOUtils; -import org.apache.syncope.client.cli.Input; -import org.apache.syncope.client.cli.util.CommandUtils; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.types.AnyTypeKind; - -public class WorkflowExportDiagram extends AbstractWorkflowCommand { - - private static final String EXPORT_HELP_MESSAGE = "workflow --export-diagram {ANY-TYPE-KIND}\n"; - - private final Input input; - - public WorkflowExportDiagram(final Input input) { - this.input = input; - } - - public void export() { - if (input.parameterNumber() == 1) { - try { - final Response response = workflowSyncopeOperations.exportDiagram(input.firstParameter()); - final byte[] diagram = IOUtils.readBytesFromStream((InputStream) response.getEntity()); - try (final OutputStream os = Files.newOutputStream(Paths.get("/tmp/diagram.png"))) { - os.write(diagram); - os.close(); - } - workflowResultManager.genericMessage("Diagram created: /tmp/diagram.png"); - } catch (final SyncopeClientException | WebServiceException ex) { - if (ex.getMessage().startsWith("NotFound")) { - workflowResultManager.notFoundError("Workflow", input.firstParameter()); - } else { - workflowResultManager.genericError(ex.getMessage()); - } - } catch (final IllegalArgumentException ex) { - workflowResultManager.typeNotValidError( - "workflow", input.firstParameter(), CommandUtils.fromEnumToArray(AnyTypeKind.class)); - } catch (final IOException ex) { - workflowResultManager.genericError(ex.getMessage()); - } - } else { - workflowResultManager.commandOptionError(EXPORT_HELP_MESSAGE); - } - } -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowResultManager.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowResultManager.java deleted file mode 100644 index f46be0786c9..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowResultManager.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.client.cli.commands.self; - -import org.apache.syncope.client.cli.commands.CommonsResultManager; - -public class WorkflowResultManager extends CommonsResultManager { - -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowSyncopeOperations.java deleted file mode 100644 index 45025d4e9be..00000000000 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/self/WorkflowSyncopeOperations.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.client.cli.commands.self; - -import javax.ws.rs.core.Response; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.syncope.client.cli.SyncopeServices; -import org.apache.syncope.common.rest.api.RESTHeaders; -import org.apache.syncope.common.rest.api.service.WorkflowService; - -public class WorkflowSyncopeOperations { - - private static final String DEFAULT = "userWorkflow"; - - private final WorkflowService workflowService = SyncopeServices.get(WorkflowService.class); - - public Response exportDiagram(final String anyType) { - WebClient.client(workflowService).accept(RESTHeaders.MEDIATYPE_IMAGE_PNG); - return workflowService.exportDiagram(anyType, DEFAULT); - } - - public Response exportDefinition(final String anyType) { - return workflowService.get(anyType, DEFAULT); - } -} diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/task/TaskSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/task/TaskSyncopeOperations.java index d577968292c..2314c044842 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/task/TaskSyncopeOperations.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/task/TaskSyncopeOperations.java @@ -24,7 +24,7 @@ import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.types.TaskType; -import org.apache.syncope.common.rest.api.beans.ExecuteQuery; +import org.apache.syncope.common.rest.api.beans.ExecSpecs; import org.apache.syncope.common.rest.api.beans.TaskQuery; import org.apache.syncope.common.rest.api.service.TaskService; @@ -58,6 +58,6 @@ public void deleteExecution(final String executionKey) { public ExecTO execute(final String executionKey, final boolean dryRun) { return taskService.execute( - new ExecuteQuery.Builder().key(executionKey).dryRun(dryRun).build()); + new ExecSpecs.Builder().key(executionKey).dryRun(dryRun).build()); } } diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteAll.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteAll.java index 30d09530a57..e54812dbe21 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteAll.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteAll.java @@ -18,12 +18,20 @@ */ package org.apache.syncope.client.cli.commands.user; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Scanner; +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.core.Response; import org.apache.syncope.client.cli.Input; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.BulkActionResult; +import org.apache.syncope.common.lib.to.ProvisioningResult; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +39,8 @@ public class UserDeleteAll extends AbstractUserCommand { private static final Logger LOG = LoggerFactory.getLogger(UserDeleteAll.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String DELETE_ALL_HELP_MESSAGE = "user --delete-all {REALM}"; private final Input input; @@ -59,21 +69,31 @@ public void delete() { userResultManager.notFoundError("Realm", realm); return; } - final Map results = userSyncopeOperations.deleteAll(realm); - final Map users = new HashMap<>(); - int deletedUsers = 0; - for (final Map.Entry entrySet : results.entrySet()) { - final String userId = entrySet.getKey(); - final BulkActionResult.Status status = entrySet.getValue(); - if (!BulkActionResult.Status.SUCCESS.equals(status)) { - users.put(userId, status.name()); + List results = userSyncopeOperations.deleteAll(realm); + + Map failedUsers = new HashMap<>(); + AtomicReference deletedUsers = new AtomicReference<>(0); + + results.forEach(item -> { + if (item.getStatus() == Response.Status.OK.getStatusCode()) { + deletedUsers.getAndSet(deletedUsers.get() + 1); } else { - deletedUsers++; + try { + ProvisioningResult user = MAPPER.readValue(item.getContent(), + new TypeReference>() { + }); + failedUsers.put( + user.getEntity().getUsername(), + item.getHeaders().get(RESTHeaders.ERROR_CODE).toString()); + } catch (IOException ioe) { + LOG.error("Error reading {}", item.getContent(), ioe); + } } - } + }); + userResultManager.genericMessage("Deleted users: " + deletedUsers); - if (!users.isEmpty()) { - userResultManager.printFailedUsers(users); + if (!failedUsers.isEmpty()) { + userResultManager.printFailedUsers(failedUsers); } } else { userResultManager.genericError("Authentication error"); @@ -83,9 +103,9 @@ public void delete() { } else { userResultManager.genericError("Invalid parameter, please use [yes/no]"); } - } catch (final SyncopeClientException ex) { - LOG.error("Error deleting user", ex); - userResultManager.genericError(ex.getMessage()); + } catch (Exception e) { + LOG.error("Error deleting user", e); + userResultManager.genericError(e.getMessage()); } } else { userResultManager.commandOptionError(DELETE_ALL_HELP_MESSAGE); diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteByAttribute.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteByAttribute.java index 3759656781a..f5e735e3185 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteByAttribute.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteByAttribute.java @@ -18,13 +18,20 @@ */ package org.apache.syncope.client.cli.commands.user; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; -import javax.xml.ws.WebServiceException; +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.core.Response; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.cli.Input; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.BulkActionResult; +import org.apache.syncope.common.lib.to.ProvisioningResult; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +39,8 @@ public class UserDeleteByAttribute extends AbstractUserCommand { private static final Logger LOG = LoggerFactory.getLogger(UserDeleteByAttribute.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String SEARCH_HELP_MESSAGE = "user --delete-by-attribute {REALM} {ATTR-NAME}={ATTR-VALUE}"; private final Input input; @@ -49,34 +58,40 @@ public void delete() { userResultManager.notFoundError("Realm", realm); return; } - Map results = userSyncopeOperations.deleteByAttribute( + List results = userSyncopeOperations.deleteByAttribute( realm, pairParameter.getKey(), pairParameter.getValue()); + Map failedUsers = new HashMap<>(); - int deletedUsers = 0; - for (final Map.Entry entrySet : results.entrySet()) { - String userId = entrySet.getKey(); - BulkActionResult.Status status = entrySet.getValue(); - if (BulkActionResult.Status.SUCCESS == status) { - deletedUsers++; + AtomicReference deletedUsers = new AtomicReference<>(0); + + results.forEach(item -> { + if (item.getStatus() == Response.Status.OK.getStatusCode()) { + deletedUsers.getAndSet(deletedUsers.get() + 1); } else { - failedUsers.put(userId, status.name()); + try { + ProvisioningResult user = MAPPER.readValue(item.getContent(), + new TypeReference>() { + }); + failedUsers.put( + user.getEntity().getUsername(), + item.getHeaders().get(RESTHeaders.ERROR_CODE).toString()); + } catch (IOException ioe) { + LOG.error("Error reading {}", item.getContent(), ioe); + } } - } + }); + userResultManager.genericMessage("Deleted users: " + deletedUsers); if (!failedUsers.isEmpty()) { userResultManager.printFailedUsers(failedUsers); } - } catch (WebServiceException | SyncopeClientException ex) { - LOG.error("Error searching user", ex); - if (ex.getMessage().startsWith("NotFound")) { + } catch (Exception e) { + LOG.error("Error searching user", e); + if (e.getMessage().startsWith("NotFound")) { userResultManager.notFoundError("User with " + pairParameter.getKey(), pairParameter.getValue()); } else { - userResultManager.genericError(ex.getMessage()); + userResultManager.genericError(e.getMessage()); } - } catch (IllegalArgumentException ex) { - LOG.error("Error searching user", ex); - userResultManager.genericError(ex.getMessage()); - userResultManager.genericError(SEARCH_HELP_MESSAGE); } } else { userResultManager.commandOptionError(SEARCH_HELP_MESSAGE); diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserResultManager.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserResultManager.java index 450ff7e8296..9c235751f89 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserResultManager.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserResultManager.java @@ -32,9 +32,9 @@ public class UserResultManager extends CommonsResultManager { public void printUsers(final List userTOs) { System.out.println(""); - for (final UserTO userTO : userTOs) { + userTOs.forEach(userTO -> { printUser(userTO); - } + }); } private void printUser(final UserTO userTO) { @@ -69,40 +69,40 @@ private void printUser(final UserTO userTO) { } private void printResource(final Set resources) { - for (final String resource : resources) { + resources.forEach(resource -> { System.out.println(" - " + resource); - } + }); } private void printRole(final List roles) { - for (final String role : roles) { + roles.forEach((role) -> { System.out.println(" - " + role); - } + }); } private void printAttributes(final Set derAttrTOs) { - for (final AttrTO attrTO : derAttrTOs) { + derAttrTOs.forEach(attrTO -> { final StringBuilder attributeSentence = new StringBuilder(); attributeSentence.append(" ") .append(attrTO.getSchema()) .append(": ") .append(attrTO.getValues()); System.out.println(attributeSentence); - } + }); } private void printRelationships(final List relationshipTOs) { - for (final RelationshipTO relationshipTO : relationshipTOs) { + relationshipTOs.forEach(relationshipTO -> { System.out.println(" type: " + relationshipTO.getType()); - } + }); } public void printFailedUsers(final Map users) { - final Table.TableBuilder tableBuilder = + Table.TableBuilder tableBuilder = new Table.TableBuilder("Users not deleted").header("user key").header("cause"); - for (final Map.Entry entrySet : users.entrySet()) { - tableBuilder.rowValues(Arrays.asList(entrySet.getKey(), entrySet.getValue())); - } + users.forEach((key, value) -> { + tableBuilder.rowValues(Arrays.asList(key, value)); + }); tableBuilder.build().print(); } diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserSyncopeOperations.java b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserSyncopeOperations.java index 42fb5a318a2..2c712ba28e1 100644 --- a/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserSyncopeOperations.java +++ b/client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserSyncopeOperations.java @@ -18,16 +18,17 @@ */ package org.apache.syncope.client.cli.commands.user; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import javax.ws.rs.core.Response; import org.apache.syncope.client.cli.SyncopeServices; import org.apache.syncope.client.lib.SyncopeClient; -import org.apache.syncope.common.lib.to.BulkAction; -import org.apache.syncope.common.lib.to.BulkActionResult; -import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.rest.api.batch.BatchPayloadParser; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; import org.apache.syncope.common.rest.api.beans.AnyQuery; import org.apache.syncope.common.rest.api.service.UserService; @@ -91,34 +92,35 @@ public void delete(final String userKey) { userService.delete(userKey); } - public Map deleteByAttribute( - final String realm, final String attributeName, final String attributeValue) { + public List deleteByAttribute( + final String realm, final String attributeName, final String attributeValue) throws IOException { - return bulkDelete(new AnyQuery.Builder().realm(realm).fiql( + return batchDelete(new AnyQuery.Builder().realm(realm).fiql( SyncopeClient.getUserSearchConditionBuilder().is(attributeName).equalTo(attributeValue).query()). build()); } - public Map deleteAll(final String realm) { - return bulkDelete(new AnyQuery.Builder().realm(realm).details(false).build()); + public List deleteAll(final String realm) throws IOException { + return batchDelete(new AnyQuery.Builder().realm(realm).details(false).build()); } - private Map bulkDelete(final AnyQuery query) { + private List batchDelete(final AnyQuery query) throws IOException { query.setPage(0); query.setSize(0); int count = userService.search(query).getTotalCount(); - BulkAction bulkAction = new BulkAction(); - bulkAction.setType(BulkAction.Type.DELETE); + BatchRequest batchRequest = SyncopeServices.batch(); + UserService batchUserService = batchRequest.getService(UserService.class); query.setSize(PAGE_SIZE); for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) { query.setPage(page); - bulkAction.getTargets().addAll(userService.search(query).getResult().stream(). - map(EntityTO::getKey).collect(Collectors.toList())); + userService.search(query).getResult().forEach(user -> batchUserService.delete(user.getKey())); } - return userService.bulk(bulkAction).readEntity(BulkActionResult.class).getResults(); + Response response = batchRequest.commit().getResponse(); + return BatchPayloadParser.parse( + (InputStream) response.getEntity(), response.getMediaType(), new BatchResponseItem()); } } diff --git a/client/cli/src/main/resources/messages.properties b/client/cli/src/main/resources/messages.properties index 48cdc159696..7bd62fa2766 100644 --- a/client/cli/src/main/resources/messages.properties +++ b/client/cli/src/main/resources/messages.properties @@ -31,5 +31,4 @@ role.help.message=\nUsage: role [options]\n Options:\n --help \n --detail schema.help.message=\nUsage: schema [options]\n Options:\n --help \n --details \n --list-all\n --list-plain\n --list-derived\n --list-virtual\n --read {SCHEMA-TYPE} {SCHEMA-KEY}\n Schema type: PLAIN / DERIVED / VIRTUAL\n --delete {SCHEMA-TYPE} {SCHEMA-KEY}\n Schema type: PLAIN / DERIVED / VIRTUAL\n task.help.message=\nUsage: task [options]\n Options:\n --help \n --details\n --list\n Syntax: --list {TASK-TYPE} \n Task type: NOTIFICATION / PROPAGATION / PUSH / SCHEDULED / PULL\n --list-running-jobs \n --list-scheduled-jobs \n --read \n Syntax: --read {TASK-TYPE} {TASK-KEY}\n Task type: PROPAGATION / NOTIFICATION / SCHEDULED / PULL / PUSH\n --delete \n Syntax: --delete {TASK-KEY} {TASK-KEY} [...]\n --delete-all-prop\n --delete-execution \n Syntax: --delete-execution {TASK-EXEC-KEY} {TASK-EXEC-KEY} [...]\n --execute \n Syntax: --execute {TASK-KEY} {DRY-RUN}\n Dry run: true / false\n user.help.message=\nUsage: user [options]\n Options:\n --help \n --details \n --list \n --get-user-key\n Syntax: --get-user-key {USERNAME}\n --get-username\n Syntax: --get-username {USER-KEY}\n --read-by-userkey \n Syntax: --read-by-userkey {USER-KEY} {USER-KEY} [...]\n --read-by-username\n Syntax: --read-by-username {USERNAME} {USERNAME} [...]\n --search-by-attribute \n Syntax: --search-by-attribute {REALM} {ATTR-NAME}={ATTR-VALUE}\n --search-by-role \n Syntax: --search-by-role {REALM} {ROLE-KEY}\n --search-by-resource \n Syntax: --search-by-resource {REALM} {RESOURCE-KEY}\n --delete \n Syntax: --delete {USER-KEY} {USER-KEY} [...]\n --delete-all \n Syntax: --delete-all {REALM}\n --delete-by-attribute \n Syntax: --delete-by-attribute {REALM} {ATTR-NAME}={ATTR-VALUE}\n -workflow.help.message=\nUsage: workflow [options]\n Options:\n --help \n --export-diagram {ANY-TYPE-KIND}\n Any type kind: ANY_OBJECT / USER / GROUP\n --export-definition {ANY-TYPE-KIND}\n Any type kind: ANY_OBJECT / USER / GROUP\n migrate.help.message=\nUsage: migrate [options]\n Options:\n --help \n --conf {SRC} {DST}\n Syncope 1.2.X content.xml SRC absolute path\n Syncope 2.0.X *Content.xml DST absolute path\n diff --git a/client/cli/src/test/java/org/apache/syncope/client/cli/util/CommandUtilsTest.java b/client/cli/src/test/java/org/apache/syncope/client/cli/util/CommandUtilsTest.java index 2b3c2ae9e3f..c75273875b0 100644 --- a/client/cli/src/test/java/org/apache/syncope/client/cli/util/CommandUtilsTest.java +++ b/client/cli/src/test/java/org/apache/syncope/client/cli/util/CommandUtilsTest.java @@ -49,6 +49,6 @@ public void fromArgs() throws Exception { public void commands() throws Exception { List commands = CommandUtils.commands(); assertFalse(commands.isEmpty()); - assertEquals(21, commands.size()); + assertEquals(20, commands.size()); } } diff --git a/client/console/NOTICE b/client/console/NOTICE index 527bc386648..a9a10ab5afa 100644 --- a/client/console/NOTICE +++ b/client/console/NOTICE @@ -1,5 +1,5 @@ Apache Syncope -Copyright 2012-2018 The Apache Software Foundation +Copyright 2012-2023 The Apache Software Foundation This product includes software developed by: The Apache Software Foundation (http://www.apache.org/). diff --git a/client/console/pom.xml b/client/console/pom.xml index 5ba1538de61..94e50102cfc 100644 --- a/client/console/pom.xml +++ b/client/console/pom.xml @@ -24,7 +24,7 @@ under the License. org.apache.syncope syncope-client - 2.1.1-SNAPSHOT + 2.1.15-SNAPSHOT Apache Syncope Client Console @@ -58,10 +58,6 @@ under the License. org.apache.wicket wicket-extensions - - org.wicketstuff - wicket-datetime - org.apache.wicket wicket-auth-roles @@ -70,6 +66,10 @@ under the License. com.googlecode.wicket-jquery-ui wicket-jquery-ui + + com.googlecode.wicket-jquery-ui + wicket-jquery-ui-theme-base + com.googlecode.wicket-jquery-ui wicket-kendo-ui @@ -94,7 +94,11 @@ under the License. de.agilecoders.wicket wicket-bootstrap-extensions - + + com.google.code.gson + gson + + org.apache.syncope.client syncope-client-lib @@ -156,6 +160,33 @@ under the License. org.apache.logging.log4j log4j-core + + org.apache.logging.log4j + log4j-slf4j-impl + + + com.lmax + disruptor + + + + + junit + junit + 4.12 + test + + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter + test + diff --git a/client/console/src/main/java/org/apache/syncope/client/console/PreferenceManager.java b/client/console/src/main/java/org/apache/syncope/client/console/PreferenceManager.java index 96374a1aca3..f5addfce7ad 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/PreferenceManager.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/PreferenceManager.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.wicket.request.Request; @@ -135,9 +136,7 @@ public void set(final Request request, final Response response, final Map { - current.put(key, StringUtils.join(values, ";")); - }); + prefs.forEach((key, values) -> current.put(key, values.stream().collect(Collectors.joining(";")))); try { COOKIE_UTILS.save(COOKIE_NAME, Base64.getEncoder().encodeToString(setPrefs(current).getBytes())); @@ -165,7 +164,7 @@ public void set(final Request request, final Response response, final String key } public void setList(final Request request, final Response response, final String key, final List values) { - set(request, response, key, StringUtils.join(values, ";")); + set(request, response, key, values.stream().collect(Collectors.joining(";"))); } public void setList(final Request request, final Response response, final Map> prefs) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java index 288c130d1c0..15e96d5f143 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.console; +import com.google.common.net.HttpHeaders; import de.agilecoders.wicket.core.Bootstrap; import de.agilecoders.wicket.core.settings.BootstrapSettings; import de.agilecoders.wicket.core.settings.IBootstrapSettings; @@ -35,6 +36,7 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.annotations.Resource; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup; import org.apache.syncope.client.console.init.ConsoleInitializer; @@ -42,16 +44,14 @@ import org.apache.syncope.client.console.pages.Dashboard; import org.apache.syncope.client.console.pages.MustChangePassword; import org.apache.syncope.client.console.pages.Login; -import org.apache.syncope.client.console.resources.FilesystemResource; -import org.apache.syncope.client.console.resources.WorkflowDefGETResource; -import org.apache.syncope.client.console.resources.WorkflowDefPUTResource; +import org.apache.syncope.client.console.panels.AnyPanel; import org.apache.syncope.client.console.themes.AdminLTE; +import org.apache.syncope.client.console.topology.Topology; import org.apache.syncope.client.lib.AnonymousAuthenticationHandler; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; import org.apache.syncope.common.lib.PropertyUtils; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.rest.api.service.DomainService; import org.apache.wicket.Page; import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession; @@ -59,11 +59,17 @@ import org.apache.wicket.authroles.authentication.AuthenticatedWebSession; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.protocol.http.CsrfPreventionRequestCycleListener; import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory; +import org.apache.wicket.protocol.ws.WebSocketAwareCsrfPreventionRequestCycleListener; +import org.apache.wicket.request.cycle.IRequestCycleListener; +import org.apache.wicket.protocol.ws.api.WebSocketResponse; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.http.WebResponse; +import org.apache.wicket.request.resource.AbstractResource; import org.apache.wicket.request.resource.IResource; import org.apache.wicket.request.resource.ResourceReference; -import org.apache.wicket.resource.DynamicJQueryResourceReference; +import org.apache.wicket.resource.JQueryResourceReference; import org.apache.wicket.util.lang.Args; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,24 +81,18 @@ public class SyncopeConsoleApplication extends AuthenticatedWebApplication { private static final String CONSOLE_PROPERTIES = "console.properties"; public static final List SUPPORTED_LOCALES = Collections.unmodifiableList(Arrays.asList( - new Locale[] { - Locale.ENGLISH, Locale.ITALIAN, new Locale("pt", "BR"), new Locale("ru"), Locale.JAPANESE - })); - - private static final String FLOWABLE_MODELER_CONTEXT = "flowable-modeler"; + Locale.ENGLISH, Locale.CANADA_FRENCH, Locale.ITALIAN, Locale.JAPANESE, new Locale("pt", "BR"), + new Locale("ru") + )); public static SyncopeConsoleApplication get() { return (SyncopeConsoleApplication) WebApplication.get(); } - private String site; - private String anonymousUser; private String anonymousKey; - private String flowableModelerDirectory; - private String reconciliationReportKey; private String scheme; @@ -109,31 +109,52 @@ public static SyncopeConsoleApplication get() { private Integer maxWaitTime; + private Integer corePoolSize; + + private Integer maxPoolSize; + + private Integer queueCapacity; + private List domains; private Map> pageClasses; + private String defaultAnyLayoutClass; + + private String defaultTopologyClass; + @SuppressWarnings("unchecked") protected void populatePageClasses(final Properties props) { Enumeration propNames = (Enumeration) props.propertyNames(); while (propNames.hasMoreElements()) { - String name = propNames.nextElement(); - if (name.startsWith("page.")) { + String className = propNames.nextElement(); + if (className.startsWith("page.")) { try { - Class clazz = ClassUtils.getClass(props.getProperty(name)); + Class clazz = ClassUtils.getClass(props.getProperty(className)); if (BasePage.class.isAssignableFrom(clazz)) { pageClasses.put( - StringUtils.substringAfter("page.", name), (Class) clazz); + StringUtils.substringAfter(className, "page."), (Class) clazz); } else { LOG.warn("{} does not extend {}, ignoring...", clazz.getName(), BasePage.class.getName()); } } catch (ClassNotFoundException e) { - LOG.error("While looking for class identified by property '{}'", name, e); + LOG.error("While looking for class identified by property '{}'", className, e); } } } } + protected void setSecurityHeaders(final Properties props, final WebResponse response) { + @SuppressWarnings("unchecked") + Enumeration propNames = (Enumeration) props.propertyNames(); + while (propNames.hasMoreElements()) { + String name = propNames.nextElement(); + if (name.startsWith("security.headers.")) { + response.setHeader(StringUtils.substringAfter(name, "security.headers."), props.getProperty(name)); + } + } + } + @Override protected void init() { super.init(); @@ -141,8 +162,6 @@ protected void init() { // read console.properties Properties props = PropertyUtils.read(getClass(), CONSOLE_PROPERTIES, "console.directory").getLeft(); - site = props.getProperty("site"); - Args.notNull(site, ""); anonymousUser = props.getProperty("anonymousUser"); Args.notNull(anonymousUser, ""); anonymousKey = props.getProperty("anonymousKey"); @@ -164,13 +183,21 @@ protected void init() { maxWaitTime = Integer.valueOf(props.getProperty("maxWaitTimeOnApplyChanges", "30")); - String csrf = props.getProperty("csrf"); + // Resource connections check thread pool size + corePoolSize = Integer.valueOf(props.getProperty("topology.corePoolSize", "5")); + maxPoolSize = Integer.valueOf(props.getProperty("topology.maxPoolSize", "10")); + queueCapacity = Integer.valueOf(props.getProperty("topology.queueCapacity", "50")); + // topology page + defaultTopologyClass = props.getProperty("page.topology", Topology.class.getName()); + // process page properties pageClasses = new HashMap<>(); populatePageClasses(props); pageClasses = Collections.unmodifiableMap(pageClasses); + defaultAnyLayoutClass = props.getProperty("default.any.panel.class", AnyPanel.class.getName()); + // Application settings IBootstrapSettings settings = new BootstrapSettings(); @@ -181,10 +208,10 @@ protected void init() { Bootstrap.install(this, settings); getResourceSettings().setUseMinifiedResources(true); + getResourceSettings().setUseDefaultOnMissingResource(true); + getResourceSettings().setThrowExceptionOnMissingResource(false); - getResourceSettings().setThrowExceptionOnMissingResource(true); - - getJavaScriptLibrarySettings().setJQueryReference(new DynamicJQueryResourceReference()); + getJavaScriptLibrarySettings().setJQueryReference(JQueryResourceReference.getV2()); getSecuritySettings().setAuthorizationStrategy(new MetaDataRoleAuthorizationStrategy(this)); @@ -196,15 +223,32 @@ protected void init() { getMarkupSettings().setStripWicketTags(true); getMarkupSettings().setCompressWhitespace(true); - if (BooleanUtils.toBoolean(csrf)) { - getRequestCycleListeners().add(new CsrfPreventionRequestCycleListener()); + if (BooleanUtils.toBoolean(props.getProperty("x-forward"))) { + XForwardedRequestWrapperFactory.Config config = new XForwardedRequestWrapperFactory.Config(); + config.setProtocolHeader(props.getProperty("x-forward.protocol.header", HttpHeaders.X_FORWARDED_PROTO)); + config.setHttpServerPort(Integer.valueOf(props.getProperty("x-forward.http.port", "80"))); + config.setHttpsServerPort(Integer.valueOf(props.getProperty("x-forward.https.port", "443"))); + + XForwardedRequestWrapperFactory factory = new XForwardedRequestWrapperFactory(); + factory.setConfig(config); + getFilterFactoryManager().add(factory); + } + + if (BooleanUtils.toBoolean(props.getProperty("csrf"))) { + getRequestCycleListeners().add(new WebSocketAwareCsrfPreventionRequestCycleListener()); } getRequestCycleListeners().add(new SyncopeConsoleRequestCycleListener()); + getRequestCycleListeners().add(new IRequestCycleListener() { - mountPage("/login", getSignInPageClass()); + @Override + public void onEndRequest(final RequestCycle cycle) { + if (cycle.getResponse() instanceof WebResponse && !(cycle.getResponse() instanceof WebSocketResponse)) { + setSecurityHeaders(props, (WebResponse) cycle.getResponse()); + } + } + }); - flowableModelerDirectory = props.getProperty("flowableModelerDirectory"); - Args.notNull(flowableModelerDirectory, ""); + mountPage("/login", getSignInPageClass()); try { reconciliationReportKey = props.getProperty("reconciliationReportKey"); @@ -213,34 +257,24 @@ protected void init() { } Args.notNull(reconciliationReportKey, ""); - mountResource("/" + FLOWABLE_MODELER_CONTEXT, new ResourceReference(FLOWABLE_MODELER_CONTEXT) { + for (Class resource : lookup.getResources()) { + Resource annotation = resource.getAnnotation(Resource.class); + try { + AbstractResource instance = resource.getDeclaredConstructor().newInstance(); - private static final long serialVersionUID = -128426276529456602L; + mountResource(annotation.path(), new ResourceReference(annotation.key()) { - @Override - public IResource getResource() { - return new FilesystemResource(FLOWABLE_MODELER_CONTEXT, flowableModelerDirectory); - } - - }); - mountResource("/workflowDefGET", new ResourceReference("workflowDefGET") { - - private static final long serialVersionUID = -128426276529456602L; - - @Override - public IResource getResource() { - return new WorkflowDefGETResource(); - } - }); - mountResource("/workflowDefPUT", new ResourceReference("workflowDefPUT") { - - private static final long serialVersionUID = -128426276529456602L; + private static final long serialVersionUID = -128426276529456602L; - @Override - public IResource getResource() { - return new WorkflowDefPUTResource(); + @Override + public IResource getResource() { + return instance; + } + }); + } catch (Exception e) { + LOG.error("Could not instantiate {}", resource.getName(), e); } - }); + } // enable component path if (getDebugSettings().isAjaxDebugModeEnabled()) { @@ -261,7 +295,7 @@ protected Class getSignInPageClass() { @Override public Class getHomePage() { return AuthenticatedWebSession.get().isSignedIn() - && SyncopeConsoleSession.get().owns(StandardEntitlement.MUST_CHANGE_PASSWORD) + && SyncopeConsoleSession.get().getSelfTO().isMustChangePassword() ? MustChangePassword.class : Dashboard.class; } @@ -270,10 +304,6 @@ public Class getPageClass(final String key) { return pageClasses.get(key); } - public String getSite() { - return site; - } - public String getAnonymousUser() { return anonymousUser; } @@ -282,10 +312,6 @@ public String getAnonymousKey() { return anonymousKey; } - public String getFlowableModelerDirectory() { - return flowableModelerDirectory; - } - public String getReconciliationReportKey() { return reconciliationReportKey; } @@ -298,9 +324,29 @@ public Integer getMaxWaitTimeInSeconds() { return maxWaitTime; } + public Integer getCorePoolSize() { + return corePoolSize; + } + + public Integer getMaxPoolSize() { + return maxPoolSize; + } + + public Integer getQueueCapacity() { + return queueCapacity; + } + + public String getDefaultAnyLayoutClass() { + return defaultAnyLayoutClass; + } + + public String getDefaultTopologyClass() { + return defaultTopologyClass; + } + public SyncopeClientFactoryBean newClientFactory() { return new SyncopeClientFactoryBean(). - setAddress(scheme + "://" + host + ":" + port + "/" + rootPath). + setAddress(scheme + "://" + host + ":" + port + StringUtils.prependIfMissing(rootPath, "/")). setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleRequestCycleListener.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleRequestCycleListener.java index 5d18bda7f55..19302a5fd7b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleRequestCycleListener.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleRequestCycleListener.java @@ -22,6 +22,7 @@ import javax.ws.rs.BadRequestException; import javax.ws.rs.ForbiddenException; import javax.xml.ws.WebServiceException; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.pages.Login; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.wicket.authorization.UnauthorizedInstantiationException; @@ -41,14 +42,6 @@ public class SyncopeConsoleRequestCycleListener implements IRequestCycleListener private static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleRequestCycleListener.class); - private static final String PAGE_EXPIRED = "Session expired: please login again"; - - private static final String MISSING_AUTHORIZATION = "Missing authorization"; - - private static final String MISSING_AUTHORIZATION_CORE = "Missing authorization while contacting Syncope core"; - - private static final String REST = "Error while contacting Syncope core"; - private Throwable instanceOf(final Exception e, final Class clazz) { return clazz.isAssignableFrom(e.getClass()) ? e @@ -68,23 +61,23 @@ public IRequestHandler onException(final RequestCycle cycle, final Exception e) IRequestablePage errorPage; if (instanceOf(e, UnauthorizedInstantiationException.class) != null) { - errorParameters.add("errorMessage", MISSING_AUTHORIZATION); + errorParameters.add("errorMessage", SyncopeConsoleSession.Error.AUTHORIZATION.fallback()); errorPage = new Login(errorParameters); } else if (instanceOf(e, AccessControlException.class) != null) { - if (instanceOf(e, AccessControlException.class).getMessage().contains("expired")) { - errorParameters.add("errorMessage", PAGE_EXPIRED); + if (StringUtils.containsIgnoreCase(instanceOf(e, AccessControlException.class).getMessage(), "expired")) { + errorParameters.add("errorMessage", SyncopeConsoleSession.Error.SESSION_EXPIRED.fallback()); } else { - errorParameters.add("errorMessage", MISSING_AUTHORIZATION_CORE); + errorParameters.add("errorMessage", SyncopeConsoleSession.Error.AUTHORIZATION.fallback()); } errorPage = new Login(errorParameters); } else if (instanceOf(e, PageExpiredException.class) != null || !SyncopeConsoleSession.get().isSignedIn()) { - errorParameters.add("errorMessage", PAGE_EXPIRED); + errorParameters.add("errorMessage", SyncopeConsoleSession.Error.SESSION_EXPIRED.fallback()); errorPage = new Login(errorParameters); } else if (instanceOf(e, BadRequestException.class) != null || instanceOf(e, WebServiceException.class) != null || instanceOf(e, SyncopeClientException.class) != null) { - errorParameters.add("errorMessage", REST); + errorParameters.add("errorMessage", SyncopeConsoleSession.Error.REST.fallback()); errorPage = new Login(errorParameters); } else { Throwable cause = instanceOf(e, ForbiddenException.class); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java index 568148cc977..0b6878ef368 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java @@ -18,31 +18,43 @@ */ package org.apache.syncope.client.console; +import java.security.AccessControlException; import java.text.DateFormat; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.stream.Collectors; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.ForbiddenException; import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.MediaType; -import org.apache.commons.collections4.list.SetUniqueList; +import javax.xml.ws.WebServiceException; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.FastDateFormat; -import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.lib.AnonymousAuthenticationHandler; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; +import org.apache.syncope.client.lib.batch.BatchRequest; +import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.info.PlatformInfo; import org.apache.syncope.common.lib.info.SystemInfo; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.rest.api.service.SyncopeService; import org.apache.wicket.Session; import org.apache.wicket.authroles.authentication.AuthenticatedWebSession; @@ -50,35 +62,64 @@ import org.apache.wicket.request.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.task.TaskRejectedException; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.CollectionUtils; public class SyncopeConsoleSession extends AuthenticatedWebSession { private static final long serialVersionUID = 747562246415852166L; - private static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleSession.class); + public enum Error { + SESSION_EXPIRED("error.session.expired", "Session expired: please login again"), + AUTHORIZATION("error.authorization", "Insufficient access rights when performing the requested operation"), + REST("error.rest", "There was an error while contacting the Core server"); - private final SyncopeClientFactoryBean clientFactory; + private final String key; - private final SyncopeClient anonymousClient; + private final String fallback; - private final PlatformInfo platformInfo; + Error(final String key, final String fallback) { + this.key = key; + this.fallback = fallback; + } + + public String key() { + return key; + } + + public String fallback() { + return fallback; + } + } + + protected static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleSession.class); + + protected final SyncopeClientFactoryBean clientFactory; + + protected final SyncopeClient anonymousClient; + + protected final PlatformInfo platformInfo; + + protected final SystemInfo systemInfo; + + protected final Map, Object> services = Collections.synchronizedMap(new HashMap<>()); - private final SystemInfo systemInfo; + protected final ThreadPoolTaskExecutor executor; - private final Map, Object> services = Collections.synchronizedMap(new HashMap<>()); + protected String domain; - private final ThreadPoolTaskExecutor executor; + protected SyncopeClient client; - private String domain; + protected UserTO selfTO; - private SyncopeClient client; + protected Map> auth; - private UserTO selfTO; + protected List delegations; - private Map> auth; + protected String delegatedBy; - private Roles roles; + protected Roles roles; public static SyncopeConsoleSession get() { return (SyncopeConsoleSession) Session.get(); @@ -88,22 +129,58 @@ public SyncopeConsoleSession(final Request request) { super(request); clientFactory = SyncopeConsoleApplication.get().newClientFactory(); - anonymousClient = clientFactory. - create(new AnonymousAuthenticationHandler( - SyncopeConsoleApplication.get().getAnonymousUser(), - SyncopeConsoleApplication.get().getAnonymousKey())); + anonymousClient = clientFactory.create(new AnonymousAuthenticationHandler( + SyncopeConsoleApplication.get().getAnonymousUser(), + SyncopeConsoleApplication.get().getAnonymousKey())); platformInfo = anonymousClient.getService(SyncopeService.class).platform(); systemInfo = anonymousClient.getService(SyncopeService.class).system(); executor = new ThreadPoolTaskExecutor(); executor.setWaitForTasksToCompleteOnShutdown(false); - executor.setCorePoolSize(5); - executor.setMaxPoolSize(10); - executor.setQueueCapacity(50); + executor.setCorePoolSize(SyncopeConsoleApplication.get().getCorePoolSize()); + executor.setMaxPoolSize(SyncopeConsoleApplication.get().getMaxPoolSize()); + executor.setQueueCapacity(SyncopeConsoleApplication.get().getQueueCapacity()); executor.initialize(); } + protected String message(final SyncopeClientException sce) { + return sce.getType().name() + ": " + sce.getElements().stream().collect(Collectors.joining(", ")); + } + + /** + * Extract and localize (if translation available) the actual message from the given exception; then, report it + * via {@link Session#error(java.io.Serializable)}. + * + * @see org.apache.syncope.client.lib.RestClientExceptionMapper + * + * @param e raised exception + */ + public void onException(final Exception e) { + Throwable root = ExceptionUtils.getRootCause(e); + String message = root.getMessage(); + + if (root instanceof SyncopeClientException) { + SyncopeClientException sce = (SyncopeClientException) root; + message = sce.isComposite() + ? sce.asComposite().getExceptions().stream().map(this::message).collect(Collectors.joining("; ")) + : message(sce); + } else if (root instanceof AccessControlException || root instanceof ForbiddenException) { + Error error = StringUtils.containsIgnoreCase(message, "expired") + ? Error.SESSION_EXPIRED + : Error.AUTHORIZATION; + message = getApplication().getResourceSettings().getLocalizer(). + getString(error.key(), null, null, null, null, error.fallback()); + } else if (root instanceof BadRequestException || root instanceof WebServiceException) { + message = getApplication().getResourceSettings().getLocalizer(). + getString(Error.REST.key(), null, null, null, null, Error.REST.fallback()); + } + + message = getApplication().getResourceSettings().getLocalizer(). + getString(message, null, null, null, null, message); + error(message); + } + public MediaType getMediaType() { return clientFactory.getContentType().getMediaType(); } @@ -113,11 +190,21 @@ public SyncopeClient getAnonymousClient() { } public void execute(final Runnable command) { - executor.execute(command); + try { + executor.execute(command); + } catch (TaskRejectedException e) { + LOG.error("Could not execute {}", command, e); + } } public Future execute(final Callable command) { - return executor.submit(command); + try { + return executor.submit(command); + } catch (TaskRejectedException e) { + LOG.error("Could not execute {}", command, e); + + return new CompletableFuture<>(); + } } public PlatformInfo getPlatformInfo() { @@ -147,7 +234,7 @@ public boolean authenticate(final String username, final String password) { try { client = clientFactory.setDomain(getDomain()).create(username, password); - refreshAuth(); + refreshAuth(username); authenticated = true; } catch (Exception e) { @@ -163,7 +250,7 @@ public boolean authenticate(final String jwt) { try { client = clientFactory.setDomain(getDomain()).create(jwt); - refreshAuth(); + refreshAuth(null); authenticated = true; } catch (Exception e) { @@ -181,6 +268,8 @@ public boolean authenticate(final String jwt) { public void cleanup() { client = null; auth = null; + delegations = null; + delegatedBy = null; selfTO = null; services.clear(); } @@ -202,28 +291,57 @@ public UserTO getSelfTO() { } public List getAuthRealms() { - List sortable = new ArrayList<>(); - List available = SetUniqueList.setUniqueList(sortable); - auth.values().forEach(entitlement -> { - available.addAll(entitlement); - }); - Collections.sort(sortable); - return sortable; + return auth.values().stream().flatMap(Set::stream).distinct().sorted().collect(Collectors.toList()); } - public boolean owns(final String entitlements) { - return owns(entitlements, SyncopeConstants.ROOT_REALM); + public List getSearchableRealms() { + Set roots = auth.get(StandardEntitlement.REALM_LIST); + return CollectionUtils.isEmpty(roots) + ? Collections.emptyList() + : roots.stream().sorted().collect(Collectors.toList()); } - public boolean owns(final String entitlements, final String realm) { + public Optional getRootRealm(final String initial) { + List searchable = getSearchableRealms(); + return searchable.isEmpty() + ? Optional.empty() + : initial != null && searchable.stream().anyMatch(initial::startsWith) + ? Optional.of(initial) + : searchable.stream().findFirst(); + } + + public boolean owns(final String entitlements, final String... realms) { if (StringUtils.isEmpty(entitlements)) { return true; } + if (auth == null) { + return false; + } + + Set requested = ArrayUtils.isEmpty(realms) + ? Collections.emptySet() + : new HashSet<>(Arrays.asList(realms)); + for (String entitlement : entitlements.split(",")) { - if (auth != null && auth.containsKey(entitlement) && (realm == null - || auth.get(entitlement).stream().anyMatch(ownedRealm -> realm.startsWith(ownedRealm)))) { - return true; + if (auth.containsKey(entitlement)) { + boolean owns = false; + + Set owned = auth.get(entitlement).stream(). + map(RealmsUtils::getFullPath).collect(Collectors.toSet()); + if (requested.isEmpty()) { + return !owned.isEmpty(); + } else { + for (String realm : requested) { + if (realm.startsWith(SyncopeConstants.ROOT_REALM)) { + owns |= owned.stream().anyMatch(ownedRealm -> realm.startsWith(ownedRealm)); + } else { + owns |= owned.contains(realm); + } + } + } + + return owns; } } @@ -240,15 +358,40 @@ public Roles getRoles() { return roles; } - public void refreshAuth() { - Pair>, UserTO> self = client.self(); - auth = self.getLeft(); - selfTO = self.getRight(); - roles = null; + public List getDelegations() { + return delegations; + } + + public String getDelegatedBy() { + return delegatedBy; + } + + public void setDelegatedBy(final String delegatedBy) { + this.delegatedBy = delegatedBy; + + this.client.delegatedBy(delegatedBy); + + refreshAuth(null); + } + + public void refreshAuth(final String username) { + try { + Triple>, List, UserTO> self = client.self(); + auth = self.getLeft(); + delegations = self.getMiddle(); + selfTO = self.getRight(); + roles = null; + } catch (ForbiddenException e) { + LOG.warn("Could not read self(), probably in a {} scenario", StandardEntitlement.MUST_CHANGE_PASSWORD, e); + + selfTO = new UserTO(); + selfTO.setUsername(username); + selfTO.setMustChangePassword(true); + } } @SuppressWarnings("unchecked") - private T getCachedService(final Class serviceClass) { + protected T getCachedService(final Class serviceClass) { T service; if (services.containsKey(serviceClass)) { service = (T) services.get(serviceClass); @@ -273,19 +416,8 @@ public T getService(final String etag, final Class serviceClass) { return serviceInstance; } - public T getService(final MediaType mediaType, final Class serviceClass) { - T service; - - synchronized (clientFactory) { - SyncopeClientFactoryBean.ContentType preType = clientFactory.getContentType(); - - clientFactory.setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString())); - service = clientFactory.create(getJWT()).getService(serviceClass); - - clientFactory.setContentType(preType); - } - - return service; + public BatchRequest batch() { + return client.batch(); } public void resetClient(final Class service) { @@ -294,7 +426,6 @@ public void resetClient(final Class service) { } public FastDateFormat getDateFormat() { - Locale locale = getLocale() == null ? Locale.ENGLISH : getLocale(); - return FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); + return FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/annotations/Resource.java b/client/console/src/main/java/org/apache/syncope/client/console/annotations/Resource.java new file mode 100644 index 00000000000..ebdba5939e1 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/annotations/Resource.java @@ -0,0 +1,41 @@ +/* + * 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.client.console.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Resource { + + /** + * @return string for {@link org.apache.wicket.request.resource.ResourceReference#ResourceReference} + */ + String key(); + + /** + * @return path for{@link org.apache.wicket.protocol.http.WebApplication#mountResource} + */ + String path(); +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/annotations/UserFormFinalize.java b/client/console/src/main/java/org/apache/syncope/client/console/annotations/UserFormFinalize.java new file mode 100644 index 00000000000..acea2bda89b --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/annotations/UserFormFinalize.java @@ -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. + */ +package org.apache.syncope.client.console.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.syncope.client.console.wizards.AjaxWizard; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface UserFormFinalize { + + AjaxWizard.Mode mode() default AjaxWizard.Mode.EDIT_APPROVAL; + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java b/client/console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java deleted file mode 100644 index c34fb007e84..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.client.console.approvals; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.commons.lang3.time.FastDateFormat; -import org.apache.syncope.client.console.commons.MapChoiceRenderer; -import org.apache.syncope.client.console.panels.MultilevelPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSpinnerFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateTimeFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; -import org.apache.syncope.common.lib.to.WorkflowFormPropertyTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.markup.html.list.ListItem; -import org.apache.wicket.markup.html.list.ListView; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.LoadableDetachableModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.PropertyModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class Approval extends Panel { - - private static final long serialVersionUID = -8847854414429745216L; - - protected static final Logger LOG = LoggerFactory.getLogger(Approval.class); - - public Approval(final PageReference pageRef, final WorkflowFormTO formTO) { - super(MultilevelPanel.FIRST_LEVEL_ID); - - IModel> formProps = new LoadableDetachableModel>() { - - private static final long serialVersionUID = 3169142472626817508L; - - @Override - protected List load() { - return formTO.getProperties(); - } - }; - - final ListView propView = new ListView("propView", formProps) { - - private static final long serialVersionUID = 9101744072914090143L; - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected void populateItem(final ListItem item) { - final WorkflowFormPropertyTO prop = item.getModelObject(); - - String label = StringUtils.isBlank(prop.getName()) ? prop.getId() : prop.getName(); - - FieldPanel field; - switch (prop.getType()) { - case Boolean: - field = new AjaxDropDownChoicePanel("value", label, new PropertyModel(prop, "value") { - - private static final long serialVersionUID = -3743432456095828573L; - - @Override - public String getObject() { - return StringUtils.isBlank(prop.getValue()) - ? null - : prop.getValue().equals("true") ? "Yes" : "No"; - } - - @Override - public void setObject(final String object) { - prop.setValue(String.valueOf(object.equalsIgnoreCase("yes"))); - } - - }, false).setChoices(Arrays.asList(new String[] { "Yes", "No" })); - break; - - case Date: - final FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern()); - field = new AjaxDateTimeFieldPanel("value", label, new PropertyModel(prop, "value") { - - private static final long serialVersionUID = -3743432456095828573L; - - @Override - public Date getObject() { - try { - if (StringUtils.isBlank(prop.getValue())) { - return null; - } else { - return formatter.parse(prop.getValue()); - } - } catch (ParseException e) { - LOG.error("Unparsable date: {}", prop.getValue(), e); - return null; - } - } - - @Override - public void setObject(final Date object) { - prop.setValue(formatter.format(object)); - } - - }, prop.getDatePattern()); - break; - - case Enum: - MapChoiceRenderer enumCR = new MapChoiceRenderer<>(prop.getEnumValues()); - - field = new AjaxDropDownChoicePanel( - "value", label, new PropertyModel(prop, "value"), false). - setChoiceRenderer(enumCR).setChoices(new Model>() { - - private static final long serialVersionUID = -858521070366432018L; - - @Override - public ArrayList getObject() { - return new ArrayList<>(prop.getEnumValues().keySet()); - } - }); - break; - - case Long: - field = new AjaxSpinnerFieldPanel.Builder().build( - "value", - label, - Long.class, - new PropertyModel(prop, "value") { - - private static final long serialVersionUID = -7688359318035249200L; - - @Override - public Long getObject() { - return StringUtils.isBlank(prop.getValue()) - ? null - : NumberUtils.toLong(prop.getValue()); - } - - @Override - public void setObject(final Long object) { - prop.setValue(String.valueOf(object)); - } - }); - break; - - case String: - default: - field = new AjaxTextFieldPanel("value", label, new PropertyModel<>(prop, "value"), false); - break; - } - - field.setReadOnly(!prop.isWritable()); - if (prop.isRequired()) { - field.addRequiredLabel(); - } - - item.add(field); - } - }; - - AjaxLink userDetails = new AjaxLink("userDetails") { - - private static final long serialVersionUID = -4804368561204623354L; - - @Override - public void onClick(final AjaxRequestTarget target) { - viewDetails(target); - } - }; - MetaDataRoleAuthorizationStrategy.authorize(userDetails, ENABLE, StandardEntitlement.USER_READ); - - boolean enabled = formTO.getUserTO() != null; - userDetails.setVisible(enabled).setEnabled(enabled); - - add(propView); - add(userDetails); - } - - protected abstract void viewDetails(final AjaxRequestTarget target); -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java deleted file mode 100644 index ec9dbabe2e1..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.client.console.approvals; - -import org.apache.syncope.client.console.layout.UserFormLayoutInfo; -import org.apache.syncope.client.console.panels.MultilevelPanel; -import org.apache.syncope.client.console.rest.AnyTypeRestClient; -import org.apache.syncope.client.console.wizards.AjaxWizard; -import org.apache.syncope.client.console.wizards.any.UserWizardBuilder; -import org.apache.syncope.common.lib.AnyOperations; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.wicket.PageReference; - -public class ApprovalDetails extends MultilevelPanel.SecondLevel { - - private static final long serialVersionUID = -8847854414429745216L; - - public ApprovalDetails(final PageReference pageRef, final WorkflowFormTO formTO) { - super(MultilevelPanel.SECOND_LEVEL_ID); - - final UserTO newUserTO; - final UserTO previousUserTO; - if (formTO.getUserPatch() == null) { - newUserTO = formTO.getUserTO(); - previousUserTO = null; - } else if (formTO.getUserTO() == null) { - // make it stronger by handling possible NPE - previousUserTO = new UserTO(); - previousUserTO.setKey(formTO.getUserPatch().getKey()); - newUserTO = AnyOperations.patch(previousUserTO, formTO.getUserPatch()); - } else { - formTO.getUserTO().setKey(formTO.getUserPatch().getKey()); - newUserTO = AnyOperations.patch(formTO.getUserTO(), formTO.getUserPatch()); - previousUserTO = formTO.getUserTO(); - } - - add(new UserWizardBuilder( - previousUserTO, - newUserTO, - new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses(), - new UserFormLayoutInfo(), - pageRef). - build(AjaxWizard.Mode.READONLY)); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java deleted file mode 100644 index 81783cda571..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * 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.client.console.approvals; - -import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; -import org.apache.syncope.client.console.panels.DirectoryPanel; -import org.apache.syncope.client.console.rest.UserWorkflowRestClient; -import org.apache.syncope.client.console.approvals.ApprovalDirectoryPanel.ApprovalProvider; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; -import org.apache.syncope.client.console.layout.UserFormLayoutInfo; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.rest.AnyTypeRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; -import org.apache.syncope.client.console.wizards.AjaxWizard; -import org.apache.syncope.client.console.wizards.any.AnyWrapper; -import org.apache.syncope.client.console.wizards.any.UserWizardBuilder; -import org.apache.syncope.common.lib.AnyOperations; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.patch.PasswordPatch; -import org.apache.syncope.common.lib.patch.UserPatch; -import org.apache.syncope.common.lib.to.ProvisioningResult; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; -import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.ResourceModel; - -public class ApprovalDirectoryPanel - extends DirectoryPanel { - - private static final long serialVersionUID = -7122136682275797903L; - - protected final BaseModal manageApprovalModal = new BaseModal("outer") { - - private static final long serialVersionUID = 389935548143327858L; - - @Override - protected void onConfigure() { - super.onConfigure(); - addSubmitButton(); - size(Modal.Size.Large); - } - - }; - - public ApprovalDirectoryPanel(final String id, final PageReference pageReference) { - super(id, pageReference, true); - disableCheckBoxes(); - setFooterVisibility(false); - modal.size(Modal.Size.Large); - - addOuterObject(manageApprovalModal); - - manageApprovalModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - ((BasePage) pageReference.getPage()).getApprovalsWidget().refreshLatestAlerts(target); - manageApprovalModal.show(false); - } - }); - - restClient = new UserWorkflowRestClient(); - - initResultTable(); - - MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.WORKFLOW_FORM_SUBMIT); - } - - @Override - protected List> getColumns() { - List> columns = new ArrayList<>(); - - columns.add(new PropertyColumn<>( - new ResourceModel("taskId"), "taskId", "taskId")); - columns.add(new PropertyColumn<>( - new ResourceModel("key"), "key", "key")); - columns.add(new PropertyColumn<>( - new ResourceModel("username"), "username", "username")); - columns.add(new DatePropertyColumn<>( - new ResourceModel("createTime"), "createTime", "createTime")); - columns.add(new DatePropertyColumn<>( - new ResourceModel("dueDate"), "dueDate", "dueDate")); - columns.add(new PropertyColumn<>(new ResourceModel("owner"), "owner", "owner")); - - return columns; - } - - @Override - public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3722207913631435501L; - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowFormTO ignore) { - claimForm(model.getObject().getTaskId()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - target.add(container); - } - }, ActionLink.ActionType.CLAIM, StandardEntitlement.WORKFLOW_FORM_CLAIM); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3722207913631435501L; - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowFormTO ignore) { - manageApprovalModal.setFormModel(new CompoundPropertyModel<>(model.getObject())); - - target.add(manageApprovalModal.setContent( - new ApprovalModal(manageApprovalModal, pageRef, model.getObject()) { - - private static final long serialVersionUID = 5546519445061007248L; - - @Override - public void onSubmit(final AjaxRequestTarget target) { - try { - super.onSubmit(target); - - ApprovalDirectoryPanel.this.getTogglePanel().close(target); - } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(getString(Constants.ERROR) + ": " + e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - - })); - - manageApprovalModal.header(new Model<>(getString("approval.manage", new Model<>(model.getObject())))); - manageApprovalModal.show(true); - } - - @Override - protected boolean statusCondition(final WorkflowFormTO modelObject) { - return SyncopeConsoleSession.get().getSelfTO().getUsername(). - equals(model.getObject().getOwner()); - } - - }, ActionLink.ActionType.MANAGE_APPROVAL, StandardEntitlement.WORKFLOW_FORM_READ); - - // SYNCOPE-1200 edit user while in approval state - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3722207913631435501L; - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowFormTO ignore) { - modal.setFormModel(new CompoundPropertyModel<>(model.getObject())); - - WorkflowFormTO formTO = model.getObject(); - UserTO newUserTO; - UserTO previousUserTO; - if (formTO.getUserPatch() == null) { - newUserTO = formTO.getUserTO(); - previousUserTO = null; - } else if (formTO.getUserTO() == null) { - // make it stronger by handling possible NPE - previousUserTO = new UserTO(); - previousUserTO.setKey(formTO.getUserPatch().getKey()); - newUserTO = AnyOperations.patch(previousUserTO, formTO.getUserPatch()); - } else { - previousUserTO = formTO.getUserTO(); - formTO.getUserTO().setKey(formTO.getUserPatch().getKey()); - formTO.getUserTO().setPassword(null); - newUserTO = AnyOperations.patch(formTO.getUserTO(), formTO.getUserPatch()); - } - - AjaxWizard.EditItemActionEvent editItemActionEvent = - new AjaxWizard.EditItemActionEvent<>(newUserTO, target); - editItemActionEvent.forceModalPanel(new ApprovalUserWizardBuilder( - model.getObject(), - previousUserTO, - newUserTO, - new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses(), - FormLayoutInfoUtils.fetch(Collections.singletonList(AnyTypeKind.USER.name())).getLeft(), - pageRef - ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)); - - send(ApprovalDirectoryPanel.this, Broadcast.EXACT, editItemActionEvent); - } - - @Override - protected boolean statusCondition(final WorkflowFormTO modelObject) { - return SyncopeConsoleSession.get().getSelfTO().getUsername(). - equals(model.getObject().getOwner()); - } - - }, ActionLink.ActionType.EDIT_APPROVAL, StandardEntitlement.WORKFLOW_FORM_SUBMIT); - - return panel; - } - - @Override - protected ApprovalProvider dataProvider() { - return new ApprovalProvider(rows); - } - - @Override - protected String paginatorRowsKey() { - return Constants.PREF_WORKFLOW_FORM_PAGINATOR_ROWS; - } - - public static class ApprovalProvider extends DirectoryDataProvider { - - private static final long serialVersionUID = -2311716167583335852L; - - private final SortableDataProviderComparator comparator; - - private final UserWorkflowRestClient restClient = new UserWorkflowRestClient(); - - public ApprovalProvider(final int paginatorRows) { - super(paginatorRows); - setSort("createTime", SortOrder.ASCENDING); - this.comparator = new SortableDataProviderComparator<>(this); - } - - @Override - public Iterator iterator(final long first, final long count) { - final List list = restClient.getForms(); - Collections.sort(list, comparator); - return list.subList((int) first, (int) first + (int) count).iterator(); - } - - @Override - public long size() { - return restClient.getForms().size(); - } - - @Override - public IModel model(final WorkflowFormTO form) { - return new IModel() { - - private static final long serialVersionUID = -2566070996511906708L; - - @Override - public WorkflowFormTO getObject() { - return form; - } - }; - } - } - - @Override - protected Collection getBulkActions() { - return Collections.emptyList(); - } - - private void claimForm(final String taskId) { - try { - restClient.claimForm(taskId); - } catch (SyncopeClientException scee) { - SyncopeConsoleSession.get().error(getString(Constants.ERROR) + ": " + scee.getMessage()); - } - } - - private class ApprovalUserWizardBuilder extends UserWizardBuilder { - - private static final long serialVersionUID = 1854981134836384069L; - - private final WorkflowFormTO formTO; - - ApprovalUserWizardBuilder( - final WorkflowFormTO formTO, - final UserTO previousUserTO, - final UserTO userTO, - final List anyTypeClasses, - final UserFormLayoutInfo formLayoutInfo, - final PageReference pageRef) { - - super(previousUserTO, userTO, anyTypeClasses, formLayoutInfo, pageRef); - this.formTO = formTO; - } - - @Override - protected Serializable onApplyInternal(final AnyWrapper modelObject) { - UserTO inner = modelObject.getInnerObject(); - - ProvisioningResult result; - - if (formTO.getUserPatch() == null) { - result = new ProvisioningResult<>(); - UserTO user = new UserWorkflowRestClient().executeTask("default", inner); - result.setEntity(user); - claimForm(restClient.getFormForUser(result.getEntity().getKey()).getTaskId()); - } else { - UserPatch patch = AnyOperations.diff(inner, formTO.getUserTO(), false); - - if (StringUtils.isNotBlank(inner.getPassword())) { - PasswordPatch passwordPatch = new PasswordPatch.Builder(). - value(inner.getPassword()).onSyncope(true).resources(inner. - getResources()). - build(); - patch.setPassword(passwordPatch); - } - // update just if it is changed - if (patch.isEmpty()) { - result = new ProvisioningResult<>(); - result.setEntity(inner); - } else { - result = userRestClient.update(getOriginalItem().getInnerObject().getETagValue(), patch); - WorkflowFormTO workFlowTO = restClient.getFormForUser(result.getEntity().getKey()); - if (workFlowTO != null) { - claimForm(workFlowTO.getTaskId()); - } - } - - } - - return result; - } - } - -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalModal.java b/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalModal.java deleted file mode 100644 index 1b1ce481ab4..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalModal.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.client.console.approvals; - -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.panels.MultilevelPanel; -import org.apache.syncope.client.console.rest.UserWorkflowRestClient; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.syncope.client.console.panels.SubmitableModalPanel; -import org.apache.syncope.client.console.panels.WizardModalPanel; - -public class ApprovalModal extends Panel implements SubmitableModalPanel, WizardModalPanel { - - private static final long serialVersionUID = -8847854414429745216L; - - private final UserWorkflowRestClient restClient = new UserWorkflowRestClient(); - - private final BaseModal modal; - - private final WorkflowFormTO formTO; - - private final PageReference pageRef; - - public ApprovalModal(final BaseModal modal, final PageReference pageRef, final WorkflowFormTO formTO) { - super(BaseModal.CONTENT_ID); - this.modal = modal; - this.formTO = formTO; - this.pageRef = pageRef; - - MultilevelPanel mlp = new MultilevelPanel("approval"); - mlp.setFirstLevel(new Approval(pageRef, formTO) { - - private static final long serialVersionUID = -2195387360323687302L; - - @Override - protected void viewDetails(final AjaxRequestTarget target) { - mlp.next(getString("approval.details"), new ApprovalDetails(pageRef, formTO), target); - } - }); - add(mlp); - } - - @Override - public void onSubmit(final AjaxRequestTarget target) { - this.restClient.submitForm(formTO); - this.modal.show(false); - this.modal.close(target); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - } - - @Override - public void onError(final AjaxRequestTarget target) { - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - - @Override - public WorkflowFormTO getItem() { - return this.formTO; - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java new file mode 100644 index 00000000000..766f6994c60 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java @@ -0,0 +1,208 @@ +/* + * 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.client.console.audit; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.StreamReadFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.io.Serializable; +import java.util.Date; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.panels.MultilevelPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.JsonDiffPanel; +import org.apache.syncope.common.lib.log.AuditEntry; +import org.apache.syncope.common.lib.to.AbstractAnnotatedBean; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.model.Model; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AuditHistoryDetails extends MultilevelPanel.SecondLevel { + + private static final long serialVersionUID = -7400543686272100483L; + + private static final Logger LOG = LoggerFactory.getLogger(AuditHistoryDetails.class); + + private static class SortingNodeFactory extends JsonNodeFactory { + + private static final long serialVersionUID = 1870252010670L; + + @Override + public ObjectNode objectNode() { + return new ObjectNode(this, new TreeMap<>()); + } + } + + private static class SortedSetJsonSerializer extends StdSerializer> { + + private static final long serialVersionUID = 3849059774309L; + + SortedSetJsonSerializer(final Class> clazz) { + super(clazz); + } + + @Override + public void serialize( + final Set set, + final JsonGenerator gen, + final SerializerProvider sp) throws IOException { + + if (set == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(); + + if (!set.isEmpty()) { + Set sorted = set; + + // create sorted set only if it itself is not already SortedSet + if (!SortedSet.class.isAssignableFrom(set.getClass())) { + Object item = set.iterator().next(); + if (Comparable.class.isAssignableFrom(item.getClass())) { + // and only if items are Comparable + sorted = new TreeSet<>(set); + } else { + LOG.debug("Cannot sort items of type {}", item.getClass()); + } + } + + for (Object item : sorted) { + gen.writeObject(item); + } + } + + gen.writeEndArray(); + } + } + + @SuppressWarnings("unchecked") + private static Class cast(final Class aClass) { + return (Class) aClass; + } + + private static final ObjectMapper MAPPER = JsonMapper.builder(). + nodeFactory(new SortingNodeFactory()).build(). + registerModule(new SimpleModule().addSerializer(new SortedSetJsonSerializer(cast(Set.class)))); + + public AuditHistoryDetails( + final MultilevelPanel mlp, + final AuditEntry selected, + final EntityTO currentEntity, + final String auditRestoreEntitlement, + final PageReference pageRef) { + + super(); + + AuditEntry current = new AuditEntry(); + if (currentEntity instanceof AbstractAnnotatedBean) { + current.setWho(((AbstractAnnotatedBean) currentEntity).getCreator()); + current.setDate(((AbstractAnnotatedBean) currentEntity).getCreationDate()); + } else { + current.setWho(SyncopeConsoleSession.get().getSelfTO().getUsername()); + current.setDate(new Date()); + } + try { + current.setBefore(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(currentEntity)); + } catch (JsonProcessingException e) { + LOG.error("While serializing current entity", e); + throw new WicketRuntimeException(e); + } + + add(new Label("current", getString("current"))); + add(new Label("previous", getString("previous"))); + + @SuppressWarnings("unchecked") + Class reference = (Class) currentEntity.getClass(); + add(new JsonDiffPanel(null, toJSON(current, reference), toJSON(selected, reference), null) { + + private static final long serialVersionUID = 2087989787864619493L; + + @Override + public void onSubmit(final AjaxRequestTarget target) { + modal.close(target); + } + }); + + AjaxLink restore = new AjaxLink("restore") { + + private static final long serialVersionUID = -817438685948164787L; + + @Override + public void onClick(final AjaxRequestTarget target) { + try { + String json = selected.getBefore() == null + ? MAPPER.readTree(selected.getOutput()).get("entity").toPrettyString() + : selected.getBefore(); + restore(json, target); + + mlp.prev(target); + } catch (JsonProcessingException e) { + throw new WicketRuntimeException(e); + } + } + }; + MetaDataRoleAuthorizationStrategy.authorize(restore, ENABLE, auditRestoreEntitlement); + add(restore); + } + + protected abstract void restore(String json, AjaxRequestTarget target); + + private Model toJSON(final AuditEntry auditEntry, final Class reference) { + try { + String content = auditEntry.getBefore() == null + ? MAPPER.readTree(auditEntry.getOutput()).get("entity").toPrettyString() + : auditEntry.getBefore(); + + T entity = MAPPER.reader(). + with(StreamReadFeature.STRICT_DUPLICATE_DETECTION). + readValue(content, reference); + if (entity instanceof UserTO) { + UserTO userTO = (UserTO) entity; + userTO.setPassword(null); + userTO.setSecurityAnswer(null); + } + + return Model.of(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(entity)); + } catch (Exception e) { + LOG.error("While (de)serializing entity {}", auditEntry, e); + throw new WicketRuntimeException(e); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java new file mode 100644 index 00000000000..13ec6a22631 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java @@ -0,0 +1,199 @@ +/* + * 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.client.console.audit; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.panels.AjaxDataTablePanel; +import org.apache.syncope.client.console.panels.DirectoryPanel; +import org.apache.syncope.client.console.panels.ModalPanel; +import org.apache.syncope.client.console.panels.MultilevelPanel; +import org.apache.syncope.client.console.rest.LoggerRestClient; +import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.common.lib.log.AuditEntry; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.StringResourceModel; + +public abstract class AuditHistoryDirectoryPanel extends DirectoryPanel< + AuditEntry, AuditEntry, AuditHistoryDirectoryPanel.AuditHistoryProvider, LoggerRestClient> + implements ModalPanel { + + private static final long serialVersionUID = -8248734710505211261L; + + private static final List EVENTS = Arrays.asList("create", "update"); + + private static final SortParam REST_SORT = new SortParam<>("event_date", false); + + private final BaseModal baseModal; + + private final MultilevelPanel mlp; + + private final AuditElements.EventCategoryType type; + + private final String category; + + private final EntityTO entity; + + private final String auditRestoreEntitlement; + + public AuditHistoryDirectoryPanel( + final BaseModal baseModal, + final MultilevelPanel mlp, + final AuditElements.EventCategoryType type, + final String category, + final EntityTO entity, + final String auditRestoreEntitlement, + final PageReference pageRef) { + + super(MultilevelPanel.FIRST_LEVEL_ID, pageRef); + disableCheckBoxes(); + + this.baseModal = baseModal; + this.mlp = mlp; + this.type = type; + this.category = category; + this.entity = entity; + this.auditRestoreEntitlement = auditRestoreEntitlement; + this.pageRef = pageRef; + + this.restClient = new LoggerRestClient(); + initResultTable(); + } + + @Override + protected AuditHistoryDirectoryPanel.AuditHistoryProvider dataProvider() { + return new AuditHistoryProvider(rows); + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_AUDIT_HISTORY_PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + List> columns = new ArrayList<>(); + columns.add(new PropertyColumn<>(new StringResourceModel("who", this), "who")); + columns.add(new DatePropertyColumn<>(new StringResourceModel("date", this), null, "date")); + return columns; + } + + @Override + protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder resultTableBuilder) { + resultTableBuilder.setMultiLevelPanel(baseModal, mlp); + } + + protected abstract void restore(String json, AjaxRequestTarget target); + + @Override + protected ActionsPanel getActions(final IModel model) { + final ActionsPanel panel = super.getActions(model); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -6745431735457245600L; + + @Override + public void onClick(final AjaxRequestTarget target, final AuditEntry modelObject) { + AuditHistoryDirectoryPanel.this.getTogglePanel().close(target); + + mlp.next( + new StringResourceModel("audit.diff.view", AuditHistoryDirectoryPanel.this).getObject(), + new AuditHistoryDetails( + mlp, + modelObject, + entity, + auditRestoreEntitlement, + pageRef) { + + private static final long serialVersionUID = -5311898419151367494L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + AuditHistoryDirectoryPanel.this.restore(json, target); + } + }, target); + + target.add(modal); + } + }, ActionLink.ActionType.VIEW, StandardEntitlement.AUDIT_READ); + + return panel; + } + + @Override + protected Collection getBatches() { + return Collections.emptyList(); + } + + protected class AuditHistoryProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 415113175628260864L; + + AuditHistoryProvider(final int paginatorRows) { + super(paginatorRows); + setSort("date", SortOrder.DESCENDING); + } + + @Override + public long size() { + return restClient.count(entity.getKey(), type, category, EVENTS, AuditElements.Result.SUCCESS); + } + + @Override + public Iterator iterator(final long first, final long count) { + int page = ((int) first / paginatorRows); + return restClient.search( + entity.getKey(), + (page < 0 ? 0 : page) + 1, + paginatorRows, + type, + category, + EVENTS, + AuditElements.Result.SUCCESS, + REST_SORT). + iterator(); + } + + @Override + public IModel model(final AuditEntry auditEntryBean) { + return new CompoundPropertyModel<>(auditEntryBean); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java new file mode 100644 index 00000000000..8ec4b18fb67 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java @@ -0,0 +1,65 @@ +/* + * 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.client.console.audit; + +import org.apache.syncope.client.console.panels.ModalPanel; +import org.apache.syncope.client.console.panels.MultilevelPanel; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.panel.Panel; + +public abstract class AuditHistoryModal extends Panel implements ModalPanel { + + private static final long serialVersionUID = 1066124171682570080L; + + public AuditHistoryModal( + final BaseModal baseModal, + final AuditElements.EventCategoryType type, + final String category, + final T entity, + final String auditRestoreEntitlement, + final PageReference pageRef) { + + super(BaseModal.CONTENT_ID); + + MultilevelPanel mlp = new MultilevelPanel("history"); + mlp.setOutputMarkupId(true); + add(mlp.setFirstLevel(new AuditHistoryDirectoryPanel( + baseModal, + mlp, + type, + category, + entity, + auditRestoreEntitlement, + pageRef) { + + private static final long serialVersionUID = 1952220682903768286L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + AuditHistoryModal.this.restore(json, target); + } + })); + } + + protected abstract void restore(String json, AjaxRequestTarget target); +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java b/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java new file mode 100644 index 00000000000..be473f1d148 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java @@ -0,0 +1,432 @@ +/* + * 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.client.console.batch; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.cxf.helpers.CastUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.status.StatusBean; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.MultilevelPanel; +import org.apache.syncope.client.console.rest.AbstractAnyRestClient; +import org.apache.syncope.client.console.rest.AnyObjectRestClient; +import org.apache.syncope.client.console.rest.GroupRestClient; +import org.apache.syncope.client.console.rest.RestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BatchResponseColumn; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.lib.batch.BatchRequest; +import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem; +import org.apache.syncope.common.lib.patch.StatusPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.ExecTO; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.ReportTO; +import org.apache.syncope.common.lib.to.TaskTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.ExecStatus; +import org.apache.syncope.common.lib.types.ResourceAssociationAction; +import org.apache.syncope.common.lib.types.ResourceDeassociationAction; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.syncope.common.lib.types.StatusPatchType; +import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.common.rest.api.beans.ExecSpecs; +import org.apache.syncope.common.rest.api.service.AnyObjectService; +import org.apache.syncope.common.rest.api.service.AnyService; +import org.apache.syncope.common.rest.api.service.GroupService; +import org.apache.syncope.common.rest.api.service.ReportService; +import org.apache.syncope.common.rest.api.service.TaskService; +import org.apache.syncope.common.rest.api.service.UserService; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +public class BatchContent extends MultilevelPanel.SecondLevel { + + private static final long serialVersionUID = 4114026480146090963L; + + private static final Logger LOG = LoggerFactory.getLogger(BatchContent.class); + + private WebMarkupContainer container; + + private ActionsPanel actionPanel; + + private SortableDataProvider dataProvider; + + public BatchContent( + final BaseModal modal, + final List items, + final List> columns, + final Collection actions, + final RestClient batchExecutor, + final String keyFieldName) { + + this(MultilevelPanel.SECOND_LEVEL_ID, modal, items, columns, actions, batchExecutor, keyFieldName); + } + + public BatchContent( + final String id, + final BaseModal modal, + final List items, + final List> columns, + final Collection actions, + final RestClient batchExecutor, + final String keyFieldName) { + + super(id); + + setup(items, columns); + + for (ActionLink.ActionType action : actions) { + actionPanel.add(new ActionLink() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + protected boolean statusCondition(final Serializable modelObject) { + return !CollectionUtils.isEmpty(items); + } + + @Override + @SuppressWarnings("unchecked") + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + if (CollectionUtils.isEmpty(items)) { + throw new IllegalArgumentException("Invalid items"); + } + + Map results; + try { + T singleItem = items.iterator().next(); + + if (singleItem instanceof ExecTO) { + results = new HashMap<>(); + items.forEach(item -> { + ExecTO exec = ExecTO.class.cast(item); + + try { + batchExecutor.getClass().getMethod("deleteExecution", String.class). + invoke(batchExecutor, exec.getKey()); + results.put(exec.getKey(), ExecStatus.SUCCESS.name()); + } catch (Exception e) { + LOG.error("Error deleting execution {}", exec.getKey(), e); + results.put(exec.getKey(), ExecStatus.FAILURE.name()); + } + }); + } else if (singleItem instanceof StatusBean) { + AbstractAnyRestClient anyRestClient = AbstractAnyRestClient.class.cast(batchExecutor); + + // Group bean information by anyKey + Map> beans = new HashMap<>(); + items.stream().map(bean -> StatusBean.class.cast(bean)). + forEachOrdered(sb -> { + final List sblist; + if (beans.containsKey(sb.getKey())) { + sblist = beans.get(sb.getKey()); + } else { + sblist = new ArrayList<>(); + beans.put(sb.getKey(), sblist); + } + sblist.add(sb); + }); + + results = new HashMap<>(); + beans.forEach((key, value) -> { + String etag = anyRestClient.read(key).getETagValue(); + + switch (action) { + case DEPROVISION: + results.putAll(anyRestClient.deassociate( + ResourceDeassociationAction.DEPROVISION, etag, key, value)); + break; + + case UNASSIGN: + results.putAll(anyRestClient.deassociate( + ResourceDeassociationAction.UNASSIGN, etag, key, value)); + break; + + case UNLINK: + results.putAll(anyRestClient.deassociate( + ResourceDeassociationAction.UNLINK, etag, key, value)); + break; + + case ASSIGN: + results.putAll(anyRestClient.associate( + ResourceAssociationAction.ASSIGN, etag, key, value)); + break; + + case LINK: + results.putAll(anyRestClient.associate( + ResourceAssociationAction.LINK, etag, key, value)); + break; + + case PROVISION: + results.putAll(anyRestClient.associate( + ResourceAssociationAction.PROVISION, etag, key, value)); + break; + + case SUSPEND: + results.putAll(((UserRestClient) anyRestClient).suspend(etag, key, value)); + break; + + case REACTIVATE: + results.putAll(((UserRestClient) anyRestClient).reactivate(etag, key, value)); + break; + + default: + } + }); + } else { + BatchRequest batch = SyncopeConsoleSession.get().batch(); + + UserService batchUserService = batch.getService(UserService.class); + GroupService batchGroupService = batch.getService(GroupService.class); + AnyObjectService batchAnyObjectService = batch.getService(AnyObjectService.class); + AnyService batchAnyService = singleItem instanceof UserTO + ? batchUserService + : singleItem instanceof GroupTO + ? batchGroupService + : batchAnyObjectService; + TaskService batchTaskService = batch.getService(TaskService.class); + ReportService batchReportService = batch.getService(ReportService.class); + + Set deletedAnys = new HashSet<>(); + + switch (action) { + case MUSTCHANGEPASSWORD: + items.forEach(item -> { + UserTO user = (UserTO) item; + + UserPatch patch = new UserPatch(); + patch.setKey(user.getKey()); + patch.setMustChangePassword(new BooleanReplacePatchItem.Builder(). + value(!user.isMustChangePassword()).build()); + + batchUserService.update(patch); + }); + break; + + case SUSPEND: + items.forEach(item -> { + UserTO user = (UserTO) item; + + StatusPatch patch = new StatusPatch.Builder(). + key(user.getKey()). + type(StatusPatchType.SUSPEND). + onSyncope(true). + resources(user.getResources()). + build(); + + batchUserService.status(patch); + }); + break; + + case REACTIVATE: + items.forEach(item -> { + UserTO user = (UserTO) item; + + StatusPatch patch = new StatusPatch.Builder(). + key(user.getKey()). + type(StatusPatchType.REACTIVATE). + onSyncope(true). + resources(user.getResources()). + build(); + + batchUserService.status(patch); + }); + break; + + case DELETE: + items.forEach(item -> { + if (singleItem instanceof AnyTO) { + AnyTO any = (AnyTO) item; + + batchAnyService.delete(any.getKey()); + deletedAnys.add(any.getKey()); + } else if (singleItem instanceof TaskTO) { + TaskTO task = (TaskTO) item; + + batchTaskService.delete( + TaskType.fromTOClass(task.getClass()), + task.getKey()); + } else if (singleItem instanceof ReportTO) { + ReportTO report = (ReportTO) item; + + batchReportService.delete(report.getKey()); + } else { + LOG.warn("Unsupported for DELETE: {}", singleItem.getClass().getName()); + } + }); + + break; + + case DRYRUN: + items.forEach(item -> { + TaskTO task = (TaskTO) item; + + batchTaskService.execute( + new ExecSpecs.Builder().dryRun(true).key(task.getKey()).build()); + }); + break; + + case EXECUTE: + items.forEach(item -> { + if (singleItem instanceof TaskTO) { + TaskTO task = (TaskTO) item; + + batchTaskService.execute(new ExecSpecs.Builder(). + dryRun(false).key(task.getKey()).build()); + } else if (singleItem instanceof ReportTO) { + ReportTO report = (ReportTO) item; + + batchReportService.execute(new ExecSpecs.Builder(). + key(report.getKey()).build()); + } + }); + break; + + default: + } + + results = CastUtils.cast(Map.class.cast( + batchExecutor.getClass().getMethod("batch", + BatchRequest.class).invoke(batchExecutor, batch))); + + if (singleItem instanceof AnyTO) { + AbstractAnyRestClient anyRestClient = singleItem instanceof UserTO + ? UserRestClient.class.cast(batchExecutor) + : singleItem instanceof GroupTO + ? GroupRestClient.class.cast(batchExecutor) + : AnyObjectRestClient.class.cast(batchExecutor); + for (int i = 0; i < items.size(); i++) { + String key = ((AnyTO) items.get(i)).getKey(); + if (!deletedAnys.contains(key)) { + items.set(i, (T) anyRestClient.read(key)); + } + } + } + } + + List> newColumnList = new ArrayList<>(columns); + newColumnList.add(newColumnList.size(), new BatchResponseColumn<>(results, keyFieldName)); + + container.addOrReplace(new AjaxFallbackDefaultDataTable<>( + "selectedObjects", + newColumnList, + dataProvider, + Integer.MAX_VALUE).setVisible(!items.isEmpty())); + + actionPanel.setEnabled(false); + actionPanel.setVisible(false); + target.add(container); + target.add(actionPanel); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("Batch failure", e); + SyncopeConsoleSession.get().error("Operation " + action.getActionId() + " failed"); + } + ((BasePage) getPage()).getNotificationPanel().refresh(target); + } + }, action, StandardEntitlement.CONFIGURATION_LIST).hideLabel(); + } + } + + public BatchContent( + final String id, + final BaseModal modal, + final List items, + final List> columns, + final Map results, + final String keyFieldName, + final AjaxRequestTarget target, + final PageReference pageRef) { + + super(id); + + List> newColumnList = new ArrayList<>(columns); + newColumnList.add(newColumnList.size(), new BatchResponseColumn<>(results, keyFieldName)); + setup(items, newColumnList); + + actionPanel.setEnabled(false); + actionPanel.setVisible(false); + target.add(container); + target.add(actionPanel); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + + private void setup( + final List items, + final List> columns) { + + container = new WebMarkupContainer("container"); + container.setOutputMarkupId(true); + add(container); + + dataProvider = new SortableDataProvider() { + + private static final long serialVersionUID = 5291903859908641954L; + + @Override + public Iterator iterator(final long first, final long count) { + return items.iterator(); + } + + @Override + public long size() { + return items.size(); + } + + @Override + public IModel model(final T object) { + return new CompoundPropertyModel<>(object); + } + }; + + container.add(new AjaxFallbackDefaultDataTable<>( + "selectedObjects", + columns, + dataProvider, + Integer.MAX_VALUE).setMarkupId("selectedObjects").setVisible(!CollectionUtils.isEmpty(items))); + + actionPanel = new ActionsPanel<>("actions", null); + container.add(actionPanel); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchModal.java b/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchModal.java new file mode 100644 index 00000000000..0a25f73113d --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchModal.java @@ -0,0 +1,64 @@ +/* + * 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.client.console.batch; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.syncope.client.console.panels.AbstractModalPanel; +import org.apache.syncope.client.console.rest.RestClient; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; + +public class BatchModal extends AbstractModalPanel { + + private static final long serialVersionUID = 4114026480146090962L; + + public BatchModal( + final BaseModal modal, + final PageReference pageRef, + final List items, + final List> columns, + final Collection actions, + final RestClient batchExecutor, + final String keyFieldName) { + + super(modal, pageRef); + add(new BatchContent<>("content", modal, items, columns, actions, batchExecutor, keyFieldName). + setRenderBodyOnly(true)); + } + + public BatchModal( + final BaseModal modal, + final PageReference pageRef, + final List items, + final List> columns, + final Map results, + final String keyFieldName, + final AjaxRequestTarget target) { + + super(modal, pageRef); + add(new BatchContent<>("content", modal, items, columns, results, keyFieldName, target, pageRef). + setRenderBodyOnly(true)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkActionModal.java b/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkActionModal.java deleted file mode 100644 index 17cf48d3abe..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkActionModal.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.client.console.bulk; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import org.apache.syncope.client.console.panels.AbstractModalPanel; -import org.apache.syncope.client.console.rest.RestClient; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.wicket.PageReference; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; - -public class BulkActionModal extends AbstractModalPanel { - - private static final long serialVersionUID = 4114026480146090962L; - - public BulkActionModal( - final BaseModal modal, - final PageReference pageRef, - final Collection items, - final List> columns, - final Collection actions, - final RestClient bulkActionExecutor, - final String keyFieldName) { - - super(modal, pageRef); - add(new BulkContent<>("content", modal, items, columns, actions, bulkActionExecutor, keyFieldName). - setRenderBodyOnly(true)); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java b/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java deleted file mode 100644 index 552b843073f..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.client.console.bulk; - -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.commons.status.StatusBean; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.panels.MultilevelPanel; -import org.apache.syncope.client.console.rest.AbstractAnyRestClient; -import org.apache.syncope.client.console.rest.RestClient; -import org.apache.syncope.client.console.rest.UserRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BulkActionResultColumn; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; -import org.apache.syncope.common.lib.to.BulkAction; -import org.apache.syncope.common.lib.to.BulkActionResult; -import org.apache.syncope.common.lib.to.ExecTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.syncope.common.lib.to.BulkActionResult.Status; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; - -public class BulkContent extends MultilevelPanel.SecondLevel { - - private static final long serialVersionUID = 4114026480146090963L; - - protected static final Logger LOG = LoggerFactory.getLogger(BulkContent.class); - - public BulkContent( - final BaseModal modal, - final Collection items, - final List> columns, - final Collection actions, - final RestClient bulkActionExecutor, - final String keyFieldName) { - - this(MultilevelPanel.SECOND_LEVEL_ID, modal, items, columns, actions, bulkActionExecutor, keyFieldName); - } - - public BulkContent( - final String id, - final BaseModal modal, - final Collection items, - final List> columns, - final Collection actions, - final RestClient bulkActionExecutor, - final String keyFieldName) { - - super(id); - - final WebMarkupContainer container = new WebMarkupContainer("container"); - container.setOutputMarkupId(true); - add(container); - - final SortableDataProvider dataProvider = new SortableDataProvider() { - - private static final long serialVersionUID = 5291903859908641954L; - - @Override - public Iterator iterator(final long first, final long count) { - return items.iterator(); - } - - @Override - public long size() { - return items.size(); - } - - @Override - public IModel model(final T object) { - return new CompoundPropertyModel<>(object); - } - }; - - container.add(new AjaxFallbackDefaultDataTable<>( - "selectedObjects", - columns, - dataProvider, - Integer.MAX_VALUE).setMarkupId("selectedObjects").setVisible(items != null && !items.isEmpty())); - - final ActionsPanel actionPanel = new ActionsPanel<>("actions", null); - container.add(actionPanel); - - for (ActionLink.ActionType action : actions) { - final ActionType actionToBeAddresed = action; - - actionPanel.add(new ActionLink() { - - private static final long serialVersionUID = -3722207913631435501L; - - @Override - protected boolean statusCondition(final Serializable modelObject) { - return items != null && !items.isEmpty(); - } - - @Override - public void onClick(final AjaxRequestTarget target, final Serializable ignore) { - try { - if (items == null || items.isEmpty()) { - throw new IllegalArgumentException("Invalid items"); - } - - BulkActionResult res = null; - try { - T singleItem = items.iterator().next(); - if (singleItem instanceof StatusBean) { - throw new IllegalArgumentException("Invalid items"); - } - - if (singleItem instanceof ExecTO) { - res = new BulkActionResult(); - final Map results = res.getResults(); - items.forEach(item -> { - ExecTO exec = ExecTO.class.cast(item); - String key = exec.getKey(); - - try { - bulkActionExecutor.getClass().getMethod("deleteExecution", - String.class).invoke(bulkActionExecutor, exec.getKey()); - results.put(String.valueOf(key), BulkActionResult.Status.SUCCESS); - } catch (Exception e) { - LOG.error("Error deleting execution {} of task {}", exec.getKey(), key, e); - results.put(String.valueOf(key), BulkActionResult.Status.FAILURE); - } - }); - } else { - final BulkAction bulkAction = new BulkAction(); - bulkAction.setType(BulkAction.Type.valueOf(actionToBeAddresed.name())); - items.forEach(item -> { - try { - bulkAction.getTargets().add(getTargetId(item, keyFieldName).toString()); - } catch (IllegalAccessException | InvocationTargetException e) { - LOG.error("Error retrieving item id {}", keyFieldName, e); - } - }); - res = BulkActionResult.class.cast( - bulkActionExecutor.getClass().getMethod("bulkAction", - BulkAction.class).invoke(bulkActionExecutor, bulkAction)); - } - - } catch (IllegalArgumentException biae) { - if (!(items.iterator().next() instanceof StatusBean)) { - throw new IllegalArgumentException("Invalid items"); - } - - if (!(bulkActionExecutor instanceof AbstractAnyRestClient)) { - throw new IllegalArgumentException("Invalid bulk action executor"); - } - - final AbstractAnyRestClient anyRestClient = AbstractAnyRestClient.class. - cast(bulkActionExecutor); - - // Group bean information by anyKey - final Map> beans = new HashMap<>(); - items.stream().map(bean -> StatusBean.class.cast(bean)). - forEachOrdered(sb -> { - final List sblist; - if (beans.containsKey(sb.getKey())) { - sblist = beans.get(sb.getKey()); - } else { - sblist = new ArrayList<>(); - beans.put(sb.getKey(), sblist); - } - sblist.add(sb); - }); - - for (Map.Entry> entry : beans.entrySet()) { - final String etag = anyRestClient.read(entry.getKey()).getETagValue(); - switch (actionToBeAddresed) { - case DEPROVISION: - res = anyRestClient.deprovision(etag, entry.getKey(), entry.getValue()); - break; - case UNASSIGN: - res = anyRestClient.unassign(etag, entry.getKey(), entry.getValue()); - break; - case UNLINK: - res = anyRestClient.unlink(etag, entry.getKey(), entry.getValue()); - break; - case ASSIGN: - res = anyRestClient.assign(etag, entry.getKey(), entry.getValue()); - break; - case LINK: - res = anyRestClient.link(etag, entry.getKey(), entry.getValue()); - break; - case PROVISION: - res = anyRestClient.provision(etag, entry.getKey(), entry.getValue()); - break; - case REACTIVATE: - res = ((UserRestClient) anyRestClient). - reactivate(etag, entry.getKey(), entry.getValue()); - break; - case SUSPEND: - res = ((UserRestClient) anyRestClient). - suspend(etag, entry.getKey(), entry.getValue()); - break; - default: - } - } - } - - if (modal != null) { - modal.changeCloseButtonLabel(getString("close", null, "Close"), target); - } - - final List> newColumnList = new ArrayList<>(columns); - newColumnList.add(newColumnList.size(), new BulkActionResultColumn<>(res, keyFieldName)); - - container.addOrReplace(new AjaxFallbackDefaultDataTable<>( - "selectedObjects", - newColumnList, - dataProvider, - Integer.MAX_VALUE).setVisible(!items.isEmpty())); - - actionPanel.setEnabled(false); - actionPanel.setVisible(false); - target.add(container); - target.add(actionPanel); - - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - } catch (IllegalArgumentException | NoSuchMethodException | SecurityException - | IllegalAccessException | InvocationTargetException e) { - LOG.error("Bulk action failure", e); - SyncopeConsoleSession.get().error( - "Operation " + actionToBeAddresed.getActionId() + " not supported"); - } - ((BasePage) getPage()).getNotificationPanel().refresh(target); - } - }, action, StandardEntitlement.CONFIGURATION_LIST).hideLabel(); - } - } - - private Object getTargetId(final Object target, final String idFieldName) - throws IllegalAccessException, InvocationTargetException { - - return BeanUtils.getPropertyDescriptor(target.getClass(), idFieldName). - getReadMethod().invoke(target, new Object[0]); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java index a13927b2d9a..f27c32af620 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java @@ -41,19 +41,21 @@ public class AnyDataProvider extends DirectoryDataProvider { protected static final Logger LOG = LoggerFactory.getLogger(AnyDataProvider.class); - private final SortableAnyProviderComparator comparator; + protected final SortableAnyProviderComparator comparator; - private final AbstractAnyRestClient restClient; + protected final AbstractAnyRestClient restClient; protected String fiql; protected final boolean filtered; - private final String realm; + protected final String realm; - private final String type; + protected final String type; - private final PageReference pageRef; + protected final PageReference pageRef; + + protected int currentPage; public AnyDataProvider( final AbstractAnyRestClient restClient, @@ -80,7 +82,7 @@ public AnyDataProvider( break; default: - setSort("key", SortOrder.ASCENDING); + setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING); } this.comparator = new SortableAnyProviderComparator<>(this); @@ -95,18 +97,21 @@ public Iterator iterator(final long first, final long count) { List result = Collections.emptyList(); try { - final int page = ((int) first / paginatorRows); + currentPage = ((int) first / paginatorRows); + if (currentPage < 0) { + currentPage = 0; + } if (filtered) { result = fiql == null ? Collections.emptyList() - : restClient.search(realm, fiql, (page < 0 ? 0 : page) + 1, paginatorRows, getSort(), type); + : restClient.search(realm, fiql, currentPage + 1, paginatorRows, getSort(), type); } else { - result = restClient.search(realm, null, (page < 0 ? 0 : page) + 1, paginatorRows, getSort(), type); + result = restClient.search(realm, null, currentPage + 1, paginatorRows, getSort(), type); } } catch (Exception e) { LOG.error("While searching with FIQL {}", fiql, e); - SyncopeConsoleSession.get().error(e.getMessage()); + SyncopeConsoleSession.get().onException(e); Optional target = RequestCycle.get().find(AjaxRequestTarget.class); if (target.isPresent()) { @@ -114,7 +119,6 @@ public Iterator iterator(final long first, final long count) { } } - Collections.sort(result, comparator); return result.iterator(); } @@ -130,12 +134,10 @@ public long size() { } } catch (Exception e) { LOG.error("While requesting for size() with FIQL {}", fiql, e); - SyncopeConsoleSession.get().error(e.getMessage()); + SyncopeConsoleSession.get().onException(e); - Optional target = RequestCycle.get().find(AjaxRequestTarget.class); - if (target.isPresent()) { - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target.get()); - } + RequestCycle.get().find(AjaxRequestTarget.class). + ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target)); } return result; @@ -150,4 +152,8 @@ public AnyDataProvider setFIQL(final String fiql) { public IModel model(final A object) { return new CompoundPropertyModel<>(object); } + + public int getCurrentPage() { + return currentPage; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java new file mode 100644 index 00000000000..f57650ef3f9 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java @@ -0,0 +1,152 @@ +/* + * 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.client.console.commons; + +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.ConnectorRestClient; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class ConnectorDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 3122389673525690470L; + + protected static final Logger LOG = LoggerFactory.getLogger(ConnectorDataProvider.class); + + private final PageReference pageRef; + + protected int currentPage; + + private String keyword; + + private final ConnectorRestClient restClient = new ConnectorRestClient(); + + public ConnectorDataProvider( + final int paginatorRows, + final PageReference pageRef, + final String keyword) { + super(paginatorRows); + setSort("displayNameSortParam", SortOrder.ASCENDING); + this.pageRef = pageRef; + this.keyword = keyword; + } + + @Override + public Iterator iterator(final long first, final long count) { + List result = Collections.emptyList(); + + try { + currentPage = ((int) first / paginatorRows); + if (currentPage < 0) { + currentPage = 0; + } + if (StringUtils.isBlank(keyword)) { + result = restClient.getAllConnectors(); + } else { + result = restClient.getAllConnectors().stream().filter(conn -> + conn.getDisplayName().toLowerCase().contains(keyword)).collect(Collectors.toList()); + } + } catch (Exception e) { + LOG.error("While searching", e); + SyncopeConsoleSession.get().onException(e); + + Optional target = RequestCycle.get().find(AjaxRequestTarget.class); + target.ifPresent(ajaxRequestTarget -> + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(ajaxRequestTarget)); + } + + SortParam sortParam = getSort(); + if (sortParam != null) { + result.sort(getComparator(sortParam)); + } + + return result.subList((int) first, (int) first + (int) count).iterator(); + } + + private Comparator getComparator(final SortParam sortParam) { + Comparator comparator; + + switch (sortParam.getProperty()) { + case "displayNameSortParam": + comparator = Comparator.nullsFirst(Comparator.comparing( + item -> item.getDisplayName().toLowerCase())); + break; + case "connectorNameSortParam": + comparator = Comparator.nullsFirst(Comparator.comparing( + item -> item.getConnectorName().toLowerCase())); + break; + default: + throw new IllegalStateException("The sort param " + sortParam.getProperty() + " is not correct"); + } + + if (!sortParam.isAscending()) { + comparator = comparator.reversed(); + } + + return comparator; + } + + @Override + public long size() { + long result = 0; + + try { + if (StringUtils.isBlank(keyword)) { + result = restClient.getAllConnectors().size(); + } else { + result = restClient.getAllConnectors().stream().filter(conn -> + conn.getDisplayName().toLowerCase().contains(keyword)).count(); + } + } catch (Exception e) { + LOG.error("While requesting for size()", e); + SyncopeConsoleSession.get().onException(e); + + RequestCycle.get().find(AjaxRequestTarget.class). + ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target)); + } + + return result; + } + + @Override + public IModel model(final Serializable object) { + return new CompoundPropertyModel<>((ConnInstanceTO) object); + } + + public int getCurrentPage() { + return currentPage; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java index 01ea08a01a3..f57ab76e873 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java @@ -29,14 +29,14 @@ public final class Constants { public static final String SYNCOPE = "syncope"; + public static final String UNKNOWN = "UNKNOWN"; + public static final String ROLE_AUTHENTICATED = "AUTHENTICATED"; public static final String MENU_COLLAPSE = "MENU_COLLAPSE"; public static final String VEIL_INDICATOR_MARKUP_ID = "veil"; - public static final String FLOWABLE_MODELER_CONTEXT = "flowable-modeler"; - public static final String MODELER_CONTEXT = "modelerContext"; public static final String MODEL_ID_PARAM = "modelId"; @@ -57,6 +57,10 @@ public final class Constants { public static final String ON_CHANGE = "change"; + public static final String ON_KEYUP = "keyup"; + + public static final String ON_KEYDOWN = "keydown"; + public static final String ON_BLUR = "blur"; public static final String PNG_EXT = ".png"; @@ -73,6 +77,12 @@ public final class Constants { public static final String ERROR = "error"; + public static final String OUTER = "outer"; + + public static final String ACTION = "action"; + + public static final String CONFIRM_DELETE = "confirmDelete"; + public static final String BEFORE_LOGOUT_PAGE = "beforeLogoutPage"; public static final String PARAM_PASSWORD_RESET_TOKEN = "pwdResetToken"; @@ -123,12 +133,16 @@ public final class Constants { public static final String PREF_ACCESS_TOKEN_PAGINATOR_ROWS = "accessToken.paginator.rows"; - public static final String PREF_WORKFLOW_FORM_PAGINATOR_ROWS = "workflow.paginator.rows"; - public static final String PREF_REMEDIATION_PAGINATOR_ROWS = "remediation.paginator.rows"; public static final String PREF_RESOURCES_PAGINATOR_ROWS = "resources.paginator.rows"; + public static final String PREF_RECONCILIATION_PAGINATOR_ROWS = "reconciliation.paginator.rows"; + + public static final String PREF_RESOURCE_STATUS_PAGINATOR_ROWS = "resource.status.paginator.rows"; + + public static final String PREF_AUDIT_HISTORY_PAGINATOR_ROWS = "audit.history.paginator.rows"; + public static final String PREF_CONNECTORS_PAGINATOR_ROWS = "connectors.paginator.rows"; public static final String PREF_NOTIFICATION_PAGINATOR_ROWS = "notification.paginator.rows"; @@ -137,6 +151,8 @@ public final class Constants { public static final String PREF_MAIL_TEMPLATE_PAGINATOR_ROWS = "mail.template.paginator.rows"; + public static final String PREF_REPORT_TEMPLATE_PAGINATOR_ROWS = "report.template.paginator.rows"; + public static final String PREF_PROPAGATION_TASKS_PAGINATOR_ROWS = "proagationtasks.paginator.rows"; public static final String PREF_CONNECTOR_HISTORY_CONF_PAGINATOR_ROWS = "connectorhistoryconf.paginator.rows"; @@ -163,6 +179,8 @@ public final class Constants { public static final String PREF_TYPE_EXTENSIONS_PAGINATOR_ROWS = "typeextensions.paginator.rows"; + public static final String PREF_DELEGATION_PAGINATOR_ROWS = "delegation.paginator.rows"; + public static final String PREF_TODO_PAGINATOR_ROWS = "todo.paginator.rows"; public static final String PREF_REPORT_PAGINATOR_ROWS = "report.paginator.rows"; @@ -189,6 +207,10 @@ public final class Constants { public static final String NOT_FOUND_ICON = "glyphicon glyphicon-remove-circle"; + public static final int MAX_GROUP_LIST_SIZE = 30; + + public static final int MAX_ROLE_LIST_SIZE = 30; + /** * ConnId's GuardedString is not in the classpath. */ @@ -200,15 +222,28 @@ public final class Constants { public static final String GUARDED_BYTE_ARRAY = "org.identityconnectors.common.security.GuardedByteArray"; public static Component getJEXLPopover(final Component caller, final TooltipConfig.Placement placement) { + return getJEXLPopover(caller, placement, caller.getString("jexl_ex1"), caller.getString("jexl_ex2")); + } + + public static Component getJEXLPopover( + final Component caller, + final TooltipConfig.Placement placement, + final String... jexlExamples) { + + StringBuilder body = new StringBuilder(caller.getString("jexl_info")). + append("
    "); + for (String jexlExample : jexlExamples) { + body.append("
  • ").append(jexlExample).append("
  • "); + } + body.append("
"). + append("
"). + append(caller.getString("jexl_syntax_url")). + append(""); + return new Label("jexlInfo", Model.of()).add(new PopoverBehavior( Model.of(), - Model.of(caller.getString("jexl_info") - + "
    " - + "
  • " + caller.getString("jexl_ex1") + "
  • " - + "
  • " + caller.getString("jexl_ex2") + "
  • " - + "
" - + "" + caller.getString("jexl_syntax_url") + ""), + Model.of(body.toString()), new PopoverConfig().withHtml(true).withPlacement(placement)) { private static final long serialVersionUID = -7867802555691605021L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/DirectoryDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/DirectoryDataProvider.java index d7bb1224a51..bdd30bc4a12 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/DirectoryDataProvider.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/DirectoryDataProvider.java @@ -33,6 +33,6 @@ public DirectoryDataProvider(final int paginatorRows) { this.paginatorRows = paginatorRows; // default sorting - setSort("key", SortOrder.ASCENDING); + setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java index feeba85d496..9fcb85abb2c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/HttpResourceStream.java @@ -21,10 +21,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.rest.ResponseHolder; import org.apache.wicket.util.lang.Bytes; import org.apache.wicket.util.resource.AbstractResourceStream; import org.apache.wicket.util.resource.IFixedLocationResourceStream; @@ -34,46 +32,25 @@ public class HttpResourceStream extends AbstractResourceStream implements IFixed private static final long serialVersionUID = 5811207817876330189L; - private transient InputStream inputStream; + private final ResponseHolder responseHolder; - private String contentType; - - private String location; - - private String filename; - - public HttpResourceStream(final Response response) { + public HttpResourceStream(final ResponseHolder responseHolder) { super(); - - Object entity = response.getEntity(); - if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode() - && (entity instanceof InputStream)) { - - this.inputStream = (InputStream) entity; - this.contentType = response.getHeaderString(HttpHeaders.CONTENT_TYPE); - this.location = response.getLocation() == null ? null : response.getLocation().toASCIIString(); - String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION); - if (StringUtils.isNotBlank(contentDisposition)) { - String[] splitted = contentDisposition.split("="); - if (splitted != null && splitted.length > 1) { - this.filename = splitted[1].trim(); - } - } - } + this.responseHolder = responseHolder; } @Override public InputStream getInputStream() throws ResourceStreamNotFoundException { - return inputStream == null + return responseHolder.getInputStream() == null ? new ByteArrayInputStream(new byte[0]) - : inputStream; + : responseHolder.getInputStream(); } @Override public Bytes length() { - return inputStream == null + return responseHolder.getInputStream() == null ? Bytes.bytes(0) : null; } @@ -85,17 +62,17 @@ public void close() throws IOException { @Override public String locationAsString() { - return location; + return responseHolder.getLocation(); } @Override public String getContentType() { - return contentType == null + return responseHolder.getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM - : contentType; + : responseHolder.getContentType(); } public String getFilename() { - return filename; + return responseHolder.getFilename(); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/ITabComponent.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/ITabComponent.java index 300487a3aca..05317df8cec 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/ITabComponent.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/ITabComponent.java @@ -74,6 +74,5 @@ protected void onRender() { } @Override - public abstract WebMarkupContainer getPanel(final String panelId); - + public abstract WebMarkupContainer getPanel(String panelId); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/LinkedAccountPlainAttrProperty.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/LinkedAccountPlainAttrProperty.java new file mode 100644 index 00000000000..28264055b30 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/LinkedAccountPlainAttrProperty.java @@ -0,0 +1,92 @@ +/* + * 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.client.console.commons; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class LinkedAccountPlainAttrProperty implements Serializable, Comparable { + + private static final long serialVersionUID = -5309050337675968950L; + + private String schema; + + private final List values = new ArrayList<>(); + + private boolean overridable; + + public String getSchema() { + return schema; + } + + public void setSchema(final String schema) { + this.schema = schema; + } + + @JsonProperty("values") + public List getValues() { + return values; + } + + public boolean isOverridable() { + return overridable; + } + + public void setOverridable(final boolean overridable) { + this.overridable = overridable; + } + + @Override + public int compareTo(final LinkedAccountPlainAttrProperty connConfProperty) { + return ObjectUtils.compare(this.getSchema(), connConfProperty.getSchema()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(). + append(schema). + append(values). + append(overridable). + build(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LinkedAccountPlainAttrProperty other = (LinkedAccountPlainAttrProperty) obj; + return new EqualsBuilder(). + append(schema, other.schema). + append(values, other.values). + append(overridable, other.overridable). + build(); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java index 284510568ef..92d060d3499 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java @@ -23,33 +23,28 @@ import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; -public class MapChoiceRenderer implements IChoiceRenderer { +public class MapChoiceRenderer implements IChoiceRenderer { private static final long serialVersionUID = -7452881117778186644L; - private final Map map; + private final Map map; - public MapChoiceRenderer(final Map map) { + public MapChoiceRenderer(final Map map) { this.map = map; } @Override - public Object getDisplayValue(final T key) { + public Object getDisplayValue(final String key) { return map.get(key); } @Override - public String getIdValue(final T key, final int index) { - return key.toString(); + public String getIdValue(final String key, final int index) { + return key; } @Override - public T getObject(final String id, final IModel> choices) { - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() != null && entry.getValue().toString().equals(id)) { - return entry.getKey(); - } - } - return null; + public String getObject(final String id, final IModel> choices) { + return id; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/PropertyList.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/PropertyList.java index a7a093305a0..654c2e62723 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/PropertyList.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/PropertyList.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.common.lib.SyncopeConstants; @@ -171,18 +172,9 @@ public List subList(final int fromIndex, final int toIndex) { } public static String getEnumValuesAsString(final List enumerationValues) { - final StringBuilder builder = new StringBuilder(); - int index = 0; - for (String str : enumerationValues) { - if (str != null) { - if (index > 0) { - builder.append(SyncopeConstants.ENUM_VALUES_SEPARATOR); - } - index++; - builder.append(str.trim()); - } - } - return builder.toString(); + return enumerationValues.stream(). + map(v -> StringUtils.isEmpty(v) ? StringUtils.EMPTY : v.trim()). + collect(Collectors.joining(SyncopeConstants.ENUM_VALUES_SEPARATOR)); } public static List getEnumValuesAsList(final String enumerationValues) { @@ -203,5 +195,5 @@ public static List getEnumValuesAsList(final String enumerationValues) { public abstract String getValues(); - public abstract void setValues(final List list); + public abstract void setValues(List list); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java new file mode 100644 index 00000000000..bfef5acc9d1 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java @@ -0,0 +1,66 @@ +/* + * 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.client.console.commons; + +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.rest.RealmRestClient; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.rest.api.beans.RealmQuery; + +public final class RealmsUtils { + + public static final int REALMS_VIEW_SIZE = 20; + + private RealmsUtils() { + // private constructor for static utility class + } + + public static String getFullPath(final String fullpath) { + String display = fullpath; + if (display.indexOf('@') != -1) { + display = StringUtils.substringBefore(display, "@"); + } + return display; + } + + public static boolean isSearchEnabled() { + return isSearchEnabled(SyncopeConsoleSession.get().getAuthRealms()); + } + + public static boolean isSearchEnabled(final List realms) { + return realms.isEmpty() + ? false + : new RealmRestClient().search( + new RealmQuery.Builder().keyword( + realms.contains(SyncopeConstants.ROOT_REALM) + ? SyncopeConstants.ROOT_REALM + : realms.get(0)).build()). + getTotalCount() > REALMS_VIEW_SIZE; + } + + public static boolean checkInput(final String input) { + return StringUtils.isNotBlank(input) && !"*".equals(input); + } + + public static RealmQuery buildQuery(final String input) { + return new RealmQuery.Builder().keyword(input.contains("*") ? input : "*" + input + "*").build(); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java new file mode 100644 index 00000000000..ba6990d5ff1 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java @@ -0,0 +1,152 @@ +/* + * 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.client.console.commons; + +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.cycle.RequestCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class ResourceDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 3189980210236051840L; + + protected static final Logger LOG = LoggerFactory.getLogger(ResourceDataProvider.class); + + private final PageReference pageRef; + + protected int currentPage; + + private String keyword; + + private final ResourceRestClient restClient = new ResourceRestClient(); + + public ResourceDataProvider( + final int paginatorRows, + final PageReference pageRef, + final String keyword) { + super(paginatorRows); + setSort("keySortParam", SortOrder.ASCENDING); + this.pageRef = pageRef; + this.keyword = keyword; + } + + @Override + public Iterator iterator(final long first, final long count) { + List result = Collections.emptyList(); + + try { + currentPage = ((int) first / paginatorRows); + if (currentPage < 0) { + currentPage = 0; + } + if (StringUtils.isBlank(keyword)) { + result = restClient.list(); + } else { + result = restClient.list().stream().filter(resource -> + resource.getKey().toLowerCase().contains(keyword)).collect(Collectors.toList()); + } + } catch (Exception e) { + LOG.error("While searching", e); + SyncopeConsoleSession.get().onException(e); + + Optional target = RequestCycle.get().find(AjaxRequestTarget.class); + target.ifPresent(ajaxRequestTarget -> + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(ajaxRequestTarget)); + } + + SortParam sortParam = getSort(); + if (sortParam != null) { + result.sort(getComparator(sortParam)); + } + + return result.subList((int) first, (int) first + (int) count).iterator(); + } + + private Comparator getComparator(final SortParam sortParam) { + Comparator comparator; + + switch (sortParam.getProperty()) { + case "keySortParam": + comparator = Comparator.nullsFirst(Comparator.comparing( + item -> item.getKey().toLowerCase())); + break; + case "connectorDisplayNameSortParam": + comparator = Comparator.nullsFirst(Comparator.comparing( + item -> item.getConnectorDisplayName().toLowerCase())); + break; + default: + throw new IllegalStateException("The sort param " + sortParam.getProperty() + " is not correct"); + } + + if (!sortParam.isAscending()) { + comparator = comparator.reversed(); + } + + return comparator; + } + + @Override + public long size() { + long result = 0; + + try { + if (StringUtils.isBlank(keyword)) { + result = restClient.list().size(); + } else { + result = restClient.list().stream().filter(resource -> + resource.getKey().toLowerCase().contains(keyword)).count(); + } + } catch (Exception e) { + LOG.error("While requesting for size()", e); + SyncopeConsoleSession.get().onException(e); + + RequestCycle.get().find(AjaxRequestTarget.class). + ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target)); + } + + return result; + } + + @Override + public IModel model(final Serializable object) { + return new CompoundPropertyModel<>((ResourceTO) object); + } + + public int getCurrentPage() { + return currentPage; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java index 9a5d86ab01f..b92eba16a8f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java @@ -33,7 +33,7 @@ public class SortableAnyProviderComparator extends SortableData private static final long serialVersionUID = 1775967163571699258L; private static final Set INLINE_PROPS = new HashSet<>(Arrays.asList( - new String[] { "key", "status", "token", "username", "name" })); + new String[] { Constants.KEY_FIELD_NAME, "status", "token", "username", "name" })); public SortableAnyProviderComparator(final SortableDataProvider provider) { super(provider); @@ -83,20 +83,20 @@ public Comparable getObject() { final AttrTO attr; if (schemaType == null) { - attr = this.anyTO.getPlainAttr(schema).get(); + attr = this.anyTO.getPlainAttr(schema).orElse(null); } else { switch (schemaType) { case PLAIN: default: - attr = this.anyTO.getPlainAttr(schema).get(); + attr = this.anyTO.getPlainAttr(schema).orElse(null); break; case DERIVED: - attr = this.anyTO.getDerAttr(schema).get(); + attr = this.anyTO.getDerAttr(schema).orElse(null); break; case VIRTUAL: - attr = this.anyTO.getVirAttr(schema).get(); + attr = this.anyTO.getVirAttr(schema).orElse(null); break; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/StyledNotificationBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/StyledNotificationBehavior.java index 262871a6be8..3b49300ce6f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/StyledNotificationBehavior.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/StyledNotificationBehavior.java @@ -28,9 +28,9 @@ public class StyledNotificationBehavior extends NotificationBehavior { private static final long serialVersionUID = -3985689554352173472L; - private static final String AUTOHIDEAFTER_SUCCESS = "3000"; + private static final String AUTOHIDEAFTER_GOOD = "3000"; - private static final String AUTOHIDEAFTER_ERROR = "0"; + private static final String AUTOHIDEAFTER_BAD = "0"; public StyledNotificationBehavior(final String selector, final Options options) { super(selector, options); @@ -41,8 +41,8 @@ public void show(final IPartialPageRequestHandler handler, final Serializable me if (handler != null) { handler.appendJavaScript(String.format("%s.options.autoHideAfter = %s; %s.show( { message: '%s' } , '%s');", this.widget(), - Notification.SUCCESS.equalsIgnoreCase(level) - || Notification.INFO.equalsIgnoreCase(level) ? AUTOHIDEAFTER_SUCCESS : AUTOHIDEAFTER_ERROR, + Notification.SUCCESS.equalsIgnoreCase(level) || Notification.INFO.equalsIgnoreCase(level) + ? AUTOHIDEAFTER_GOOD : AUTOHIDEAFTER_BAD, this.widget(), this.format(String.valueOf(message), level), level.toLowerCase())); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/TaskDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/TaskDataProvider.java index 50f6fe70ed4..d842f3c4f50 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/TaskDataProvider.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/TaskDataProvider.java @@ -28,22 +28,15 @@ public abstract class TaskDataProvider extends DirectoryDataPr private static final long serialVersionUID = -20112718133295756L; - private final SortableDataProviderComparator comparator; - protected final TaskType taskType; public TaskDataProvider(final int paginatorRows, final TaskType taskType) { super(paginatorRows); setSort("start", SortOrder.ASCENDING); - comparator = new SortableDataProviderComparator<>(this); this.taskType = taskType; } - public SortableDataProviderComparator getComparator() { - return comparator; - } - @Override public IModel model(final T object) { return new CompoundPropertyModel<>(object); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/AbstractStatusBeanProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/AbstractStatusBeanProvider.java index 93303b8b7e5..7992362b16e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/AbstractStatusBeanProvider.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/AbstractStatusBeanProvider.java @@ -31,8 +31,8 @@ public abstract class AbstractStatusBeanProvider extends DirectoryDataProvider comparator; - public AbstractStatusBeanProvider(final String sort) { - super(10); + public AbstractStatusBeanProvider(final int paginatorRows, final String sort) { + super(paginatorRows); setSort(sort, SortOrder.ASCENDING); comparator = new SortableDataProviderComparator<>(this); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusBean.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusBean.java index fc834b5a54e..3a5c766e28c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusBean.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusBean.java @@ -21,8 +21,7 @@ import java.io.Serializable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.GroupTO; @@ -98,17 +97,48 @@ public void setLinked(final boolean linked) { } @Override - public String toString() { - return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + public int hashCode() { + return new HashCodeBuilder(). + append(key). + append(name). + append(resource). + append(connObjectLink). + append(status). + append(linked). + build(); } @Override public boolean equals(final Object obj) { - return EqualsBuilder.reflectionEquals(this, obj); + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final StatusBean other = (StatusBean) obj; + return new EqualsBuilder(). + append(key, other.key). + append(name, other.name). + append(resource, other.resource). + append(connObjectLink, other.connObjectLink). + append(status, other.status). + append(linked, other.linked). + build(); } @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + public String toString() { + return new ToStringBuilder(this). + append(key). + append(name). + append(resource). + append(connObjectLink). + append(status). + append(linked). + build(); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java index 85df442ca69..d1ac417bd40 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java @@ -30,15 +30,14 @@ import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.LabelPanel; import org.apache.syncope.client.console.rest.ReconciliationRestClient; -import org.apache.syncope.common.lib.patch.PasswordPatch; import org.apache.syncope.common.lib.patch.StatusPatch; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.lib.to.ReconStatus; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.syncope.common.lib.types.PropagationTaskExecStatus; +import org.apache.syncope.common.lib.types.ExecStatus; +import org.apache.syncope.common.rest.api.beans.ReconQuery; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; @@ -53,14 +52,28 @@ public final class StatusUtils implements Serializable { private static final ReconciliationRestClient RECONCILIATION_REST_CLIENT = new ReconciliationRestClient(); + public static Optional getReconStatus( + final String anyTypeKey, final String connObjectKeyValue, final String resource) { + + ReconStatus result = null; + try { + result = RECONCILIATION_REST_CLIENT.status(new ReconQuery.Builder(anyTypeKey, resource). + fiql(ConnIdSpecialName.UID + "==" + connObjectKeyValue).build()); + } catch (Exception e) { + LOG.warn("Unexpected error for {} {} on {}", anyTypeKey, connObjectKeyValue, resource, e); + } + return Optional.ofNullable(result); + } + public static List> getReconStatuses( - final AnyTypeKind anyTypeKind, final String anyKey, final Collection resources) { + final String anyTypeKey, final String anyKey, final Collection resources) { return resources.stream().map(resource -> { try { - return Pair.of(resource, RECONCILIATION_REST_CLIENT.status(anyTypeKind, anyKey, resource)); + return Pair.of(resource, RECONCILIATION_REST_CLIENT.status( + new ReconQuery.Builder(anyTypeKey, resource).anyKey(anyKey).build())); } catch (Exception e) { - LOG.warn("Unexpected error for {} {} on {}", anyTypeKind, anyKey, resource, e); + LOG.warn("Unexpected error for {} {} on {}", anyTypeKey, anyKey, resource, e); return null; } }).filter(Objects::nonNull).collect(Collectors.toList()); @@ -89,31 +102,27 @@ public static StatusBean getStatusBean( } public static StatusBean getStatusBean( - final RealmTO anyTO, - final String resourceName, - final ConnObjectTO objectTO) { - - final StatusBean statusBean = new StatusBean(anyTO, resourceName); + final RealmTO realmTO, + final String resource, + final ConnObjectTO connObjectTO) { - if (objectTO != null) { - final Boolean enabled = isEnabled(objectTO); + StatusBean statusBean = new StatusBean(realmTO, resource); - final Status status = enabled == null + if (connObjectTO != null) { + Boolean enabled = isEnabled(connObjectTO); + statusBean.setStatus(enabled == null ? Status.ACTIVE : enabled ? Status.ACTIVE - : Status.SUSPENDED; - - String connObjectLink = getConnObjectLink(objectTO); + : Status.SUSPENDED); - statusBean.setStatus(status); - statusBean.setConnObjectLink(connObjectLink); + statusBean.setConnObjectLink(getConnObjectLink(connObjectTO)); } return statusBean; } - private static Boolean isEnabled(final ConnObjectTO objectTO) { + public static Boolean isEnabled(final ConnObjectTO objectTO) { Optional status = objectTO.getAttr(ConnIdSpecialName.ENABLE); return status.isPresent() && status.get().getValues() != null && !status.get().getValues().isEmpty() ? Boolean.valueOf(status.get().getValues().get(0)) @@ -127,36 +136,18 @@ private static String getConnObjectLink(final ConnObjectTO objectTO) { : null; } - public static PasswordPatch buildPasswordPatch(final String password, final Collection statuses) { - PasswordPatch.Builder builder = new PasswordPatch.Builder(); - builder.value(password); - - statuses.forEach((status) -> { - if (Constants.SYNCOPE.equalsIgnoreCase(status.getResource())) { - builder.onSyncope(true); - } else { - builder.resource(status.getResource()); - } - }); - return builder.build(); - } - - public static StatusPatch buildStatusPatch(final Collection statuses) { - return buildStatusPatch(statuses, null); - } - - public static StatusPatch buildStatusPatch(final Collection statuses, final Boolean enable) { + public static StatusPatch.Builder statusPatch(final Collection statuses) { StatusPatch.Builder builder = new StatusPatch.Builder(); builder.onSyncope(false); - statuses.forEach((status) -> { - if ("syncope".equalsIgnoreCase(status.getResource())) { + statuses.forEach(status -> { + if (Constants.SYNCOPE.equalsIgnoreCase(status.getResource())) { builder.onSyncope(true); } else { builder.resource(status.getResource()); } }); - return builder.build(); + return builder; } public static Panel getStatusImagePanel(final String componentId, final Status status) { @@ -195,7 +186,7 @@ public static Label getStatusImage(final String componentId, final Status status case CREATED: alt = "created icon"; title = "Created"; - clazz = Constants.UNDEFINED_ICON; + clazz = Constants.CREATED_ICON; break; case SUSPENDED: @@ -214,11 +205,11 @@ public static Label getStatusImage(final String componentId, final Status status return getLabel(componentId, alt, title, clazz); } - public static Panel getStatusImagePanel(final String componentId, final PropagationTaskExecStatus status) { + public static Panel getStatusImagePanel(final String componentId, final ExecStatus status) { return new LabelPanel(componentId, getStatusImage("label", status)); } - public static Label getStatusImage(final String componentId, final PropagationTaskExecStatus status) { + public static Label getStatusImage(final String componentId, final ExecStatus status) { final String alt, title, clazz; switch (status) { @@ -257,7 +248,7 @@ public static Label getStatusImage(final String componentId, final PropagationTa return getLabel(componentId, alt, title, clazz); } - private static Label getLabel(final String componentId, final String alt, final String title, final String clazz) { + public static Label getLabel(final String componentId, final String alt, final String title, final String clazz) { return new Label(componentId, StringUtils.EMPTY) { private static final long serialVersionUID = 4755868673082976208L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java index e31ffdc0107..bf49fc267b3 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/events/EventCategoryPanel.java @@ -35,7 +35,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; -import org.apache.syncope.common.lib.log.EventCategoryTO; +import org.apache.syncope.common.lib.log.EventCategory; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType; import org.apache.syncope.common.lib.types.AuditLoggerName; @@ -57,9 +57,9 @@ public abstract class EventCategoryPanel extends Panel { private static final long serialVersionUID = 6429053774964787734L; - private final List eventCategoryTOs; + private final List eventCategoryTOs; - private final EventCategoryTO eventCategoryTO = new EventCategoryTO(); + private final EventCategory eventCategoryTO = new EventCategory(); private final WebMarkupContainer categoryContainer; @@ -75,13 +75,13 @@ public abstract class EventCategoryPanel extends Panel { private final AjaxTextFieldPanel custom; - private final ActionsPanel actionsPanel; + private final ActionsPanel actionsPanel; private final IModel> model; public EventCategoryPanel( final String id, - final List eventCategoryTOs, + final List eventCategoryTOs, final IModel> model) { super(id); @@ -187,14 +187,14 @@ protected void onUpdate(final AjaxRequestTarget target) { categoryContainer.add(custom.hideLabel()); actionsPanel = new ActionsPanel<>("customActions", null); - actionsPanel.add(new ActionLink() { + actionsPanel.add(new ActionLink() { private static final long serialVersionUID = -3722207913631435501L; @Override - public void onClick(final AjaxRequestTarget target, final EventCategoryTO ignore) { + public void onClick(final AjaxRequestTarget target, final EventCategory ignore) { if (StringUtils.isNotBlank(custom.getModelObject())) { - Pair parsed = AuditLoggerName.parseEventCategory(custom. + Pair parsed = AuditLoggerName.parseEventCategory(custom. getModelObject()); String eventString = AuditLoggerName.buildEvent( @@ -214,14 +214,14 @@ public void onClick(final AjaxRequestTarget target, final EventCategoryTO ignore } } }, ActionLink.ActionType.CREATE, StringUtils.EMPTY).hideLabel(); - actionsPanel.add(new ActionLink() { + actionsPanel.add(new ActionLink() { private static final long serialVersionUID = -3722207913631435521L; @Override - public void onClick(final AjaxRequestTarget target, final EventCategoryTO ignore) { + public void onClick(final AjaxRequestTarget target, final EventCategory ignore) { if (StringUtils.isNotBlank(custom.getModelObject())) { - Pair parsed = AuditLoggerName.parseEventCategory(custom. + Pair parsed = AuditLoggerName.parseEventCategory(custom. getModelObject()); String eventString = AuditLoggerName.buildEvent( @@ -259,7 +259,7 @@ protected void onEventAction(final IEvent event) { }); } - private List filter(final List eventCategoryTOs, final EventCategoryType type) { + private List filter(final List eventCategoryTOs, final EventCategoryType type) { Set res = new HashSet<>(); eventCategoryTOs.stream().filter(eventCategory @@ -274,7 +274,7 @@ private List filter(final List eventCategoryTOs, final } private List filter( - final List eventCategoryTOs, final EventCategoryType type, final String category) { + final List eventCategoryTOs, final EventCategoryType type, final String category) { Set res = new HashSet<>(); @@ -348,7 +348,7 @@ public void onEvent(final IEvent event) { final InspectSelectedEvent inspectSelectedEvent = (InspectSelectedEvent) event.getPayload(); - final Map.Entry categoryEvent = AuditLoggerName.parseEventCategory( + final Map.Entry categoryEvent = AuditLoggerName.parseEventCategory( inspectSelectedEvent.getEvent()); eventCategoryTO.setType(categoryEvent.getKey().getType()); @@ -389,9 +389,9 @@ public void onEvent(final IEvent event) { } private void setEvents() { - final Iterator itor = eventCategoryTOs.iterator(); + final Iterator itor = eventCategoryTOs.iterator(); while (itor.hasNext() && eventCategoryTO.getEvents().isEmpty()) { - final EventCategoryTO eventCategory = itor.next(); + final EventCategory eventCategory = itor.next(); if (eventCategory.getType() == eventCategoryTO.getType() && StringUtils.equals(eventCategory.getCategory(), eventCategoryTO.getCategory()) && StringUtils.equals(eventCategory.getSubcategory(), eventCategoryTO.getSubcategory())) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java index 33844d97e10..ea53bd809de 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/events/EventSelectionPanel.java @@ -24,7 +24,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior; -import org.apache.syncope.common.lib.log.EventCategoryTO; +import org.apache.syncope.common.lib.log.EventCategory; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditLoggerName; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -48,7 +48,7 @@ public abstract class EventSelectionPanel extends Panel { private final Set selected = new HashSet<>(); public EventSelectionPanel( - final String id, final EventCategoryTO eventCategoryTO, final IModel> model) { + final String id, final EventCategory eventCategoryTO, final IModel> model) { super(id); setOutputMarkupId(true); @@ -102,9 +102,7 @@ protected void onUpdate(final AjaxRequestTarget target) { @Override protected void populateItem(final ListItem item) { - final String subcategory = item.getModelObject(); - - item.add(new Label("subcategory", new ResourceModel(subcategory, subcategory))); + item.add(new Label("subcategory", Model.of(item.getModelObject()))); } }; add(categoryView); @@ -184,7 +182,7 @@ protected void populateItem(final ListItem item) { failureGroup.add(failureView); } - private List getEvents(final EventCategoryTO eventCategoryTO) { + private List getEvents(final EventCategory eventCategoryTO) { final List res; res = eventCategoryTO.getEvents(); @@ -211,7 +209,7 @@ private List getEvents(final EventCategoryTO eventCategoryTO) { * * @param event event. */ - protected abstract void onEventAction(final IEvent event); + protected abstract void onEventAction(IEvent event); @Override public void onEvent(final IEvent event) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java b/client/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java index 876d29909ec..9ca47eb3f8b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java @@ -23,21 +23,24 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.ObjectUtils; import org.apache.syncope.client.console.pages.BaseExtPage; import org.apache.syncope.client.console.annotations.BinaryPreview; import org.apache.syncope.client.console.annotations.ExtPage; import org.apache.syncope.client.console.annotations.ExtWidget; +import org.apache.syncope.client.console.annotations.Resource; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.SSOLoginFormPanel; import org.apache.syncope.client.console.wicket.markup.html.form.preview.AbstractBinaryPreviewer; import org.apache.syncope.client.console.widgets.BaseExtWidget; +import org.apache.syncope.client.console.widgets.ExtAlertWidget; +import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.policy.AccountRuleConf; import org.apache.syncope.common.lib.policy.PasswordRuleConf; import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf; @@ -46,11 +49,14 @@ import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.wicket.request.resource.AbstractResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.util.ClassUtils; +import org.apache.syncope.client.console.wizards.any.UserFormFinalizer; +import org.apache.syncope.client.console.annotations.UserFormFinalize; public class ClassPathScanImplementationLookup { @@ -75,7 +81,7 @@ private static void initFieldNames(final Class entityClass, final Set classes.add(entityClass); for (Class clazz : classes) { for (Field field : clazz.getDeclaredFields()) { - if (!Modifier.isStatic(field.getModifiers()) + if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers()) && !Collection.class.isAssignableFrom(field.getType()) && !Map.class.isAssignableFrom(field.getType())) { @@ -89,10 +95,14 @@ private static void initFieldNames(final Class entityClass, final Set private List> previewers; + private List> userFormFinalizers; + private List> extPages; private List> extWidgets; + private List>> extAlertWidgets; + private List> ssoLoginFormPanels; private Map> reportletConfs; @@ -105,6 +115,8 @@ private static void initFieldNames(final Class entityClass, final Set private Map> pushCorrelationRuleConfs; + private List> resources; + /** * This method can be overridden by subclasses to customize classpath scan. * @@ -120,24 +132,30 @@ public void load() { previewers = new ArrayList<>(); extPages = new ArrayList<>(); extWidgets = new ArrayList<>(); + extAlertWidgets = new ArrayList<>(); ssoLoginFormPanels = new ArrayList<>(); reportletConfs = new HashMap<>(); accountRuleConfs = new HashMap<>(); passwordRuleConfs = new HashMap<>(); pullCorrelationRuleConfs = new HashMap<>(); pushCorrelationRuleConfs = new HashMap<>(); + resources = new ArrayList<>(); + userFormFinalizers = new ArrayList<>(); ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AssignableTypeFilter(BasePage.class)); scanner.addIncludeFilter(new AssignableTypeFilter(AbstractBinaryPreviewer.class)); scanner.addIncludeFilter(new AssignableTypeFilter(BaseExtPage.class)); scanner.addIncludeFilter(new AssignableTypeFilter(BaseExtWidget.class)); + scanner.addIncludeFilter(new AssignableTypeFilter(ExtAlertWidget.class)); scanner.addIncludeFilter(new AssignableTypeFilter(SSOLoginFormPanel.class)); scanner.addIncludeFilter(new AssignableTypeFilter(ReportletConf.class)); scanner.addIncludeFilter(new AssignableTypeFilter(AccountRuleConf.class)); scanner.addIncludeFilter(new AssignableTypeFilter(PasswordRuleConf.class)); scanner.addIncludeFilter(new AssignableTypeFilter(PullCorrelationRuleConf.class)); scanner.addIncludeFilter(new AssignableTypeFilter(PushCorrelationRuleConf.class)); + scanner.addIncludeFilter(new AssignableTypeFilter(AbstractResource.class)); + scanner.addIncludeFilter(new AssignableTypeFilter(UserFormFinalizer.class)); scanner.findCandidateComponents(getBasePackage()).forEach(bd -> { try { @@ -160,6 +178,13 @@ public void load() { LOG.error("Could not find annotation {} in {}, ignoring", ExtWidget.class.getName(), clazz.getName()); } + } else if (ExtAlertWidget.class.isAssignableFrom(clazz)) { + if (clazz.isAnnotationPresent(ExtWidget.class)) { + extAlertWidgets.add((Class>) clazz); + } else { + LOG.error("Could not find annotation {} in {}, ignoring", + ExtWidget.class.getName(), clazz.getName()); + } } else if (BasePage.class.isAssignableFrom(clazz)) { pages.add((Class) clazz); } else if (AbstractBinaryPreviewer.class.isAssignableFrom(clazz)) { @@ -176,6 +201,15 @@ public void load() { pullCorrelationRuleConfs.put(clazz.getName(), (Class) clazz); } else if (PushCorrelationRuleConf.class.isAssignableFrom(clazz)) { pushCorrelationRuleConfs.put(clazz.getName(), (Class) clazz); + } else if (AbstractResource.class.isAssignableFrom(clazz)) { + if (clazz.isAnnotationPresent(Resource.class)) { + resources.add((Class) clazz); + } else { + LOG.error("Could not find annotation {} in {}, ignoring", + Resource.class.getName(), clazz.getName()); + } + } else if (UserFormFinalizer.class.isAssignableFrom(clazz)) { + userFormFinalizers.add((Class) clazz); } } } catch (Throwable t) { @@ -183,20 +217,19 @@ public void load() { } }); pages = Collections.unmodifiableList(pages); + previewers = Collections.unmodifiableList(previewers); + userFormFinalizers = Collections.unmodifiableList(userFormFinalizers); - Collections.sort(extPages, (o1, o2) - -> ObjectUtils.compare( - o1.getAnnotation(ExtPage.class).priority(), - o2.getAnnotation(ExtPage.class).priority())); + extPages.sort(Comparator.comparing(o -> o.getAnnotation(ExtPage.class).priority())); extPages = Collections.unmodifiableList(extPages); - Collections.sort(extWidgets, (o1, o2) - -> ObjectUtils.compare( - o1.getAnnotation(ExtWidget.class).priority(), - o2.getAnnotation(ExtWidget.class).priority())); + extWidgets.sort(Comparator.comparing(o -> o.getAnnotation(ExtWidget.class).priority())); extWidgets = Collections.unmodifiableList(extWidgets); + extAlertWidgets.sort(Comparator.comparing(o -> o.getAnnotation(ExtWidget.class).priority())); + extAlertWidgets = Collections.unmodifiableList(extAlertWidgets); + ssoLoginFormPanels = Collections.unmodifiableList(ssoLoginFormPanels); reportletConfs = Collections.unmodifiableMap(reportletConfs); @@ -205,15 +238,20 @@ public void load() { pullCorrelationRuleConfs = Collections.unmodifiableMap(pullCorrelationRuleConfs); pushCorrelationRuleConfs = Collections.unmodifiableMap(pushCorrelationRuleConfs); + resources = Collections.unmodifiableList(resources); + LOG.debug("Binary previewers found: {}", previewers); + LOG.debug("User Form finalizers found {}", userFormFinalizers); LOG.debug("Extension pages found: {}", extPages); LOG.debug("Extension widgets found: {}", extWidgets); + LOG.debug("Extension alert widgets found: {}", extAlertWidgets); LOG.debug("SSO Login pages found: {}", ssoLoginFormPanels); LOG.debug("Reportlet configurations found: {}", reportletConfs); LOG.debug("Account Rule configurations found: {}", accountRuleConfs); LOG.debug("Password Rule configurations found: {}", passwordRuleConfs); LOG.debug("Pull Correlation Rule configurations found: {}", pullCorrelationRuleConfs); LOG.debug("Push Correlation Rule configurations found: {}", pushCorrelationRuleConfs); + LOG.debug("Resources found: {}", resources); } public Class getPreviewerClass(final String mimeType) { @@ -230,6 +268,20 @@ public Class getPreviewerClass(final String m return previewer; } + public List> getUserFormFinalizerClasses(final AjaxWizard.Mode mode) { + List> classes = new ArrayList<>(); + + userFormFinalizers.forEach(candidate -> { + if (candidate.isAnnotationPresent(UserFormFinalize.class) + && candidate.getAnnotation(UserFormFinalize.class).mode() == mode) { + + classes.add(candidate); + } + }); + + return classes; + } + public List> getPageClasses() { return pages; } @@ -242,6 +294,10 @@ public List> getExtWidgetClasses() { return extWidgets; } + public List>> getExtAlertWidgetClasses() { + return extAlertWidgets; + } + public List> getSSOLoginFormPanels() { return ssoLoginFormPanels; } @@ -265,4 +321,8 @@ public Map> getPullCorrelationR public Map> getPushCorrelationRuleConfs() { return pushCorrelationRuleConfs; } + + public List> getResources() { + return resources; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/AbstractAnyFormLayout.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/AbstractAnyFormLayout.java index 6b85887f6e6..00f18383c1b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/layout/AbstractAnyFormLayout.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/AbstractAnyFormLayout.java @@ -116,5 +116,4 @@ public boolean isResources() { public void setResources(final boolean resources) { this.resources = resources; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.java new file mode 100644 index 00000000000..e742f268509 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.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.client.console.layout; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.client.console.SyncopeConsoleApplication; + +public class AnyLayout implements Serializable { + + private static final long serialVersionUID = 488645029994410970L; + + private String anyPanelClass; + + @JsonProperty("USER") + private UserFormLayoutInfo user; + + @JsonProperty("GROUP") + private GroupFormLayoutInfo group; + + private final Map anyObjects = new HashMap<>(); + + public AnyLayout() { + this.anyPanelClass = SyncopeConsoleApplication.get().getDefaultAnyLayoutClass(); + } + + public String getAnyPanelClass() { + return anyPanelClass; + } + + public void setAnyPanelClass(final String anyPanelClass) { + this.anyPanelClass = anyPanelClass; + } + + public UserFormLayoutInfo getUser() { + return user; + } + + public void setUser(final UserFormLayoutInfo user) { + this.user = user; + } + + public GroupFormLayoutInfo getGroup() { + return group; + } + + public void setGroup(final GroupFormLayoutInfo group) { + this.group = group; + } + + @JsonAnyGetter + public Map getAnyObjects() { + return anyObjects; + } + + @JsonAnySetter + public void setAnyObjects(final String anyType, final AnyObjectFormLayoutInfo layoutInfo) { + anyObjects.put(anyType, layoutInfo); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java new file mode 100644 index 00000000000..37c97fcc2f2 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java @@ -0,0 +1,209 @@ +/* + * 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.client.console.layout; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.panels.AnyPanel; +import org.apache.syncope.client.console.rest.RoleRestClient; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.AnyTypeTO; +import org.apache.syncope.common.lib.to.RealmTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.wicket.PageReference; +import org.springframework.util.ClassUtils; + +public final class AnyLayoutUtils { + + private static final RoleRestClient ROLE_REST_CLIENT = new RoleRestClient(); + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static void setUserIfEmpty(final AnyLayout anyLayout) { + if (anyLayout.getUser() == null) { + anyLayout.setUser(new UserFormLayoutInfo()); + } + } + + private static void setGroupIfEmpty(final AnyLayout anyLayout) { + if (anyLayout.getGroup() == null) { + anyLayout.setGroup(new GroupFormLayoutInfo()); + } + } + + private static void setAnyObjectsIfEmpty(final AnyLayout anyLayout, final List anyTypes) { + if (anyLayout.getAnyObjects().isEmpty()) { + anyLayout.getAnyObjects().putAll(anyTypes.stream().filter( + anyType -> !anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())). + collect(Collectors.toMap(Function.identity(), anyType -> new AnyObjectFormLayoutInfo()))); + } + } + + private static AnyLayout empty(final List anyTypes) { + AnyLayout anyLayout = new AnyLayout(); + setUserIfEmpty(anyLayout); + setGroupIfEmpty(anyLayout); + setAnyObjectsIfEmpty(anyLayout, anyTypes); + return anyLayout; + } + + public static AnyLayout fetch(final List anyTypes) { + List ownedRoles = Stream.concat( + SyncopeConsoleSession.get().getSelfTO().getRoles().stream(), + SyncopeConsoleSession.get().getSelfTO().getDynRoles().stream()). + distinct().collect(Collectors.toList()); + try { + AnyLayout anyLayout = null; + for (int i = 0; i < ownedRoles.size() && anyLayout == null; i++) { + String anyLayoutJSON = ROLE_REST_CLIENT.readAnyLayout(ownedRoles.get(i)); + if (StringUtils.isNotBlank(anyLayoutJSON)) { + anyLayout = MAPPER.readValue(anyLayoutJSON, AnyLayout.class); + } + } + + if (anyLayout == null) { + anyLayout = empty(anyTypes); + } + setUserIfEmpty(anyLayout); + setGroupIfEmpty(anyLayout); + setAnyObjectsIfEmpty(anyLayout, anyTypes); + + return anyLayout; + } catch (IOException e) { + throw new IllegalArgumentException("While parsing console layout for " + + SyncopeConsoleSession.get().getSelfTO().getUsername(), e); + } + } + + public static String defaultIfEmpty(final String content, final List anyTypes) { + String result; + + if (StringUtils.isBlank(content)) { + AnyLayout anyLayout = empty(anyTypes); + + try { + result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(anyLayout); + } catch (IOException e) { + throw new IllegalArgumentException("While generating default console layout for " + + SyncopeConsoleSession.get().getSelfTO().getUsername(), e); + } + } else { + try { + result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readTree(content)); + } catch (IOException e) { + result = content; + } + } + + return result; + } + + public static , FL extends AbstractAnyFormLayout> F newLayoutInfo( + final A anyTO, + final List anyTypeClasses, + final FL anyFormLayout, + final PageReference pageRef) { + + try { + if (anyTO instanceof UserTO) { + return anyFormLayout.getFormClass().getConstructor( + anyTO.getClass(), // previous + anyTO.getClass(), // actual + List.class, + anyFormLayout.getClass(), + pageRef.getClass()). + newInstance(null, anyTO, anyTypeClasses, anyFormLayout, pageRef); + } else { + return anyFormLayout.getFormClass().getConstructor( + anyTO.getClass(), // actual + List.class, + anyFormLayout.getClass(), + pageRef.getClass()). + newInstance(anyTO, anyTypeClasses, anyFormLayout, pageRef); + } + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalArgumentException("Could not instantiate " + anyFormLayout.getFormClass().getName(), e); + } + } + + public static AP newAnyPanel( + final String panelClass, + final String id, + final AnyTypeTO anyTypeTO, + final RealmTO realmTO, + final AnyLayout anyLayout, + final boolean enableSearch, + final PageReference pageRef) { + + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) ClassUtils.forName(panelClass, ClassUtils.getDefaultClassLoader()); + return clazz.getConstructor( + String.class, + AnyTypeTO.class, + RealmTO.class, + AnyLayout.class, + boolean.class, + PageReference.class). + newInstance(id, anyTypeTO, realmTO, anyLayout, enableSearch, pageRef); + } catch (Exception e) { + throw new IllegalArgumentException("Could not instantiate " + panelClass, e); + } + } + + public static AP newAnyPanel( + final String panelClass, + final String id, + final AnyTypeTO anyTypeTO, + final RealmTO realmTO, + final AnyLayout anyLayout, + final boolean enableSearch, + final AnyPanel.DirectoryPanelSupplier directoryPanelSupplier, + final PageReference pageRef) { + + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) ClassUtils.forName(panelClass, ClassUtils.getDefaultClassLoader()); + return clazz.getConstructor( + String.class, + AnyTypeTO.class, + RealmTO.class, + AnyLayout.class, + boolean.class, + AnyPanel.DirectoryPanelSupplier.class, + PageReference.class). + newInstance(id, anyTypeTO, realmTO, anyLayout, enableSearch, directoryPanelSupplier, pageRef); + } catch (Exception e) { + throw new IllegalArgumentException("Could not instantiate " + panelClass, e); + } + } + + private AnyLayoutUtils() { + // private constructor for static utility class + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java new file mode 100644 index 00000000000..e3d7e864813 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java @@ -0,0 +1,43 @@ +/* + * 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.client.console.layout; + +import java.io.Serializable; + +public class AnyLayoutWrapper implements Serializable { + + private static final long serialVersionUID = 961267717148831831L; + + private final String key; + + private final String content; + + public AnyLayoutWrapper(final String key, final String content) { + this.key = key; + this.content = content; + } + + public String getKey() { + return key; + } + + public String getContent() { + return content; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java deleted file mode 100644 index c82eccd149a..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.client.console.layout; - -import java.io.Serializable; -import org.apache.syncope.client.console.rest.AnyTypeRestClient; - -public class ConsoleLayoutInfo implements Serializable { - - private static final long serialVersionUID = 961267717148831831L; - - private final String key; - - private String content; - - public ConsoleLayoutInfo(final String key) { - this.key = key; - } - - public String getKey() { - return key; - } - - public String getContent() { - return content; - } - - public void setContent(final String content) { - this.content = FormLayoutInfoUtils.defaultConsoleLayoutInfoIfEmpty(content, new AnyTypeRestClient().list()); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java deleted file mode 100644 index 2a6a191e167..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.client.console.layout; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Triple; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.rest.RoleRestClient; -import org.apache.syncope.common.lib.to.AnyTO; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.wicket.PageReference; - -/** - * Utility methods for dealing with form layout information. - */ -public final class FormLayoutInfoUtils { - - private static final RoleRestClient ROLE_REST_CLIENT = new RoleRestClient(); - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - public static Triple> fetch( - final Collection anyTypes) { - - List ownedRoles = SyncopeConsoleSession.get().getSelfTO().getRoles(); - try { - JsonNode tree = null; - for (int i = 0; i < ownedRoles.size() && tree == null; i++) { - String consoleLayoutInfo = ROLE_REST_CLIENT.readConsoleLayoutInfo(ownedRoles.get(i)); - if (StringUtils.isNotBlank(consoleLayoutInfo)) { - tree = MAPPER.readTree(consoleLayoutInfo); - } - } - if (tree == null) { - tree = MAPPER.createObjectNode(); - } - - UserFormLayoutInfo userFormLayoutInfo = tree.has(AnyTypeKind.USER.name()) - ? MAPPER.treeToValue(tree.get(AnyTypeKind.USER.name()), UserFormLayoutInfo.class) - : new UserFormLayoutInfo(); - - GroupFormLayoutInfo groupFormLayoutInfo = tree.has(AnyTypeKind.USER.name()) - ? MAPPER.treeToValue(tree.get(AnyTypeKind.GROUP.name()), GroupFormLayoutInfo.class) - : new GroupFormLayoutInfo(); - - Map anyObjectFormLayoutInfos = new HashMap<>(); - for (String anyType : anyTypes) { - if (!anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())) { - - anyObjectFormLayoutInfos.put( - anyType, - tree.has(anyType) - ? MAPPER.treeToValue(tree.get(anyType), AnyObjectFormLayoutInfo.class) - : new AnyObjectFormLayoutInfo()); - } - } - - return Triple.of(userFormLayoutInfo, groupFormLayoutInfo, anyObjectFormLayoutInfos); - } catch (IOException e) { - throw new IllegalArgumentException("While parsing console layout info for " - + SyncopeConsoleSession.get().getSelfTO().getUsername(), e); - } - } - - public static String defaultConsoleLayoutInfoIfEmpty(final String content, final List anyTypes) { - String result; - - if (StringUtils.isBlank(content)) { - try { - ObjectNode tree = MAPPER.createObjectNode(); - - tree.set(AnyTypeKind.USER.name(), MAPPER.valueToTree(new UserFormLayoutInfo())); - tree.set(AnyTypeKind.GROUP.name(), MAPPER.valueToTree(new GroupFormLayoutInfo())); - for (String anyType : anyTypes) { - if (!anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())) { - tree.set(anyType, MAPPER.valueToTree(new AnyObjectFormLayoutInfo())); - } - } - - result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tree); - } catch (IOException e) { - throw new IllegalArgumentException("While generating default console layout info for " - + SyncopeConsoleSession.get().getSelfTO().getUsername(), e); - } - } else { - try { - result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readTree(content)); - } catch (IOException e) { - result = content; - } - } - - return result; - } - - public static , FL extends AbstractAnyFormLayout> F instantiate( - final A anyTO, - final List anyTypeClasses, - final FL anyFormLayout, - final PageReference pageRef) { - - try { - if (anyTO instanceof UserTO) { - return anyFormLayout.getFormClass().getConstructor( - anyTO.getClass(), // previous - anyTO.getClass(), // actual - List.class, - anyFormLayout.getClass(), - pageRef.getClass()). - newInstance(null, anyTO, anyTypeClasses, anyFormLayout, pageRef); - } else { - return anyFormLayout.getFormClass().getConstructor( - anyTO.getClass(), // actual - List.class, - anyFormLayout.getClass(), - pageRef.getClass()). - newInstance(anyTO, anyTypeClasses, anyFormLayout, pageRef); - } - } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - throw new IllegalArgumentException("Could not instantiate " + anyFormLayout.getFormClass().getName(), e); - } - } - - private FormLayoutInfoUtils() { - // private constructor for static utility class - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountForm.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountForm.java new file mode 100644 index 00000000000..21bc0a3aa74 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountForm.java @@ -0,0 +1,25 @@ +/* + * 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.client.console.layout; + +import org.apache.syncope.client.console.wizards.ModalPanelBuilder; +import org.apache.syncope.common.lib.to.LinkedAccountTO; + +public interface LinkedAccountForm extends ModalPanelBuilder { +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountFormLayoutInfo.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountFormLayoutInfo.java new file mode 100644 index 00000000000..5ba91155877 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/LinkedAccountFormLayoutInfo.java @@ -0,0 +1,86 @@ +/* + * 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.client.console.layout; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.apache.syncope.client.console.wizards.any.LinkedAccountWizardBuilder; + +public class LinkedAccountFormLayoutInfo implements Serializable { + + private static final long serialVersionUID = -5573691733739618511L; + + private Class formClass; + + private boolean credentials = true; + + private final List whichCredentials = new ArrayList<>(); + + private boolean plainAttrs = true; + + private final List whichPlainAttrs = new ArrayList<>(); + + private boolean privileges = true; + + protected Class getDefaultFormClass() { + return LinkedAccountWizardBuilder.class; + } + + public Class getFormClass() { + return formClass == null ? getDefaultFormClass() : formClass; + } + + public void setFormClass(final Class formClass) { + this.formClass = formClass; + } + + public boolean isPlainAttrs() { + return plainAttrs; + } + + public void setPlainAttrs(final boolean plainAttrs) { + this.plainAttrs = plainAttrs; + } + + public List getWhichPlainAttrs() { + return whichPlainAttrs; + } + + public boolean isCredentials() { + return credentials; + } + + public void setCredentials(final boolean credentials) { + this.credentials = credentials; + } + + public List getWhichCredentials() { + return whichCredentials; + } + + public boolean isPrivileges() { + return privileges; + } + + public void setPrivileges(final boolean privileges) { + this.privileges = privileges; + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/layout/UserFormLayoutInfo.java b/client/console/src/main/java/org/apache/syncope/client/console/layout/UserFormLayoutInfo.java index c1001437229..77f9ce85282 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/layout/UserFormLayoutInfo.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/layout/UserFormLayoutInfo.java @@ -31,6 +31,12 @@ public class UserFormLayoutInfo extends AbstractAnyFormLayout private boolean relationships = true; + private LinkedAccountFormLayoutInfo linkedAccountFormLayoutInfo; + + public UserFormLayoutInfo() { + this.linkedAccountFormLayoutInfo = new LinkedAccountFormLayoutInfo(); + } + @Override protected Class getDefaultFormClass() { return UserWizardBuilder.class; @@ -60,4 +66,11 @@ public void setRelationships(final boolean relationships) { this.relationships = relationships; } + public LinkedAccountFormLayoutInfo getLinkedAccountFormLayoutInfo() { + return linkedAccountFormLayoutInfo; + } + + public void setLinkedAccountFormLayoutInfo(final LinkedAccountFormLayoutInfo linkedAccountFormLayoutInfo) { + this.linkedAccountFormLayoutInfo = linkedAccountFormLayoutInfo; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java index 95137d1f984..c4e84d88da3 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java @@ -54,7 +54,6 @@ import org.apache.syncope.client.console.panels.WizardModalPanel; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.model.PropertyModel; public class MailTemplateDirectoryPanel @@ -62,7 +61,7 @@ public class MailTemplateDirectoryPanel private static final long serialVersionUID = -3789392431954221446L; - protected final BaseModal utilityModal = new BaseModal<>("outer"); + protected final BaseModal utilityModal = new BaseModal<>(Constants.OUTER); public MailTemplateDirectoryPanel(final String id, final PageReference pageReference) { super(id, pageReference, true); @@ -70,18 +69,11 @@ public MailTemplateDirectoryPanel(final String id, final PageReference pageRefer modal.size(Modal.Size.Small); modal.addSubmitButton(); - setFooterVisibility(true); - - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); }); + setFooterVisibility(true); addOuterObject(utilityModal); setWindowClosedReloadCallback(utilityModal); @@ -109,7 +101,9 @@ public WizardModalPanel build( @Override protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key")); + columns.add(new PropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), + Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); return columns; } @@ -161,12 +155,11 @@ public void onClick(final AjaxRequestTarget target, final MailTemplateTO ignore) public void onClick(final AjaxRequestTarget target, final MailTemplateTO ignore) { try { restClient.deleteTemplate(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -186,8 +179,8 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - return Collections.emptyList(); + protected Collection getBatches() { + return Collections.emptyList(); } protected final class MailTemplateProvider extends DirectoryDataProvider { @@ -198,7 +191,7 @@ protected final class MailTemplateProvider extends DirectoryDataProvider(this); } @@ -250,13 +243,12 @@ public void onSubmit(final AjaxRequestTarget target) { try { restClient.updateTemplateFormat( content.getKey(), content.getContent(), content.getFormat()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.show(false); modal.close(target); } catch (Exception e) { LOG.error("While updating template for {}", content.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java index a27a6506cdd..4a9c43a228e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -59,7 +58,7 @@ public class NotificationDirectoryPanel private static final long serialVersionUID = -3789392431954221446L; - protected final BaseModal utilityModal = new BaseModal<>("outer"); + protected final BaseModal utilityModal = new BaseModal<>(Constants.OUTER); public NotificationDirectoryPanel(final String id, final PageReference pageRef) { super(id, pageRef, true); @@ -84,7 +83,8 @@ public NotificationDirectoryPanel(final String id, final PageReference pageRef) @Override protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key")); + columns.add(new KeyPropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("sender", this), "sender", "sender")); columns.add(new PropertyColumn<>( @@ -136,12 +136,11 @@ public void onClick(final AjaxRequestTarget target, final NotificationTO ignore) public void onClick(final AjaxRequestTarget target, final NotificationTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -161,7 +160,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -174,7 +173,7 @@ protected class NotificationProvider extends DirectoryDataProvider(this); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java index 733aaabe629..b62334611fb 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java @@ -199,7 +199,7 @@ public About(final String id, final IModel>> mod super(id, model); setOutputMarkupId(true); - final AjaxDropDownChoicePanel type = + AjaxDropDownChoicePanel type = new AjaxDropDownChoicePanel<>("about", "anyType", new Model() { private static final long serialVersionUID = -2350296434572623272L; @@ -213,13 +213,12 @@ public String getObject() { public void setObject(final String object) { model.setObject(Pair.of(object, model.getObject().getRight())); } - }); type.setChoices(new AnyTypeRestClient().list()); type.addRequiredLabel(); add(type); - final ListModel clauseModel = new ListModel() { + ListModel clauseModel = new ListModel() { private static final long serialVersionUID = 3769540249683319782L; @@ -232,10 +231,9 @@ public List getObject() { public void setObject(final List object) { model.getObject().setValue(object); } - }; - final WebMarkupContainer searchContainer = new WebMarkupContainer("search"); + WebMarkupContainer searchContainer = new WebMarkupContainer("search"); add(searchContainer.setOutputMarkupId(true)); searchContainer.add(getClauseBuilder(model.getObject().getLeft(), clauseModel).build("clauses")); @@ -283,7 +281,7 @@ public class Abouts extends WizardStep { public Abouts(final NotificationWrapper modelObject) { setTitleModel(new ResourceModel("about")); - final WebMarkupContainer aboutContainer = new WebMarkupContainer("about"); + WebMarkupContainer aboutContainer = new WebMarkupContainer("about"); aboutContainer.setOutputMarkupId(true); add(aboutContainer); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java index cc3e74e1843..e58f485b78a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.panels.search.SearchClause; @@ -52,10 +53,8 @@ public final String getKey() { public List>> getAboutClauses() { if (this.aboutClauses == null) { this.aboutClauses = new ArrayList<>(); - SearchUtils.getSearchClauses(this.notificationTO.getAbouts()).entrySet(). - forEach(entry -> { - this.aboutClauses.add(Pair.of(entry.getKey(), (entry.getValue()))); - }); + this.aboutClauses = SearchUtils.getSearchClauses(this.notificationTO.getAbouts()).entrySet().stream(). + map(entry -> Pair.of(entry.getKey(), entry.getValue())).collect(Collectors.toList()); } return this.aboutClauses; @@ -77,12 +76,12 @@ public void setRecipientClauses(final List dynClauses) { } public Map getAboutFIQLs() { - if (CollectionUtils.isEmpty(this.aboutClauses)) { + if (CollectionUtils.isEmpty(this.aboutClauses) || this.aboutClauses.get(0).getValue().isEmpty()) { return this.notificationTO.getAbouts(); } else { Map res = new HashMap<>(); for (Pair> pair : this.aboutClauses) { - AbstractFiqlSearchConditionBuilder builder; + AbstractFiqlSearchConditionBuilder builder; switch (pair.getLeft()) { case "USER": builder = SyncopeClient.getUserSearchConditionBuilder(); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java index b2e3bff2eab..beb608c6c0a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.notifications; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -50,7 +49,9 @@ public TemplateModal( this.templateTO = templateTO; AjaxTextFieldPanel key = new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(templateTO, "key"), false); + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, + new PropertyModel<>(templateTO, Constants.KEY_FIELD_NAME), false); key.setOutputMarkupPlaceholderTag(true); add(key.setRenderBodyOnly(true)); } @@ -64,12 +65,11 @@ public T getItem() { public void onSubmit(final AjaxRequestTarget target) { try { restClient.createTemplate(templateTO); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.close(target); } catch (SyncopeClientException e) { LOG.error("While creating template for {}", templateTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Approvals.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Approvals.java deleted file mode 100644 index 0dbc4dbb05e..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Approvals.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.client.console.pages; - -import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; -import org.apache.syncope.client.console.approvals.ApprovalDirectoryPanel; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -public class Approvals extends BasePage { - - private static final long serialVersionUID = -1100228004207271271L; - - public Approvals(final PageParameters parameters) { - super(parameters); - - body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class)); - - WebMarkupContainer content = new WebMarkupContainer("content"); - content.setOutputMarkupId(true); - body.add(content); - - content.add(new ApprovalDirectoryPanel("wfPanel", getPageReference())); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java index 09ab57e62ed..7a01e562e8c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Audit.java @@ -28,7 +28,7 @@ import org.apache.syncope.client.console.events.EventCategoryPanel; import org.apache.syncope.client.console.events.SelectedEventsPanel; import org.apache.syncope.client.console.rest.LoggerRestClient; -import org.apache.syncope.common.lib.log.EventCategoryTO; +import org.apache.syncope.common.lib.log.EventCategory; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditLoggerName; import org.apache.syncope.common.lib.types.StandardEntitlement; @@ -89,31 +89,31 @@ public void onEventAction(final IEvent event) { (SelectedEventsPanel.EventSelectionChanged) event.getPayload(); eventSelectionChanged.getToBeRemoved().forEach(toBeRemoved -> { - Pair eventCategory = + Pair eventCategory = AuditLoggerName.parseEventCategory(toBeRemoved); - AuditLoggerName auditLoggerName = new AuditLoggerName( - eventCategory.getKey().getType(), - eventCategory.getKey().getCategory(), - eventCategory.getKey().getSubcategory(), - CollectionUtils.isEmpty(eventCategory.getKey().getEvents()) - ? null : eventCategory.getKey().getEvents().iterator().next(), - eventCategory.getValue()); + AuditLoggerName auditLoggerName = new AuditLoggerName.Builder(). + type(eventCategory.getKey().getType()). + category(eventCategory.getKey().getCategory()). + subcategory(eventCategory.getKey().getSubcategory()). + event(CollectionUtils.isEmpty(eventCategory.getKey().getEvents()) + ? null : eventCategory.getKey().getEvents().iterator().next()). + result(eventCategory.getValue()).build(); loggerRestClient.disableAudit(auditLoggerName); }); eventSelectionChanged.getToBeAdded().forEach(toBeAdded -> { - Pair eventCategory = + Pair eventCategory = AuditLoggerName.parseEventCategory(toBeAdded); - AuditLoggerName auditLoggerName = new AuditLoggerName( - eventCategory.getKey().getType(), - eventCategory.getKey().getCategory(), - eventCategory.getKey().getSubcategory(), - CollectionUtils.isEmpty(eventCategory.getKey().getEvents()) - ? null : eventCategory.getKey().getEvents().iterator().next(), - eventCategory.getValue()); + AuditLoggerName auditLoggerName = new AuditLoggerName.Builder(). + type(eventCategory.getKey().getType()). + category(eventCategory.getKey().getCategory()). + subcategory(eventCategory.getKey().getSubcategory()). + event(CollectionUtils.isEmpty(eventCategory.getKey().getEvents()) + ? null : eventCategory.getKey().getEvents().iterator().next()). + result(eventCategory.getValue()).build(); loggerRestClient.enableAudit(auditLoggerName); }); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java index d79cec7a422..fa893b12381 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java @@ -19,6 +19,7 @@ package org.apache.syncope.client.console.pages; import java.io.Serializable; +import java.lang.reflect.Constructor; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; @@ -29,12 +30,15 @@ import org.apache.syncope.client.console.commons.HttpResourceStream; import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup; import org.apache.syncope.client.console.init.ConsoleInitializer; +import org.apache.syncope.client.console.panels.DelegationSelectionPanel; import org.apache.syncope.client.console.panels.NotificationPanel; import org.apache.syncope.client.console.rest.ConfRestClient; +import org.apache.syncope.client.console.rest.ResponseHolder; +import org.apache.syncope.client.console.topology.TabularTopology; import org.apache.syncope.client.console.topology.Topology; import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.widgets.ApprovalsWidget; +import org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink; +import org.apache.syncope.client.console.widgets.ExtAlertWidget; import org.apache.syncope.client.console.widgets.RemediationsWidget; import org.apache.syncope.common.lib.info.PlatformInfo; import org.apache.syncope.common.lib.info.SystemInfo; @@ -42,6 +46,7 @@ import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.Page; +import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.IAjaxIndicatorAware; import org.apache.wicket.ajax.attributes.AjaxCallListener; @@ -50,7 +55,6 @@ import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.behavior.Behavior; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.head.HeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; @@ -62,6 +66,7 @@ import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.ContentDisposition; @@ -81,8 +86,6 @@ public class BasePage extends WebPage implements IAjaxIndicatorAware { protected NotificationPanel notificationPanel; - protected ApprovalsWidget approvalsWidget; - protected RemediationsWidget remediationWidget; public BasePage() { @@ -103,22 +106,23 @@ public BasePage(final PageParameters parameters) { body.addOrReplace(notificationPanel.setOutputMarkupId(true)); // header, footer - body.add(new Label("username", SyncopeConsoleSession.get().getSelfTO().getUsername())); + String username = SyncopeConsoleSession.get().getSelfTO().getUsername(); + if (SyncopeConsoleSession.get().getDelegatedBy() != null) { + username += " (" + SyncopeConsoleSession.get().getDelegatedBy() + ")"; + } + body.add(new Label("username", username)); remediationWidget = new RemediationsWidget("remediationWidget", getPageReference()); body.add(remediationWidget.setRenderBodyOnly(true)); - approvalsWidget = new ApprovalsWidget("approvalsWidget", getPageReference()); - body.add(approvalsWidget.setRenderBodyOnly(true)); - // right sidebar PlatformInfo platformInfo = SyncopeConsoleSession.get().getPlatformInfo(); Label version = new Label("version", platformInfo.getVersion()); String versionLink = StringUtils.isNotBlank(platformInfo.getBuildNumber()) && platformInfo.getVersion().endsWith("-SNAPSHOT") - ? "https://git-wip-us.apache.org/repos/asf?p=syncope.git;a=commit;h=" + ? "https://gitbox.apache.org/repos/asf?p=syncope.git;a=commit;h=" + platformInfo.getBuildNumber() - : "https://cwiki.apache.org/confluence/display/SYNCOPE/Jazz"; + : "https://cwiki.apache.org/confluence/display/SYNCOPE/Fusion"; version.add(new AttributeModifier("onclick", "window.open('" + versionLink + "', '_blank')")); body.add(version); @@ -135,7 +139,8 @@ public BasePage(final PageParameters parameters) { @Override public void onClick() { try { - HttpResourceStream stream = new HttpResourceStream(new ConfRestClient().dbExport()); + HttpResourceStream stream = new HttpResourceStream( + new ResponseHolder(new ConfRestClient().dbExport())); ResourceStreamRequestHandler rsrh = new ResourceStreamRequestHandler(stream); rsrh.setFileName(stream.getFilename() == null @@ -146,7 +151,7 @@ public void onClick() { getRequestCycle().scheduleRequestHandlerAfterCurrent(rsrh); } catch (Exception e) { - SyncopeConsoleSession.get().error(getString(Constants.ERROR) + ": " + e.getMessage()); + SyncopeConsoleSession.get().onException(e); } } }; @@ -169,7 +174,11 @@ public void onClick() { liContainer = new WebMarkupContainer(getLIContainerId("topology")); body.add(liContainer); - link = BookmarkablePageLinkBuilder.build("topology", Topology.class); + if (SyncopeConsoleApplication.get().getDefaultTopologyClass().contains("TabularTopology")) { + link = BookmarkablePageLinkBuilder.build("topology", TabularTopology.class); + } else { + link = BookmarkablePageLinkBuilder.build("topology", Topology.class); + } MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, String.format("%s,%s", StandardEntitlement.CONNECTOR_LIST, @@ -187,14 +196,6 @@ public void onClick() { WebMarkupContainer confULContainer = new WebMarkupContainer(getULContainerId("configuration")); confLIContainer.add(confULContainer); - liContainer = new WebMarkupContainer(getLIContainerId("workflow")); - liContainer.setOutputMarkupPlaceholderTag(true); - liContainer.setVisible(platformInfo.getUserWorkflowAdapter().contains("Flowable")); - confULContainer.add(liContainer); - link = BookmarkablePageLinkBuilder.build("workflow", Workflow.class); - MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, StandardEntitlement.WORKFLOW_DEF_GET); - liContainer.add(link); - liContainer = new WebMarkupContainer(getLIContainerId("audit")); confULContainer.add(liContainer); link = BookmarkablePageLinkBuilder.build("audit", Audit.class); @@ -216,16 +217,12 @@ public void onClick() { liContainer = new WebMarkupContainer(getLIContainerId("types")); confULContainer.add(liContainer); link = BookmarkablePageLinkBuilder.build("types", Types.class); - MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, StandardEntitlement.SCHEMA_LIST); + MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, StandardEntitlement.ANYTYPECLASS_LIST); liContainer.add(link); liContainer = new WebMarkupContainer(getLIContainerId("security")); confULContainer.add(liContainer); link = BookmarkablePageLinkBuilder.build("security", Security.class); - MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, - String.format("%s,%s", - StandardEntitlement.ROLE_LIST, - StandardEntitlement.APPLICATION_LIST)); liContainer.add(link); liContainer = new WebMarkupContainer(getLIContainerId("policies")); @@ -260,8 +257,30 @@ public void onClick(final AjaxRequestTarget target) { }); body.add(new Label("domain", SyncopeConsoleSession.get().getDomain())); + WebMarkupContainer delegationsContainer = new WebMarkupContainer("delegationsContainer"); + body.add(delegationsContainer.setOutputMarkupPlaceholderTag(true). + setVisible(!SyncopeConsoleSession.get().getDelegations().isEmpty())); + delegationsContainer.add(new Label("delegationsHeader", new ResourceModel("delegations"))); + delegationsContainer.add(new ListView("delegations", SyncopeConsoleSession.get().getDelegations()) { + + @Override + protected void populateItem(final ListItem item) { + item.add(new DelegationSelectionPanel("delegation", item.getModelObject())); + } + }); + + body.add(new IndicatingOnConfirmAjaxLink("endDelegation", "confirmDelegation", true) { + + @Override + public void onClick(final AjaxRequestTarget target) { + SyncopeConsoleSession.get().setDelegatedBy(null); + setResponsePage(Dashboard.class); + } + }.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true). + setVisible(SyncopeConsoleSession.get().getDelegatedBy() != null)); + @SuppressWarnings("unchecked") - final Class beforeLogout = (Class) SyncopeConsoleSession.get(). + Class beforeLogout = (Class) SyncopeConsoleSession.get(). getAttribute(Constants.BEFORE_LOGOUT_PAGE); if (beforeLogout == null) { body.add(new BookmarkablePageLink<>("logout", Logout.class)); @@ -331,10 +350,33 @@ public void onComponentTag(final Component component, final ComponentTag tag) { } // Extensions - ClassPathScanImplementationLookup classPathScanImplementationLookup = - (ClassPathScanImplementationLookup) SyncopeConsoleApplication.get(). - getServletContext().getAttribute(ConsoleInitializer.CLASSPATH_LOOKUP); - List> extPageClasses = classPathScanImplementationLookup.getExtPageClasses(); + ClassPathScanImplementationLookup lookup = (ClassPathScanImplementationLookup) SyncopeConsoleApplication.get(). + getServletContext().getAttribute(ConsoleInitializer.CLASSPATH_LOOKUP); + + List>> extAlertWidgetClasses = lookup.getExtAlertWidgetClasses(); + ListView>> extAlertWidgets = new ListView>>( + "extAlertWidgets", extAlertWidgetClasses) { + + private static final long serialVersionUID = -9112553137618363167L; + + @Override + protected void populateItem(final ListItem>> item) { + try { + Constructor> constructor = + item.getModelObject().getDeclaredConstructor(String.class, PageReference.class); + ExtAlertWidget widget = constructor.newInstance("extAlertWidget", getPageReference()); + + SyncopeConsoleSession.get().setAttribute(widget.getClass().getName(), widget); + + item.add(widget.setRenderBodyOnly(true)); + } catch (Exception e) { + LOG.error("Could not instantiate {}", item.getModelObject().getName(), e); + } + } + }; + body.add(extAlertWidgets); + + List> extPageClasses = lookup.getExtPageClasses(); WebMarkupContainer extensionsLI = new WebMarkupContainer(getLIContainerId("extensions")); extensionsLI.setOutputMarkupPlaceholderTag(true); @@ -430,28 +472,4 @@ public NotificationPanel getNotificationPanel() { public RemediationsWidget getRemediationWidget() { return remediationWidget; } - - public ApprovalsWidget getApprovalsWidget() { - return approvalsWidget; - } - - /** - * Set a WindowClosedCallback for a Modal instance. - * - * @param modal window - * @param container container - */ - public void setWindowClosedCallback(final BaseModal modal, final WebMarkupContainer container) { - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - if (container != null) { - target.add(container); - } - } - }); - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java new file mode 100644 index 00000000000..9c8784e851e --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java @@ -0,0 +1,78 @@ +/* + * 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.client.console.pages; + +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.panels.ConnectorDirectoryPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; +import java.io.Serializable; + +public class Connectors extends Panel { + + private static final long serialVersionUID = 305521359617401936L; + + public Connectors(final String id, final PageReference pageRef) { + super(id); + + WebMarkupContainer content = new WebMarkupContainer("content"); + content.setOutputMarkupId(true); + add(content); + + Model keywordModel = new Model<>(StringUtils.EMPTY); + + WebMarkupContainer searchBoxContainer = new WebMarkupContainer("searchBox"); + content.add(searchBoxContainer); + + Form form = new Form<>("form"); + searchBoxContainer.add(form); + + AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", keywordModel, true); + form.add(filter.hideLabel().setOutputMarkupId(true)); + + AjaxButton search = new AjaxButton("search") { + + private static final long serialVersionUID = 8390605330558248736L; + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + send(Connectors.this, Broadcast.DEPTH, + new ConnectorDirectoryPanel.ConnectorSearchEvent(target, keywordModel.getObject())); + } + }; + search.setOutputMarkupId(true); + form.add(search); + form.setDefaultButton(search); + + WizardMgtPanel connectorDirectoryPanel = new ConnectorDirectoryPanel.Builder(pageRef). + addNewItemPanelBuilder(new ConnectorWizardBuilder(new ConnInstanceTO(), pageRef), true). + build("connectorDirectoryPanel"); + content.add(connectorDirectoryPanel.setOutputMarkupId(true)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java index ed08b11ee35..da27cf04850 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java @@ -25,7 +25,7 @@ import org.apache.syncope.client.console.panels.LogStatementPanel; import org.apache.syncope.client.console.rest.LoggerRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; -import org.apache.syncope.common.lib.log.LogStatementTO; +import org.apache.syncope.common.lib.log.LogStatement; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior; @@ -64,13 +64,13 @@ public LogViewer() { viewer.add(stContainer); final Model lastTimeInMillis = Model.of(0L); - final IModel> statementViewModel = new ListModel<>(new ArrayList<>()); - final ListView statementView = new ListView("statements", statementViewModel) { + final IModel> statementViewModel = new ListModel<>(new ArrayList<>()); + final ListView statementView = new ListView("statements", statementViewModel) { private static final long serialVersionUID = -9180479401817023838L; @Override - protected void populateItem(final ListItem item) { + protected void populateItem(final ListItem item) { LogStatementPanel panel = new LogStatementPanel("statement", item.getModelObject()); panel.setOutputMarkupId(true); item.add(panel); @@ -88,7 +88,7 @@ protected void onPostProcessTarget(final AjaxRequestTarget target) { target.prependJavaScript( String.format("window.scrollTop = $('#%s').scrollTop();", stContainer.getMarkupId())); - List recentLogStatements = appenders.getModelObject() == null + List recentLogStatements = appenders.getModelObject() == null ? new ArrayList<>() : restClient.getLastLogStatements(appenders.getModelObject(), lastTimeInMillis.getObject()); if (!recentLogStatements.isEmpty()) { @@ -97,7 +97,7 @@ protected void onPostProcessTarget(final AjaxRequestTarget target) { int currentSize = statementView.getModelObject().size(); int recentSize = recentLogStatements.size(); - List newModelObject = SetUniqueList.setUniqueList( + List newModelObject = SetUniqueList.setUniqueList( new ArrayList<>(MAX_STATEMENTS_PER_APPENDER)); if (currentSize <= MAX_STATEMENTS_PER_APPENDER - recentSize) { newModelObject.addAll(statementView.getModelObject()); @@ -124,7 +124,7 @@ protected void onPostProcessTarget(final AjaxRequestTarget target) { @Override protected void onUpdate(final AjaxRequestTarget target) { - List lastStatements = appenders.getModelObject() == null + List lastStatements = appenders.getModelObject() == null ? new ArrayList<>() : restClient.getLastLogStatements(appenders.getModelObject(), 0); statementViewModel.setObject(lastStatements); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Login.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Login.java index 796408e4c58..60875795533 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Login.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Login.java @@ -21,8 +21,11 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.select.BootstrapSelect; import java.security.AccessControlException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; +import javax.ws.rs.core.HttpHeaders; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -31,13 +34,13 @@ import org.apache.syncope.client.console.panels.NotificationPanel; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.authentication.IAuthenticationStrategy; import org.apache.wicket.authroles.authentication.AuthenticatedWebSession; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; -import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.PasswordTextField; import org.apache.wicket.markup.html.form.StatelessForm; import org.apache.wicket.markup.html.form.TextField; @@ -46,6 +49,8 @@ import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; +import org.apache.wicket.protocol.http.servlet.ServletWebRequest; +import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,10 +69,6 @@ public class Login extends WebPage { private final TextField passwordField; - private final DropDownChoice languageSelect; - - private final DropDownChoice domainSelect; - public Login(final PageParameters parameters) { super(parameters); setStatelessHint(true); @@ -94,14 +95,49 @@ public Login(final PageParameters parameters) { passwordField.setMarkupId("password"); form.add(passwordField); - languageSelect = new LocaleDropDown("language"); - form.add(languageSelect); + LocaleDropDown languageSelect = new LocaleDropDown("language"); + languageSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + target.add(form); + } + }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + target.add(form); + } + }); + form.add(languageSelect.setOutputMarkupId(true)); - domainSelect = new DomainDropDown("domain"); + DomainDropDown domainSelect = new DomainDropDown("domain"); + domainSelect.setOutputMarkupPlaceholderTag(true); if (SyncopeConsoleApplication.get().getDomains().size() == 1) { - domainSelect.setOutputMarkupPlaceholderTag(true); + domainSelect.setVisible(false); } - form.add(domainSelect); + domainSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + target.add(form); + } + }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + target.add(form); + } + }); + form.add(domainSelect.setOutputMarkupId(true)); AjaxButton submitButton = new AjaxButton("submit", new Model<>(getString("submit"))) { @@ -197,15 +233,31 @@ public void detach() { } }); - // set default value to English - getModel().setObject(Locale.ENGLISH); + // set default language selection + List filtered = Collections.emptyList(); + + String acceptLanguage = ((ServletWebRequest) RequestCycle.get().getRequest()). + getHeader(HttpHeaders.ACCEPT_LANGUAGE); + if (StringUtils.isNotBlank(acceptLanguage)) { + try { + filtered = Locale.filter( + Locale.LanguageRange.parse(acceptLanguage), SyncopeConsoleApplication.SUPPORTED_LOCALES); + } catch (Exception e) { + LOG.debug("Could not parse {} HTTP header value '{}'", + HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage, e); + } + } + + getModel().setObject(filtered.isEmpty() + ? Locale.ENGLISH + : filtered.get(0)); } } /** * Inner class which implements (custom) Domain DropDownChoice component. */ - private class DomainDropDown extends BootstrapSelect { + private static class DomainDropDown extends BootstrapSelect { private static final long serialVersionUID = -7401167913360133325L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/MustChangePassword.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/MustChangePassword.java index 4fcca5bb31a..f1aae6dc279 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/MustChangePassword.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/MustChangePassword.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.pages; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.NotificationPanel; @@ -97,8 +96,7 @@ protected void onSubmit(final AjaxRequestTarget target) { } catch (Exception e) { LOG.error("While changing password for {}", SyncopeConsoleSession.get().getSelfTO().getUsername(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); notificationPanel.refresh(target); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java index 294b238abf5..5688f31d5c5 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java @@ -24,6 +24,7 @@ import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; import org.apache.syncope.client.console.policies.AccountPolicyDirectoryPanel; import org.apache.syncope.client.console.policies.PasswordPolicyDirectoryPanel; +import org.apache.syncope.client.console.policies.PropagationPolicyDirectoryPanel; import org.apache.syncope.client.console.policies.PullPolicyDirectoryPanel; import org.apache.syncope.client.console.policies.PushPolicyDirectoryPanel; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; @@ -72,6 +73,16 @@ public Panel getPanel(final String panelId) { } }); + tabs.add(new AbstractTab(new ResourceModel("policy.propagation")) { + + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + return new PropagationPolicyDirectoryPanel(panelId, getPageReference()); + } + }); + tabs.add(new AbstractTab(new ResourceModel("policy.pull")) { private static final long serialVersionUID = -6815067322125799251L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java index b113144b90b..c8e65280ad8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java @@ -20,6 +20,7 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; +import java.util.List; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -27,19 +28,21 @@ import org.apache.syncope.client.console.panels.RealmChoicePanel; import org.apache.syncope.client.console.panels.RealmChoicePanel.ChosenRealm; import org.apache.syncope.client.console.panels.WizardModalPanel; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.tasks.TemplatesTogglePanel; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.client.console.wizards.any.ResultPage; import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.AnyTypeTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.lib.to.TemplatableTO; +import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; import org.apache.wicket.event.IEvent; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow.WindowClosedCallback; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.CompoundPropertyModel; @@ -51,17 +54,23 @@ public class Realms extends BasePage { private static final long serialVersionUID = -1100228004207271270L; - private final RealmRestClient realmRestClient = new RealmRestClient(); + public static final String SELECTED_INDEX = "selectedIndex"; - private final TemplatesTogglePanel templates; + public static final String INITIAL_REALM = "initialRealm"; - private final RealmChoicePanel realmChoicePanel; + protected final RealmRestClient realmRestClient = new RealmRestClient(); - private final WebMarkupContainer content; + protected final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); - private final BaseModal modal; + protected final TemplatesTogglePanel templates; - private final BaseModal templateModal; + protected final RealmChoicePanel realmChoicePanel; + + protected final WebMarkupContainer content; + + protected final BaseModal modal; + + protected final BaseModal templateModal; public Realms(final PageParameters parameters) { super(parameters); @@ -73,8 +82,9 @@ public Realms(final PageParameters parameters) { @Override protected Serializable onApplyInternal( final TemplatableTO targetObject, final String type, final AnyTO anyTO) { + targetObject.getTemplates().put(type, anyTO); - new RealmRestClient().update(RealmTO.class.cast(targetObject)); + realmRestClient.update(RealmTO.class.cast(targetObject)); return targetObject; } }; @@ -83,8 +93,7 @@ protected Serializable onApplyInternal( content = new WebMarkupContainer("content"); - realmChoicePanel = new RealmChoicePanel("realmChoicePanel", getPageReference()); - realmChoicePanel.setOutputMarkupId(true); + realmChoicePanel = buildRealmChoicePanel(parameters.get(INITIAL_REALM).toOptionalString(), getPageReference()); content.add(realmChoicePanel); content.add(new Label("body", "Root realm")); @@ -110,35 +119,28 @@ protected void onConfigure() { templateModal.size(Modal.Size.Large); content.add(templateModal); - modal.setWindowClosedCallback(new WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - target.add(realmChoicePanel.reloadRealmTree(target)); - target.add(content); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + target.add(realmChoicePanel.reloadRealmTree(target)); + target.add(content); + modal.show(false); }); - templateModal.setWindowClosedCallback(new WindowClosedCallback() { + templateModal.setWindowClosedCallback(target -> { + target.add(content); + templateModal.show(false); + }); - private static final long serialVersionUID = 8804221891699487139L; + updateRealmContent(realmChoicePanel.getCurrentRealm(), parameters.get(SELECTED_INDEX).toInt(0)); + } - @Override - public void onClose(final AjaxRequestTarget target) { - target.add(content); - templateModal.show(false); - } - }); + protected RealmChoicePanel buildRealmChoicePanel(final String initialRealm, final PageReference pageRef) { + RealmChoicePanel panel = new RealmChoicePanel("realmChoicePanel", initialRealm, pageRef); + panel.setOutputMarkupId(true); + return panel; + } - try { - updateRealmContent(realmChoicePanel.getCurrentRealm(), parameters.get("selectedIndex").toInteger()); - } catch (Exception e) { - LOG.debug("Unexpected error", e); - updateRealmContent(realmChoicePanel.getCurrentRealm(), 0); - } + public RealmChoicePanel getRealmChoicePanel() { + return realmChoicePanel; } @Override @@ -158,100 +160,105 @@ public void onEvent(final IEvent event) { final IModel model = new CompoundPropertyModel<>(modalPanel.getItem()); templateModal.setFormModel(model); templateModal.header(newItemEvent.getResourceModel()); - newItemEvent.getTarget().add(templateModal.setContent(modalPanel)); + newItemEvent.getTarget().ifPresent(t -> t.add(templateModal.setContent(modalPanel))); templateModal.show(true); } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { - templateModal.close(newItemEvent.getTarget()); + if (newItemEvent.getTarget().isPresent()) { + templateModal.close(newItemEvent.getTarget().get()); + } } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - ((BasePage) getPage()).getNotificationPanel().refresh(newItemEvent.getTarget()); - templateModal.close(newItemEvent.getTarget()); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + if (newItemEvent.getTarget().isPresent()) { + ((BasePage) getPage()).getNotificationPanel().refresh(newItemEvent.getTarget().get()); + templateModal.close(newItemEvent.getTarget().get()); + } } } } - private WebMarkupContainer updateRealmContent(final RealmTO realmTO, final int selectedIndex) { - if (realmTO == null) { - return content; + protected WebMarkupContainer updateRealmContent(final RealmTO realmTO, final int selectedIndex) { + if (realmTO != null) { + content.addOrReplace(new Content(realmTO, anyTypeRestClient.listAnyTypes(), selectedIndex)); } - content.addOrReplace(new Realm("body", realmTO, Realms.this.getPageReference(), selectedIndex) { + return content; + } - private static final long serialVersionUID = 8221398624379357183L; + protected class Content extends Realm { - @Override - protected void onClickTemplate(final AjaxRequestTarget target) { - templates.setTargetObject(realmTO); - templates.toggle(target, true); - } - - @Override - protected void setWindowClosedReloadCallback(final BaseModal modal) { - modal.setWindowClosedCallback(target -> { - if (modal.getContent() instanceof ResultPage) { - Serializable result = ResultPage.class.cast(modal.getContent()).getResult(); - - RealmTO newRealmTO = RealmTO.class.cast(ProvisioningResult.class.cast(result).getEntity()); - // reload realmChoicePanel label too - SYNCOPE-1151 - target.add(realmChoicePanel.reloadRealmTree(target, Model.of(newRealmTO))); - realmChoicePanel.setCurrentRealm(newRealmTO); - send(Realms.this, Broadcast.DEPTH, new ChosenRealm<>(newRealmTO, target)); - } else { - target.add(realmChoicePanel.reloadRealmTree(target)); - } - target.add(content); - modal.show(false); - }); - } + private static final long serialVersionUID = 8221398624379357183L; - @Override - protected void onClickCreate(final AjaxRequestTarget target) { - this.wizardBuilder.setParentPath(realmChoicePanel.getCurrentRealm().getFullPath()); - send(this, Broadcast.EXACT, new AjaxWizard.NewItemActionEvent(new RealmTO(), target) { - - @Override - public String getEventDescription() { - return "realm.new"; - } - }); - } + protected Content(final RealmTO realmTO, final List anyTypes, final int selectedIndex) { + super("body", realmTO, anyTypes, selectedIndex, Realms.this.getPageReference()); + } - @Override - protected void onClickEdit(final AjaxRequestTarget target, final RealmTO realmTO) { - this.wizardBuilder.setParentPath(realmTO.getFullPath()); - send(this, Broadcast.EXACT, new AjaxWizard.EditItemActionEvent(realmTO, target) { - - @Override - public String getEventDescription() { - return "realm.edit"; - } - }); - } + @Override + protected void onClickTemplate(final AjaxRequestTarget target) { + templates.setTargetObject(realmTO); + templates.toggle(target, true); + } - @Override - protected void onClickDelete(final AjaxRequestTarget target, final RealmTO realmTO) { - try { - if (realmTO.getKey() == null) { - throw new Exception("Root realm cannot be deleted"); - } - realmRestClient.delete(realmTO.getFullPath()); - RealmTO parent = realmChoicePanel.moveToParentRealm(realmTO.getKey()); + @Override + protected void setWindowClosedReloadCallback(final BaseModal modal) { + modal.setWindowClosedCallback(target -> { + if (modal.getContent() instanceof ResultPage) { + Serializable result = ResultPage.class.cast(modal.getContent()).getResult(); + + RealmTO newRealmTO = RealmTO.class.cast(ProvisioningResult.class.cast(result).getEntity()); + // reload realmChoicePanel label too - SYNCOPE-1151 + target.add(realmChoicePanel.reloadRealmTree(target, Model.of(newRealmTO))); + realmChoicePanel.setCurrentRealm(newRealmTO); + send(Realms.this, Broadcast.DEPTH, new ChosenRealm<>(newRealmTO, target)); + } else { target.add(realmChoicePanel.reloadRealmTree(target)); + } + target.add(content); + modal.show(false); + }); + } + + @Override + protected void onClickCreate(final AjaxRequestTarget target) { + this.wizardBuilder.setParentPath(realmChoicePanel.getCurrentRealm().getFullPath()); + send(this, Broadcast.EXACT, new AjaxWizard.NewItemActionEvent(new RealmTO(), target) { - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - updateRealmContent(parent, selectedIndex); - target.add(content); - } catch (Exception e) { - LOG.error("While deleting realm", e); - // Escape line breaks - SyncopeConsoleSession.get().error(e.getMessage().replace("\n", " ")); + @Override + public String getEventDescription() { + return "realm.new"; } - ((BasePage) Realms.this.getPage()).getNotificationPanel().refresh(target); - } - }); - return content; - } + }); + } - public RealmChoicePanel getRealmChoicePanel() { - return realmChoicePanel; + @Override + protected void onClickEdit(final AjaxRequestTarget target, final RealmTO realmTO) { + this.wizardBuilder.setParentPath(realmTO.getFullPath()); + send(this, Broadcast.EXACT, new AjaxWizard.EditItemActionEvent(realmTO, target) { + + @Override + public String getEventDescription() { + return "realm.edit"; + } + }); + } + + @Override + protected void onClickDelete(final AjaxRequestTarget target, final RealmTO realmTO) { + try { + if (realmTO.getKey() == null) { + throw new Exception("Root realm cannot be deleted"); + } + realmRestClient.delete(realmTO.getFullPath()); + RealmTO parent = realmChoicePanel.moveToParentRealm(realmTO.getKey()); + target.add(realmChoicePanel.reloadRealmTree(target)); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + updateRealmContent(parent, selectedIndex); + target.add(content); + } catch (Exception e) { + LOG.error("While deleting realm", e); + // Escape line breaks + SyncopeConsoleSession.get().error(e.getMessage().replace("\n", " ")); + } + ((BasePage) Realms.this.getPage()).getNotificationPanel().refresh(target); + } } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Reports.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Reports.java index c01da4c9ffd..889559d0d96 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Reports.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Reports.java @@ -62,13 +62,13 @@ private List buildTabList() { @Override public Panel getPanel(final String panelId) { - final MultilevelPanel mlp = new MultilevelPanel(panelId); + MultilevelPanel mlp = new MultilevelPanel(panelId); mlp.setFirstLevel(new ReportDirectoryPanel(mlp, getPageReference()) { private static final long serialVersionUID = -2195387360323687302L; @Override - protected void viewTask(final ReportTO reportTO, final AjaxRequestTarget target) { + protected void viewReport(final ReportTO reportTO, final AjaxRequestTarget target) { mlp.next( new StringResourceModel("report.view", this, new Model<>(reportTO)).getObject(), new ReportExecutionDetails(reportTO, getPageReference()), diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java new file mode 100644 index 00000000000..dec7fba7a04 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java @@ -0,0 +1,78 @@ +/* + * 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.client.console.pages; + +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.panels.ResourceDirectoryPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; +import java.io.Serializable; + +public class Resources extends Panel { + + private static final long serialVersionUID = 7240865652350993779L; + + public Resources(final String id, final PageReference pageRef) { + super(id); + + WebMarkupContainer content = new WebMarkupContainer("content"); + content.setOutputMarkupId(true); + add(content); + + Model keywordModel = new Model<>(StringUtils.EMPTY); + + WebMarkupContainer searchBoxContainer = new WebMarkupContainer("searchBox"); + content.add(searchBoxContainer); + + Form form = new Form<>("form"); + searchBoxContainer.add(form); + + AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", keywordModel, true); + form.add(filter.hideLabel().setOutputMarkupId(true)); + + AjaxButton search = new AjaxButton("search") { + + private static final long serialVersionUID = 8390605330558248736L; + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + send(Resources.this, Broadcast.DEPTH, + new ResourceDirectoryPanel.ResourceSearchEvent(target, keywordModel.getObject())); + } + }; + search.setOutputMarkupId(true); + form.add(search); + form.setDefaultButton(search); + + WizardMgtPanel resourceDirectoryPanel = new ResourceDirectoryPanel.Builder(pageRef). + addNewItemPanelBuilder(new ResourceWizardBuilder(new ResourceTO(), pageRef), true). + build("resourceDirectoryPanel"); + content.add(resourceDirectoryPanel.setOutputMarkupId(true)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Security.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Security.java index 56efb31aba0..1a0b7a11089 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Security.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Security.java @@ -22,12 +22,17 @@ import java.util.ArrayList; import java.util.List; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; +import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.panels.ApplicationDirectoryPanel; +import org.apache.syncope.client.console.panels.DelegationDirectoryPanel; import org.apache.syncope.client.console.panels.DynRealmDirectoryPanel; import org.apache.syncope.client.console.panels.RoleDirectoryPanel; import org.apache.syncope.client.console.panels.SecurityQuestionsPanel; +import org.apache.syncope.client.console.wizards.DelegationWizardBuilder; import org.apache.syncope.client.console.wizards.role.RoleWizardBuilder; +import org.apache.syncope.common.lib.to.DelegationTO; import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -51,19 +56,37 @@ public Security(final PageParameters parameters) { } private List buildTabList() { - final List tabs = new ArrayList<>(); + List tabs = new ArrayList<>(); - tabs.add(new AbstractTab(new ResourceModel("roles")) { + if (SyncopeConsoleSession.get().owns(StandardEntitlement.ROLE_LIST)) { + tabs.add(new AbstractTab(new ResourceModel("roles")) { - private static final long serialVersionUID = -6815067322125799251L; + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + return new RoleDirectoryPanel.Builder(getPageReference()) { + + private static final long serialVersionUID = -5960765294082359003L; + + }.addNewItemPanelBuilder(new RoleWizardBuilder(new RoleTO(), getPageReference()), true).build( + panelId); + } + }); + } + + tabs.add(new AbstractTab(new ResourceModel("delegations")) { + + private static final long serialVersionUID = 29722178554609L; @Override public Panel getPanel(final String panelId) { - return new RoleDirectoryPanel.Builder(getPageReference()) { + return new DelegationDirectoryPanel.Builder(getPageReference()) { - private static final long serialVersionUID = -5960765294082359003L; + private static final long serialVersionUID = 30231721305047L; - }.addNewItemPanelBuilder(new RoleWizardBuilder(new RoleTO(), getPageReference()), true).build(panelId); + }.addNewItemPanelBuilder( + new DelegationWizardBuilder(new DelegationTO(), getPageReference()), true).build(panelId); } }); @@ -81,19 +104,21 @@ public Panel getPanel(final String panelId) { } }); - tabs.add(new AbstractTab(new ResourceModel("applications")) { + if (SyncopeConsoleSession.get().owns(StandardEntitlement.APPLICATION_LIST)) { + tabs.add(new AbstractTab(new ResourceModel("applications")) { - private static final long serialVersionUID = -6815067322125799251L; + private static final long serialVersionUID = -6815067322125799251L; - @Override - public Panel getPanel(final String panelId) { - return new ApplicationDirectoryPanel.Builder(getPageReference()) { + @Override + public Panel getPanel(final String panelId) { + return new ApplicationDirectoryPanel.Builder(getPageReference()) { - private static final long serialVersionUID = -5960765294082359003L; + private static final long serialVersionUID = -5960765294082359003L; - }.build(panelId); - } - }); + }.build(panelId); + } + }); + } tabs.add(new AbstractTab(new ResourceModel("securityQuestions")) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Workflow.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Workflow.java deleted file mode 100644 index 5ab09c336b9..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Workflow.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.client.console.pages; - -import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.panels.WorkflowDirectoryPanel; -import org.apache.syncope.client.console.wizards.WizardMgtPanel; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -public class Workflow extends BasePage { - - private static final long serialVersionUID = -8781434495150074529L; - - public Workflow(final PageParameters parameters) { - super(parameters); - - body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class)); - - WebMarkupContainer content = new WebMarkupContainer("content"); - content.setOutputMarkupId(true); - body.add(content); - - WebMarkupContainer disabled = new WebMarkupContainer("disabled"); - disabled.setOutputMarkupPlaceholderTag(true); - content.add(disabled); - - WizardMgtPanel workflowsPanel = new WorkflowDirectoryPanel.Builder(getPageReference()) { - - private static final long serialVersionUID = -5960765294082359003L; - - }.disableCheckBoxes().build("workflowsPanel"); - workflowsPanel.setOutputMarkupPlaceholderTag(true); - MetaDataRoleAuthorizationStrategy.authorize(workflowsPanel, ENABLE, StandardEntitlement.WORKFLOW_DEF_LIST); - - content.add(workflowsPanel); - - if (SyncopeConsoleSession.get().getPlatformInfo().getUserWorkflowAdapter().contains("Flowable")) { - disabled.setVisible(false); - } else { - workflowsPanel.setVisible(false); - } - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java index 5246509b2cf..d5514f71738 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java @@ -18,15 +18,14 @@ */ package org.apache.syncope.client.console.panels; +import java.io.Serializable; import java.util.Arrays; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; -import org.apache.syncope.common.lib.AbstractBaseBean; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.log.LoggerTO; import org.apache.syncope.common.lib.types.LoggerLevel; @@ -42,7 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractLogsPanel extends Panel { +public abstract class AbstractLogsPanel extends Panel { private static final Logger LOG = LoggerFactory.getLogger(AbstractLogsPanel.class); @@ -84,12 +83,11 @@ protected void onUpdate(final AjaxRequestTarget target) { try { loggerTO.setLevel(loggerTOs.getModelObject()); update(loggerTO); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(loggerTOs); } catch (SyncopeClientException e) { LOG.error("Error updating the logger level", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -103,12 +101,12 @@ protected void onUpdate(final AjaxRequestTarget target) { builder.setItems(loggerTOs). setModel(new ListModel<>(loggerTOs)). - includes("key", "level"). + includes(Constants.KEY_FIELD_NAME, "level"). withChecks(ListViewPanel.CheckAvailability.NONE). setReuseItem(false); loggerContainer.add(builder.build("logger")); } - protected abstract void update(final LoggerTO loggerTO); + protected abstract void update(LoggerTO loggerTO); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java index c3a4bb78d9e..83da4607439 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.console.panels; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.to.SchemaTO; import org.apache.wicket.markup.html.panel.Panel; @@ -34,7 +35,10 @@ public abstract class AbstractSchemaDetailsPanel extends Panel { public AbstractSchemaDetailsPanel(final String id, final SchemaTO schemaTO) { super(id); - AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>(schemaTO, "key")); + AjaxTextFieldPanel key = new AjaxTextFieldPanel( + Constants.KEY_FIELD_NAME, + getString(Constants.KEY_FIELD_NAME), + new PropertyModel<>(schemaTO, Constants.KEY_FIELD_NAME)); key.addRequiredLabel(); key.setEnabled(schemaTO == null || schemaTO.getKey() == null || schemaTO.getKey().isEmpty()); add(key); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AccessTokenDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AccessTokenDirectoryPanel.java index 29924fc2878..f6d89109602 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AccessTokenDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AccessTokenDirectoryPanel.java @@ -25,7 +25,6 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -102,7 +101,7 @@ public void populateItem( JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(model.getObject().getBody()); cellItem.add(new Label(componentId, SyncopeConsoleSession.get().getDateFormat().format( - new Date(consumer.getJwtClaims().getIssuedAt())))); + new Date(consumer.getJwtClaims().getIssuedAt() * 1000)))); } }); @@ -123,12 +122,11 @@ public ActionsPanel getActions(final IModel model) public void onClick(final AjaxRequestTarget target, final AccessTokenTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -138,7 +136,7 @@ public void onClick(final AjaxRequestTarget target, final AccessTokenTO ignore) } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ActionDataTablePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ActionDataTablePanel.java index 7ea7baa8761..cff19eb8f0d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ActionDataTablePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ActionDataTablePanel.java @@ -45,7 +45,7 @@ public class ActionDataTablePanel extends DataTablePa private static final String CANCEL = "cancel"; - private final Form bulkActionForm; + private final Form batchForm; private final ActionsPanel actionPanel; @@ -57,8 +57,8 @@ public ActionDataTablePanel( super(id); - bulkActionForm = new Form<>("groupForm"); - add(bulkActionForm); + batchForm = new Form<>("groupForm"); + add(batchForm); group = new ActionTableCheckGroup("checkgroup", model) { @@ -78,14 +78,14 @@ protected void onUpdate(final AjaxRequestTarget target) { // triggers AJAX form submit } }); - bulkActionForm.add(group); + batchForm.add(group); columns.add(0, new CheckGroupColumn<>(group)); dataTable = new AjaxFallbackDataTable<>("dataTable", columns, dataProvider, rowsPerPage, this); group.add(dataTable); final WebMarkupContainer actionPanelContainer = new WebMarkupContainer("actionPanelContainer"); - bulkActionForm.add(actionPanelContainer); + batchForm.add(actionPanelContainer); actionPanel = new ActionsPanel<>("actions", null); actionPanelContainer.add(actionPanel); @@ -94,7 +94,7 @@ protected void onUpdate(final AjaxRequestTarget target) { actionPanelContainer.add(new AttributeModifier("style", "display: none")); } - bulkActionForm.add(new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) { + batchForm.add(new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) { private static final long serialVersionUID = -2341391430136818025L; @@ -105,14 +105,12 @@ protected void onSubmit(final AjaxRequestTarget target) { }.setVisible(false).setEnabled(false)); } - public void addAction( - final ActionLink action, final ActionType type, final String entitlements) { + public void addAction(final ActionLink action, final ActionType type, final String entitlements) { actionPanel.add(action, type, entitlements); } public void addCancelButton(final BaseModal modal) { - - final AjaxButton cancel = new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) { + AjaxButton cancel = new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) { private static final long serialVersionUID = -4804368561204623354L; @@ -121,9 +119,8 @@ protected void onSubmit(final AjaxRequestTarget target) { modal.close(target); } }; - cancel.setDefaultFormProcessing(false); - bulkActionForm.addOrReplace(cancel); + batchForm.addOrReplace(cancel); } public Collection getModelObject() { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java index 885a3d529ad..ac2f5ff2444 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java @@ -18,16 +18,18 @@ */ package org.apache.syncope.client.console.panels; +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.apache.syncope.client.console.rest.BaseRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior; -import org.apache.syncope.client.console.bulk.BulkActionModal; -import org.apache.syncope.client.console.bulk.BulkContent; +import org.apache.syncope.client.console.batch.BatchModal; +import org.apache.syncope.client.console.batch.BatchContent; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.DirectoryPanel.EventDataWrapper; import org.apache.syncope.client.console.rest.RestClient; @@ -68,9 +70,9 @@ public static class Builder implements Serializable { private int rowsPerPage = 10; - private final Collection bulkActions = new ArrayList<>(); + private final Collection batches = new ArrayList<>(); - private RestClient bulkActionExecutor; + private RestClient batchExecutor; private String itemKeyField; @@ -96,13 +98,13 @@ public Builder setContainer(final WebMarkupContainer container) { return this; } - public Builder addBulkAction(final ActionLink.ActionType actionType) { - bulkActions.add(actionType); + public Builder addBatch(final ActionLink.ActionType actionType) { + batches.add(actionType); return this; } - public Builder setBulkActionExecutor(final BaseRestClient bulkActionExecutor) { - this.bulkActionExecutor = bulkActionExecutor; + public Builder setBatchExecutor(final BaseRestClient batchExecutor) { + this.batchExecutor = batchExecutor; return this; } @@ -111,15 +113,16 @@ public Builder setItemKeyField(final String itemKeyField) { return this; } - public Builder setBulkActions( - final Collection bulkActions, - final RestClient bulkActionExecutor, + public Builder setBatches( + final Collection batches, + final RestClient batchExecutor, final String itemKeyField) { - this.bulkActions.clear(); - if (bulkActions != null) { - this.bulkActions.addAll(bulkActions); + + this.batches.clear(); + if (batches != null) { + this.batches.addAll(batches); } - this.bulkActionExecutor = bulkActionExecutor; + this.batchExecutor = batchExecutor; this.itemKeyField = itemKeyField; return this; } @@ -147,8 +150,8 @@ public Builder disableCheckBoxes() { return this; } - private boolean isBulkEnabled() { - return checkBoxEnabled && bulkActionExecutor != null && !bulkActions.isEmpty(); + private boolean isBatchEnabled() { + return checkBoxEnabled && batchExecutor != null && !batches.isEmpty(); } public void setMultiLevelPanel(final BaseModal baseModal, final MultilevelPanel multiLevelPanel) { @@ -165,37 +168,38 @@ protected ActionLinksTogglePanel getTogglePanel() { } } + protected final BaseModal batchModal; + private AjaxDataTablePanel(final String id, final Builder builder) { super(id); - final BaseModal bulkModal = new BaseModal<>("bulkModal"); - add(bulkModal); + batchModal = new BaseModal<>("batchModal"); + batchModal.size(Modal.Size.Large); + add(batchModal); - bulkModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { + batchModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { private static final long serialVersionUID = 8804221891699487149L; @Override public void onClose(final AjaxRequestTarget target) { - bulkModal.show(false); + batchModal.show(false); EventDataWrapper data = new EventDataWrapper(); data.setTarget(target); data.setRows(builder.rowsPerPage); send(builder.pageRef.getPage(), Broadcast.BREADTH, data); - BasePage page = (BasePage) findPage(); - if (page != null) { - page.getNotificationPanel().refresh(target); - } + Optional.ofNullable((BasePage) findPage()). + ifPresent(page -> page.getNotificationPanel().refresh(target)); } }); - Fragment fragment = new Fragment("tablePanel", "bulkAvailable", this); + Fragment fragment = new Fragment("tablePanel", "batchAvailable", this); add(fragment); - Form bulkActionForm = new Form<>("groupForm"); - fragment.add(bulkActionForm); + Form groupForm = new Form<>("groupForm"); + fragment.add(groupForm); group = new CheckGroup<>("checkgroup", model); group.add(new IndicatorAjaxFormChoiceComponentUpdatingBehavior() { @@ -210,7 +214,7 @@ protected void onUpdate(final AjaxRequestTarget target) { }); } }); - bulkActionForm.add(group); + groupForm.add(group); if (builder.checkBoxEnabled) { builder.columns.add(0, new CheckGroupColumn<>(group)); @@ -237,7 +241,7 @@ protected ActionLinksTogglePanel getTogglePanel() { group.add(dataTable); - fragment.add(new IndicatingAjaxButton("bulkActionLink", bulkActionForm) { + fragment.add(new IndicatingAjaxButton("batchLink", groupForm) { private static final long serialVersionUID = 382302811235019988L; @@ -249,40 +253,39 @@ protected void onSubmit(final AjaxRequestTarget target) { } if (builder.multiLevelPanel == null) { - bulkModal.header(new ResourceModel("bulk.action")); - bulkModal.changeCloseButtonLabel(getString("cancel", null, "Cancel"), target); + batchModal.header(new ResourceModel("batch")); + batchModal.changeCloseButtonLabel(getString("cancel", null, "Cancel"), target); - target.add(bulkModal.setContent(new BulkActionModal<>( - bulkModal, + target.add(batchModal.setContent(new BatchModal<>( + batchModal, builder.pageRef, new ArrayList<>(group.getModelObject()), builder.columns.size() == 1 ? builder.columns // serialization problem with sublist only : new ArrayList<>(builder.columns.subList(1, builder.columns.size())), - builder.bulkActions, - builder.bulkActionExecutor, + builder.batches, + builder.batchExecutor, builder.itemKeyField))); - bulkModal.show(true); + batchModal.show(true); } else { - builder.multiLevelPanel.next( - getString("bulk.action"), - new BulkContent<>( + builder.multiLevelPanel.next(getString("batch"), + new BatchContent<>( builder.baseModal, new ArrayList<>(group.getModelObject()), builder.columns.size() == 1 ? builder.columns // serialization problem with sublist only : new ArrayList<>(builder.columns.subList(1, builder.columns.size())), - builder.bulkActions, - builder.bulkActionExecutor, + builder.batches, + builder.batchExecutor, builder.itemKeyField), target); } - group.setModelObject(Collections.emptyList()); + group.setModelObject(Collections.emptyList()); target.add(group); } - }.setEnabled(builder.isBulkEnabled()).setVisible(builder.isBulkEnabled())); + }.setEnabled(builder.isBatchEnabled()).setVisible(builder.isBatchEnabled())); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java index bea77ae2f19..3a98693a71c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java @@ -26,13 +26,18 @@ import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Triple; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.AnyDataProvider; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.status.ConnObjectWrapper; +import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.rest.AbstractAnyRestClient; +import org.apache.syncope.client.console.rest.BaseRestClient; +import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior; import org.apache.syncope.client.console.rest.SchemaRestClient; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.AttrColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; @@ -41,23 +46,39 @@ import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.TokenColumn; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.CSVPullWizardBuilder; +import org.apache.syncope.client.console.wizards.CSVPushWizardBuilder; import org.apache.syncope.client.console.wizards.any.AnyWrapper; +import org.apache.syncope.client.console.wizards.any.ProvisioningReportsPanel; import org.apache.syncope.client.console.wizards.any.ResultPage; import org.apache.syncope.client.console.wizards.any.StatusPanel; +import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.common.lib.to.ProvisioningReport; +import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AnyTypeClassTO; -import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.common.lib.to.DerSchemaTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.types.SchemaType; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.syncope.common.rest.api.beans.AnyQuery; +import org.apache.syncope.common.rest.api.beans.CSVPullSpec; +import org.apache.syncope.common.rest.api.beans.CSVPushSpec; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; +import org.apache.wicket.event.IEvent; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.model.util.ListModel; import org.springframework.util.ReflectionUtils; @@ -68,9 +89,9 @@ public abstract class AnyDirectoryPanel pSchemaNames; + protected final List plainSchemas; - protected final List dSchemaNames; + protected final List derSchemas; /** * Filter used in case of filtered search. @@ -87,7 +108,11 @@ public abstract class AnyDirectoryPanel utilityModal = new BaseModal<>("outer"); + protected final BaseModal utilityModal = new BaseModal<>(Constants.OUTER); + + protected final AjaxLink csvPushLink; + + protected final AjaxLink csvPullLink; protected AnyDirectoryPanel(final String id, final Builder builder) { this(id, builder, true); @@ -101,74 +126,205 @@ protected AnyDirectoryPanel(final String id, final Builder builder, final } else { MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER); } - setReadOnly(!SyncopeConsoleSession.get().owns(String.format("%s_UPDATE", builder.type), builder.realm)); + if (builder.dynRealm == null) { + setReadOnly(!SyncopeConsoleSession.get().owns(String.format("%s_UPDATE", builder.type), builder.realm)); + } else { + setReadOnly(!SyncopeConsoleSession.get().owns(String.format("%s_UPDATE", builder.type), builder.dynRealm)); + } - this.realm = builder.realm; - this.type = builder.type; - this.fiql = builder.fiql; + realm = builder.realm; + type = builder.type; + fiql = builder.fiql; utilityModal.size(Modal.Size.Large); addOuterObject(utilityModal); setWindowClosedReloadCallback(utilityModal); modal.size(Modal.Size.Large); + altDefaultModal.size(Modal.Size.Large); - this.pSchemaNames = new ArrayList<>(); - AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().forEach(anyTypeClassTO -> { - this.pSchemaNames.addAll(anyTypeClassTO.getPlainSchemas()); - }); - this.dSchemaNames = new ArrayList<>(); - AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().forEach(anyTypeClassTO -> { - this.dSchemaNames.addAll(anyTypeClassTO.getDerSchemas()); - }); + plainSchemas = AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().stream(). + flatMap(anyTypeClassTO -> anyTypeClassTO.getPlainSchemas().stream()). + map(schema -> { + try { + return schemaRestClient.read(SchemaType.PLAIN, schema); + } catch (SyncopeClientException e) { + LOG.warn("Could not read plain schema {}, ignoring", e); + return null; + } + }). + filter(Objects::nonNull). + collect(Collectors.toList()); + + derSchemas = AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().stream(). + flatMap(anyTypeClassTO -> anyTypeClassTO.getDerSchemas().stream()). + map(schema -> { + try { + return schemaRestClient.read(SchemaType.DERIVED, schema); + } catch (SyncopeClientException e) { + LOG.warn("Could not read derived schema {}, ignoring", e); + return null; + } + }). + filter(Objects::nonNull). + collect(Collectors.toList()); initResultTable(); - // change close callback in order to update header after model update - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { + AjaxDownloadBehavior csvDownloadBehavior = new AjaxDownloadBehavior(); + WebMarkupContainer csvEventSink = new WebMarkupContainer(Constants.OUTER) { - private static final long serialVersionUID = 8804221891699487139L; + private static final long serialVersionUID = -957948639666058749L; @Override - public void onClose(final AjaxRequestTarget target) { - if (actionTogglePanel.isVisibleInHierarchy() && modal.getContent() instanceof ResultPage) { - actionTogglePanel.updateHeader( - target, ResultPage.class.cast(modal.getContent()).getItem()); + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { + ((AjaxWizard.NewItemCancelEvent) event.getPayload()).getTarget().ifPresent(modal::close); + } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { + AjaxWizard.NewItemFinishEvent payload = (AjaxWizard.NewItemFinishEvent) event.getPayload(); + Optional target = payload.getTarget(); + + if (payload.getResult() instanceof ArrayList) { + modal.setContent(new ResultPage( + null, + payload.getResult()) { + + private static final long serialVersionUID = -2630573849050255233L; + + @Override + protected void closeAction(final AjaxRequestTarget target) { + modal.close(target); + } + + @Override + protected Panel customResultBody( + final String id, final Serializable item, final Serializable result) { + + @SuppressWarnings("unchecked") + ArrayList reports = (ArrayList) result; + return new ProvisioningReportsPanel(id, reports, pageRef); + } + }); + target.ifPresent(t -> t.add(modal.getForm())); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } else if (Constants.OPERATION_SUCCEEDED.equals(payload.getResult())) { + target.ifPresent(modal::close); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } else if (payload.getResult() instanceof Exception) { + SyncopeConsoleSession.get().onException((Exception) payload.getResult()); + } else { + SyncopeConsoleSession.get().error(payload.getResult()); + } + + if (target.isPresent()) { + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target.get()); + target.get().add(container); + } } - modal.show(false); } - }); + }; + csvEventSink.add(csvDownloadBehavior); + addOuterObject(csvEventSink); + csvPushLink = new AjaxLink("csvPush") { + + private static final long serialVersionUID = -817438685948164787L; + + @Override + public void onClick(final AjaxRequestTarget target) { + CSVPushSpec spec = csvPushSpec(); + AnyQuery query = csvAnyQuery(); + + target.add(modal.setContent(new CSVPushWizardBuilder(spec, query, csvDownloadBehavior, pageRef). + setEventSink(csvEventSink).setAsync(false). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT))); + + modal.header(new StringResourceModel("csvPush", AnyDirectoryPanel.this, Model.of(spec))); + modal.show(true); + } + }; + csvPushLink.setOutputMarkupPlaceholderTag(true).setVisible(wizardInModal).setEnabled(wizardInModal); + MetaDataRoleAuthorizationStrategy.authorize(csvPushLink, RENDER, + String.format("%s,%s", StandardEntitlement.IMPLEMENTATION_LIST, StandardEntitlement.TASK_EXECUTE)); + addInnerObject(csvPushLink.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true)); + csvPullLink = new AjaxLink("csvPull") { + + private static final long serialVersionUID = -817438685948164787L; + + @Override + public void onClick(final AjaxRequestTarget target) { + CSVPullSpec spec = csvPullSpec(); + + target.add(modal.setContent(new CSVPullWizardBuilder(spec, pageRef). + setEventSink(csvEventSink). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT))); + + modal.header(new StringResourceModel("csvPull", AnyDirectoryPanel.this, Model.of(spec))); + modal.show(true); + } + }; + csvPullLink.setOutputMarkupPlaceholderTag(true).setVisible(wizardInModal).setEnabled(wizardInModal); + MetaDataRoleAuthorizationStrategy.authorize(csvPullLink, RENDER, + String.format("%s,%s", StandardEntitlement.IMPLEMENTATION_LIST, StandardEntitlement.TASK_EXECUTE)); + addInnerObject(csvPullLink.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true)); + } + + protected CSVPushSpec csvPushSpec() { + CSVPushSpec spec = new CSVPushSpec.Builder(type).build(); + spec.setFields(prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefDetailView(type)). + stream().filter(name -> !Constants.KEY_FIELD_NAME.equalsIgnoreCase(name)). + collect(Collectors.toList())); + spec.setPlainAttrs( + prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefPlainAttributeView(type)).stream(). + filter(a -> plainSchemas.stream().anyMatch(p -> p.getKey().equals(a))). + collect(Collectors.toList())); + spec.setDerAttrs( + prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefPlainAttributeView(type)).stream(). + filter(a -> derSchemas.stream().anyMatch(p -> p.getKey().equals(a))). + collect(Collectors.toList())); + return spec; + } + + protected CSVPullSpec csvPullSpec() { + CSVPullSpec spec = new CSVPullSpec(); + spec.setAnyTypeKey(type); + spec.setDestinationRealm(realm); + return spec; + } + + protected AnyQuery csvAnyQuery() { + return new AnyQuery.Builder().realm(realm). + fiql(fiql).page(dataProvider.getCurrentPage() + 1).size(rows). + orderBy(BaseRestClient.toOrderBy(dataProvider.getSort())). + build(); } @Override protected List> getColumns() { - final List> columns = new ArrayList<>(); - final List> prefcolumns = new ArrayList<>(); - + List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( new ResourceModel(Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME)); + List> prefcolumns = new ArrayList<>(); prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefDetailView(type)).stream(). filter(name -> !Constants.KEY_FIELD_NAME.equalsIgnoreCase(name)). - forEachOrdered(name -> { - addPropertyColumn( - name, - ReflectionUtils.findField(DisplayAttributesModalPanel.getTOClass(type), name), - prefcolumns); - }); + forEach(name -> addPropertyColumn( + name, + ReflectionUtils.findField(DisplayAttributesModalPanel.getTOClass(type), name), + prefcolumns)); prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefPlainAttributeView(type)).stream(). - filter(name -> pSchemaNames.contains(name)). - forEachOrdered(name -> { - prefcolumns.add(new AttrColumn<>(name, SchemaType.PLAIN)); - }); + map(a -> plainSchemas.stream().filter(p -> p.getKey().equals(a)).findFirst()). + filter(Optional::isPresent).map(Optional::get). + forEach(s -> prefcolumns.add(new AttrColumn<>( + s.getKey(), s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.PLAIN))); prefMan.getList(getRequest(), DisplayAttributesModalPanel.getPrefDerivedAttributeView(type)).stream(). - filter(name -> (dSchemaNames.contains(name))). - forEachOrdered(name -> { - prefcolumns.add(new AttrColumn<>(name, SchemaType.DERIVED)); - }); + map(a -> derSchemas.stream().filter(p -> p.getKey().equals(a)).findFirst()). + filter(Optional::isPresent).map(Optional::get). + forEach(s -> prefcolumns.add(new AttrColumn<>( + s.getKey(), s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.DERIVED))); // Add defaults in case of no selection if (prefcolumns.isEmpty()) { @@ -179,7 +335,8 @@ protected List> getColumns() { prefcolumns); } - prefMan.setList(getRequest(), getResponse(), DisplayAttributesModalPanel.getPrefDetailView(type), + prefMan.setList( + getRequest(), getResponse(), DisplayAttributesModalPanel.getPrefDetailView(type), Arrays.asList(getDefaultAttributeSelection())); } @@ -198,11 +355,11 @@ protected void addPropertyColumn( columns.add(new KeyPropertyColumn<>(new ResourceModel(name, name), name, name)); } else if (Constants.DEFAULT_TOKEN_FIELD_NAME.equalsIgnoreCase(name)) { columns.add(new TokenColumn<>(new ResourceModel(name, name), name)); - } else if (field != null + } else if (field != null && !field.isSynthetic() && (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class))) { columns.add(new BooleanPropertyColumn<>(new ResourceModel(name, name), name, name)); - } else if (field != null && field.getType().equals(Date.class)) { + } else if (field != null && !field.isSynthetic() && field.getType().equals(Date.class)) { columns.add(new DatePropertyColumn<>(new ResourceModel(name, name), name, name)); } else { columns.add(new PropertyColumn<>(new ResourceModel(name, name), name, name)); @@ -221,12 +378,10 @@ public void search(final String fiql, final AjaxRequestTarget target) { } @Override - protected Collection getBulkActions() { - List bulkActions = new ArrayList<>(); - - bulkActions.add(ActionLink.ActionType.DELETE); - - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionLink.ActionType.DELETE); + return batches; } public interface AnyDirectoryPanelBuilder extends Serializable { @@ -245,6 +400,8 @@ public abstract static class Builder setRealm(final String realm) { return this; } + public Builder setDynRealm(final String dynRealm) { + this.dynRealm = dynRealm; + return this; + } + @Override public List getAnyTypeClassTOs() { return this.anyTypeClassTOs; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java index 7a642dc5187..398f55c8eb0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java @@ -20,9 +20,10 @@ import java.io.Serializable; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.notifications.NotificationTasks; import org.apache.syncope.client.console.pages.BasePage; @@ -35,12 +36,18 @@ import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.client.console.wizards.WizardMgtPanel; import org.apache.syncope.client.console.wizards.any.AnyWrapper; +import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.patch.AnyObjectPatch; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTypeClassTO; +import org.apache.syncope.common.lib.to.DerSchemaTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.types.AnyEntitlement; import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -80,7 +87,11 @@ public ActionsPanel getHeader(final String componentId) { @Override public void onClick(final AjaxRequestTarget target, final Serializable ignore) { target.add(displayAttributeModal.setContent(new AnyObjectDisplayAttributesModalPanel<>( - displayAttributeModal, page.getPageReference(), pSchemaNames, dSchemaNames, type))); + displayAttributeModal, + page.getPageReference(), + plainSchemas.stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()), + derSchemas.stream().map(DerSchemaTO::getKey).collect(Collectors.toList()), + type))); displayAttributeModal.addSubmitButton(); displayAttributeModal.header(new ResourceModel("any.attr.display")); displayAttributeModal.show(true); @@ -111,25 +122,7 @@ public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { } }, ActionType.EDIT, String.format("%s,%s", AnyEntitlement.READ.getFor(type), AnyEntitlement.UPDATE.getFor(type))). - setRealm(realm); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -7978723352517770645L; - - @Override - public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { - final AnyObjectTO clone = SerializationUtils.clone(model.getObject()); - clone.setKey(null); - send(AnyObjectDirectoryPanel.this, Broadcast.EXACT, - new AjaxWizard.NewItemActionEvent<>(new AnyWrapper<>(clone), target)); - } - - @Override - protected boolean statusCondition(final AnyObjectTO modelObject) { - return addAjaxLink.isVisibleInHierarchy() && realm.startsWith(SyncopeConstants.ROOT_REALM); - } - }, ActionType.CLONE, AnyEntitlement.CREATE.getFor(type)).setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); if (wizardInModal) { panel.add(new ActionLink() { @@ -156,10 +149,9 @@ public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { } }, ActionType.MANAGE_RESOURCES, String.format("%s,%s", AnyEntitlement.READ.getFor(type), AnyEntitlement.UPDATE.getFor(type))). - setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); - panel.add( - new ActionLink() { + panel.add(new ActionLink() { private static final long serialVersionUID = -7978723352517770644L; @@ -188,6 +180,68 @@ public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { } }, ActionType.NOTIFICATION_TASKS, StandardEntitlement.TASK_LIST); } + panel.add(new ActionLink() { + + private static final long serialVersionUID = -2878723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); + target.add(altDefaultModal.setContent(new AuditHistoryModal( + altDefaultModal, + AuditElements.EventCategoryType.LOGIC, + "AnyObjectLogic", + model.getObject(), + AnyEntitlement.UPDATE.getFor(type), + pageRef) { + + private static final long serialVersionUID = -7440902560249531201L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + AnyObjectTO original = model.getObject(); + try { + AnyObjectTO updated = MAPPER.readValue(json, AnyObjectTO.class); + AnyObjectPatch anyObjectPatch = AnyOperations.diff(updated, original, false); + ProvisioningResult result = + restClient.update(original.getETagValue(), anyObjectPatch); + model.getObject().setLastChangeDate(result.getEntity().getLastChangeDate()); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + target.add(container); + } catch (Exception e) { + LOG.error("While restoring any object {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + + altDefaultModal.header(new StringResourceModel("auditHistory.title", model)); + + altDefaultModal.show(true); + } + }, ActionType.VIEW_AUDIT_HISTORY, + String.format("%s,%s", AnyEntitlement.READ.getFor(type), StandardEntitlement.AUDIT_LIST)). + setRealms(realm, model.getObject().getDynRealms()); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -7978723352517770645L; + + @Override + public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { + final AnyObjectTO clone = SerializationUtils.clone(model.getObject()); + clone.setKey(null); + send(AnyObjectDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.NewItemActionEvent<>(new AnyWrapper<>(clone), target)); + } + + @Override + protected boolean statusCondition(final AnyObjectTO modelObject) { + return addAjaxLink.isVisibleInHierarchy() && realm.startsWith(SyncopeConstants.ROOT_REALM); + } + }, ActionType.CLONE, AnyEntitlement.CREATE.getFor(type)).setRealm(realm); panel.add(new ActionLink() { @@ -197,12 +251,12 @@ public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) { try { restClient.delete(model.getObject().getETagValue(), model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { - LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + LOG.error("While deleting any object {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java index 127471b0f1c..3d67cc5ddf5 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java @@ -21,13 +21,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Triple; -import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; -import org.apache.syncope.client.console.layout.GroupFormLayoutInfo; -import org.apache.syncope.client.console.layout.UserFormLayoutInfo; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; +import org.apache.syncope.client.console.layout.AnyLayout; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; import org.apache.syncope.client.console.panels.search.AbstractSearchPanel; import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel; import org.apache.syncope.client.console.panels.search.GroupSearchPanel; @@ -37,6 +35,7 @@ import org.apache.syncope.client.console.panels.search.UserSearchPanel; import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyObjectTO; @@ -51,6 +50,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.event.Broadcast; import org.apache.wicket.event.IEvent; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.extensions.markup.html.tabs.ITab; @@ -59,7 +59,7 @@ import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.Model; -import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.util.ListModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,43 +70,147 @@ public class AnyPanel extends Panel implements ModalPanel { protected static final Logger LOG = LoggerFactory.getLogger(AnyPanel.class); - private final AnyTypeTO anyTypeTO; + protected static final String DIRECTORY_PANEL_ID = "searchResult"; - private final RealmTO realmTO; + @FunctionalInterface + public interface DirectoryPanelSupplier { - private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); - - private final Triple> formLayoutInfo; + Panel supply( + String id, + AnyTypeTO anyTypeTO, + RealmTO realmTO, + AnyLayout anyLayout, + PageReference pageRef); + } - private final PageReference pageRef; + protected static final DirectoryPanelSupplier DEFAULT_DIRECTORYPANEL_SUPPLIER = + (id, anyTypeTO, realmTO, anyLayout, pageRef) -> { + AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); + + Panel panel; + String fiql; + + String realm; + String dynRealm; + if (StringUtils.startsWith(realmTO.getFullPath(), SyncopeConstants.ROOT_REALM)) { + realm = RealmsUtils.getFullPath(realmTO.getFullPath()); + dynRealm = null; + } else { + realm = SyncopeConstants.ROOT_REALM; + dynRealm = realmTO.getKey(); + } + + switch (anyTypeTO.getKind()) { + case USER: + fiql = dynRealm == null + ? SyncopeClient.getUserSearchConditionBuilder(). + is(Constants.KEY_FIELD_NAME).notNullValue().query() + : SyncopeClient.getUserSearchConditionBuilder(). + inDynRealms(dynRealm).query(); + + UserTO userTO = new UserTO(); + userTO.setRealm(RealmsUtils.getFullPath(realmTO.getFullPath())); + panel = new UserDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyTypeTO.getClasses()), + anyTypeTO.getKey(), + pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true). + setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder( + AnyLayoutUtils.newLayoutInfo( + userTO, anyTypeTO.getClasses(), anyLayout.getUser(), pageRef)). + build(id); + MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.RENDER, + StandardEntitlement.USER_SEARCH); + break; + + case GROUP: + fiql = dynRealm == null + ? SyncopeClient.getGroupSearchConditionBuilder(). + is(Constants.KEY_FIELD_NAME).notNullValue().query() + : SyncopeClient.getGroupSearchConditionBuilder().inDynRealms(dynRealm).query(); + + GroupTO groupTO = new GroupTO(); + groupTO.setRealm(RealmsUtils.getFullPath(realmTO.getFullPath())); + panel = new GroupDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyTypeTO.getClasses()), + anyTypeTO.getKey(), + pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true). + setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder( + AnyLayoutUtils.newLayoutInfo( + groupTO, anyTypeTO.getClasses(), anyLayout.getGroup(), pageRef)). + build(id); + // list of group is available to all authenticated users + break; + + case ANY_OBJECT: + fiql = dynRealm == null + ? SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()). + is(Constants.KEY_FIELD_NAME).notNullValue().query() + : SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()). + inDynRealms(dynRealm).query(); + + AnyObjectTO anyObjectTO = new AnyObjectTO(); + anyObjectTO.setRealm(RealmsUtils.getFullPath(realmTO.getFullPath())); + anyObjectTO.setType(anyTypeTO.getKey()); + panel = new AnyObjectDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyTypeTO.getClasses()), + anyTypeTO.getKey(), + pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true). + setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder( + AnyLayoutUtils.newLayoutInfo(anyObjectTO, anyTypeTO.getClasses(), + anyLayout.getAnyObjects().get(anyTypeTO.getKey()), pageRef)). + build(id); + MetaDataRoleAuthorizationStrategy.authorize( + panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey())); + break; + + default: + panel = new LabelPanel(id, null); + } + return panel; + }; + + protected final AnyTypeTO anyTypeTO; + + protected final RealmTO realmTO; + + protected final PageReference pageRef; + + protected AbstractSearchPanel searchPanel; + + protected final Panel directoryPanel; - private AbstractSearchPanel searchPanel; + public AnyPanel( + final String id, + final AnyTypeTO anyTypeTO, + final RealmTO realmTO, + final AnyLayout anyLayout, + final boolean enableSearch, + final PageReference pageRef) { - private final Panel directoryPanel; + this(id, anyTypeTO, realmTO, anyLayout, enableSearch, DEFAULT_DIRECTORYPANEL_SUPPLIER, pageRef); + } public AnyPanel( final String id, final AnyTypeTO anyTypeTO, final RealmTO realmTO, - final Triple> formLayoutInfo, + final AnyLayout anyLayout, final boolean enableSearch, + final DirectoryPanelSupplier directoryPanelSupplier, final PageReference pageRef) { super(id); this.anyTypeTO = anyTypeTO; this.realmTO = realmTO; - this.formLayoutInfo = formLayoutInfo; this.pageRef = pageRef; // ------------------------ // Accordion // ------------------------ - final Model model = Model.of(-1); - final StringResourceModel res = new StringResourceModel("search.result", this, new Model<>(anyTypeTO)); + Model model = Model.of(-1); + Accordion accordion = new Accordion("accordionPanel", + Collections.singletonList(new AbstractTab(new ResourceModel("search.result")) { - final Accordion accordion = new Accordion("accordionPanel", - Collections.singletonList(new AbstractTab(res) { - - private static final long serialVersionUID = 1037272333056449377L; + protected static final long serialVersionUID = 1037272333056449377L; @Override public WebMarkupContainer getPanel(final String panelId) { @@ -116,47 +220,56 @@ public WebMarkupContainer getPanel(final String panelId) { }), model) { - private static final long serialVersionUID = 1L; + protected static final long serialVersionUID = -3056452800492734900L; @Override protected Component newTitle(final String markupId, final ITab tab, final Accordion.State state) { return new AjaxLink(markupId) { - private static final long serialVersionUID = 1L; + protected static final long serialVersionUID = 6250423506463465679L; @Override protected void onComponentTag(final ComponentTag tag) { super.onComponentTag(tag); - tag.put("style", "color: #337ab7 !important"); + tag.put("style", "color: #337ab7"); } @Override public void onClick(final AjaxRequestTarget target) { model.setObject(model.getObject() == 0 ? -1 : 0); } - }.setBody(res); + }.setBody(tab.getTitle()).setEscapeModelStrings(false); } }; accordion.setOutputMarkupId(true); add(accordion.setEnabled(enableSearch).setVisible(enableSearch)); - directoryPanel = getDirectoryPanel("searchResult"); + directoryPanel = createDirectoryPanel(anyTypeTO, realmTO, anyLayout, enableSearch, directoryPanelSupplier); add(directoryPanel); // ------------------------ } + protected Panel createDirectoryPanel( + final AnyTypeTO anyTypeTO, + final RealmTO realmTO, + final AnyLayout anyLayout, + final boolean enableSearch, + final DirectoryPanelSupplier directoryPanelSupplier) { + + return directoryPanelSupplier.supply(DIRECTORY_PANEL_ID, anyTypeTO, realmTO, anyLayout, pageRef); + } + @Override public void onEvent(final IEvent event) { if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { - final AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); + AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); - final String precond; + send(AnyPanel.this.directoryPanel, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); - if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) { - precond = StringUtils.EMPTY; - } else { - precond = String.format("$dynRealms=~%s;", realmTO.getKey()); - } + String precond = realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM) + ? StringUtils.EMPTY + : String.format("$dynRealms=~%s;", realmTO.getKey()); switch (anyTypeTO.getKind()) { case USER: @@ -164,22 +277,31 @@ public void onEvent(final IEvent event) { precond + SearchUtils.buildFIQL( AnyPanel.this.searchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder(), - AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target); + AnyPanel.this.searchPanel.getAvailableSchemaTypes(), + SearchUtils.NO_CUSTOM_CONDITION), + target); break; + case GROUP: GroupDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search( precond + SearchUtils.buildFIQL( AnyPanel.this.searchPanel.getModel().getObject(), SyncopeClient.getGroupSearchConditionBuilder(), - AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target); + AnyPanel.this.searchPanel.getAvailableSchemaTypes(), + SearchUtils.NO_CUSTOM_CONDITION), + target); break; + case ANY_OBJECT: AnyObjectDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search( precond + SearchUtils.buildFIQL( AnyPanel.this.searchPanel.getModel().getObject(), SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()), - AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target); + AnyPanel.this.searchPanel.getAvailableSchemaTypes(), + SearchUtils.NO_CUSTOM_CONDITION), + target); break; + default: } } else { @@ -187,13 +309,12 @@ public void onEvent(final IEvent event) { } } - private AbstractSearchPanel getSearchPanel(final String id) { - final AbstractSearchPanel panel; - - final List clauses = new ArrayList<>(); - final SearchClause clause = new SearchClause(); + protected AbstractSearchPanel getSearchPanel(final String id) { + List clauses = new ArrayList<>(); + SearchClause clause = new SearchClause(); clauses.add(clause); + AbstractSearchPanel panel; switch (anyTypeTO.getKind()) { case USER: clause.setComparator(SearchClause.Comparator.EQUALS); @@ -203,6 +324,7 @@ private AbstractSearchPanel getSearchPanel(final String id) { panel = new UserSearchPanel.Builder( new ListModel<>(clauses)).required(true).enableSearch().build(id); break; + case GROUP: clause.setComparator(SearchClause.Comparator.EQUALS); clause.setType(SearchClause.Type.ATTRIBUTE); @@ -211,6 +333,7 @@ private AbstractSearchPanel getSearchPanel(final String id) { panel = new GroupSearchPanel.Builder( new ListModel<>(clauses)).required(true).enableSearch().build(id); break; + case ANY_OBJECT: clause.setComparator(SearchClause.Comparator.EQUALS); clause.setType(SearchClause.Type.ATTRIBUTE); @@ -219,92 +342,11 @@ private AbstractSearchPanel getSearchPanel(final String id) { panel = new AnyObjectSearchPanel.Builder(anyTypeTO.getKey(), new ListModel<>(clauses)).required(true).enableSearch().build(id); break; + default: panel = null; } - return panel; - } - - protected Panel getDirectoryPanel(final String id) { - final Panel panel; - String fiql; - final String realm; - final String dynRealm; - if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) { - realm = realmTO.getFullPath(); - dynRealm = null; - } else { - realm = SyncopeConstants.ROOT_REALM; - dynRealm = realmTO.getKey(); - } - - switch (anyTypeTO.getKind()) { - case USER: - fiql = dynRealm == null - ? SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue().query() - : SyncopeClient.getUserSearchConditionBuilder().inDynRealms(dynRealm).query(); - - final UserTO userTO = new UserTO(); - userTO.setRealm(realmTO.getFullPath()); - panel = new UserDirectoryPanel.Builder( - anyTypeClassRestClient.list(anyTypeTO.getClasses()), - anyTypeTO.getKey(), - pageRef).setRealm(realm).setFiltered(true). - setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - userTO, - anyTypeTO.getClasses(), - formLayoutInfo.getLeft(), - pageRef)).build(id); - MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.RENDER, StandardEntitlement.USER_SEARCH); - break; - - case GROUP: - fiql = dynRealm == null - ? SyncopeClient.getGroupSearchConditionBuilder().is("key").notNullValue().query() - : SyncopeClient.getGroupSearchConditionBuilder().inDynRealms(dynRealm).query(); - - final GroupTO groupTO = new GroupTO(); - groupTO.setRealm(realmTO.getFullPath()); - panel = new GroupDirectoryPanel.Builder( - anyTypeClassRestClient.list(anyTypeTO.getClasses()), - anyTypeTO.getKey(), - pageRef).setRealm(realm).setFiltered(true). - setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - groupTO, - anyTypeTO.getClasses(), - formLayoutInfo.getMiddle(), - pageRef)).build(id); - // list of group is available to all authenticated users - break; - - case ANY_OBJECT: - fiql = dynRealm == null - ? SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()).is("key").notNullValue() - .query() - : SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()).inDynRealms(dynRealm) - .query(); - - final AnyObjectTO anyObjectTO = new AnyObjectTO(); - anyObjectTO.setRealm(realmTO.getFullPath()); - anyObjectTO.setType(anyTypeTO.getKey()); - panel = new AnyObjectDirectoryPanel.Builder( - anyTypeClassRestClient.list(anyTypeTO.getClasses()), - anyTypeTO.getKey(), - pageRef).setRealm(realm).setFiltered(true). - setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - anyObjectTO, - anyTypeTO.getClasses(), - formLayoutInfo.getRight().get(anyTypeTO.getKey()), - pageRef)).build(id); - MetaDataRoleAuthorizationStrategy.authorize( - panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey())); - break; - - default: - panel = new LabelPanel(id, null); - } return panel; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java index bb809beb514..5a5000f1e7a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java @@ -18,9 +18,9 @@ */ package org.apache.syncope.client.console.panels; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; import org.apache.syncope.client.console.rest.ConfRestClient; import org.apache.syncope.client.console.rest.SchemaRestClient; @@ -28,6 +28,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.to.AnyTypeClassTO; import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; @@ -43,15 +44,11 @@ public class AnyTypeClassDetailsPanel extends Panel { private final SchemaRestClient schemaRestClient = new SchemaRestClient(); - private final List availablePlainSchemas = schemaRestClient.getPlainSchemaNames(); + private final List availablePlainSchemas = schemaRestClient.getSchemaNames(SchemaType.PLAIN); - private final List availableDerSchemas = schemaRestClient.getDerSchemaNames(); + private final List availableDerSchemas = schemaRestClient.getSchemaNames(SchemaType.DERIVED); - private final List availableVirSchemas = schemaRestClient.getVirSchemaNames(); - - private static final List LAYOUT_PARAMETERS = - Arrays.asList(new String[] { "admin.user.layout", "self.user.layout", - "admin.group.layout", "self.group.layout", "admin.membership.layout", "self.membership.layout" }); + private final List availableVirSchemas = schemaRestClient.getSchemaNames(SchemaType.VIRTUAL); public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeClassTO) { super(id); @@ -59,18 +56,20 @@ public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeCla this.anyTypeClassTO = anyTypeClassTO; buildAvailableSchemas(anyTypeClassTO.getKey()); - final Form antTypeClassForm = new Form<>("form"); + Form antTypeClassForm = new Form<>("form"); antTypeClassForm.setModel(new CompoundPropertyModel<>(anyTypeClassTO)); antTypeClassForm.setOutputMarkupId(true); add(antTypeClassForm); - final AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>( - this.anyTypeClassTO, "key")); + AjaxTextFieldPanel key = new AjaxTextFieldPanel( + Constants.KEY_FIELD_NAME, + getString(Constants.KEY_FIELD_NAME), + new PropertyModel<>(this.anyTypeClassTO, Constants.KEY_FIELD_NAME)); key.addRequiredLabel(); key.setEnabled(anyTypeClassTO.getKey() == null || this.anyTypeClassTO.getKey().isEmpty()); antTypeClassForm.add(key); - final WebMarkupContainer container = new WebMarkupContainer("container"); + WebMarkupContainer container = new WebMarkupContainer("container"); container.setOutputMarkupId(true); antTypeClassForm.add(container); @@ -78,7 +77,7 @@ public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeCla setAllowOrder(true). setAllowMoveAll(true). build("plainSchemas", - new PropertyModel>(this.anyTypeClassTO, "plainSchemas"), + new PropertyModel<>(this.anyTypeClassTO, "plainSchemas"), new ListModel<>(availablePlainSchemas)); plainSchema.hideLabel(); plainSchema.setOutputMarkupId(true); @@ -88,7 +87,7 @@ public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeCla setAllowOrder(true). setAllowMoveAll(true). build("derSchemas", - new PropertyModel>(this.anyTypeClassTO, "derSchemas"), + new PropertyModel<>(this.anyTypeClassTO, "derSchemas"), new ListModel<>(availableDerSchemas)); derSchema.hideLabel(); derSchema.setOutputMarkupId(true); @@ -98,7 +97,7 @@ public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeCla setAllowOrder(true). setAllowMoveAll(true). build("virSchemas", - new PropertyModel>(this.anyTypeClassTO, "virSchemas"), + new PropertyModel<>(this.anyTypeClassTO, "virSchemas"), new ListModel<>(availableVirSchemas)); virSchema.hideLabel(); virSchema.setOutputMarkupId(true); @@ -106,7 +105,6 @@ public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeCla } private void buildAvailableSchemas(final String key) { - List configurationSchemas = new ConfRestClient().list().stream(). map(AttrTO::getSchema).collect(Collectors.toList()); @@ -119,6 +117,5 @@ private void buildAvailableSchemas(final String key) { }); availablePlainSchemas.removeAll(configurationSchemas); - availablePlainSchemas.removeAll(LAYOUT_PARAMETERS); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java index 8754d01363f..a0a155340ab 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java @@ -80,13 +80,12 @@ public void onSubmit(final AjaxRequestTarget target) { } else { restClient.update(modelObject); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); AnyTypeClassesPanel.this.updateResultTable(target); modal.close(target); } catch (Exception e) { LOG.error("While creating or updating AnyTypeClassTO", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -110,7 +109,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -119,7 +118,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); for (Field field : AnyTypeClassTO.class.getDeclaredFields()) { - if (field != null && !Modifier.isStatic(field.getModifiers())) { + if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) { final String fieldName = field.getName(); if (field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) @@ -139,7 +138,7 @@ protected List> getColumns() { @Override public String getCssClass() { String css = super.getCssClass(); - if ("key".equals(fieldName)) { + if (Constants.KEY_FIELD_NAME.equals(fieldName)) { css = StringUtils.isBlank(css) ? "col-xs-1" : css + " col-xs-1"; @@ -177,12 +176,11 @@ public void onClick(final AjaxRequestTarget target, final AnyTypeClassTO ignore) public void onClick(final AjaxRequestTarget target, final AnyTypeClassTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java index 0e31a67c8b0..47e116d1257 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; @@ -50,8 +51,10 @@ public AnyTypeDetailsPanel(final String id, final AnyTypeTO anyTypeTO) { form.setModel(new CompoundPropertyModel<>(anyTypeTO)); container.add(form); - final AjaxTextFieldPanel key = - new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>(anyTypeTO, "key")); + final AjaxTextFieldPanel key = new AjaxTextFieldPanel( + Constants.KEY_FIELD_NAME, + getString(Constants.KEY_FIELD_NAME), + new PropertyModel<>(anyTypeTO, Constants.KEY_FIELD_NAME)); key.addRequiredLabel(); key.setEnabled(key.getModelObject() == null || key.getModelObject().isEmpty()); form.add(key); @@ -67,7 +70,7 @@ public AnyTypeDetailsPanel(final String id, final AnyTypeTO anyTypeTO) { form.add(kind); form.add(new AjaxPalettePanel.Builder().setAllowOrder(true).build("classes", - new PropertyModel>(anyTypeTO, "classes"), + new PropertyModel<>(anyTypeTO, "classes"), new ListModel<>(getAvailableAnyTypeClasses())).hideLabel().setOutputMarkupId(true)); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java index 132a05a8c2e..ad494b30438 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java @@ -75,18 +75,17 @@ public void onSubmit(final AjaxRequestTarget target) { try { if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey())) { restClient.create(modelObject); - SyncopeConsoleSession.get().refreshAuth(); + SyncopeConsoleSession.get().refreshAuth(null); } else { restClient.update(modelObject); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); AnyTypesPanel.this.updateResultTable(target); modal.close(target); } catch (Exception e) { LOG.error("While creating or updating {}", modelObject, e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -109,8 +108,8 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - return Collections.emptyList(); + protected Collection getBatches() { + return Collections.emptyList(); } @Override @@ -118,7 +117,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); for (Field field : AnyTypeTO.class.getDeclaredFields()) { - if (field != null && !Modifier.isStatic(field.getModifiers())) { + if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) { final String fieldName = field.getName(); if (field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) @@ -138,7 +137,7 @@ protected List> getColumns() { @Override public String getCssClass() { String css = super.getCssClass(); - if ("key".equals(fieldName)) { + if (Constants.KEY_FIELD_NAME.equals(fieldName)) { css = StringUtils.isBlank(css) ? "col-xs-1" : css + " col-xs-1"; @@ -175,14 +174,13 @@ public void onClick(final AjaxRequestTarget target, final AnyTypeTO ignore) { public void onClick(final AjaxRequestTarget target, final AnyTypeTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().refreshAuth(); + SyncopeConsoleSession.get().refreshAuth(null); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java index ba9e03335c5..3a70ffdebde 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java @@ -25,7 +25,6 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -46,7 +45,6 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; @@ -65,7 +63,7 @@ public class ApplicationDirectoryPanel extends private static final long serialVersionUID = -5491515010207202168L; - protected final BaseModal privilegeModal = new BaseModal("outer") { + protected final BaseModal privilegeModal = new BaseModal(Constants.OUTER) { private static final long serialVersionUID = 389935548143327858L; @@ -74,7 +72,6 @@ protected void onConfigure() { super.onConfigure(); setFooterVisible(false); } - }; protected ApplicationDirectoryPanel(final String id, final Builder builder) { @@ -87,20 +84,17 @@ protected ApplicationDirectoryPanel(final String id, final Builder builder) { modal.size(Modal.Size.Medium); modal.addSubmitButton(); - setFooterVisibility(true); - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); }); + setFooterVisibility(true); privilegeModal.size(Modal.Size.Large); - setWindowClosedReloadCallback(privilegeModal); + privilegeModal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); + }); addOuterObject(privilegeModal); AjaxLink newApplLink = new AjaxLink("add") { @@ -135,7 +129,8 @@ protected String paginatorRowsKey() { protected List> getColumns() { final List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key")); + columns.add(new PropertyColumn<>( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>(new ResourceModel("description"), "description", "description")); columns.add(new AbstractColumn(new ResourceModel("privileges")) { @@ -199,12 +194,11 @@ public void onClick(final AjaxRequestTarget target, final ApplicationTO ignore) public void onClick(final AjaxRequestTarget target, final ApplicationTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting application {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -214,7 +208,7 @@ public void onClick(final AjaxRequestTarget target, final ApplicationTO ignore) } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java index 72aec89fe15..1482fb1275f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.panels; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -53,7 +52,9 @@ public ApplicationModalPanel( modal.setFormModel(application); AjaxTextFieldPanel key = new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(application, "key"), false); + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, + new PropertyModel<>(application, Constants.KEY_FIELD_NAME), false); key.setReadOnly(!create); key.setRequired(true); add(key); @@ -77,14 +78,12 @@ public void onSubmit(final AjaxRequestTarget target) { } else { restClient.update(application); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); this.modal.close(target); } catch (Exception e) { LOG.error("While creating/updating application", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java index e38bfc6f6aa..e4e91c1f66a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java @@ -75,7 +75,7 @@ public class BeanPanel extends Panel { private final List excluded; - private final Map>> sCondWrapper; + private final Map, List>> sCondWrapper; public BeanPanel(final String id, final IModel bean, final String... excluded) { this(id, bean, null, excluded); @@ -84,7 +84,7 @@ public BeanPanel(final String id, final IModel bean, final String... excluded public BeanPanel( final String id, final IModel bean, - final Map>> sCondWrapper, + final Map, List>> sCondWrapper, final String... excluded) { super(id, bean); setOutputMarkupId(true); @@ -95,19 +95,20 @@ public BeanPanel( this.excluded.add("serialVersionUID"); this.excluded.add("class"); - final LoadableDetachableModel> model = new LoadableDetachableModel>() { + LoadableDetachableModel> model = new LoadableDetachableModel>() { private static final long serialVersionUID = 5275935387613157437L; @Override protected List load() { - final List result = new ArrayList<>(); + List result = new ArrayList<>(); if (BeanPanel.this.getDefaultModelObject() != null) { - ReflectionUtils.doWithFields(BeanPanel.this.getDefaultModelObject().getClass(), field -> { - result.add(field.getName()); - }, field -> !BeanPanel.this.excluded.contains(field.getName())); + ReflectionUtils.doWithFields(BeanPanel.this.getDefaultModelObject().getClass(), + field -> result.add(field.getName()), + field -> !field.isSynthetic() && !BeanPanel.this.excluded.contains(field.getName())); } + return result; } }; @@ -146,8 +147,7 @@ protected void populateItem(final ListItem item) { clauses = SearchUtils.getSearchClauses(fiql); } - final AbstractFiqlSearchConditionBuilder builder; - + final AbstractFiqlSearchConditionBuilder builder; switch (scondAnnot.type()) { case "USER": panel = new UserSearchPanel.Builder( diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java new file mode 100644 index 00000000000..e406155e48b --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java @@ -0,0 +1,106 @@ +/* + * 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.client.console.panels; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCharacterFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wizards.CSVPullWizardBuilder; +import org.apache.syncope.common.rest.api.beans.AbstractCSVSpec; +import org.apache.wicket.markup.html.form.IChoiceRenderer; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.validation.validator.StringValidator; + +public class CSVConfPanel extends Panel { + + private static final long serialVersionUID = -1562409114958459620L; + + public CSVConfPanel(final String id, final AbstractCSVSpec spec) { + super(id); + + AjaxCharacterFieldPanel columnSeparator = new AjaxCharacterFieldPanel( + "columnSeparator", "columnSeparator", new PropertyModel<>(spec, "columnSeparator")); + columnSeparator.setChoices(Arrays.asList(',', ';', ' ')); + columnSeparator.setRequired(true); + add(columnSeparator); + + AjaxTextFieldPanel arrayElementSeparator = new AjaxTextFieldPanel( + "arrayElementSeparator", "arrayElementSeparator", + new PropertyModel<>(spec, "arrayElementSeparator")); + arrayElementSeparator.setChoices(Arrays.asList(";")); + arrayElementSeparator.setRequired(true); + arrayElementSeparator.addValidator(new StringValidator(1, 1)); + add(arrayElementSeparator); + + AjaxCharacterFieldPanel quoteChar = new AjaxCharacterFieldPanel( + "quoteChar", "quoteChar", new PropertyModel<>(spec, "quoteChar")); + quoteChar.setChoices(Arrays.asList('"')); + quoteChar.setRequired(true); + add(quoteChar); + + AjaxCharacterFieldPanel escapeChar = new AjaxCharacterFieldPanel( + "escapeChar", "escapeChar", new PropertyModel<>(spec, "escapeChar")); + escapeChar.setRequired(false); + add(escapeChar); + + AjaxDropDownChoicePanel lineSeparator = new AjaxDropDownChoicePanel<>( + "lineSeparator", "lineSeparator", new PropertyModel<>(spec, "lineSeparator"), false); + lineSeparator.setChoices(Stream.of(CSVPullWizardBuilder.LineSeparator.values()). + map(CSVPullWizardBuilder.LineSeparator::name).collect(Collectors.toList())); + lineSeparator.setChoiceRenderer(new IChoiceRenderer() { + + private static final long serialVersionUID = 8551710814349123350L; + + @Override + public Object getDisplayValue(final String object) { + return CSVPullWizardBuilder.LineSeparator.valueOf(object).name(); + } + + @Override + public String getIdValue(final String object, final int index) { + return object; + } + + @Override + public String getObject( + final String id, final IModel> choices) { + + return CSVPullWizardBuilder.LineSeparator.valueOf(id).getRepr(); + } + }); + lineSeparator.setRequired(true); + add(lineSeparator); + + AjaxTextFieldPanel nullValue = new AjaxTextFieldPanel( + "nullValue", "nullValue", new PropertyModel<>(spec, "nullValue")); + nullValue.setRequired(false); + add(nullValue); + + AjaxCheckBoxPanel allowComments = new AjaxCheckBoxPanel( + "allowComments", "allowComments", new PropertyModel<>(spec, "allowComments"), true); + add(allowComments); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java deleted file mode 100644 index 2e836ba776d..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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.client.console.panels; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.panels.ConnInstanceHistoryConfDirectoryPanel.CHConfProvider; -import org.apache.syncope.client.console.rest.ConnectorHistoryRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.event.IEvent; -import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.StringResourceModel; - -/** - * List all connector configuration history instances for the selected connector. - */ -public abstract class ConnInstanceHistoryConfDirectoryPanel extends DirectoryPanel< - ConnInstanceHistoryConfTO, ConnInstanceHistoryConfTO, CHConfProvider, ConnectorHistoryRestClient> - implements ModalPanel { - - private static final long serialVersionUID = 4984337552918213290L; - - protected final BaseModal baseModal; - - private final MultilevelPanel multiLevelPanelRef; - - private final String entityKey; - - public ConnInstanceHistoryConfDirectoryPanel( - final BaseModal baseModal, - final MultilevelPanel multiLevelPanelRef, - final String entityKey, - final PageReference pageRef) { - - super(MultilevelPanel.FIRST_LEVEL_ID, pageRef, false, false); - - this.baseModal = baseModal; - this.multiLevelPanelRef = multiLevelPanelRef; - restClient = new ConnectorHistoryRestClient(); - setShowResultPage(false); - disableCheckBoxes(); - - this.entityKey = entityKey; - initResultTable(); - } - - @Override - protected List> getColumns() { - final List> columns = new ArrayList<>(); - - columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); - - columns.add(new PropertyColumn<>(new StringResourceModel( - "creator", this), "creator", "creator")); - - columns.add(new DatePropertyColumn<>( - new StringResourceModel("creation", this), "creation", "creation")); - - return columns; - } - - @Override - public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); - final ConnInstanceHistoryConfTO connHistoryConfTO = model.getObject(); - - // -- view - panel.add(new ActionLink() { - - private static final long serialVersionUID = -6745431735457245600L; - - @Override - public void onClick(final AjaxRequestTarget target, final ConnInstanceHistoryConfTO modelObject) { - - ConnInstanceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - viewConfiguration(modelObject, target); - target.add(modal); - } - }, ActionLink.ActionType.VIEW, StandardEntitlement.CONNECTOR_HISTORY_LIST); - - // -- restore - panel.add(new ActionLink() { - - private static final long serialVersionUID = -6745431735457245600L; - - @Override - public void onClick(final AjaxRequestTarget target, final ConnInstanceHistoryConfTO modelObject) { - try { - restClient.restore(modelObject.getKey()); - ConnInstanceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - target.add(container); - } catch (SyncopeClientException e) { - LOG.error("While restoring {}", connHistoryConfTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }, ActionLink.ActionType.RESTORE, StandardEntitlement.CONNECTOR_HISTORY_RESTORE); - - // -- delete - panel.add(new ActionLink() { - - private static final long serialVersionUID = -6745431735457245600L; - - @Override - public void onClick(final AjaxRequestTarget target, final ConnInstanceHistoryConfTO modelObject) { - try { - restClient.delete(modelObject.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - target.add(container); - ConnInstanceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - } catch (SyncopeClientException e) { - LOG.error("While deleting {}", connHistoryConfTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }, ActionLink.ActionType.DELETE, StandardEntitlement.CONNECTOR_HISTORY_DELETE, true); - - return panel; - } - - @Override - protected Collection getBulkActions() { - return Collections.emptyList(); - } - - @Override - protected String paginatorRowsKey() { - return Constants.PREF_CONNECTOR_HISTORY_CONF_PAGINATOR_ROWS; - } - - protected abstract void viewConfiguration(ConnInstanceHistoryConfTO connHistoryTO, - final AjaxRequestTarget target); - - @Override - protected void resultTableCustomChanges( - final AjaxDataTablePanel.Builder resultTableBuilder) { - resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef); - } - - @Override - protected CHConfProvider dataProvider() { - return new CHConfProvider(rows); - } - - @Override - public void onEvent(final IEvent event) { - super.onEvent(event); - if (event.getPayload() instanceof ExitEvent && modal != null) { - final AjaxRequestTarget target = ExitEvent.class.cast(event.getPayload()).getTarget(); - baseModal.show(false); - baseModal.close(target); - } - } - - protected class CHConfProvider extends DirectoryDataProvider { - - private static final long serialVersionUID = -4402560904215049574L; - - private final SortableDataProviderComparator comparator; - - public CHConfProvider(final int paginatorRows) { - super(paginatorRows); - - setSort("creation", SortOrder.ASCENDING); - comparator = new SortableDataProviderComparator<>(this); - } - - @Override - public Iterator iterator(final long first, final long count) { - final List configurations = restClient.list(entityKey); - - Collections.sort(configurations, getComparator()); - return configurations.iterator(); - } - - public SortableDataProviderComparator getComparator() { - return comparator; - } - - @Override - public long size() { - return restClient.list(entityKey).size(); - } - - @Override - public IModel model(final ConnInstanceHistoryConfTO object) { - return new CompoundPropertyModel<>(object); - } - - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java index 1f01e596666..7df6bd5bc7e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java @@ -18,60 +18,191 @@ */ package org.apache.syncope.client.console.panels; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverBehavior; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.ConnIdSpecialName; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.status.StatusUtils; +import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload; +import org.apache.syncope.client.console.panels.search.AbstractSearchPanel; +import org.apache.syncope.client.console.panels.search.ConnObjectSearchPanel; +import org.apache.syncope.client.console.panels.search.SearchClause; +import org.apache.syncope.client.console.panels.search.SearchClausePanel; +import org.apache.syncope.client.console.panels.search.SearchUtils; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.rest.ReconciliationRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.CollectionPanel; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.ReconStatus; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.MatchType; import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery; +import org.apache.syncope.common.rest.api.beans.ReconQuery; import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.event.Broadcast; +import org.apache.wicket.event.IEvent; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; +import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; +import org.apache.wicket.extensions.markup.html.tabs.ITab; +import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.model.util.ListModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class ConnObjectListViewPanel extends Panel { private static final long serialVersionUID = 4986172040062752781L; + private static final Logger LOG = LoggerFactory.getLogger(ConnObjectListViewPanel.class); + + private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + + private final ReconciliationRestClient reconRestClient = new ReconciliationRestClient(); + private static final int SIZE = 10; + private static final String STATUS = "Status"; + private String nextPageCookie; + private AbstractSearchPanel searchPanel; + + private WebMarkupContainer arrows; + + private String anyType; + + private ResourceTO resource; + protected ConnObjectListViewPanel( final String id, - final String resource, + final ResourceTO resource, final String anyType, final PageReference pageRef) { super(id); - final List listOfItems = reloadItems(resource, anyType, null); + this.anyType = anyType; + this.resource = resource; + + final Model model = Model.of(-1); + final StringResourceModel res = new StringResourceModel("search.result", this, new Model<>(anyType)); + + final Accordion accordion = new Accordion("accordionPanel", + Collections.singletonList(new AbstractTab(res) { + + private static final long serialVersionUID = 1037272333056449377L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + searchPanel = getSearchPanel(panelId, anyType); + return searchPanel; + } + + }), model) { + + private static final long serialVersionUID = 6581261306163L; + + @Override + protected Component newTitle(final String markupId, final ITab tab, final Accordion.State state) { + return new AjaxLink(markupId) { + + private static final long serialVersionUID = 6584438659172L; + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + tag.put("style", "color: #337ab7"); + } + + @Override + public void onClick(final AjaxRequestTarget target) { + model.setObject(model.getObject() == 0 ? -1 : 0); + } + }.setBody(res); + } + }; + accordion.setOutputMarkupId(true); + add(accordion.setEnabled(true).setVisible(true)); + + List listOfItems = reloadItems(resource.getKey(), anyType, null, null); - final ListViewPanel.Builder builder = new ListViewPanel.Builder( + ListViewPanel.Builder builder = new ListViewPanel.Builder( ConnObjectTO.class, pageRef) { private static final long serialVersionUID = -8251750413385566738L; @Override protected Component getValueComponent(final String key, final ConnObjectTO bean) { - Optional attrTO = - bean.getAttrs().stream().filter(object -> object.getSchema().equals(key)).findAny(); + if (StringUtils.equals(key, STATUS)) { + ReconStatus status; + try { + status = reconRestClient.status( + new ReconQuery.Builder(anyType, resource.getKey()).fiql(bean.getFiql()).build()); + } catch (Exception e) { + LOG.error("While requesting for reconciliation status of {} {} with FIQL '{}'", + anyType, resource.getKey(), bean.getFiql(), e); + + status = new ReconStatus(); + } + + return status.getOnSyncope() == null + ? StatusUtils.getLabel("field", "notfound icon", "Not found", Constants.NOT_FOUND_ICON) + : new Label("field", Model.of()).add(new PopoverBehavior( + Model.of(), + Model.of(status.getAnyKey()), + new PopoverConfig(). + withTitle(status.getMatchType() == MatchType.LINKED_ACCOUNT + ? MatchType.LINKED_ACCOUNT.name() + ", " + AnyTypeKind.USER + : status.getAnyTypeKind().name()). + withPlacement(TooltipConfig.Placement.left)) { + + private static final long serialVersionUID = -7867802555691605021L; + + @Override + protected String createRelAttribute() { + return "field"; + } + + @Override + public void onComponentTag(final Component component, final ComponentTag tag) { + super.onComponentTag(component, tag); + tag.put("class", Constants.ACTIVE_ICON); + } + }); + } else { + Optional attrTO = + bean.getAttrs().stream().filter(object -> object.getSchema().equals(key)).findAny(); - return !attrTO.isPresent() || attrTO.get().getValues().isEmpty() - ? new Label("field", StringUtils.EMPTY) - : new CollectionPanel("field", attrTO.get().getValues()); + return !attrTO.isPresent() || attrTO.get().getValues().isEmpty() + ? new Label("field", StringUtils.EMPTY) + : new CollectionPanel("field", attrTO.get().getValues()); + } } }; @@ -93,9 +224,41 @@ public void onClick(final AjaxRequestTarget target, final ConnObjectTO modelObje withChecks(ListViewPanel.CheckAvailability.NONE). setReuseItem(false); + if (!StringUtils.equals(anyType, SyncopeConstants.REALM_ANYTYPE)) { + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 6377238742125L; + + @Override + public void onClick(final AjaxRequestTarget target, final ConnObjectTO modelObject) { + try { + ReconStatus status = reconRestClient.status( + new ReconQuery.Builder(anyType, resource.getKey()).fiql(modelObject.getFiql()).build()); + + pullConnObject( + modelObject.getFiql(), + target, + resource.getKey(), + anyType, + status.getRealm(), + StringUtils.isNotBlank(status.getAnyKey()), + pageRef); + } catch (Exception e) { + LOG.error("While puling single object {} {} with FIQL '{}'", + anyType, resource.getKey(), modelObject.getFiql(), e); + + SyncopeConsoleSession.get().onException(e); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + } + }, ActionLink.ActionType.RECONCILIATION_PULL, StandardEntitlement.TASK_EXECUTE); + + builder.includes(STATUS); + } + add(builder.build("objs")); - final WebMarkupContainer arrows = new WebMarkupContainer("arrows"); + arrows = new WebMarkupContainer("arrows"); add(arrows.setOutputMarkupId(true)); arrows.add(new AjaxLink("next") { @@ -104,7 +267,7 @@ public void onClick(final AjaxRequestTarget target, final ConnObjectTO modelObje @Override public void onClick(final AjaxRequestTarget target) { - final List listOfItems = reloadItems(resource, anyType, nextPageCookie); + List listOfItems = reloadItems(resource.getKey(), anyType, nextPageCookie, getFiql()); target.add(arrows); send(ConnObjectListViewPanel.this, Broadcast.DEPTH, new ListViewReload<>(listOfItems, target)); } @@ -116,20 +279,71 @@ public boolean isVisible() { }); } + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { + this.nextPageCookie = null; + AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); + List listOfItems = reloadItems(resource.getKey(), anyType, null, getFiql()); + target.add(arrows); + send(ConnObjectListViewPanel.this, Broadcast.DEPTH, new ListViewReload<>(listOfItems, target)); + } else { + super.onEvent(event); + } + } + protected abstract void viewConnObject(ConnObjectTO connObjectTO, AjaxRequestTarget target); + protected abstract void pullConnObject( + String fiql, + AjaxRequestTarget target, + String resource, + String anyType, + String realm, + boolean isOnSyncope, + PageReference pageRef); + private List reloadItems( final String resource, final String anyType, - final String cookie) { + final String cookie, + final String fiql) { - Pair> items = new ResourceRestClient().listConnObjects(resource, + Pair> items = new ResourceRestClient().searchConnObjects(resource, anyType, - SIZE, - cookie, + new ConnObjectTOQuery.Builder(). + size(SIZE). + pagedResultsCookie(cookie). + fiql(fiql), new SortParam<>(ConnIdSpecialName.UID, true)); nextPageCookie = items.getLeft(); return items.getRight(); } + + private AbstractSearchPanel getSearchPanel(final String id, final String anyType) { + final List clauses = new ArrayList<>(); + final SearchClause clause = new SearchClause(); + clauses.add(clause); + + clause.setComparator(SearchClause.Comparator.EQUALS); + clause.setType(SearchClause.Type.ATTRIBUTE); + clause.setProperty(""); + + AnyTypeKind anyTypeKind = + StringUtils.equals(anyType, SyncopeConstants.REALM_ANYTYPE) || StringUtils.isEmpty(anyType) + ? AnyTypeKind.ANY_OBJECT + : anyTypeRestClient.read(anyType).getKind(); + + return new ConnObjectSearchPanel.Builder(resource, anyTypeKind, anyType, + new ListModel<>(clauses)).required(true).enableSearch().build(id); + } + + private String getFiql() { + return SearchUtils.buildFIQL( + searchPanel.getModel().getObject(), + SyncopeClient.getConnObjectTOFiqlSearchConditionBuilder(), + searchPanel.getAvailableSchemaTypes(), + SearchUtils.NO_CUSTOM_CONDITION); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java index 881ae749444..e84ca06333f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java @@ -22,11 +22,13 @@ import java.util.stream.Collectors; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.status.ReconTaskPanel; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.ProvisionTO; +import org.apache.syncope.common.lib.to.PullTaskTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.wicket.PageReference; @@ -82,7 +84,7 @@ public void prev(final AjaxRequestTarget target) { }; connObjects.setFirstLevel(new NextableConnObjectDirectoryPanel( - connObjects, resource.getKey(), anyTypes.getField().getModelObject(), pageRef)); + connObjects, resource, anyTypes.getField().getModelObject(), pageRef)); connObjects.setOutputMarkupId(true); add(connObjects); @@ -93,7 +95,7 @@ public void prev(final AjaxRequestTarget target) { @Override protected void onUpdate(final AjaxRequestTarget target) { connObjects.setFirstLevel(new NextableConnObjectDirectoryPanel( - connObjects, resource.getKey(), anyTypes.getField().getModelObject(), pageRef)); + connObjects, resource, anyTypes.getField().getModelObject(), pageRef)); target.add(connObjects); } }); @@ -105,7 +107,7 @@ private class NextableConnObjectDirectoryPanel extends ConnObjectListViewPanel { NextableConnObjectDirectoryPanel( final MultilevelPanel multiLevelPanelRef, - final String resource, + final ResourceTO resource, final String anyType, final PageReference pageRef) { @@ -123,5 +125,33 @@ protected void viewConnObject(final ConnObjectTO connObjectTO, final AjaxRequest target); } + @Override + protected void pullConnObject( + final String fiql, + final AjaxRequestTarget target, + final String resource, + final String anyType, + final String realm, + final boolean isOnSyncope, + final PageReference pageRef) { + + anyTypes.setEnabled(false); + target.add(anyTypes); + + PullTaskTO pullTaskTO = new PullTaskTO(); + pullTaskTO.setDestinationRealm(realm); + connObjects.next("PULL " + resource, + new ReconTaskPanel( + resource, + pullTaskTO, + anyType, + null, + fiql, + isOnSyncope, + connObjects, + pageRef), + target + ); + } } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java new file mode 100644 index 00000000000..f5420a189ad --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java @@ -0,0 +1,283 @@ +/* + * 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.client.console.panels; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; +import org.apache.syncope.client.console.commons.ConnectorDataProvider; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.ConnectorRestClient; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder; +import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class ConnectorDirectoryPanel extends + DirectoryPanel { + + private static final long serialVersionUID = 2041468935602350821L; + + private String keyword; + + protected ConnectorDirectoryPanel(final String id, final ConnectorDirectoryPanel.Builder builder) { + super(id, builder); + + if (SyncopeConsoleSession.get().owns("CONNECTOR_CREATE")) { + MetaDataRoleAuthorizationStrategy.authorizeAll(addAjaxLink, RENDER); + } else { + MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER); + } + + setShowResultPage(false); + modal.size(Modal.Size.Large); + initResultTable(); + + restClient = builder.restClient; + } + + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof ConnectorSearchEvent) { + ConnectorSearchEvent payload = (ConnectorSearchEvent) event.getPayload(); + AjaxRequestTarget target = payload.getTarget(); + if (StringUtils.isNotBlank(payload.getKeyword())) { + keyword = payload.getKeyword().toLowerCase(); + } + updateResultTable(target); + } else { + super.onEvent(event); + } + } + + @Override + protected ConnectorDataProvider dataProvider() { + dataProvider = new ConnectorDataProvider(rows, pageRef, keyword); + return dataProvider; + } + + public ConnectorDataProvider getDataProvider() { + return dataProvider; + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_PARAMETERS_PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + final List> columns = new ArrayList<>(); + columns.add(new PropertyColumn<>( + new ResourceModel("displayName"), "displayNameSortParam", "displayName")); + columns.add(new PropertyColumn<>( + new ResourceModel("connectorName"), "connectorNameSortParam", "connectorName")); + return columns; + } + + @Override + protected Collection getBatches() { + return Collections.singletonList(ActionLink.ActionType.DELETE); + } + + @Override + public ActionsPanel getActions(final IModel model) { + final ActionsPanel panel = super.getActions(model); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 8345646188740279483L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + final ResourceTO modelObject = new ResourceTO(); + modelObject.setConnector(((ConnInstanceTO) model.getObject()).getKey()); + modelObject.setConnectorDisplayName(((ConnInstanceTO) model.getObject()).getDisplayName()); + + final IModel model = new CompoundPropertyModel<>(modelObject); + modal.setFormModel(model.getObject()); + + target.add(modal.setContent(new ResourceWizardBuilder(modelObject, pageRef). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE))); + + modal.header(new Model<>(MessageFormat.format(getString("resource.new"), + model.getObject().getKey()))); + modal.show(true); + + target.add(modal); + } + + }, ActionLink.ActionType.CREATE_RESOURCE, String.format("%s", StandardEntitlement.RESOURCE_CREATE)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 8200500789152854321L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ConnInstanceTO connInstance = restClient.read(((ConnInstanceTO) model.getObject()).getKey()); + + final IModel model = new CompoundPropertyModel<>(connInstance); + modal.setFormModel(model); + + target.add(modal.setContent(new ConnectorWizardBuilder(connInstance, pageRef). + build(BaseModal.CONTENT_ID, + SyncopeConsoleSession.get(). + owns(StandardEntitlement.CONNECTOR_UPDATE, connInstance.getAdminRealm()) + ? AjaxWizard.Mode.EDIT + : AjaxWizard.Mode.READONLY))); + + modal.header( + new Model<>(MessageFormat.format(getString("connector.edit"), connInstance.getDisplayName()))); + modal.show(true); + + } + }, ActionLink.ActionType.EDIT, String.format("%s,%s", StandardEntitlement.CONNECTOR_READ, + StandardEntitlement.CONNECTOR_UPDATE)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 1085863437941911947L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ConnInstanceTO modelObject = restClient.read(((ConnInstanceTO) model.getObject()).getKey()); + + target.add(altDefaultModal.setContent(new AuditHistoryModal( + altDefaultModal, + AuditElements.EventCategoryType.LOGIC, + "ConnectorLogic", + modelObject, + StandardEntitlement.CONNECTOR_UPDATE, + pageRef) { + + private static final long serialVersionUID = -3225348282675513648L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + try { + ConnInstanceTO updated = MAPPER.readValue(json, ConnInstanceTO.class); + restClient.update(updated); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("While restoring connector {}", + ((ConnInstanceTO) model.getObject()).getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + + altDefaultModal.header( + new Model<>(MessageFormat.format(getString("connector.menu.history"), + ((ConnInstanceTO) model.getObject()).getDisplayName()))); + + altDefaultModal.show(true); + } + + }, ActionLink.ActionType.VIEW_AUDIT_HISTORY, + String.format("%s,%s", StandardEntitlement.CONNECTOR_READ, StandardEntitlement.AUDIT_LIST)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -1544718936080799146L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + try { + restClient.delete(((ConnInstanceTO) model.getObject()).getKey()); + target.appendJavaScript(String.format("jsPlumb.remove('%s');", + ((ConnInstanceTO) model.getObject()).getKey())); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (SyncopeClientException e) { + LOG.error("While deleting resource {}", ((ConnInstanceTO) model.getObject()).getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + + }, ActionLink.ActionType.DELETE, StandardEntitlement.CONNECTOR_DELETE, true); + + return panel; + } + + public static class ConnectorSearchEvent implements Serializable { + + private static final long serialVersionUID = -282052400565266028L; + + private final AjaxRequestTarget target; + + private final String keyword; + + public ConnectorSearchEvent(final AjaxRequestTarget target, final String keyword) { + this.target = target; + this.keyword = keyword; + } + + public AjaxRequestTarget getTarget() { + return target; + } + + public String getKeyword() { + return keyword; + } + } + + public static class Builder extends DirectoryPanel.Builder { + + private static final long serialVersionUID = 6128427903964630093L; + + public Builder(final PageReference pageRef) { + super(new ConnectorRestClient(), pageRef); + setShowResultPage(false); + } + + @Override + protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { + return new ConnectorDirectoryPanel(id, this); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java new file mode 100644 index 00000000000..61dc38c2b62 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java @@ -0,0 +1,170 @@ +/* + * 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.client.console.panels; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.rest.RestClient; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; + +public class ConnidLocations extends + DirectoryPanel { + + private static final long serialVersionUID = 1L; + + public ConnidLocations(final String id, final Builder builder) { + super(id, builder); + + disableCheckBoxes(); + setShowResultPage(true); + + modal.size(Modal.Size.Large); + initResultTable(); + } + + @Override + protected ConnidLocationsDataProvider dataProvider() { + return new ConnidLocationsDataProvider(rows); + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_DYNREALM_PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + final List> columns = new ArrayList<>(); + + columns.add(new AbstractColumn( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME) { + + @Override + public void populateItem( + final Item> cellItem, + final String componentId, + final IModel rowModel) { + + cellItem.add(new Label(componentId, rowModel.getObject().toString())); + } + }); + + return columns; + } + + @Override + public ActionsPanel getActions(final IModel model) { + final ActionsPanel panel = super.getActions(model); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 293293495682202660L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + final ConnInstanceTO modelObject = new ConnInstanceTO(); + modelObject.setLocation((String) ignore); + + final IModel model = new CompoundPropertyModel<>(modelObject); + modal.setFormModel(model); + + target.add(modal.setContent(new ConnectorWizardBuilder(modelObject, pageRef). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE))); + + modal.header(new Model<>(MessageFormat.format(getString("connector.new"), ignore))); + modal.show(true); + } + + }, ActionLink.ActionType.CREATE, String.format("%s", StandardEntitlement.CONNECTOR_CREATE)); + + return panel; + } + + @Override + protected Collection getBatches() { + return Collections.emptyList(); + } + + public abstract static class Builder + extends DirectoryPanel.Builder { + + private static final long serialVersionUID = 4448348557808690524L; + + public Builder(final PageReference pageRef) { + super(null, pageRef); + } + + @Override + protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { + return new ConnidLocations(id, this); + } + } + + protected class ConnidLocationsDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 3161906945317209169L; + + public ConnidLocationsDataProvider(final int paginatorRows) { + super(paginatorRows); + } + + @Override + public Iterator iterator(final long first, final long count) { + List result = new ArrayList<>(SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations()); + return result.subList((int) first, (int) first + (int) count).iterator(); + } + + @Override + public long size() { + return SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations().size(); + } + + @Override + public IModel model(final Serializable object) { + return new CompoundPropertyModel<>(object); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java index f1e9dc2f87c..b0f7f6801c6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java @@ -20,9 +20,8 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; import java.util.List; -import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.LoggerConfig; @@ -35,22 +34,20 @@ public class ConsoleLogPanel extends AbstractLogsPanel { private static final long serialVersionUID = -9165749229623482717L; - private static final ConsoleLoggerController CONSOLE_LOGGER_CONTROLLER = new ConsoleLoggerController(); - public ConsoleLogPanel(final String id, final PageReference pageReference) { - super(id, pageReference, CONSOLE_LOGGER_CONTROLLER.getLoggers()); + super(id, pageReference, ConsoleLoggerController.getLoggers()); } @Override protected void update(final LoggerTO loggerTO) { - CONSOLE_LOGGER_CONTROLLER.setLogLevel(loggerTO.getKey(), loggerTO.getLevel()); + ConsoleLoggerController.setLogLevel(loggerTO.getKey(), loggerTO.getLevel()); } private static class ConsoleLoggerController implements Serializable { private static final long serialVersionUID = -1550459341476431714L; - public List getLoggers() { + public static List getLoggers() { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); List result = new ArrayList<>(); @@ -64,14 +61,14 @@ public List getLoggers() { result.add(loggerTO); } }); - Collections.sort(result, (o1, o2) -> ObjectUtils.compare(o1.getKey(), o2.getKey())); + result.sort(Comparator.comparing(LoggerTO::getKey)); return result; } - public void setLogLevel(final String name, final LoggerLevel level) { - final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - final LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name) + public static void setLogLevel(final String name, final LoggerLevel level) { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name) ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME) : ctx.getConfiguration().getLoggerConfig(name); logConf.setLevel(level.getLevel()); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java new file mode 100644 index 00000000000..41b90410a93 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java @@ -0,0 +1,222 @@ +/* + * 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.client.console.panels; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.commons.SortableDataProviderComparator; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.DelegationDirectoryPanel.DelegationDataProvider; +import org.apache.syncope.client.console.rest.DelegationRestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; +import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.common.lib.to.DelegationTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; + +public class DelegationDirectoryPanel extends + DirectoryPanel { + + private static final long serialVersionUID = 28300423726398L; + + private final UserRestClient userRestClient = new UserRestClient(); + + protected DelegationDirectoryPanel(final String id, final Builder builder) { + super(id, builder); + + disableCheckBoxes(); + setShowResultPage(true); + + modal.size(Modal.Size.Large); + initResultTable(); + } + + @Override + protected DelegationDataProvider dataProvider() { + return new DelegationDataProvider(rows); + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_DELEGATION_PAGINATOR_ROWS; + } + + @Override + protected Collection getBatches() { + return Collections.emptyList(); + } + + @Override + protected List> getColumns() { + List> columns = new ArrayList<>(); + + columns.add(new KeyPropertyColumn<>( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); + + columns.add(new AbstractColumn(new ResourceModel("delegating"), "delegating") { + + @Override + public void populateItem( + final Item> cellItem, + final String componentId, + final IModel rowModel) { + + String delegating = rowModel.getObject().getDelegating(); + if (SyncopeConsoleSession.get().owns(StandardEntitlement.USER_READ)) { + delegating = userRestClient.read(delegating).getUsername(); + } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegating)) { + delegating = SyncopeConsoleSession.get().getSelfTO().getUsername(); + } + + cellItem.add(new Label(componentId, delegating)); + } + }); + + columns.add(new AbstractColumn(new ResourceModel("delegated"), "delegated") { + + @Override + public void populateItem( + final Item> cellItem, + final String componentId, + final IModel rowModel) { + + String delegated = rowModel.getObject().getDelegated(); + if (SyncopeConsoleSession.get().owns(StandardEntitlement.USER_READ)) { + delegated = userRestClient.read(delegated).getUsername(); + } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegated)) { + delegated = SyncopeConsoleSession.get().getSelfTO().getUsername(); + } + + cellItem.add(new Label(componentId, delegated)); + } + }); + + columns.add(new DatePropertyColumn<>(new StringResourceModel("start", this), "start", "start")); + + columns.add(new DatePropertyColumn<>(new StringResourceModel("end", this), "end", "end")); + + columns.add(new PropertyColumn<>(new ResourceModel("roles"), null, "roles")); + + return columns; + } + + @Override + public ActionsPanel getActions(final IModel model) { + ActionsPanel panel = super.getActions(model); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + public void onClick(final AjaxRequestTarget target, final DelegationTO ignore) { + send(DelegationDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.EditItemActionEvent<>(model.getObject(), target)); + } + }, ActionLink.ActionType.EDIT, StringUtils.EMPTY); + panel.add(new ActionLink() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + public void onClick(final AjaxRequestTarget target, final DelegationTO ignore) { + try { + restClient.delete(model.getObject().getKey()); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + target.add(container); + } catch (Exception e) { + LOG.error("While deleting {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + }, ActionLink.ActionType.DELETE, StringUtils.EMPTY, true); + + return panel; + } + + public abstract static class Builder + extends DirectoryPanel.Builder { + + private static final long serialVersionUID = 5530948153889495221L; + + public Builder(final PageReference pageRef) { + super(new DelegationRestClient(), pageRef); + } + + @Override + protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { + return new DelegationDirectoryPanel(id, this); + } + } + + protected class DelegationDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 28297380054779L; + + private final SortableDataProviderComparator comparator; + + public DelegationDataProvider(final int paginatorRows) { + super(paginatorRows); + this.comparator = new SortableDataProviderComparator<>(this); + } + + @Override + public Iterator iterator(final long first, final long count) { + List result = restClient.list(); + Collections.sort(result, comparator); + return result.subList((int) first, (int) first + (int) count).iterator(); + } + + @Override + public long size() { + return restClient.list().size(); + } + + @Override + public IModel model(final DelegationTO object) { + return new CompoundPropertyModel<>(object); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java new file mode 100644 index 00000000000..680d8fe7d0d --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java @@ -0,0 +1,57 @@ +/* + * 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.client.console.panels; + +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.pages.Dashboard; +import org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.panel.Panel; + +public class DelegationSelectionPanel extends Panel { + + private static final long serialVersionUID = 5820439948762L; + + public DelegationSelectionPanel(final String id, final String delegating) { + super(id); + + IndicatingOnConfirmAjaxLink link = + new IndicatingOnConfirmAjaxLink("link", "confirmDelegation", true) { + + @Override + public void onClick(final AjaxRequestTarget target) { + SyncopeConsoleSession.get().setDelegatedBy(delegating); + setResponsePage(Dashboard.class); + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + if (delegating.equals(SyncopeConsoleSession.get().getDelegatedBy())) { + tag.append("class", "disabled", " "); + } + } + }; + add(link); + link.setOutputMarkupId(true); + link.add(new Label("label", delegating)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java index 22c104ed344..43202977c1c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.console.panels; +import com.fasterxml.jackson.databind.ObjectMapper; import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.util.Collection; @@ -38,7 +39,6 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; import org.apache.wicket.event.IEvent; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.DropDownChoice; @@ -57,6 +57,8 @@ public abstract class DirectoryPanel< protected static final Logger LOG = LoggerFactory.getLogger(DirectoryPanel.class); + protected static final ObjectMapper MAPPER = new ObjectMapper(); + /** * Application preferences. */ @@ -87,7 +89,7 @@ public abstract class DirectoryPanel< /** * Result table. */ - private AjaxDataTablePanel resultTable; + protected AjaxDataTablePanel resultTable; /** * Data provider used to search for entities. @@ -101,11 +103,11 @@ public abstract class DirectoryPanel< protected String itemKeyFieldName = Constants.KEY_FIELD_NAME; - protected final BaseModal altDefaultModal = new BaseModal<>("outer"); + protected final BaseModal altDefaultModal = new BaseModal<>(Constants.OUTER); - protected final BaseModal displayAttributeModal = new BaseModal<>("outer"); + protected final BaseModal displayAttributeModal = new BaseModal<>(Constants.OUTER); - protected ActionLinksTogglePanel actionTogglePanel; + protected final ActionLinksTogglePanel actionTogglePanel; /** * Create simple unfiltered search result panel. @@ -146,7 +148,7 @@ protected DirectoryPanel(final String id, final Builder builder, final super(id, wizardInModal); setOutputMarkupId(true); - actionTogglePanel = new ActionLinksTogglePanel<>("outer", builder.getPageRef()); + actionTogglePanel = actionTogglePanel(); addOuterObject(actionTogglePanel); addOuterObject(altDefaultModal); @@ -168,27 +170,25 @@ protected DirectoryPanel(final String id, final Builder builder, final rows = prefMan.getPaginatorRows(getRequest(), paginatorRowsKey()); - setWindowClosedReloadCallback(modal); - setWindowClosedReloadCallback(altDefaultModal); - setWindowClosedReloadCallback(displayAttributeModal); + modal.setWindowClosedCallback(target -> { + if (actionTogglePanel.isVisibleInHierarchy() && modal.getContent() instanceof WizardModalPanel) { + actionTogglePanel.updateHeader(target, WizardModalPanel.class.cast(modal.getContent()).getItem()); + } + modal.show(false); + }); - displayAttributeModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { + setWindowClosedReloadCallback(altDefaultModal); + altDefaultModal.size(Modal.Size.Medium); - private static final long serialVersionUID = 8804221891699487139L; + displayAttributeModal.setWindowClosedCallback(target -> { + EventDataWrapper data = new EventDataWrapper(); + data.setTarget(target); + data.setRows(rows); - @Override - public void onClose(final AjaxRequestTarget target) { - final EventDataWrapper data = new EventDataWrapper(); - data.setTarget(target); - data.setRows(rows); + send(DirectoryPanel.this, Broadcast.EXACT, data); - send(DirectoryPanel.this, Broadcast.EXACT, data); - - modal.show(false); - } + modal.show(false); }); - - altDefaultModal.size(Modal.Size.Medium); displayAttributeModal.size(Modal.Size.Medium); displayAttributeModal.addSubmitButton(); } @@ -269,11 +269,11 @@ public void updateResultTable(final AjaxRequestTarget target) { } } - private void updateResultTable(final boolean create) { + protected void updateResultTable(final boolean create) { updateResultTable(create, rows); } - private void updateResultTable(final boolean create, final int rows) { + protected void updateResultTable(final boolean create, final int rows) { dataProvider = dataProvider(); final int currentPage = resultTable != null @@ -295,10 +295,9 @@ protected ActionLinksTogglePanel getTogglePanel() { return DirectoryPanel.this.getTogglePanel(); } - }. - setColumns(getColumns()). + }.setColumns(getColumns()). setRowsPerPage(rows). - setBulkActions(getBulkActions(), restClient, itemKeyFieldName). + setBatches(getBatches(), restClient, itemKeyFieldName). setContainer(container); if (!checkBoxEnabled) { @@ -356,12 +355,12 @@ protected ActionsPanel getActions(final IModel model) { return model == null ? new ActionsPanel<>("actions", new Model<>()) : new ActionsPanel<>("actions", model); } - protected ActionLinksTogglePanel getTogglePanel() { - return actionTogglePanel; + protected ActionLinksTogglePanel actionTogglePanel() { + return new ActionLinksTogglePanel<>(Constants.OUTER, pageRef); } - protected void setTogglePanel(final ActionLinksTogglePanel actionTogglePanel) { - this.actionTogglePanel = actionTogglePanel; + protected ActionLinksTogglePanel getTogglePanel() { + return actionTogglePanel; } public static class EventDataWrapper { @@ -397,7 +396,7 @@ public void setRows(final int rows) { } } - protected abstract Collection getBulkActions(); + protected abstract Collection getBatches(); public abstract static class Builder extends WizardMgtPanel.Builder { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java index af6867aabe0..562a4f356e6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.syncope.client.console.PreferenceManager; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -77,7 +78,8 @@ public DisplayAttributesModalPanel( super(modal, pageRef); this.type = type; - final List detailslList = SearchableFields.get(DisplayAttributesModalPanel.getTOClass(type)); + final List detailslList = SearchableFields.get(DisplayAttributesModalPanel.getTOClass(type)) + .keySet().stream().collect(Collectors.toList()); Collections.sort(detailslList); Collections.sort(pSchemaNames); Collections.sort(dSchemaNames); @@ -122,7 +124,7 @@ protected List load() { // remove old schemas from selected lists selectedPlainSchemas.retainAll(pSchemaNames); selectedDerSchemas.retainAll(dSchemaNames); - + final WebMarkupContainer container = new WebMarkupContainer("container"); container.setOutputMarkupId(true); add(container); @@ -131,7 +133,7 @@ protected List load() { setAllowOrder(true). setAllowMoveAll(true). build("details", - new PropertyModel>(this, "selectedDetails"), + new PropertyModel<>(this, "selectedDetails"), new ListModel<>(fnames.getObject())); details.hideLabel(); details.setOutputMarkupId(true); @@ -141,7 +143,7 @@ protected List load() { setAllowOrder(true). setAllowMoveAll(true). build("plainSchemas", - new PropertyModel>(this, "selectedPlainSchemas"), + new PropertyModel<>(this, "selectedPlainSchemas"), new ListModel<>(psnames.getObject())); plainSchemas.hideLabel(); plainSchemas.setOutputMarkupId(true); @@ -151,7 +153,7 @@ protected List load() { setAllowOrder(true). setAllowMoveAll(true). build("derSchemas", - new PropertyModel>(this, "selectedDerSchemas"), + new PropertyModel<>(this, "selectedDerSchemas"), new ListModel<>(dsnames.getObject())); derSchemas.hideLabel(); derSchemas.setOutputMarkupId(true); @@ -164,14 +166,14 @@ public void onSubmit(final AjaxRequestTarget target) { SyncopeConsoleSession.get().error(getString("tooManySelections")); onError(target); } else { - final Map> prefs = new HashMap<>(); + Map> prefs = new HashMap<>(); prefs.put(DisplayAttributesModalPanel.getPrefDetailView(type), selectedDetails); prefs.put(DisplayAttributesModalPanel.getPrefPlainAttributeView(type), selectedPlainSchemas); prefs.put(DisplayAttributesModalPanel.getPrefDerivedAttributeView(type), selectedDerSchemas); prefMan.setList(getRequest(), getResponse(), prefs); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.close(target); ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -192,7 +194,7 @@ protected static final String getPrefDerivedAttributeView(final String type) { protected static final Class getTOClass(final String type) { if (type.equalsIgnoreCase(AnyTypeKind.USER.name())) { return UserTO.class; - } else if (type.equalsIgnoreCase(AnyTypeKind.USER.name())) { + } else if (type.equalsIgnoreCase(AnyTypeKind.GROUP.name())) { return GroupTO.class; } else { return AnyObjectTO.class; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java index ebbacd47792..65a2fea8da2 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -43,7 +42,6 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -67,17 +65,11 @@ protected DynRealmDirectoryPanel(final String id, final Builder builder) { modal.size(Modal.Size.Large); modal.addSubmitButton(); - setFooterVisibility(true); - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); }); + setFooterVisibility(true); AjaxLink newDynRealmlLink = new AjaxLink("add") { @@ -111,7 +103,8 @@ protected String paginatorRowsKey() { protected List> getColumns() { final List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key")); + columns.add(new PropertyColumn<>( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); return columns; } @@ -141,12 +134,11 @@ public void onClick(final AjaxRequestTarget target, final DynRealmTO ignore) { public void onClick(final AjaxRequestTarget target, final DynRealmTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting dynamic realm {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -156,8 +148,8 @@ public void onClick(final AjaxRequestTarget target, final DynRealmTO ignore) { } @Override - protected Collection getBulkActions() { - return Collections.emptyList(); + protected Collection getBatches() { + return Collections.emptyList(); } public abstract static class Builder diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java index 7f260e06845..5ca33ec7125 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java @@ -70,7 +70,9 @@ public DynRealmModalPanel( modal.setFormModel(dynRealmWrapper); AjaxTextFieldPanel key = new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(dynRealmWrapper.getInnerObject(), "key"), false); + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, + new PropertyModel<>(dynRealmWrapper.getInnerObject(), Constants.KEY_FIELD_NAME), false); key.setReadOnly(!create); key.setRequired(true); add(key); @@ -138,14 +140,12 @@ public void onSubmit(final AjaxRequestTarget target) { } else { restClient.update(dynRealmWrapper.getInnerObject()); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); this.modal.close(target); } catch (Exception e) { LOG.error("While creating/updating dynamic realm", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java index 6bef561a4d7..b40d9094b38 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java @@ -21,12 +21,14 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; +import org.apache.syncope.client.console.layout.AnyLayout; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; import org.apache.syncope.client.console.notifications.NotificationTasks; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; @@ -43,16 +45,21 @@ import org.apache.syncope.client.console.wizards.any.AnyWrapper; import org.apache.syncope.client.console.wizards.any.GroupWrapper; import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.patch.GroupPatch; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTypeClassTO; -import org.apache.syncope.common.lib.to.AnyTypeTO; +import org.apache.syncope.common.lib.to.DerSchemaTO; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.AnyEntitlement; import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.syncope.common.lib.types.BulkMembersActionType; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.ProvisionAction; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -70,9 +77,9 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel typeExtensionsModal = new BaseModal<>("outer"); + private final BaseModal typeExtensionsModal = new BaseModal<>(Constants.OUTER); - protected final BaseModal membersModal = new BaseModal<>("outer"); + protected final BaseModal membersModal = new BaseModal<>(Constants.OUTER); protected final MembersTogglePanel templates; @@ -95,72 +102,67 @@ protected GroupDirectoryPanel(final String id, final Builder builder, final bool protected Serializable onApplyInternal( final GroupTO groupTO, final String type, final AjaxRequestTarget target) { - final AnyTypeRestClient typeRestClient = new AnyTypeRestClient(); - final AnyTypeClassRestClient classRestClient = new AnyTypeClassRestClient(); - - final AnyTypeTO anyTypeTO = typeRestClient.read(type); - - ModalPanel panel = new AnyPanel(BaseModal.CONTENT_ID, anyTypeTO, null, null, false, pageRef) { - - private static final long serialVersionUID = 7980820232811890502L; - - @Override - protected Panel getDirectoryPanel(final String id) { - - final Panel panel; - - if (AnyTypeKind.USER.name().equals(type)) { - String query = SyncopeClient.getUserSearchConditionBuilder().and( - SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()), - SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query(); - - panel = new UserDirectoryPanel.Builder( - classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). - setRealm("/"). - setFiltered(true). - setFiql(query). - disableCheckBoxes(). - addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - new UserTO(), - anyTypeTO.getClasses(), - FormLayoutInfoUtils.fetch(typeRestClient.list()).getLeft(), - pageRef), false). - setWizardInModal(false).build(id); - - MetaDataRoleAuthorizationStrategy.authorize( - panel, WebPage.RENDER, StandardEntitlement.USER_SEARCH); - } else { - String query = SyncopeClient.getAnyObjectSearchConditionBuilder(type).and( - SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()), - SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query(); - - panel = new AnyObjectDirectoryPanel.Builder( - classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). - setRealm("/"). - setFiltered(true). - setFiql(query). - disableCheckBoxes(). - addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - new AnyObjectTO(), - anyTypeTO.getClasses(), - FormLayoutInfoUtils.fetch(typeRestClient.list()).getRight().get(type), - pageRef), false). - setWizardInModal(false).build(id); - - MetaDataRoleAuthorizationStrategy.authorize( - panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey())); - } - - return panel; - } - }; + AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + AnyTypeClassRestClient classRestClient = new AnyTypeClassRestClient(); + + AnyLayout layout = AnyLayoutUtils.fetch(anyTypeRestClient.list()); + ModalPanel anyPanel = AnyLayoutUtils.newAnyPanel( + layout.getAnyPanelClass(), + BaseModal.CONTENT_ID, anyTypeRestClient.read(type), null, layout, false, + (id, anyTypeTO, realmTO, anyLayout, pageRef) -> { + final Panel panel; + if (AnyTypeKind.USER.name().equals(type)) { + String fiql = SyncopeClient.getUserSearchConditionBuilder(). + inGroups(groupTO.getKey()). + and(Constants.KEY_FIELD_NAME).notNullValue().query(); + + panel = new UserDirectoryPanel.Builder( + classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). + setRealm(realm). + setFiltered(true). + setFiql(fiql). + disableCheckBoxes(). + addNewItemPanelBuilder(AnyLayoutUtils.newLayoutInfo( + new UserTO(), + anyTypeTO.getClasses(), + anyLayout.getUser(), + pageRef), false). + setWizardInModal(false).build(id); + + MetaDataRoleAuthorizationStrategy.authorize( + panel, WebPage.RENDER, StandardEntitlement.USER_SEARCH); + } else { + String fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(type). + inGroups(groupTO.getKey()). + and(Constants.KEY_FIELD_NAME).notNullValue().query(); + + panel = new AnyObjectDirectoryPanel.Builder( + classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). + setRealm(realm). + setFiltered(true). + setFiql(fiql). + disableCheckBoxes(). + addNewItemPanelBuilder(AnyLayoutUtils.newLayoutInfo( + new AnyObjectTO(), + anyTypeTO.getClasses(), + layout.getAnyObjects().get(type), + pageRef), false). + setWizardInModal(false).build(id); + + MetaDataRoleAuthorizationStrategy.authorize( + panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey())); + } + + return panel; + }, + pageRef); membersModal.header(new StringResourceModel( "group.members", GroupDirectoryPanel.this, - Model.of(Pair.of(groupTO, type)))); + Model.of(Pair.of(groupTO, new ResourceModel("anyType." + type, type).getObject())))); - membersModal.setContent(panel); + membersModal.setContent(anyPanel); membersModal.show(true); target.add(membersModal); @@ -192,7 +194,10 @@ public ActionsPanel getHeader(final String componentId) { @Override public void onClick(final AjaxRequestTarget target, final Serializable ignore) { target.add(displayAttributeModal.setContent(new GroupDisplayAttributesModalPanel<>( - displayAttributeModal, page.getPageReference(), pSchemaNames, dSchemaNames))); + displayAttributeModal, + page.getPageReference(), + plainSchemas.stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()), + derSchemas.stream().map(DerSchemaTO::getKey).collect(Collectors.toList())))); displayAttributeModal.header(new ResourceModel("any.attr.display")); displayAttributeModal.show(true); } @@ -216,25 +221,7 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { } }, ActionType.EDIT, String.format("%s,%s", StandardEntitlement.GROUP_READ, StandardEntitlement.GROUP_UPDATE)). - setRealm(realm); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = 6242834621660352855L; - - @Override - public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { - GroupTO clone = SerializationUtils.clone(model.getObject()); - clone.setKey(null); - send(GroupDirectoryPanel.this, Broadcast.EXACT, - new AjaxWizard.NewItemActionEvent<>(new GroupWrapper(clone), target)); - } - - @Override - protected boolean statusCondition(final GroupTO modelObject) { - return realm.startsWith(SyncopeConstants.ROOT_REALM); - } - }, ActionType.CLONE, StandardEntitlement.GROUP_CREATE).setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -247,7 +234,8 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { typeExtensionsModal.header(new StringResourceModel("typeExtensions", model)); typeExtensionsModal.show(true); } - }, ActionType.TYPE_EXTENSIONS, StandardEntitlement.GROUP_UPDATE).setRealm(realm); + }, ActionType.TYPE_EXTENSIONS, StandardEntitlement.GROUP_UPDATE). + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -265,7 +253,7 @@ public boolean isIndicatorEnabled() { } }, ActionType.MEMBERS, String.format("%s,%s", StandardEntitlement.GROUP_READ, StandardEntitlement.GROUP_UPDATE)). - setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -274,13 +262,12 @@ public boolean isIndicatorEnabled() { @Override public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { try { - restClient.bulkMembersAction(model.getObject().getKey(), BulkMembersActionType.PROVISION); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + restClient.provisionMembers(model.getObject().getKey(), ProvisionAction.PROVISION); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While provisioning members of group {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -288,21 +275,19 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { String.format("%s,%s", StandardEntitlement.TASK_CREATE, StandardEntitlement.TASK_EXECUTE)). setRealm(realm); - panel.add( - new ActionLink() { + panel.add(new ActionLink() { private static final long serialVersionUID = -7978723352517770644L; @Override public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { try { - restClient.bulkMembersAction(model.getObject().getKey(), BulkMembersActionType.DEPROVISION); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + restClient.provisionMembers(model.getObject().getKey(), ProvisionAction.DEPROVISION); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While provisioning members of group {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -334,7 +319,7 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { } }, ActionType.MANAGE_RESOURCES, String.format("%s,%s", StandardEntitlement.GROUP_READ, StandardEntitlement.GROUP_UPDATE)). - setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -362,6 +347,69 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { } }, ActionType.NOTIFICATION_TASKS, StandardEntitlement.TASK_LIST); + panel.add(new ActionLink() { + + private static final long serialVersionUID = -2878723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); + target.add(altDefaultModal.setContent(new AuditHistoryModal( + altDefaultModal, + AuditElements.EventCategoryType.LOGIC, + "GroupLogic", + model.getObject(), + StandardEntitlement.GROUP_UPDATE, + pageRef) { + + private static final long serialVersionUID = -5819724478921691835L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + GroupTO original = model.getObject(); + try { + GroupTO updated = MAPPER.readValue(json, GroupTO.class); + GroupPatch groupPatch = AnyOperations.diff(updated, original, false); + ProvisioningResult result = restClient.update(original.getETagValue(), groupPatch); + model.getObject().setLastChangeDate(result.getEntity().getLastChangeDate()); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + target.add(container); + } catch (Exception e) { + LOG.error("While restoring group {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + + altDefaultModal.header(new Model<>( + getString("auditHistory.title", new Model<>(new AnyWrapper<>(model.getObject()))))); + + altDefaultModal.show(true); + } + }, ActionType.VIEW_AUDIT_HISTORY, + String.format("%s,%s", StandardEntitlement.GROUP_READ, StandardEntitlement.AUDIT_LIST)). + setRealms(realm, model.getObject().getDynRealms()); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 6242834621660352855L; + + @Override + public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { + GroupTO clone = SerializationUtils.clone(model.getObject()); + clone.setKey(null); + send(GroupDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.NewItemActionEvent<>(new GroupWrapper(clone), target)); + } + + @Override + protected boolean statusCondition(final GroupTO modelObject) { + return realm.startsWith(SyncopeConstants.ROOT_REALM); + } + }, ActionType.CLONE, StandardEntitlement.GROUP_CREATE).setRealm(realm); + panel.add(new ActionLink() { private static final long serialVersionUID = -7978723352517770644L; @@ -370,12 +418,12 @@ public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { public void onClick(final AjaxRequestTarget target, final GroupTO ignore) { try { restClient.delete(model.getObject().getETagValue(), model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { - LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + LOG.error("While deleting group {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java deleted file mode 100644 index 7afb0c64766..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.client.console.panels; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.rest.ConnectorRestClient; -import org.apache.syncope.client.console.rest.ResourceRestClient; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; -import org.apache.syncope.client.console.wicket.markup.html.form.JsonDiffPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel; -import org.apache.syncope.common.lib.to.AbstractHistoryConf; -import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO; -import org.apache.syncope.common.lib.to.ConnInstanceTO; -import org.apache.syncope.common.lib.to.ResourceHistoryConfTO; -import org.apache.syncope.common.lib.to.ResourceTO; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.form.IChoiceRenderer; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.PropertyModel; - -public class HistoryConfDetails extends MultilevelPanel.SecondLevel { - - private static final long serialVersionUID = -7400543686272100483L; - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private final T selectedHistoryConfTO; - - private final List availableHistoryConfTOs; - - private AbstractModalPanel jsonPanel; - - public HistoryConfDetails(final BaseModal baseModal, final T selectedHistoryConfTO, - final PageReference pageRef, final List availableHistoryConfTOs) { - super(); - - // remove selected conf from list - this.availableHistoryConfTOs = availableHistoryConfTOs.stream(). - filter(object -> object.getKey().equals(selectedHistoryConfTO.getKey())).collect(Collectors.toList()); - this.selectedHistoryConfTO = selectedHistoryConfTO; - - // add current conf to list - addCurrentInstanceConf(); - - Form form = initDropdownDiffConfForm(); - add(form); - form.setVisible(!availableHistoryConfTOs.isEmpty()); - - showConfigurationSinglePanel(); - } - - private void showConfigurationSinglePanel() { - Pair info = getJSONInfo(selectedHistoryConfTO); - - jsonPanel = new JsonEditorPanel(null, new PropertyModel(info, "right"), true, null) { - - private static final long serialVersionUID = -8927036362466990179L; - - @Override - public void onSubmit(final AjaxRequestTarget target) { - modal.close(target); - } - }; - jsonPanel.setOutputMarkupId(true); - - addOrReplace(jsonPanel); - } - - private void showConfigurationDiffPanel(final List historyConfTOs) { - List> infos = new ArrayList<>(); - historyConfTOs.forEach(historyConfTO -> { - infos.add(getJSONInfo(historyConfTO)); - }); - - jsonPanel = new JsonDiffPanel(null, new PropertyModel(infos.get(0), "value"), - new PropertyModel(infos.get(1), "value"), null) { - - private static final long serialVersionUID = -8927036362466990179L; - - @Override - public void onSubmit(final AjaxRequestTarget target) { - modal.close(target); - } - }; - - replace(jsonPanel); - } - - private Pair getJSONInfo(final T historyConfTO) { - Object conf = null; // selected configuration instance - String key = ""; - if (historyConfTO instanceof ConnInstanceHistoryConfTO) { - ConnInstanceHistoryConfTO historyConf = ConnInstanceHistoryConfTO.class.cast(historyConfTO); - conf = historyConf.getConnInstanceTO(); - key = historyConf.getKey(); - } else if (historyConfTO instanceof ResourceHistoryConfTO) { - ResourceHistoryConfTO historyConf = ResourceHistoryConfTO.class.cast(historyConfTO); - conf = historyConf.getResourceTO(); - key = historyConf.getKey(); - } - - String json = ""; - try { - json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(conf); - } catch (IOException ex) { - DirectoryPanel.LOG.error("Error converting objects to JSON", ex); - } - - return Pair.of(key, json); - } - - private Map getDropdownNamesMap(final List historyConfTOs) { - Map historyConfMap = new LinkedHashMap<>(); - - String current = null; - for (T historyConf : historyConfTOs) { - if (historyConf.getCreation() == null) { - current = historyConf.getKey(); - } else { - historyConfMap.put(historyConf.getKey(), historyConf.getCreator() + " - " - + SyncopeConsoleSession.get().getDateFormat().format( - historyConf.getCreation()) + " - " + historyConf.getKey()); - } - } - if (current != null) { - historyConfMap.put(current, getString("current")); - } - - return historyConfMap; - } - - private Form initDropdownDiffConfForm() { - final Form form = new Form<>("form"); - form.setModel(new CompoundPropertyModel<>(selectedHistoryConfTO)); - form.setOutputMarkupId(true); - - final Map namesMap = getDropdownNamesMap(availableHistoryConfTOs); - List keys = new ArrayList<>(namesMap.keySet()); - - final AjaxDropDownChoicePanel dropdownElem = new AjaxDropDownChoicePanel<>( - "compareDropdown", - getString("compare"), - new PropertyModel<>(selectedHistoryConfTO, "key"), - false); - dropdownElem.setChoices(keys); - dropdownElem.setChoiceRenderer(new IChoiceRenderer() { - - private static final long serialVersionUID = -6265603675261014912L; - - @Override - public Object getDisplayValue(final String value) { - return namesMap.get(value) == null ? value : namesMap.get(value); - } - - @Override - public String getIdValue(final String value, final int i) { - return value; - } - - @Override - public String getObject( - final String id, final IModel> choices) { - return id; - } - }); - dropdownElem.setNullValid(true); - dropdownElem.getField().add(new AjaxFormComponentUpdatingBehavior("onchange") { - - private static final long serialVersionUID = -1107858522700306810L; - - @Override - protected void onUpdate(final AjaxRequestTarget target) { - List elemsToCompare = new ArrayList<>(); - elemsToCompare.add(selectedHistoryConfTO); - - final String selectedKey = dropdownElem.getModelObject(); - if (selectedKey != null) { - if (!selectedKey.isEmpty()) { - T confToCompare = availableHistoryConfTOs.stream(). - filter(object -> object.getKey().equals(selectedKey)).findAny().orElse(null); - elemsToCompare.add(confToCompare); - showConfigurationDiffPanel(elemsToCompare); - } else { - showConfigurationSinglePanel(); - } - } - target.add(jsonPanel); - } - }); - form.add(dropdownElem); - - return form; - } - - @SuppressWarnings("unchecked") - private void addCurrentInstanceConf() { - T conf = null; - - if (selectedHistoryConfTO instanceof ConnInstanceHistoryConfTO) { - ConnInstanceTO current = new ConnectorRestClient().read( - ConnInstanceHistoryConfTO.class.cast(selectedHistoryConfTO).getConnInstanceTO().getKey()); - conf = (T) new ConnInstanceHistoryConfTO(); - ((ConnInstanceHistoryConfTO) conf).setConnInstanceTO(current); - } else if (selectedHistoryConfTO instanceof ResourceHistoryConfTO) { - ResourceTO current = new ResourceRestClient().read( - ResourceHistoryConfTO.class.cast(selectedHistoryConfTO).getResourceTO().getKey()); - conf = (T) new ResourceHistoryConfTO(); - ((ResourceHistoryConfTO) conf).setResourceTO(current); - } - - if (conf != null) { - conf.setCreator(selectedHistoryConfTO.getCreator()); - conf.setKey("current"); - availableHistoryConfTOs.add(conf); - } - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfList.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfList.java deleted file mode 100644 index aa6880d2536..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfList.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.client.console.panels; - -import java.io.Serializable; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.common.lib.AbstractBaseBean; -import org.apache.syncope.common.lib.to.AnyTO; -import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO; -import org.apache.syncope.common.lib.to.ConnInstanceTO; -import org.apache.syncope.common.lib.to.ResourceHistoryConfTO; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.model.StringResourceModel; - -public class HistoryConfList extends Panel implements ModalPanel { - - private static final long serialVersionUID = 2482507052037665907L; - - public HistoryConfList( - final BaseModal baseModal, - final String entityKey, - final PageReference pageReference, - final AbstractBaseBean modelObj) { - - super(BaseModal.CONTENT_ID); - - final MultilevelPanel mlp = new MultilevelPanel("history"); - - mlp.setFirstLevel(modelObj instanceof ConnInstanceTO - ? new ConnInstanceHistoryConfDirectoryPanel(baseModal, mlp, entityKey, pageReference) { - - private static final long serialVersionUID = 1422189028000709100L; - - @Override - protected void viewConfiguration(final ConnInstanceHistoryConfTO historyTO, - final AjaxRequestTarget target) { - mlp.next( - new StringResourceModel("history.diff.view", this).getObject(), - new HistoryConfDetails<>(modal, historyTO, pageReference, restClient.list(entityKey)), target); - } - } : new ResourceHistoryConfDirectoryPanel(baseModal, mlp, entityKey, pageReference) { - - private static final long serialVersionUID = 1422189028000709100L; - - @Override - protected void viewConfiguration(final ResourceHistoryConfTO historyTO, - final AjaxRequestTarget target) { - mlp.next( - new StringResourceModel("history.diff.view", this).getObject(), - new HistoryConfDetails<>(modal, historyTO, pageReference, restClient.list(entityKey)), target); - } - }); - - add(mlp); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java index 174526cc352..b51bfd65e7e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -45,7 +44,6 @@ import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; @@ -72,16 +70,10 @@ public ImplementationDirectoryPanel(final String id, final ImplementationType ty modal.size(Modal.Size.Large); modal.addSubmitButton(); - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - implementation.setEngine(null); - updateResultTable(target); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + implementation.setEngine(null); + updateResultTable(target); + modal.show(false); }); setFooterVisibility(true); @@ -129,7 +121,9 @@ public void onClick(final AjaxRequestTarget target) { protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key")); + columns.add(new PropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), + Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>(new StringResourceModel("engine", this), "engine", "engine")); return columns; @@ -160,12 +154,11 @@ public void onClick(final AjaxRequestTarget target, final ImplementationTO ignor public void onClick(final AjaxRequestTarget target, final ImplementationTO ignore) { try { restClient.delete(model.getObject().getType(), model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -185,7 +178,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -198,7 +191,7 @@ protected class ImplementationProvider extends DirectoryDataProvider(this); } @@ -227,5 +220,4 @@ public ImplementationTO getObject() { }; } } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java index e26698166a4..cefb74a4ad1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java @@ -92,7 +92,9 @@ public ImplementationModalPanel( this.create = implementation.getKey() == null; add(new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(implementation, "key"), false). + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, + new PropertyModel<>(implementation, Constants.KEY_FIELD_NAME), false). addRequiredLabel().setEnabled(create)); List classes = Collections.emptyList(); @@ -234,6 +236,10 @@ public ImplementationModalPanel( templateClassName = "MyRecipientsProvider"; break; + case PROVISION_SORTER: + templateClassName = "MyProvisionSorter"; + break; + default: } @@ -339,14 +345,11 @@ public void onSubmit(final AjaxRequestTarget target) { } modal.close(target); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While creating or updating AttrTO", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java new file mode 100644 index 00000000000..13ef887f130 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java @@ -0,0 +1,375 @@ +/* + * 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.client.console.panels; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.ConnIdSpecialName; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.layout.AnyLayout; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; +import org.apache.syncope.client.console.layout.LinkedAccountForm; +import org.apache.syncope.client.console.layout.LinkedAccountFormLayoutInfo; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.status.LinkedAccountStatusPanel; +import org.apache.syncope.client.console.status.ReconTaskPanel; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.any.LinkedAccountWizardBuilder; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.patch.LinkedAccountPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.to.PullTaskTO; +import org.apache.syncope.common.lib.to.PushTaskTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.Component; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LinkedAccountModalPanel extends Panel implements ModalPanel { + + private static final long serialVersionUID = -4603032036433309900L; + + private static final Logger LOG = LoggerFactory.getLogger(LinkedAccountModalPanel.class); + + private LinkedAccountForm wizard; + + private final WizardMgtPanel list; + + private final AjaxLink addAjaxLink; + + private ActionLinksTogglePanel actionTogglePanel; + + private UserRestClient userRestClient = new UserRestClient(); + + private AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + + private final List linkedAccountTOs; + + public LinkedAccountModalPanel( + final BaseModal baseModal, + final IModel model, + final PageReference pageRef, + final boolean recounciliationOnly) { + + super(BaseModal.getContentId(), model); + + final MultilevelPanel mlp = new MultilevelPanel("mlpContainer"); + mlp.setOutputMarkupId(true); + + setOutputMarkupId(true); + + actionTogglePanel = new ActionLinksTogglePanel<>("toggle", pageRef); + add(actionTogglePanel); + + AnyLayout anyLayout = AnyLayoutUtils.fetch( + anyTypeRestClient.listAnyTypes().stream().map(EntityTO::getKey).collect(Collectors.toList())); + LinkedAccountFormLayoutInfo linkedAccountFormLayoutInfo = anyLayout.getUser().getLinkedAccountFormLayoutInfo(); + + try { + wizard = linkedAccountFormLayoutInfo.getFormClass(). + getConstructor(model.getClass(), LinkedAccountFormLayoutInfo.class, PageReference.class). + newInstance(model, linkedAccountFormLayoutInfo, pageRef); + } catch (Exception e) { + LOG.error("Error instantiating form layout", e); + wizard = new LinkedAccountWizardBuilder(model, linkedAccountFormLayoutInfo, pageRef); + } + + ListViewPanel.Builder builder = new ListViewPanel.Builder( + LinkedAccountTO.class, pageRef) { + + private static final long serialVersionUID = -5322423525438435153L; + + @Override + protected LinkedAccountTO getActualItem(final LinkedAccountTO item, final List list) { + return item == null + ? null + : list.stream().filter( + in -> ((item.getKey() == null && in.getKey() == null) + || (in.getKey() != null && in.getKey().equals(item.getKey())))). + findAny().orElse(null); + } + + @Override + protected void customActionCallback(final AjaxRequestTarget target) { + // change modal footer visibility + send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); + } + + @Override + protected void customActionOnCancelCallback(final AjaxRequestTarget target) { + // change modal footer visibility + send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); + } + + @Override + @SuppressWarnings("unchecked") + protected void customActionOnFinishCallback(final AjaxRequestTarget target) { + checkAddButton(model.getObject().getRealm()); + + linkedAccountTOs.clear(); + linkedAccountTOs.addAll(model.getObject().getLinkedAccounts()); + sortLinkedAccounts(); + ListViewPanel.class.cast(list).refreshList(linkedAccountTOs); + + // change modal footer visibility + send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target)); + } + + @Override + protected Component getValueComponent(final String key, final LinkedAccountTO bean) { + if ("suspended".equalsIgnoreCase(key)) { + Label label = new Label("field", StringUtils.EMPTY); + if (bean.isSuspended()) { + label.add(new AttributeModifier("class", "glyphicon glyphicon-ok")); + label.add(new AttributeModifier("style", "display: table-cell; text-align: center;")); + } + return label; + } + return super.getValueComponent(key, bean); + } + + @Override + protected ActionLinksTogglePanel getTogglePanel() { + return actionTogglePanel; + } + }; + + linkedAccountTOs = new ArrayList<>(model.getObject().getLinkedAccounts()); + sortLinkedAccounts(); + + builder.setItems(linkedAccountTOs); + builder.includes("connObjectKeyValue", "resource", "suspended"); + builder.setReuseItem(false); + builder.withChecks(ListViewPanel.CheckAvailability.NONE); + + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + mlp.next(linkedAccountTO.getResource(), new LinkedAccountStatusPanel( + linkedAccountTO.getResource(), + model.getObject().getType(), + linkedAccountTO.getConnObjectKeyValue()), + target); + target.add(mlp); + + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + send(LinkedAccountModalPanel.this, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); + } + }, ActionLink.ActionType.VIEW, StandardEntitlement.USER_READ); + + if (!recounciliationOnly) { + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + try { + send(LinkedAccountModalPanel.this, Broadcast.DEPTH, + new AjaxWizard.NewItemActionEvent<>(linkedAccountTO, 1, target). + setResourceModel(new StringResourceModel("inner.edit.linkedAccount", + LinkedAccountModalPanel.this, Model.of(linkedAccountTO)))); + } catch (SyncopeClientException e) { + LOG.error("While attempting to create new linked account", e); + SyncopeConsoleSession.get().onException(e); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + + send(LinkedAccountModalPanel.this, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); + } + }, ActionLink.ActionType.EDIT, StandardEntitlement.USER_UPDATE); + + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + try { + linkedAccountTO.setSuspended(!linkedAccountTO.isSuspended()); + LinkedAccountPatch linkedAccountPatch = new LinkedAccountPatch.Builder(). + operation(PatchOperation.ADD_REPLACE). + linkedAccountTO(linkedAccountTO).build(); + + UserPatch patch = new UserPatch(); + patch.setKey(model.getObject().getKey()); + patch.getLinkedAccounts().add(linkedAccountPatch); + model.setObject(userRestClient.update(model.getObject().getETagValue(), patch).getEntity()); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (SyncopeClientException e) { + LOG.error("While toggling status of linked account", e); + SyncopeConsoleSession.get().onException(e); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + + checkAddButton(model.getObject().getRealm()); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + send(LinkedAccountModalPanel.this, Broadcast.DEPTH, new ListViewPanel.ListViewReload<>(target)); + } + }, ActionLink.ActionType.ENABLE, StandardEntitlement.USER_UPDATE); + } + + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + mlp.next("PUSH " + linkedAccountTO.getResource(), + new ReconTaskPanel( + linkedAccountTO.getResource(), + new PushTaskTO(), + model.getObject().getType(), + null, + ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(), + true, + mlp, + pageRef), + target); + target.add(mlp); + + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + send(LinkedAccountModalPanel.this, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); + } + }, ActionLink.ActionType.RECONCILIATION_PUSH, + String.format("%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.TASK_EXECUTE)); + + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + mlp.next("PULL " + linkedAccountTO.getResource(), + new ReconTaskPanel( + linkedAccountTO.getResource(), + new PullTaskTO(), + model.getObject().getType(), + null, + ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(), + true, + mlp, + pageRef), + target); + target.add(mlp); + + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + send(LinkedAccountModalPanel.this, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); + } + }, ActionLink.ActionType.RECONCILIATION_PULL, + String.format("%s,%s", StandardEntitlement.USER_UPDATE, StandardEntitlement.TASK_EXECUTE)); + + if (!recounciliationOnly) { + builder.addAction(new ActionLink() { + + private static final long serialVersionUID = 2555747430358755813L; + + @Override + public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) { + try { + LinkedAccountPatch linkedAccountPatch = new LinkedAccountPatch.Builder(). + operation(PatchOperation.DELETE). + linkedAccountTO(linkedAccountTO).build(); + + UserPatch patch = new UserPatch(); + patch.setKey(model.getObject().getKey()); + patch.getLinkedAccounts().add(linkedAccountPatch); + model.setObject(userRestClient.update(model.getObject().getETagValue(), patch).getEntity()); + linkedAccountTOs.remove(linkedAccountTO); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("While removing linked account {}", linkedAccountTO.getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + + checkAddButton(model.getObject().getRealm()); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + send(LinkedAccountModalPanel.this, Broadcast.DEPTH, new ListViewPanel.ListViewReload<>(target)); + } + }, ActionLink.ActionType.DELETE, StandardEntitlement.USER_UPDATE, true); + } + + builder.addNewItemPanelBuilder(wizard); + + list = builder.build(MultilevelPanel.FIRST_LEVEL_ID); + list.setOutputMarkupId(true); + list.setReadOnly(!SyncopeConsoleSession.get(). + owns(StandardEntitlement.USER_UPDATE, model.getObject().getRealm())); + + addAjaxLink = new AjaxLink("add") { + + private static final long serialVersionUID = -7978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target) { + send(LinkedAccountModalPanel.this, Broadcast.BREADTH, + new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target)); + + // this opens the wizard (set above) in CREATE mode + send(list, Broadcast.DEPTH, new AjaxWizard.NewItemActionEvent<>(new LinkedAccountTO(), target). + setResourceModel( + new StringResourceModel("inner.create.linkedAccount", LinkedAccountModalPanel.this))); + } + }; + list.addOrReplaceInnerObject(addAjaxLink.setEnabled(!recounciliationOnly).setVisible(!recounciliationOnly)); + + add(mlp.setFirstLevel(list)); + } + + private void sortLinkedAccounts() { + linkedAccountTOs.sort(Comparator.comparing(LinkedAccountTO::getConnObjectKeyValue)); + } + + private void checkAddButton(final String realm) { + addAjaxLink.setVisible(SyncopeConsoleSession.get().owns(StandardEntitlement.USER_UPDATE, realm)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountsStatusModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountsStatusModalPanel.java new file mode 100644 index 00000000000..0a88afb24e0 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountsStatusModalPanel.java @@ -0,0 +1,40 @@ +/* + * 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.client.console.panels; + +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.model.IModel; + +public class LinkedAccountsStatusModalPanel extends MultilevelPanel.SecondLevel { + + private static final long serialVersionUID = 8804361394735366181L; + + public LinkedAccountsStatusModalPanel( + final BaseModal baseModal, + final IModel model, + final PageReference pageRef) { + + super(MultilevelPanel.SECOND_LEVEL_ID); + + add(new LinkedAccountModalPanel(baseModal, model, pageRef, true).setOutputMarkupId(true)); + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ListViewPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ListViewPanel.java index fc056a07c4c..3a39376d33d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ListViewPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ListViewPanel.java @@ -41,6 +41,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.core.util.lang.PropertyResolver; import org.apache.wicket.event.IEvent; +import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Check; import org.apache.wicket.markup.html.form.CheckGroup; @@ -105,7 +106,9 @@ private ListViewPanel( final CheckAvailability check, final boolean reuseItem, final boolean wizardInModal, + final boolean captionVisible, final IModel> model) { + super(id, wizardInModal); setOutputMarkupId(true); @@ -113,9 +116,15 @@ private ListViewPanel( this.check = Model.of(check); - addInnerObject(new Label("caption", new ResourceModel("listview.caption", StringUtils.EMPTY))); + WebMarkupContainer captionContainer = new WebMarkupContainer("captionContainer"); + captionContainer.setOutputMarkupPlaceholderTag(true); + captionContainer.setVisible(captionVisible); + addInnerObject(captionContainer); + + Label caption = new Label("caption", new ResourceModel("listview.caption", StringUtils.EMPTY)); + captionContainer.add(caption); - final CheckGroup checkGroup = new CheckGroup<>("group", model); + CheckGroup checkGroup = new CheckGroup<>("group", model); checkGroup.setOutputMarkupId(true); checkGroup.add(new IndicatorAjaxFormChoiceComponentUpdatingBehavior() { @@ -152,32 +161,28 @@ protected void onUpdate(final AjaxRequestTarget target) { listOfItems = new ArrayList<>(); } else { listOfItems = list; - if (LOG.isDebugEnabled()) { - toBeIncluded.forEach(field -> { - LOG.debug("Show field {}", field); - }); - } + LOG.debug("Show fields {}", toBeIncluded); } addInnerObject(header(toBeIncluded)); beans = new ListView("beans", listOfItems) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -9112553137618363167L; @Override protected void populateItem(final ListItem beanItem) { - beanItem.add(new Check<>("check", beanItem.getModel(), checkGroup).setOutputMarkupId(true) - .setOutputMarkupPlaceholderTag(true) - .setVisible(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE - || ListViewPanel.this.check.getObject() == CheckAvailability.DISABLED) - .setEnabled(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE)); + beanItem.add(new Check<>("check", beanItem.getModel(), checkGroup).setOutputMarkupId(true). + setOutputMarkupPlaceholderTag(true). + setVisible(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE + || ListViewPanel.this.check.getObject() == CheckAvailability.DISABLED). + setEnabled(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE)); final T bean = beanItem.getModelObject(); final ListView fields = new ListView("fields", toBeIncluded) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -9112553137618363167L; @Override protected void populateItem(final ListItem fieldItem) { @@ -205,7 +210,7 @@ protected void onEvent(final AjaxRequestTarget target) { } }; - beanItem.add(fields); + beanItem.add(fields.setOutputMarkupId(true)); if (togglePanel == null) { beanItem.add(actions.clone("actions", new Model<>(bean))); @@ -223,7 +228,7 @@ protected void onEvent(final AjaxRequestTarget target) { private ListView header(final List labels) { return new ListView("names", labels) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -9112553137618363167L; @Override protected void populateItem(final ListItem item) { @@ -232,6 +237,15 @@ protected void populateItem(final ListItem item) { }; } + /** + * Use this to refresh the ListView with updated items (e.g. from callback methods) + * + * @param elements + */ + public void refreshList(final List elements) { + beans.setList(elements); + } + public void setCheckAvailability(final CheckAvailability check) { // used to perform selectable enabling check condition this.check.setObject(check); @@ -246,7 +260,7 @@ public void setCheckAvailability(final CheckAvailability check) { } } - protected abstract Component getValueComponent(final String key, final T bean); + protected abstract Component getValueComponent(String key, T bean); /** * ListViewPanel builder. @@ -269,6 +283,8 @@ public static class Builder extends WizardMgtPanel.Build private boolean reuseItem = true; + private boolean captionVisible = true; + private final Class reference; public Builder(final Class reference, final PageReference pageRef) { @@ -318,8 +334,14 @@ public Builder withChecks(final CheckAvailability check) { return this; } - public void setReuseItem(final boolean reuseItem) { + public Builder setReuseItem(final boolean reuseItem) { this.reuseItem = reuseItem; + return this; + } + + public Builder setCaptionVisible(final boolean captionVisible) { + this.captionVisible = captionVisible; + return this; } /** @@ -402,7 +424,7 @@ protected T getActualItem(final T item, final List list) { @Override protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { return new ListViewPanel( - id, items, reference, includes, actions, check, reuseItem, wizardInModal, model) { + id, items, reference, includes, actions, check, reuseItem, wizardInModal, captionVisible, model) { private static final long serialVersionUID = -1715389337530657988L; @@ -457,7 +479,7 @@ protected void customActionOnFinishCallback(final AjaxRequestTarget target) { public void onEvent(final IEvent event) { if (event.getPayload() instanceof AjaxWizard.NewItemEvent) { final T item = ((AjaxWizard.NewItemEvent) event.getPayload()).getItem(); - final AjaxRequestTarget target = ((AjaxWizard.NewItemEvent) event.getPayload()).getTarget(); + final Optional target = ((AjaxWizard.NewItemEvent) event.getPayload()).getTarget(); if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { final T old = getActualItem(item, ListViewPanel.this.listOfItems); @@ -469,7 +491,7 @@ public void onEvent(final IEvent event) { ListViewPanel.this.listOfItems.add(indexOf, item); } - target.add(ListViewPanel.this); + target.ifPresent(t -> t.add(ListViewPanel.this)); super.onEvent(event); } else if (event.getPayload() instanceof ListViewPanel.ListViewReload) { final ListViewPanel.ListViewReload payload = (ListViewPanel.ListViewReload) event.getPayload(); @@ -487,7 +509,7 @@ public void onEvent(final IEvent event) { } } - protected abstract T getActualItem(final T item, final List list); + protected abstract T getActualItem(T item, List list); public static class ListViewReload implements Serializable { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java index abae16b4977..fa1833851cc 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java @@ -24,7 +24,7 @@ import de.agilecoders.wicket.core.util.Attributes; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; -import org.apache.syncope.common.lib.log.LogStatementTO; +import org.apache.syncope.common.lib.log.LogStatement; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; @@ -39,7 +39,7 @@ public class LogStatementPanel extends Panel { private final String labelCssClass; - public LogStatementPanel(final String id, final LogStatementTO statement) { + public LogStatementPanel(final String id, final LogStatement statement) { super(id); Alert.Type type; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/MembersTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/MembersTogglePanel.java index 9fc398e3f07..0c71ac7145c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/MembersTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/MembersTogglePanel.java @@ -21,8 +21,8 @@ import java.io.Serializable; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.rest.AnyTypeRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; @@ -34,8 +34,10 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; public abstract class MembersTogglePanel extends TogglePanel { @@ -59,13 +61,22 @@ protected List load() { }; public MembersTogglePanel(final PageReference pageRef) { - super("outer", "groupMembers", pageRef); + super(Constants.OUTER, "groupMembers", pageRef); form = new Form<>("membersForm"); addInnerObject(form); - final FieldPanel type = new AjaxDropDownChoicePanel<>("type", "type", typeModel, false). + FieldPanel type = new AjaxDropDownChoicePanel<>("type", "type", typeModel, false). setChoices(anyTypes). + setChoiceRenderer(new IChoiceRenderer() { + + private static final long serialVersionUID = -200150326532439794L; + + @Override + public Object getDisplayValue(final String anyType) { + return new ResourceModel("anyType." + anyType, anyType).getObject(); + } + }). setStyleSheet("form-control"). setRequired(true); @@ -82,10 +93,8 @@ protected void onSubmit(final AjaxRequestTarget target) { onApplyInternal(groupTO, typeModel.getObject(), target); toggle(target, false); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); LOG.error("While inspecting group memebers of type {}", typeModel.getObject(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -98,7 +107,7 @@ protected void onError(final AjaxRequestTarget target) { } protected abstract Serializable onApplyInternal( - final GroupTO groupTO, final String type, final AjaxRequestTarget target); + GroupTO groupTO, String type, AjaxRequestTarget target); public void setTargetObject(final GroupTO groupTO) { this.groupTO = groupTO; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/NewWorkflowProcess.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/NewWorkflowProcess.java deleted file mode 100644 index f0971c6cb14..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/NewWorkflowProcess.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.client.console.panels; - -import java.io.Serializable; -import javax.ws.rs.core.MediaType; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.pdfbox.util.Charsets; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.rest.WorkflowRestClient; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.form.TextField; -import org.apache.wicket.model.Model; - -public class NewWorkflowProcess extends TogglePanel { - - private static final long serialVersionUID = -4886361549305302161L; - - private final WorkflowRestClient restClient = new WorkflowRestClient(); - - private final Form form; - - public NewWorkflowProcess(final String id, final WebMarkupContainer container, final PageReference pageRef) { - super(id, pageRef); - - form = new Form<>("form"); - addInnerObject(form); - - final TextField key = new TextField<>("key", new Model<>()); - key.setRequired(true); - form.add(key); - - form.add(new AjaxSubmitLink("submit", form) { - - private static final long serialVersionUID = 4947613489823025052L; - - @Override - protected void onSubmit(final AjaxRequestTarget target) { - try { - restClient.setDefinition(MediaType.APPLICATION_XML_TYPE, key.getModelObject(), - IOUtils.toString( - getClass().getResourceAsStream("empty.bpmn20.xml"), - Charsets.UTF_8.name()).replaceAll("%KEY%", key.getModelObject())); - - key.getModel().setObject(null); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - toggle(target, false); - target.add(container); - } catch (Exception e) { - LOG.error("While creating new workflow process", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - - @Override - protected void onError(final AjaxRequestTarget target) { - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/NotificationPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/NotificationPanel.java index ec2fdc27daf..967be616ec8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/NotificationPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/NotificationPanel.java @@ -41,12 +41,12 @@ public class NotificationPanel extends Panel public NotificationPanel(final String id) { super(id); - final Options options = new Options(); + Options options = new Options(); options.set("appendTo", "'#appendto'"); options.set("stacking", "'up'"); options.set("templates", "[ { type: 'success', template: $('#successTemplate').html() }," - + " { type: 'info', template: $('#successTemplate').html() }," + + " { type: 'info', template: $('#infoTemplate').html() }," + " { type: 'error', template: $('#errorTemplate').html() }," + " { type: 'warning', template: $('#warningTemplate').html() } ] "); @@ -63,6 +63,10 @@ public StyledNotificationBehavior newWidgetBehavior(final String selector) { add(notification); } + public final void hide(final IPartialPageRequestHandler handler) { + this.notification.hide(handler); + } + public final void refresh(final IPartialPageRequestHandler handler) { this.getModelObject().forEach(message -> { if (message.isError()) { @@ -71,8 +75,10 @@ public final void refresh(final IPartialPageRequestHandler handler) { // this is necessary before check for success and info in order to show warnings: isSuccess and isInfo // return true also in case of warnings ... this.notification.warn(handler, message.getMessage()); - } else if (message.isSuccess() || message.isInfo()) { + } else if (message.isSuccess()) { this.notification.success(handler, message.getMessage()); + } else if (message.isInfo()) { + this.notification.info(handler, message.getMessage()); } else { this.notification.warn(handler, message.getMessage()); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java index 7fa17747292..580e82c116c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardAttrStep.java @@ -62,7 +62,7 @@ public ParametersCreateWizardAttrStep(final ParametersCreateWizardPanel.Paramete schema.setRequired(true); content.add(schema); - final LoadableDetachableModel> loadableDetachableModel = + LoadableDetachableModel> loadableDetachableModel = new LoadableDetachableModel>() { private static final long serialVersionUID = 7172461137064525667L; @@ -74,14 +74,13 @@ protected List load() { }; - final ListView listView = new ListView("attrs", loadableDetachableModel) { + ListView listView = new ListView("attrs", loadableDetachableModel) { private static final long serialVersionUID = 9101744072914090143L; @Override protected void populateItem(final ListItem item) { - final Panel panel = getFieldPanel("panel", modelObject.getAttrTO(), item.getModelObject()); - item.add(panel); + item.add(getFieldPanel("panel", modelObject.getAttrTO(), item.getModelObject())); } }; @@ -89,16 +88,15 @@ protected void populateItem(final ListItem item) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private Panel getFieldPanel(final String id, final AttrTO attrTO, final PlainSchemaTO plainSchemaTO) { + private Panel getFieldPanel(final String id, final AttrTO attrTO, final PlainSchemaTO plainSchema) { final String valueHeaderName = getString("values"); final FieldPanel panel; - switch (plainSchemaTO.getType()) { + switch (plainSchema.getType()) { case Date: - final String dataPattern = plainSchemaTO.getConversionPattern() == null + String dataPattern = plainSchema.getConversionPattern() == null ? SyncopeConstants.DEFAULT_DATE_PATTERN - : plainSchemaTO.getConversionPattern(); - + : plainSchema.getConversionPattern(); if (dataPattern.contains("H")) { panel = new AjaxDateTimeFieldPanel( id, valueHeaderName, new Model<>(), dataPattern); @@ -139,7 +137,7 @@ public String getObject( case Enum: panel = new AjaxDropDownChoicePanel<>(id, valueHeaderName, new Model<>(), false); - ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchemaTO)); + ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema)); if (!attrTO.getValues().isEmpty()) { ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer() { @@ -164,7 +162,7 @@ public String getObject( }); } ((AjaxDropDownChoicePanel) panel).setNullValid( - "true".equalsIgnoreCase(plainSchemaTO.getMandatoryCondition())); + "true".equalsIgnoreCase(plainSchema.getMandatoryCondition())); break; case Long: @@ -178,25 +176,26 @@ public String getObject( break; case Binary: - panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchemaTO.getMimeType(), + panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchema.getMimeType(), schema.getModelObject()); break; case Encrypted: - panel = new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true); + panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern()) + ? new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false) + : new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true); break; default: panel = new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false); } - if (plainSchemaTO.isMultivalue()) { + if (plainSchema.isMultivalue()) { return new MultiFieldPanel.Builder<>( new PropertyModel<>(attrTO, "values")).build(id, valueHeaderName, panel); - } else { - panel.setNewModel(attrTO.getValues()); } - panel.setRequired("true".equalsIgnoreCase(plainSchemaTO.getMandatoryCondition())); + panel.setNewModel(attrTO.getValues()); + panel.setRequired("true".equalsIgnoreCase(plainSchema.getMandatoryCondition())); return panel; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java index 39ad8ae9952..d9ddc77d939 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java @@ -63,7 +63,7 @@ protected Serializable onApplyInternal(final ParametersForm modelObject) { confRestClient.set(modelObject.getAttrTO()); } catch (Exception e) { LOG.error("While setting {}, removing {}", modelObject.getAttrTO(), plainSchemaTO, e); - schemaRestClient.deletePlainSchema(plainSchemaTO.getKey()); + schemaRestClient.delete(SchemaType.PLAIN, plainSchemaTO.getKey()); throw e; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java index c7e01fb77e6..427ed8d433f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDetailsPanel.java @@ -78,21 +78,21 @@ public ParametersDetailsPanel(final String id, final AttrTO attrTO) { @SuppressWarnings({ "rawtypes", "unchecked" }) private Panel getFieldPanel(final String id, final AttrTO attrTO) { - final String valueHeaderName = getString("values"); + String valueHeaderName = getString("values"); - final PlainSchemaTO schemaTO = schemaRestClient.read(SchemaType.PLAIN, attrTO.getSchema()); + PlainSchemaTO plainSchema = schemaRestClient.read(SchemaType.PLAIN, attrTO.getSchema()); - final FieldPanel panel; - switch (schemaTO.getType()) { + FieldPanel panel; + switch (plainSchema.getType()) { case Date: - final String datePattern = schemaTO.getConversionPattern() == null + final String datePattern = plainSchema.getConversionPattern() == null ? SyncopeConstants.DEFAULT_DATE_PATTERN - : schemaTO.getConversionPattern(); + : plainSchema.getConversionPattern(); if (StringUtils.containsIgnoreCase(datePattern, "H")) { - panel = new AjaxDateTimeFieldPanel("panel", schemaTO.getKey(), new Model<>(), datePattern); + panel = new AjaxDateTimeFieldPanel("panel", plainSchema.getKey(), new Model<>(), datePattern); } else { - panel = new AjaxDateFieldPanel("panel", schemaTO.getKey(), new Model<>(), datePattern); + panel = new AjaxDateFieldPanel("panel", plainSchema.getKey(), new Model<>(), datePattern); } break; case Boolean: @@ -125,7 +125,7 @@ public String getObject( break; case Enum: panel = new AjaxDropDownChoicePanel<>(id, valueHeaderName, new Model<>(), false); - ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(schemaTO)); + ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema)); if (!attrTO.getValues().isEmpty()) { ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer() { @@ -150,7 +150,7 @@ public String getObject( }); } ((AjaxDropDownChoicePanel) panel).setNullValid( - "false".equalsIgnoreCase(schemaTO.getMandatoryCondition())); + "false".equalsIgnoreCase(plainSchema.getMandatoryCondition())); break; case Long: @@ -164,25 +164,26 @@ public String getObject( break; case Binary: - panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), schemaTO.getMimeType(), + panel = new BinaryFieldPanel(id, valueHeaderName, new Model<>(), plainSchema.getMimeType(), schema.getModelObject()); break; case Encrypted: - panel = new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true); + panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern()) + ? new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false) + : new EncryptedFieldPanel(id, valueHeaderName, new Model<>(), true); break; default: panel = new AjaxTextFieldPanel(id, valueHeaderName, new Model<>(), false); } - if (schemaTO.isMultivalue()) { + if (plainSchema.isMultivalue()) { return new MultiFieldPanel.Builder<>( new PropertyModel>(attrTO, "values")).build(id, valueHeaderName, panel); - } else { - panel.setNewModel(attrTO.getValues()); } - panel.setRequired("true".equalsIgnoreCase(schemaTO.getMandatoryCondition())); + panel.setNewModel(attrTO.getValues()); + panel.setRequired("true".equalsIgnoreCase(plainSchema.getMandatoryCondition())); return panel; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java index dbf222cc69a..8612c73fbbe 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -41,6 +40,7 @@ import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.AttrSchemaType; +import org.apache.syncope.common.lib.types.SchemaType; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.AttributeModifier; import org.apache.wicket.PageReference; @@ -133,7 +133,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.singletonList(ActionLink.ActionType.DELETE); } @@ -146,10 +146,18 @@ protected List> getColumns() { private static final long serialVersionUID = -1822504503325964706L; @Override - public void populateItem(final Item> item, - final String componentId, final IModel rowModel) { - PlainSchemaTO modelSchemaTO = (PlainSchemaTO) rowModel.getObject().getSchemaInfo(); - AttrSchemaType type = modelSchemaTO.getType(); + public void populateItem( + final Item> item, + final String componentId, + final IModel rowModel) { + + PlainSchemaTO schema = null; + try { + schema = schemaRestClient.read(SchemaType.PLAIN, rowModel.getObject().getSchema()); + } catch (Exception e) { + LOG.error("Could not read conf schema {}", rowModel.getObject().getSchema(), e); + } + AttrSchemaType type = schema == null ? null : schema.getType(); if (type == AttrSchemaType.Binary || type == AttrSchemaType.Encrypted) { item.add(new Label(componentId, type.name()). add(new AttributeModifier("style", "font-style:italic"))); @@ -157,7 +165,6 @@ public void populateItem(final Item> item, super.populateItem(item, componentId, rowModel); } } - }); return columns; } @@ -188,13 +195,12 @@ public void onClick(final AjaxRequestTarget target, final AttrTO ignore) { public void onClick(final AjaxRequestTarget target, final AttrTO ignore) { try { restClient.delete(model.getObject().getSchema()); - schemaRestClient.deletePlainSchema(model.getObject().getSchema()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + schemaRestClient.delete(SchemaType.PLAIN, model.getObject().getSchema()); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersEditModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersEditModalPanel.java index 335091abc33..2eb2a28ed3a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersEditModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersEditModalPanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.panels; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -56,12 +55,10 @@ public void onSubmit(final AjaxRequestTarget target) { try { restClient.set(attrTO); modal.close(target); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While creating or updating AttrTO", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java index 310864d4930..c2e3d134a89 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/PlainSchemaDetails.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.commons.Constants; @@ -37,6 +38,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.AttrSchemaType; @@ -74,26 +76,28 @@ public PlainSchemaDetails(final String id, final PlainSchemaTO schemaTO) { type = new AjaxDropDownChoicePanel<>("type", getString("type"), new PropertyModel<>(schemaTO, "type")); + boolean isCreate = schemaTO == null || schemaTO.getKey() == null || schemaTO.getKey().isEmpty(); + type.setChoices(Arrays.asList(AttrSchemaType.values())); - type.setEnabled(schemaTO == null || schemaTO.getKey() == null || schemaTO.getKey().isEmpty()); + type.setEnabled(isCreate); type.addRequiredLabel(); add(type); // long, double, date - final AjaxTextFieldPanel conversionPattern = new AjaxTextFieldPanel("conversionPattern", + AjaxTextFieldPanel conversionPattern = new AjaxTextFieldPanel("conversionPattern", getString("conversionPattern"), new PropertyModel<>(schemaTO, "conversionPattern")); add(conversionPattern); - final WebMarkupContainer conversionParams = new WebMarkupContainer("conversionParams"); + WebMarkupContainer conversionParams = new WebMarkupContainer("conversionParams"); conversionParams.setOutputMarkupPlaceholderTag(true); conversionParams.add(conversionPattern); add(conversionParams); - final WebMarkupContainer typeParams = new WebMarkupContainer("typeParams"); + WebMarkupContainer typeParams = new WebMarkupContainer("typeParams"); typeParams.setOutputMarkupPlaceholderTag(true); // enum - final AjaxTextFieldPanel enumerationValuesPanel = new AjaxTextFieldPanel("panel", + AjaxTextFieldPanel enumerationValuesPanel = new AjaxTextFieldPanel("panel", "enumerationValues", new Model<>(null)); enumerationValues = new MultiFieldPanel.Builder( @@ -172,36 +176,54 @@ protected String newModelObject() { }.build( "enumerationKeys", "enumerationKeys", - new AjaxTextFieldPanel("panel", "enumerationKeys", new Model())); + new AjaxTextFieldPanel("panel", "enumerationKeys", new Model<>(null))); - final WebMarkupContainer enumParams = new WebMarkupContainer("enumParams"); + WebMarkupContainer enumParams = new WebMarkupContainer("enumParams"); enumParams.setOutputMarkupPlaceholderTag(true); enumParams.add(enumerationValues); enumParams.add(enumerationKeys); typeParams.add(enumParams); // encrypted - final AjaxTextFieldPanel secretKey = new AjaxTextFieldPanel("secretKey", + AjaxTextFieldPanel secretKey = new AjaxTextFieldPanel("secretKey", getString("secretKey"), new PropertyModel<>(schemaTO, "secretKey")); - final AjaxDropDownChoicePanel cipherAlgorithm = new AjaxDropDownChoicePanel<>( + AjaxDropDownChoicePanel cipherAlgorithm = new AjaxDropDownChoicePanel<>( "cipherAlgorithm", getString("cipherAlgorithm"), new PropertyModel<>(schemaTO, "cipherAlgorithm")); - cipherAlgorithm.setChoices(Arrays.asList(CipherAlgorithm.values())); - final WebMarkupContainer encryptedParams = new WebMarkupContainer("encryptedParams"); + AjaxCheckBoxPanel transparentEncryption = new AjaxCheckBoxPanel( + "transparentEncryption", "transparentEncryption", new Model() { + + private static final long serialVersionUID = 5636572627689425575L; + + @Override + public Boolean getObject() { + return SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(schemaTO.getConversionPattern()); + } + + @Override + public void setObject(final Boolean object) { + schemaTO.setConversionPattern(BooleanUtils.isTrue(object) + ? SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN + : null); + } + }, true); + + WebMarkupContainer encryptedParams = new WebMarkupContainer("encryptedParams"); encryptedParams.setOutputMarkupPlaceholderTag(true); encryptedParams.add(secretKey); encryptedParams.add(cipherAlgorithm); + encryptedParams.add(transparentEncryption); typeParams.add(encryptedParams); // binary - final AjaxTextFieldPanel mimeType = new AjaxTextFieldPanel("mimeType", + AjaxTextFieldPanel mimeType = new AjaxTextFieldPanel("mimeType", getString("mimeType"), new PropertyModel<>(schemaTO, "mimeType")); - final WebMarkupContainer binaryParams = new WebMarkupContainer("binaryParams"); + WebMarkupContainer binaryParams = new WebMarkupContainer("binaryParams"); binaryParams.setOutputMarkupPlaceholderTag(true); binaryParams.add(mimeType); typeParams.add(binaryParams); @@ -287,7 +309,8 @@ protected void onUpdate(final AjaxRequestTarget target) { "readonly", getString("readonly"), new PropertyModel<>(schemaTO, "readonly"))); add(new AjaxCheckBoxPanel("uniqueConstraint", - getString("uniqueConstraint"), new PropertyModel<>(schemaTO, "uniqueConstraint"))); + getString("uniqueConstraint"), new PropertyModel<>(schemaTO, "uniqueConstraint")). + setEnabled(isCreate)); } private void showHide(final PlainSchemaTO schema, final AjaxDropDownChoicePanel type, @@ -298,7 +321,7 @@ private void showHide(final PlainSchemaTO schema, final AjaxDropDownChoicePanel< final AjaxTextFieldPanel secretKey, final AjaxDropDownChoicePanel cipherAlgorithm, final WebMarkupContainer binaryParams, final AjaxTextFieldPanel mimeType) { - final int typeOrdinal = Integer.parseInt(type.getField().getValue()); + int typeOrdinal = Integer.parseInt(type.getField().getValue()); if (AttrSchemaType.Long.ordinal() == typeOrdinal || AttrSchemaType.Double.ordinal() == typeOrdinal || AttrSchemaType.Date.ordinal() == typeOrdinal) { @@ -351,7 +374,6 @@ private void showHide(final PlainSchemaTO schema, final AjaxDropDownChoicePanel< mimeType.setChoices(null); } else if (AttrSchemaType.Encrypted.ordinal() == typeOrdinal) { conversionParams.setVisible(false); - conversionPattern.setModelObject(null); enumParams.setVisible(false); if (enumerationValuesPanel.isRequired()) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java index 2444812ee6a..b66c9ba433a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -68,6 +67,8 @@ protected PrivilegeDirectoryPanel( this.baseModal = baseModal; this.application = application; + restClient = new ApplicationRestClient(); + disableCheckBoxes(); enableExitButton(); @@ -81,7 +82,8 @@ protected PrivilegeDirectoryPanel( protected List> getColumns() { final List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key")); + columns.add(new PropertyColumn<>( + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>(new ResourceModel("description"), "description", "description")); return columns; @@ -112,12 +114,11 @@ public void onClick(final AjaxRequestTarget target, final PrivilegeTO ignore) { try { application.getPrivileges().remove(model.getObject()); restClient.update(application); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); customActionOnFinishCallback(target); } catch (SyncopeClientException e) { LOG.error("While deleting {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -127,7 +128,7 @@ public void onClick(final AjaxRequestTarget target, final PrivilegeTO ignore) { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java index e1c4f5d654b..2c857098a1d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java @@ -19,6 +19,7 @@ package org.apache.syncope.client.console.panels; import java.io.Serializable; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.ApplicationRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel; @@ -74,7 +75,8 @@ public Profile(final PrivilegeTO privilege) { : new StringResourceModel("privilege.edit", Model.of(privilege))); AjaxTextFieldPanel key = new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(privilege, "key"), false); + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, new PropertyModel<>(privilege, Constants.KEY_FIELD_NAME), false); key.setReadOnly(privilege.getKey() != null); key.setRequired(true); add(key); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java index 02cc13d04fb..5fe9858ef94 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ProvisionAuxClassesPanel.java @@ -100,8 +100,7 @@ protected void onBeforeRender() { "uidOnCreate", new ResourceModel("uidOnCreate", "uidOnCreate").getObject(), new PropertyModel<>(provision, "uidOnCreate")); uidOnCreate.setChoices(getSchemas(anyType, model.getObject())); - uidOnCreate.setOutputMarkupId(true). - setEnabled(provision != null); + uidOnCreate.setOutputMarkupId(true).setEnabled(provision != null); addOrReplace(uidOnCreate); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java index 95331ee54c0..e154e27a019 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java @@ -23,21 +23,16 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.commons.ConnIdSpecialName; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.ITabComponent; import org.apache.syncope.client.console.commons.status.StatusUtils; -import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; -import org.apache.syncope.client.console.layout.GroupFormLayoutInfo; -import org.apache.syncope.client.console.layout.UserFormLayoutInfo; -import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.layout.AnyLayout; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.console.wizards.WizardMgtPanel; @@ -45,11 +40,10 @@ import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyTypeTO; import org.apache.syncope.common.lib.to.ConnObjectTO; -import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.PropagationStatus; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.RealmTO; -import org.apache.syncope.common.lib.types.PropagationTaskExecStatus; +import org.apache.syncope.common.lib.types.ExecStatus; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.Component; import org.apache.wicket.PageReference; @@ -59,7 +53,6 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,24 +63,32 @@ public abstract class Realm extends WizardMgtPanel { protected static final Logger LOG = LoggerFactory.getLogger(Realm.class); - private final RealmTO realmTO; + protected final RealmTO realmTO; - private final List anyTypes; + protected final List anyTypes; + + protected final int selectedIndex; protected final RealmWizardBuilder wizardBuilder; - public Realm(final String id, final RealmTO realmTO, final PageReference pageRef, final int selectedIndex) { + public Realm( + final String id, + final RealmTO realmTO, + final List anyTypes, + final int selectedIndex, + final PageReference pageRef) { + super(id, true); this.realmTO = realmTO; - this.anyTypes = new AnyTypeRestClient().listAnyTypes(); + this.anyTypes = anyTypes; + this.selectedIndex = selectedIndex; setPageRef(pageRef); - AjaxBootstrapTabbedPanel tabbedPanel = - new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList(pageRef)); - tabbedPanel.setSelectedTab(selectedIndex); - addInnerObject(tabbedPanel); - this.wizardBuilder = new RealmWizardBuilder(pageRef); + addInnerObject(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList(pageRef)). + setSelectedTab(selectedIndex)); + + this.wizardBuilder = buildNewItemPanelBuilder(pageRef); addNewItemPanelBuilder(this.wizardBuilder, false); setShowResultPage(true); @@ -96,90 +97,32 @@ public Realm(final String id, final RealmTO realmTO, final PageReference pageRef setWindowClosedReloadCallback(modal); } + protected RealmWizardBuilder buildNewItemPanelBuilder(final PageReference pageRef) { + return new RealmWizardBuilder(pageRef); + } + public RealmTO getRealmTO() { return realmTO; } - private List buildTabList(final PageReference pageRef) { + protected List buildTabList(final PageReference pageRef) { List tabs = new ArrayList<>(); - tabs.add(new ITabComponent(new Model<>("DETAILS"), - StandardEntitlement.REALM_CREATE, StandardEntitlement.REALM_UPDATE, StandardEntitlement.REALM_DELETE) { - - private static final long serialVersionUID = -5861786415855103549L; - - @Override - public Panel getPanel(final String panelId) { - final ActionsPanel actionPanel = new ActionsPanel<>("actions", null); - - if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) { - - actionPanel.add(new ActionLink(realmTO) { - - private static final long serialVersionUID = 2802988981431379827L; - - @Override - public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { - onClickCreate(target); - } - }, ActionLink.ActionType.CREATE, StandardEntitlement.REALM_CREATE).hideLabel(); - - actionPanel.add(new ActionLink(realmTO) { - - private static final long serialVersionUID = 2802988981431379828L; - - @Override - public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { - onClickEdit(target, realmTO); - } - }, ActionLink.ActionType.EDIT, StandardEntitlement.REALM_UPDATE).hideLabel(); - - actionPanel.add(new ActionLink(realmTO) { - - private static final long serialVersionUID = 2802988981431379827L; - - @Override - public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { - onClickTemplate(target); - } - }, ActionLink.ActionType.TEMPLATE, StandardEntitlement.REALM_UPDATE).hideLabel(); - - actionPanel.add(new ActionLink(realmTO) { - - private static final long serialVersionUID = 2802988981431379829L; - - @Override - public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { - onClickDelete(target, realmTO); - } - }, ActionLink.ActionType.DELETE, StandardEntitlement.REALM_DELETE, true).hideLabel(); - } - - RealmDetails panel = new RealmDetails(panelId, realmTO, actionPanel, false); - panel.setContentEnabled(false); - actionPanel.setEnabled(true); - return panel; - } - - @Override - public boolean isVisible() { - return SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy(). - isActionAuthorized(this, RENDER); - } - }); - - final Triple> formLayoutInfo = - FormLayoutInfoUtils.fetch(anyTypes.stream().map(EntityTO::getKey).collect(Collectors.toList())); + tabs.add(new RealmDetailsTabPanel()); - for (final AnyTypeTO anyType : anyTypes) { + AnyLayout anyLayout = AnyLayoutUtils.fetch( + anyTypes.stream().map(AnyTypeTO::getKey).collect(Collectors.toList())); + for (AnyTypeTO anyType : anyTypes) { tabs.add(new ITabComponent( - new Model<>(anyType.getKey()), String.format("%s_SEARCH", anyType.getKey())) { + new ResourceModel("anyType." + anyType.getKey(), anyType.getKey()), + String.format("%s_SEARCH", anyType.getKey())) { private static final long serialVersionUID = 1169585538404171118L; @Override public WebMarkupContainer getPanel(final String panelId) { - return new AnyPanel(panelId, anyType, realmTO, formLayoutInfo, true, pageRef); + return AnyLayoutUtils.newAnyPanel( + anyLayout.getAnyPanelClass(), panelId, anyType, realmTO, anyLayout, true, pageRef); } @Override @@ -200,19 +143,19 @@ protected Panel customResultBody(final String panelId, final RealmTO item, final throw new IllegalStateException("Unsupported result type"); } - final MultilevelPanel mlp = new MultilevelPanel(panelId); + MultilevelPanel mlp = new MultilevelPanel(panelId); add(mlp); - final PropagationStatus syncope = new PropagationStatus(); - syncope.setStatus(PropagationTaskExecStatus.SUCCESS); + PropagationStatus syncope = new PropagationStatus(); + syncope.setStatus(ExecStatus.SUCCESS); syncope.setResource(Constants.SYNCOPE); List propagations = new ArrayList<>(); propagations.add(syncope); propagations.addAll(((ProvisioningResult) result).getPropagationStatuses()); - ListViewPanel.Builder builder = new ListViewPanel.Builder( - PropagationStatus.class, pageRef) { + ListViewPanel.Builder builder = + new ListViewPanel.Builder(PropagationStatus.class, pageRef) { private static final long serialVersionUID = -6809736686861678498L; @@ -229,11 +172,13 @@ protected Component getValueComponent(final String key, final PropagationStatus : afterObj.getAttr(ConnIdSpecialName.NAME).get().getValues().get(0); return new Label("field", remoteId); - } else if ("status".equalsIgnoreCase(key)) { + } + + if ("status".equalsIgnoreCase(key)) { return StatusUtils.getStatusImagePanel("field", bean.getStatus()); - } else { - return super.getValueComponent(key, bean); } + + return super.getValueComponent(key, bean); } }; @@ -250,8 +195,8 @@ protected Component getValueComponent(final String key, final PropagationStatus @Override protected boolean statusCondition(final PropagationStatus bean) { return !Constants.SYNCOPE.equals(bean.getResource()) - && (PropagationTaskExecStatus.CREATED == bean.getStatus() - || PropagationTaskExecStatus.SUCCESS == bean.getStatus()); + && (ExecStatus.CREATED == bean.getStatus() + || ExecStatus.SUCCESS == bean.getStatus()); } @Override @@ -279,21 +224,21 @@ public void onClick(final AjaxRequestTarget target, final PropagationStatus stat return mlp; } - protected abstract void onClickTemplate(final AjaxRequestTarget target); + protected abstract void onClickTemplate(AjaxRequestTarget target); - protected abstract void onClickCreate(final AjaxRequestTarget target); + protected abstract void onClickCreate(AjaxRequestTarget target); - protected abstract void onClickEdit(final AjaxRequestTarget target, final RealmTO realmTO); + protected abstract void onClickEdit(AjaxRequestTarget target, RealmTO realmTO); - protected abstract void onClickDelete(final AjaxRequestTarget target, final RealmTO realmTO); + protected abstract void onClickDelete(AjaxRequestTarget target, RealmTO realmTO); - class RemoteRealmPanel extends RemoteObjectPanel { + protected static class RemoteRealmPanel extends RemoteObjectPanel { private static final long serialVersionUID = 4303365227411467563L; - private final PropagationStatus bean; + protected final PropagationStatus bean; - RemoteRealmPanel(final PropagationStatus bean) { + protected RemoteRealmPanel(final PropagationStatus bean) { this.bean = bean; add(new ConnObjectPanel( REMOTE_OBJECT_PANEL_ID, @@ -307,4 +252,74 @@ protected final Pair getConnObjectTOs() { return Pair.of(bean.getBeforeObj(), bean.getAfterObj()); } } + + protected class RealmDetailsTabPanel extends ITabComponent { + + private static final long serialVersionUID = -5861786415855103549L; + + protected RealmDetailsTabPanel() { + super(new ResourceModel("realm.details", "DETAILS"), + StandardEntitlement.REALM_CREATE, + StandardEntitlement.REALM_UPDATE, + StandardEntitlement.REALM_DELETE); + } + + @Override + public Panel getPanel(final String panelId) { + ActionsPanel actionPanel = new ActionsPanel<>("actions", null); + + if (StringUtils.startsWith(realmTO.getFullPath(), SyncopeConstants.ROOT_REALM)) { + actionPanel.add(new ActionLink(realmTO) { + + private static final long serialVersionUID = 2802988981431379827L; + + @Override + public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { + onClickCreate(target); + } + }, ActionLink.ActionType.CREATE, StandardEntitlement.REALM_CREATE).hideLabel(); + + actionPanel.add(new ActionLink(realmTO) { + + private static final long serialVersionUID = 2802988981431379828L; + + @Override + public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { + onClickEdit(target, realmTO); + } + }, ActionLink.ActionType.EDIT, StandardEntitlement.REALM_UPDATE).hideLabel(); + + actionPanel.add(new ActionLink(realmTO) { + + private static final long serialVersionUID = 2802988981431379827L; + + @Override + public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { + onClickTemplate(target); + } + }, ActionLink.ActionType.TEMPLATE, StandardEntitlement.REALM_UPDATE).hideLabel(); + + actionPanel.add(new ActionLink(realmTO) { + + private static final long serialVersionUID = 2802988981431379829L; + + @Override + public void onClick(final AjaxRequestTarget target, final RealmTO ignore) { + onClickDelete(target, realmTO); + } + }, ActionLink.ActionType.DELETE, StandardEntitlement.REALM_DELETE, true).hideLabel(); + } + + RealmDetails panel = new RealmDetails(panelId, realmTO, actionPanel, false); + panel.setContentEnabled(false); + actionPanel.setEnabled(true); + return panel; + } + + @Override + public boolean isVisible() { + return SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy(). + isActionAuthorized(this, RENDER); + } + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java index d4bae1a94c0..21bd724dfa2 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java @@ -24,95 +24,106 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; import de.agilecoders.wicket.core.markup.html.bootstrap.button.dropdown.DropDownButton; import de.agilecoders.wicket.core.markup.html.bootstrap.image.GlyphIconType; -import de.agilecoders.wicket.core.markup.html.bootstrap.image.IconType; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.rest.RealmRestClient; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.markup.html.WebMarkupContainerNoVeil; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.DynRealmTO; import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.event.Broadcast; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AbstractAutoCompleteRenderer; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer; import org.apache.wicket.markup.ComponentTag; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.request.Response; public class RealmChoicePanel extends Panel { private static final long serialVersionUID = -1100228004207271270L; - private final RealmRestClient realmRestClient = new RealmRestClient(); + protected static final String SEARCH_REALMS = "searchRealms"; - private final PageReference pageRef; + protected final RealmRestClient realmRestClient = new RealmRestClient(); - private final LoadableDetachableModel>> realmTree; + protected final PageReference pageRef; - private final LoadableDetachableModel> dynRealmTree; + protected final LoadableDetachableModel>> realmTree; - private final WebMarkupContainer container; + protected final LoadableDetachableModel> dynRealmTree; - private Model model; + protected final WebMarkupContainerNoVeil container; - private final Collection availableRealms; + protected Model model; - private final Map>> tree; + protected final Map>> tree; - private final List links = new ArrayList<>(); + protected final List links = new ArrayList<>(); - public RealmChoicePanel(final String id, final PageReference pageRef) { + protected String searchQuery; + + protected List realmsChoices; + + protected final boolean isSearchEnabled; + + protected final ListView breadcrumb; + + public RealmChoicePanel(final String id, final String initialRealm, final PageReference pageRef) { super(id); this.pageRef = pageRef; - tree = new HashMap<>(); - RealmTO fakeRootRealm = new RealmTO(); - fakeRootRealm.setName(SyncopeConstants.ROOT_REALM); - fakeRootRealm.setFullPath(SyncopeConstants.ROOT_REALM); - model = Model.of(fakeRootRealm); + tree = new HashMap<>(); + isSearchEnabled = RealmsUtils.isSearchEnabled(SyncopeConsoleSession.get().getSearchableRealms()); realmTree = new LoadableDetachableModel>>() { private static final long serialVersionUID = -7688359318035249200L; - private void getChildren( - final List> full, - final String key, - final Map>> tree, - final String indent) { - - if (tree.containsKey(key)) { - Pair> subtree = tree.get(key); - subtree.getValue().forEach(child -> { - full.add(Pair.of(indent + child.getName(), child)); - getChildren(full, child.getKey(), tree, " " + indent + (indent.isEmpty() ? "|--- " : "")); - }); - } - } - @Override protected List> load() { Map>> map = reloadRealmParentMap(); - model.setObject(map.get(null).getKey()); - - final List> full = new ArrayList<>(); - getChildren(full, null, map, StringUtils.EMPTY); - return full; + Stream> full; + if (isSearchEnabled) { + full = map.entrySet().stream(). + map(el -> Pair.of(el.getKey(), el.getValue().getLeft())); + } else { + full = map.entrySet().stream(). + map(el -> Pair.of(el.getValue().getLeft().getFullPath(), el.getValue().getKey())). + sorted(Comparator.comparing(Pair::getLeft)); + } + return full.filter(realm -> SyncopeConsoleSession.get().getSearchableRealms().stream().anyMatch( + availableRealm -> realm.getValue().getFullPath().startsWith(availableRealm))). + collect(Collectors.toList()); } }; @@ -122,180 +133,291 @@ protected List> load() { @Override protected List load() { - List dynRealms = realmRestClient.listDynReams(); + List dynRealms = realmRestClient.listDynRealms(); dynRealms.sort((left, right) -> { if (left == null) { return -1; - } else if (right == null) { + } + if (right == null) { return 1; - } else { - return left.getKey().compareTo(right.getKey()); } + return left.getKey().compareTo(right.getKey()); }); - - return dynRealms; + return dynRealms.stream().filter(dynRealm -> SyncopeConsoleSession.get().getSearchableRealms().stream(). + anyMatch(availableRealm -> SyncopeConstants.ROOT_REALM.equals(availableRealm) + || dynRealm.getKey().equals(availableRealm))).collect(Collectors.toList()); } }; - container = new WebMarkupContainer("container", realmTree); - container.setOutputMarkupId(true); - add(container); - - availableRealms = SyncopeConsoleSession.get().getAuthRealms(); - - reloadRealmTree(); - } + RealmTO realm = SyncopeConsoleSession.get().getRootRealm(initialRealm).map(rootRealm -> { + String rootRealmName = StringUtils.substringAfterLast(rootRealm, "/"); - public final void reloadRealmTree() { - final Label realmLabel = new Label("realmLabel", new Model<>()); - realmLabel.setOutputMarkupId(true); + List realmTOs = realmRestClient.search( + RealmsUtils.buildQuery(SyncopeConstants.ROOT_REALM.equals(rootRealm) + ? SyncopeConstants.ROOT_REALM : rootRealmName)).getResult(); - container.addOrReplace(realmLabel); + return realmTOs.stream(). + filter(r -> rootRealm.equals(r.getFullPath())).findFirst(). + orElseGet(() -> { + RealmTO placeholder = new RealmTO(); + placeholder.setName(rootRealmName); + placeholder.setFullPath(rootRealm); + return placeholder; + }); + }).orElseGet(RealmTO::new); - if (model.getObject().getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) { - realmLabel.setDefaultModel(new ResourceModel("realmLabel", "Realm")); - } else { - realmLabel.setDefaultModel(new ResourceModel("dynRealmLabel", "Dynamic Realm")); - } + model = Model.of(realm); + searchQuery = realm.getName(); - final Label label = new Label("realm", model.getObject().getFullPath()); - label.setOutputMarkupId(true); - container.addOrReplace(label); + container = new WebMarkupContainerNoVeil("container", realmTree); + add(container.setOutputMarkupId(true)); - final DropDownButton realms = new DropDownButton( - "realms", new ResourceModel("select", ""), new Model(GlyphIconType.folderopen)) { + breadcrumb = new ListView("breadcrumb") { - private static final long serialVersionUID = -5560086780455361131L; + private static final long serialVersionUID = -8746795666847966508L; @Override - protected List newSubMenuButtons(final String buttonMarkupId) { - RealmChoicePanel.this.links.clear(); - - RealmChoicePanel.this.links.add(new BootstrapAjaxLink( - ButtonList.getButtonMarkupId(), - new Model(), - Buttons.Type.Link, - new ResourceModel("realms", "Realms")) { + protected void populateItem(final ListItem item) { + AjaxLink bcitem = new AjaxLink("bcitem") { - private static final long serialVersionUID = -7978723352517770744L; + private static final long serialVersionUID = -817438685948164787L; @Override public void onClick(final AjaxRequestTarget target) { + realmRestClient.list(item.getModelObject()).stream(). + filter(r -> item.getModelObject().equals(r.getFullPath())). + findFirst().ifPresent(t -> chooseRealm(t, target)); } + }; + bcitem.setBody(Model.of(SyncopeConstants.ROOT_REALM.equals(item.getModelObject()) + ? SyncopeConstants.ROOT_REALM + : StringUtils.substringAfterLast(item.getModelObject(), "/"))); + bcitem.setEnabled(!model.getObject().getFullPath().equals(item.getModelObject())); + item.add(bcitem); + } + }; + container.addOrReplace(breadcrumb.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true)); + setBreadcrumb(model.getObject()); - @Override - public boolean isEnabled() { - return false; - } - - @Override - protected void onComponentTag(final ComponentTag tag) { - tag.put("class", "panel box box-primary box-header with-border"); - tag.put("style", "margin: 20px 5px 0px 5px; width: 90%"); - } - }); + reloadRealmTree(); + } - for (Pair link : realmTree.getObject()) { - final RealmTO realmTO = link.getValue(); - RealmChoicePanel.this.links.add(new BootstrapAjaxLink( - ButtonList.getButtonMarkupId(), - Model.of(realmTO), - Buttons.Type.Link, - new Model<>(link.getKey())) { + protected void setBreadcrumb(final RealmTO realm) { + if (SyncopeConstants.ROOT_REALM.equals(realm.getFullPath())) { + breadcrumb.setList(Arrays.asList(realm.getFullPath())); + } else { + List bcitems = new ArrayList<>(); + bcitems.add(SyncopeConstants.ROOT_REALM); + + String[] split = realm.getFullPath().split("/"); + for (int i = 1; i < split.length; i++) { + StringBuilder bcitem = new StringBuilder(); + for (int j = 1; j <= i; j++) { + bcitem.append('/').append(split[j]); + } + bcitems.add(bcitem.toString()); + } - private static final long serialVersionUID = -7978723352517770644L; + breadcrumb.setList(bcitems); + } + } - @Override - public void onClick(final AjaxRequestTarget target) { - model.setObject(realmTO); - label.setDefaultModelObject(model.getObject().getFullPath()); - realmLabel.setDefaultModel(new ResourceModel("realmLabel", "Realm")); - target.add(label); - send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm<>(realmTO, target)); - } + protected void chooseRealm(final RealmTO realm, final AjaxRequestTarget target) { + model.setObject(realm); + setBreadcrumb(realm); + target.add(container); + send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm<>(realm, target)); + } - @Override - public boolean isEnabled() { - return availableRealms.stream(). - anyMatch(availableRealm -> realmTO.getFullPath().startsWith(availableRealm)); - } - }); + public void reloadRealmTree() { + if (isSearchEnabled) { + realmsChoices = buildRealmChoices(); + AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(false); + settings.setShowListOnEmptyInput(false); + + AutoCompleteTextField searchRealms = + new AutoCompleteTextField(SEARCH_REALMS, new Model<>(), settings) { + + private static final long serialVersionUID = -6635259975264955783L; + + @Override + protected Iterator getChoices(final String input) { + searchQuery = input; + realmsChoices = RealmsUtils.checkInput(input) + ? buildRealmChoices() + : Collections.emptyList(); + return realmsChoices.stream().map(RealmTO::getFullPath).sorted().iterator(); } - if (!dynRealmTree.getObject().isEmpty()) { - RealmChoicePanel.this.links.add(new BootstrapAjaxLink( - ButtonList.getButtonMarkupId(), - new Model(), - Buttons.Type.Link, - new ResourceModel("dynrealms", "Dynamic Realms")) { + @Override + protected AutoCompleteBehavior newAutoCompleteBehavior( + final IAutoCompleteRenderer renderer, + final AutoCompleteSettings settings) { - private static final long serialVersionUID = -7978723352517770744L; + return super.newAutoCompleteBehavior(new AbstractAutoCompleteRenderer() { + + private static final long serialVersionUID = -4789925973199139157L; @Override - public void onClick(final AjaxRequestTarget target) { + protected void renderChoice( + final String object, + final Response response, + final String criteria) { + response.write(object); } @Override - public boolean isEnabled() { - return false; + protected String getTextValue(final String object) { + return object; } + }, settings); + } + }; - @Override - protected void onComponentTag(final ComponentTag tag) { - tag.put("class", "panel box box-primary box-header with-border"); - tag.put("style", "margin: 20px 5px 0px 5px; width: 90%"); - } - }); + searchRealms.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - for (DynRealmTO dynRealmTO : dynRealmTree.getObject()) { - final RealmTO realmTO = new RealmTO(); - realmTO.setKey(dynRealmTO.getKey()); - realmTO.setName(dynRealmTO.getKey()); - realmTO.setFullPath(dynRealmTO.getKey()); - - RealmChoicePanel.this.links.add(new BootstrapAjaxLink( - ButtonList.getButtonMarkupId(), - new Model(), - Buttons.Type.Link, - new Model<>(realmTO.getKey())) { - - private static final long serialVersionUID = -7978723352517770644L; - - @Override - public void onClick(final AjaxRequestTarget target) { - model.setObject(realmTO); - label.setDefaultModelObject(realmTO.getKey()); - realmLabel.setDefaultModel(new ResourceModel("dynRealmLabel", "Dynamic Realm")); - target.add(label); - send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm<>(realmTO, target)); - } - - @Override - public boolean isEnabled() { - return availableRealms.stream().anyMatch(availableRealm -> { - return SyncopeConstants.ROOT_REALM.equals(availableRealm) - || realmTO.getKey().equals(availableRealm); - }); - } - }); - } + private static final long serialVersionUID = -6139318907146065915L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + realmsChoices.stream(). + filter(item -> item.getFullPath().equals(searchRealms.getModelObject())). + findFirst().ifPresent(realm -> chooseRealm(realm, target)); } + }); + + Fragment fragment = new Fragment("realmsFragment", "realmsSearchFragment", container); + fragment.addOrReplace(searchRealms); + container.addOrReplace(fragment); + } else { + DropDownButton realms = new DropDownButton( + "realms", new ResourceModel("select", ""), new Model<>(GlyphIconType.folderopen)) { + + private static final long serialVersionUID = -5560086780455361131L; + + @Override + protected List newSubMenuButtons(final String buttonMarkupId) { + buildRealmLinks(); + return RealmChoicePanel.this.links; + } + }; + realms.setOutputMarkupId(true); + realms.setAlignment(AlignmentBehavior.Alignment.RIGHT); + realms.setType(Buttons.Type.Menu); + + MetaDataRoleAuthorizationStrategy.authorize(realms, ENABLE, StandardEntitlement.REALM_LIST); + Fragment fragment = new Fragment("realmsFragment", "realmsListFragment", container); + fragment.addOrReplace(realms); + container.addOrReplace(fragment); + } + } + + protected void buildRealmLinks() { + RealmChoicePanel.this.links.clear(); + RealmChoicePanel.this.links.add(new BootstrapAjaxLink( + ButtonList.getButtonMarkupId(), + new Model<>(), + Buttons.Type.Link, + new ResourceModel("realms", "Realms")) { + + private static final long serialVersionUID = -7978723352517770744L; - return RealmChoicePanel.this.links; + @Override + public void onClick(final AjaxRequestTarget target) { } - }; - realms.setOutputMarkupId(true); - realms.setAlignment(AlignmentBehavior.Alignment.RIGHT); - realms.setType(Buttons.Type.Menu); - MetaDataRoleAuthorizationStrategy.authorize(realms, ENABLE, StandardEntitlement.REALM_LIST); + @Override + public boolean isEnabled() { + return false; + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + tag.put("class", "dropdown-header disabled"); + } + }); + + realmTree.getObject().forEach(link -> { + RealmChoicePanel.this.links.add(new BootstrapAjaxLink( + ButtonList.getButtonMarkupId(), + Model.of(link.getRight()), + Buttons.Type.Link, + new Model<>(link.getLeft())) { - container.addOrReplace(realms); + private static final long serialVersionUID = -7978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target) { + chooseRealm(link.getRight(), target); + } + }); + }); + + if (!dynRealmTree.getObject().isEmpty()) { + RealmChoicePanel.this.links.add(new BootstrapAjaxLink( + ButtonList.getButtonMarkupId(), + new Model<>(), + Buttons.Type.Link, + new ResourceModel("dynrealms", "Dynamic Realms")) { + + private static final long serialVersionUID = -7978723352517770744L; + + @Override + public void onClick(final AjaxRequestTarget target) { + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + tag.put("class", "dropdown-header disabled"); + } + }); + + dynRealmTree.getObject().forEach(dynRealmTO -> { + RealmTO realm = new RealmTO(); + realm.setKey(dynRealmTO.getKey()); + realm.setName(dynRealmTO.getKey()); + realm.setFullPath(dynRealmTO.getKey()); + + RealmChoicePanel.this.links.add(new BootstrapAjaxLink( + ButtonList.getButtonMarkupId(), + new Model<>(), + Buttons.Type.Link, + new Model<>(realm.getKey())) { + + private static final long serialVersionUID = -7978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target) { + chooseRealm(realm, target); + } + }); + }); + } + } + + protected List buildRealmChoices() { + return Stream.of( + realmTree.getObject().stream().map(Pair::getValue).collect(Collectors.toList()), + dynRealmTree.getObject().stream().map(item -> { + RealmTO realm = new RealmTO(); + realm.setKey(item.getKey()); + realm.setName(item.getKey()); + realm.setFullPath(item.getKey()); + return realm; + }).collect(Collectors.toList())).flatMap(Collection::stream). + collect(Collectors.toList()); } public final RealmChoicePanel reloadRealmTree(final AjaxRequestTarget target) { reloadRealmTree(); + chooseRealm(model.getObject(), target); target.add(container); return this; } @@ -306,20 +428,23 @@ public final RealmChoicePanel reloadRealmTree(final AjaxRequestTarget target, fi return this; } - private Map>> reloadRealmParentMap() { - return reloadRealmParentMap(realmRestClient.list().stream(). + protected Map>> reloadRealmParentMap() { + List realmsToList = isSearchEnabled + ? realmRestClient.search(RealmsUtils.buildQuery(searchQuery)).getResult() + : realmRestClient.list(SyncopeConstants.ROOT_REALM); + + return reloadRealmParentMap(realmsToList.stream(). sorted(Comparator.comparing(RealmTO::getName)). collect(Collectors.toList())); } - private Map>> reloadRealmParentMap(final List realms) { + protected Map>> reloadRealmParentMap(final List realms) { tree.clear(); - tree.put(null, Pair.>of(realms.get(0), new ArrayList<>())); - final Map> cache = new HashMap<>(); + Map> cache = new HashMap<>(); realms.forEach(realm -> { - final List children = new ArrayList<>(); + List children = new ArrayList<>(); tree.put(realm.getKey(), Pair.>of(realm, children)); if (cache.containsKey(realm.getKey())) { @@ -332,10 +457,9 @@ private Map>> reloadRealmParentMap(final Lis } else if (cache.containsKey(realm.getParent())) { cache.get(realm.getParent()).add(realm); } else { - cache.put(realm.getParent(), new ArrayList<>(Collections.singleton(realm))); + cache.put(realm.getParent(), Stream.of(realm).collect(Collectors.toList())); } }); - return tree; } @@ -366,9 +490,9 @@ public RealmTO moveToParentRealm(final String key) { public static class ChosenRealm { - private final AjaxRequestTarget target; + protected final AjaxRequestTarget target; - private final T obj; + protected final T obj; public ChosenRealm(final T obj, final AjaxRequestTarget target) { this.obj = obj; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmDetails.java index 763422e12f0..74ff503db4b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmDetails.java @@ -91,6 +91,8 @@ protected List load() { } }; + private final ActionsPanel actionsPanel; + private final WebMarkupContainer container; public RealmDetails(final String id, final RealmTO realmTO) { @@ -100,11 +102,13 @@ public RealmDetails(final String id, final RealmTO realmTO) { public RealmDetails( final String id, final RealmTO realmTO, - final ActionsPanel actionsPanel, + final ActionsPanel actionsPanel, final boolean unwrapped) { super(id); + this.actionsPanel = actionsPanel; + container = new WebMarkupContainer("container"); container.setOutputMarkupId(true); container.setRenderBodyOnly(unwrapped); @@ -146,7 +150,7 @@ public RealmDetails( AjaxPalettePanel actions = new AjaxPalettePanel.Builder(). setAllowMoveAll(true).setAllowOrder(true). build("actions", - new PropertyModel>(realmTO, "actions"), + new PropertyModel<>(realmTO, "actions"), new ListModel<>(logicActions.getObject())); actions.setOutputMarkupId(true); container.add(actions); @@ -172,4 +176,8 @@ public RealmDetails setContentEnabled(final boolean enable) { container.setEnabled(enable); return this; } + + public ActionsPanel getActionsPanel() { + return actionsPanel; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmWizardBuilder.java index 9edcded3015..edcdb8d8b99 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RealmWizardBuilder.java @@ -32,9 +32,9 @@ public class RealmWizardBuilder extends AjaxWizardBuilder { private static final long serialVersionUID = 5945391813567245081L; - private final RealmRestClient realmRestClient = new RealmRestClient(); + protected final RealmRestClient realmRestClient = new RealmRestClient(); - private String parentPath; + protected String parentPath; /** * Construct. @@ -71,7 +71,6 @@ public Realm(final RealmTO modelObject) { realmDetail.add(new AttributeAppender("style", "overflow-x:hidden;")); add(realmDetail); } - } public void setParentPath(final String parentPath) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java index 068ce5bd501..942e5f4f78d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.console.panels; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.to.RelationshipTypeTO; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -43,8 +44,9 @@ public RelationshipTypeDetailsPanel( form.setModel(new CompoundPropertyModel<>(relationshipTypeTO)); container.add(form); - final AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"), - new PropertyModel<>(relationshipTypeTO, "key")); + final AjaxTextFieldPanel key = new AjaxTextFieldPanel( + Constants.KEY_FIELD_NAME, getString(Constants.KEY_FIELD_NAME), + new PropertyModel<>(relationshipTypeTO, Constants.KEY_FIELD_NAME)); key.addRequiredLabel(); key.setEnabled(key.getModelObject() == null || key.getModelObject().isEmpty()); form.add(key); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java index 939ac8c1307..2756e88965e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java @@ -83,13 +83,12 @@ public void onSubmit(final AjaxRequestTarget target) { } else { restClient.update(modelObject); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); RelationshipTypesPanel.this.updateResultTable(target); modal.close(target); } catch (Exception e) { LOG.error("While creating or updating {}", modelObject, e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -112,8 +111,8 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - return Collections.emptyList(); + protected Collection getBatches() { + return Collections.emptyList(); } @Override @@ -122,7 +121,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); for (Field field : RelationshipTypeTO.class.getDeclaredFields()) { - if (field != null && !Modifier.isStatic(field.getModifiers())) { + if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) { final String fieldName = field.getName(); if (field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) @@ -142,7 +141,7 @@ protected List> getColumns() { @Override public String getCssClass() { String css = super.getCssClass(); - if ("key".equals(fieldName)) { + if (Constants.KEY_FIELD_NAME.equals(fieldName)) { css = StringUtils.isBlank(css) ? "col-xs-1" : css + " col-xs-1"; @@ -179,12 +178,11 @@ public void onClick(final AjaxRequestTarget target, final RelationshipTypeTO ign public void onClick(final AjaxRequestTarget target, final RelationshipTypeTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java index 242c4221acd..1e543592f81 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java @@ -30,9 +30,8 @@ import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; import org.apache.syncope.client.console.layout.GroupFormLayoutInfo; import org.apache.syncope.client.console.layout.UserFormLayoutInfo; import org.apache.syncope.client.console.pages.BasePage; @@ -102,7 +101,7 @@ protected List> getColumns() { List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new ResourceModel("operation"), "operation", "operation")); columns.add(new PropertyColumn<>( @@ -151,12 +150,11 @@ public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) { try { restClient.remedy(model.getObject().getKey(), model.getObject().getKeyPayload()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While performing remediation {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -208,7 +206,7 @@ public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) previousUserTO, newUserTO, new AnyTypeRestClient().read(remediationTO.getAnyType()).getClasses(), - FormLayoutInfoUtils.fetch(Arrays.asList(remediationTO.getAnyType())).getLeft(), + AnyLayoutUtils.fetch(Arrays.asList(remediationTO.getAnyType())).getUser(), pageRef ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)); send(RemediationDirectoryPanel.this, Broadcast.EXACT, userEvent); @@ -234,7 +232,7 @@ public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) previousGroupTO, newGroupTO, new AnyTypeRestClient().read(remediationTO.getAnyType()).getClasses(), - FormLayoutInfoUtils.fetch(Arrays.asList(remediationTO.getAnyType())).getMiddle(), + AnyLayoutUtils.fetch(Arrays.asList(remediationTO.getAnyType())).getGroup(), pageRef ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)); send(RemediationDirectoryPanel.this, Broadcast.EXACT, groupEvent); @@ -260,8 +258,8 @@ public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) previousAnyObjectTO, newAnyObjectTO, new AnyTypeRestClient().read(remediationTO.getAnyType()).getClasses(), - FormLayoutInfoUtils.fetch(Arrays.asList(remediationTO.getAnyType())). - getRight().values().iterator().next(), + AnyLayoutUtils.fetch(Arrays.asList(remediationTO.getAnyType())).getAnyObjects(). + get(remediationTO.getAnyType()), pageRef ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)); send(RemediationDirectoryPanel.this, Broadcast.EXACT, anyObjectEvent); @@ -278,12 +276,11 @@ public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) public void onClick(final AjaxRequestTarget target, final RemediationTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -303,7 +300,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -311,26 +308,23 @@ public static class RemediationProvider extends DirectoryDataProvider comparator; - private final RemediationRestClient restClient = new RemediationRestClient(); public RemediationProvider(final int paginatorRows) { super(paginatorRows); + setSort("instant", SortOrder.ASCENDING); - this.comparator = new SortableDataProviderComparator<>(this); } @Override public Iterator iterator(final long first, final long count) { - final List list = restClient.getRemediations(); - Collections.sort(list, comparator); - return list.subList((int) first, (int) first + (int) count).iterator(); + int page = ((int) first / paginatorRows); + return restClient.getRemediations((page < 0 ? 0 : page) + 1, paginatorRows, getSort()).iterator(); } @Override public long size() { - return restClient.getRemediations().size(); + return restClient.countRemediations(); } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java new file mode 100644 index 00000000000..c05b9d09337 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java @@ -0,0 +1,432 @@ +/* + * 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.client.console.panels; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.ResourceDataProvider; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.ConnectorRestClient; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.status.ResourceStatusModal; +import org.apache.syncope.client.console.tasks.PropagationTasks; +import org.apache.syncope.client.console.tasks.PullTasks; +import org.apache.syncope.client.console.tasks.PushTasks; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.WizardMgtPanel; +import org.apache.syncope.client.console.wizards.resources.ResourceProvisionPanel; +import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class ResourceDirectoryPanel extends + DirectoryPanel { + + private static final long serialVersionUID = -5223129956783782225L; + + protected String keyword; + + protected final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + + protected final BaseModal propTaskModal; + + protected final BaseModal schedTaskModal; + + protected final BaseModal provisionModal; + + protected final BaseModal historyModal; + + protected ResourceDirectoryPanel(final String id, final ResourceDirectoryPanel.Builder builder) { + super(id, builder); + + if (SyncopeConsoleSession.get().owns("RESOURCE_CREATE")) { + MetaDataRoleAuthorizationStrategy.authorizeAll(addAjaxLink, RENDER); + } else { + MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER); + } + + setShowResultPage(false); + modal.size(Modal.Size.Large); + initResultTable(); + + restClient = builder.restClient; + + propTaskModal = new BaseModal<>(Constants.OUTER); + propTaskModal.size(Modal.Size.Large); + addOuterObject(propTaskModal); + + schedTaskModal = new BaseModal(Constants.OUTER) { + + private static final long serialVersionUID = -6165152045136958913L; + + @Override + protected void onConfigure() { + super.onConfigure(); + setFooterVisible(false); + } + }; + schedTaskModal.size(Modal.Size.Large); + addOuterObject(schedTaskModal); + + provisionModal = new BaseModal<>(Constants.OUTER); + provisionModal.size(Modal.Size.Large); + provisionModal.addSubmitButton(); + addOuterObject(provisionModal); + + historyModal = new BaseModal<>(Constants.OUTER); + historyModal.size(Modal.Size.Large); + addOuterObject(historyModal); + } + + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof ResourceSearchEvent) { + ResourceSearchEvent payload = (ResourceSearchEvent) event.getPayload(); + AjaxRequestTarget target = payload.getTarget(); + if (StringUtils.isNotEmpty(payload.getKeyword())) { + keyword = payload.getKeyword().toLowerCase(); + } + updateResultTable(target); + } else { + super.onEvent(event); + } + } + + @Override + protected ResourceDataProvider dataProvider() { + dataProvider = new ResourceDataProvider(rows, pageRef, keyword); + return dataProvider; + } + + public ResourceDataProvider getDataProvider() { + return dataProvider; + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_PARAMETERS_PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + final List> columns = new ArrayList<>(); + columns.add(new PropertyColumn<>( + new ResourceModel("key"), "keySortParam", "key")); + columns.add(new PropertyColumn<>( + new ResourceModel("connectorDisplayName"), "connectorDisplayNameSortParam", "connectorDisplayName")); + return columns; + } + + @Override + protected Collection getBatches() { + return Collections.singletonList(ActionLink.ActionType.DELETE); + } + + @Override + public ActionsPanel getActions(final IModel model) { + final ActionsPanel panel = super.getActions(model); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -7220222653598674870L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ResourceTO resource = restClient.read(((ResourceTO) model.getObject()).getKey()); + ConnInstanceTO connInstance = connectorRestClient.read(resource.getConnector()); + + IModel model = new CompoundPropertyModel<>(resource); + modal.setFormModel(model); + + target.add(modal.setContent(new ResourceWizardBuilder(resource, pageRef). + build(BaseModal.CONTENT_ID, + SyncopeConsoleSession.get(). + owns(StandardEntitlement.RESOURCE_UPDATE, connInstance.getAdminRealm()) + ? AjaxWizard.Mode.EDIT + : AjaxWizard.Mode.READONLY))); + + modal.header(new Model<>(MessageFormat.format(getString("resource.edit"), model.getObject().getKey()))); + modal.show(true); + } + }, ActionLink.ActionType.EDIT, String.format("%s,%s", StandardEntitlement.RESOURCE_READ, + StandardEntitlement.RESOURCE_UPDATE)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -6467344504797047254L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ResourceTO resource = restClient.read(((ResourceTO) model.getObject()).getKey()); + ConnInstanceTO connInstance = connectorRestClient.read(resource.getConnector()); + + if (SyncopeConsoleSession.get(). + owns(StandardEntitlement.RESOURCE_UPDATE, connInstance.getAdminRealm())) { + + provisionModal.addSubmitButton(); + } else { + provisionModal.removeSubmitButton(); + } + + IModel model = new CompoundPropertyModel<>(resource); + provisionModal.setFormModel(model); + + target.add(provisionModal.setContent( + new ResourceProvisionPanel(provisionModal, resource, connInstance.getAdminRealm(), pageRef))); + + provisionModal.header(new Model<>(MessageFormat.format(getString("resource.edit"), + model.getObject().getKey()))); + provisionModal.show(true); + } + }, ActionLink.ActionType.MAPPING, String.format("%s,%s", StandardEntitlement.RESOURCE_READ, + StandardEntitlement.RESOURCE_UPDATE)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -1448897313753684142L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ResourceTO resource = restClient.read(((ResourceTO) model.getObject()).getKey()); + + target.add(propTaskModal.setContent(new ConnObjects(resource, pageRef))); + propTaskModal.header(new StringResourceModel("resource.explore.list", Model.of(model.getObject()))); + propTaskModal.show(true); + } + }, ActionLink.ActionType.EXPLORE_RESOURCE, StandardEntitlement.RESOURCE_LIST_CONNOBJECT); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 4800323783814856195L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + target.add(propTaskModal.setContent( + new PropagationTasks(propTaskModal, ((ResourceTO) model.getObject()).getKey(), pageRef))); + propTaskModal.header(new Model<>(MessageFormat.format(getString("task.propagation.list"), + ((ResourceTO) model.getObject()).getKey()))); + propTaskModal.show(true); + } + }, ActionLink.ActionType.PROPAGATION_TASKS, StandardEntitlement.TASK_LIST); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -4699610013584898667L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + target.add(schedTaskModal.setContent(new PullTasks(schedTaskModal, pageRef, + ((ResourceTO) model.getObject()).getKey()))); + schedTaskModal.header(new Model<>(MessageFormat.format(getString("task.pull.list"), + ((ResourceTO) model.getObject()).getKey()))); + schedTaskModal.show(true); + } + }, ActionLink.ActionType.PULL_TASKS, StandardEntitlement.TASK_LIST); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 2042227976628604686L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + target.add(schedTaskModal.setContent(new PushTasks(schedTaskModal, pageRef, + ((ResourceTO) model.getObject()).getKey()))); + schedTaskModal.header(new Model<>(MessageFormat.format(getString("task.push.list"), + ((ResourceTO) model.getObject()).getKey()))); + schedTaskModal.show(true); + } + }, ActionLink.ActionType.PUSH_TASKS, StandardEntitlement.TASK_LIST); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -5962061673680621813L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ResourceTO modelObject = restClient.read(((ResourceTO) model.getObject()).getKey()); + target.add(propTaskModal.setContent( + new ResourceStatusModal(propTaskModal, pageRef, modelObject))); + propTaskModal.header(new Model<>(MessageFormat.format(getString("resource.reconciliation"), + ((ResourceTO) model.getObject()).getKey()))); + propTaskModal.show(true); + } + }, ActionLink.ActionType.RECONCILIATION_RESOURCE, StandardEntitlement.USER_UPDATE); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = -5432034353017728766L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + ResourceTO modelObject = restClient.read(((ResourceTO) model.getObject()).getKey()); + + target.add(historyModal.setContent(new AuditHistoryModal( + historyModal, + AuditElements.EventCategoryType.LOGIC, + "ResourceLogic", + modelObject, + StandardEntitlement.RESOURCE_UPDATE, + pageRef) { + + private static final long serialVersionUID = -3712506022627033811L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + try { + ResourceTO updated = MAPPER.readValue(json, ResourceTO.class); + restClient.update(updated); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (Exception e) { + LOG.error("While restoring resource {}", ((ResourceTO) model.getObject()).getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + + historyModal.header( + new Model<>(MessageFormat.format(getString("resource.menu.history"), + ((ResourceTO) model.getObject()).getKey()))); + + historyModal.show(true); + } + }, ActionLink.ActionType.VIEW_AUDIT_HISTORY, String.format("%s,%s", StandardEntitlement.RESOURCE_READ, + StandardEntitlement.AUDIT_LIST)); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 7019899256702149874L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + try { + ResourceTO resource = restClient.read(((ResourceTO) model.getObject()).getKey()); + resource.setKey("Copy of " + resource.getKey()); + // reset some resource objects keys + if (resource.getOrgUnit() != null) { + resource.getOrgUnit().setKey(null); + resource.getOrgUnit().getItems().forEach(item -> item.setKey(null)); + } + resource.getProvisions().forEach(provision -> { + provision.setKey(null); + if (provision.getMapping() != null) { + provision.getMapping().getItems().forEach(item -> item.setKey(null)); + provision.getMapping().getLinkingItems().clear(); + } + provision.getVirSchemas().clear(); + }); + target.add(modal.setContent(new ResourceWizardBuilder(resource, pageRef). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE))); + + modal.header(new Model<>(MessageFormat.format(getString("resource.clone"), resource.getKey()))); + modal.show(true); + } catch (SyncopeClientException e) { + LOG.error("While cloning resource {}", ((ResourceTO) model.getObject()).getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + }, ActionLink.ActionType.CLONE, StandardEntitlement.RESOURCE_CREATE); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 4516186028545701573L; + + @Override + public void onClick(final AjaxRequestTarget target, final Serializable ignore) { + try { + restClient.delete(((ResourceTO) model.getObject()).getKey()); + target.appendJavaScript(String.format("jsPlumb.remove('%s');", + ((ResourceTO) model.getObject()).getKey())); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + } catch (SyncopeClientException e) { + LOG.error("While deleting resource {}", ((ResourceTO) model.getObject()).getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + }, ActionLink.ActionType.DELETE, StandardEntitlement.RESOURCE_DELETE, true); + + return panel; + } + + public static class ResourceSearchEvent implements Serializable { + + private static final long serialVersionUID = 213974502541311941L; + + protected final AjaxRequestTarget target; + + protected final String keyword; + + public ResourceSearchEvent(final AjaxRequestTarget target, final String keyword) { + this.target = target; + this.keyword = keyword; + } + + public AjaxRequestTarget getTarget() { + return target; + } + + public String getKeyword() { + return keyword; + } + } + + public static class Builder extends DirectoryPanel.Builder { + + private static final long serialVersionUID = -1391308721262593468L; + + public Builder(final PageReference pageRef) { + super(new ResourceRestClient(), pageRef); + setShowResultPage(false); + } + + @Override + protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { + return new ResourceDirectoryPanel(id, this); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java deleted file mode 100644 index c964c290899..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.client.console.panels; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.panels.ResourceHistoryConfDirectoryPanel.RHConfProvider; -import org.apache.syncope.client.console.rest.ResourceHistoryRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; -import org.apache.syncope.client.console.wizards.WizardMgtPanel; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.ResourceHistoryConfTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.event.IEvent; -import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.StringResourceModel; - -/** - * List all resource configuration history instances for the selected resource. - */ -public abstract class ResourceHistoryConfDirectoryPanel extends DirectoryPanel< - ResourceHistoryConfTO, ResourceHistoryConfTO, RHConfProvider, ResourceHistoryRestClient> - implements ModalPanel { - - private static final long serialVersionUID = 7636531280703026376L; - - protected final BaseModal baseModal; - - private final MultilevelPanel multiLevelPanelRef; - - private final String entityKey; - - public ResourceHistoryConfDirectoryPanel( - final BaseModal baseModal, - final MultilevelPanel multiLevelPanelRef, - final String entityKey, - final PageReference pageRef) { - - super(MultilevelPanel.FIRST_LEVEL_ID, pageRef, false, false); - - this.baseModal = baseModal; - this.multiLevelPanelRef = multiLevelPanelRef; - restClient = new ResourceHistoryRestClient(); - setShowResultPage(false); - disableCheckBoxes(); - - this.entityKey = entityKey; - initResultTable(); - } - - @Override - protected List> getColumns() { - final List> columns = new ArrayList<>(); - - columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); - - columns.add(new PropertyColumn<>(new StringResourceModel( - "creator", this), "creator", "creator")); - - columns.add(new DatePropertyColumn<>( - new StringResourceModel("creation", this), "creation", "creation")); - - return columns; - } - - @Override - public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); - final ResourceHistoryConfTO resHistoryConfTO = model.getObject(); - - // -- view - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3369924994540304232L; - - @Override - public void onClick(final AjaxRequestTarget target, final ResourceHistoryConfTO modelObject) { - - ResourceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - viewConfiguration(modelObject, target); - target.add(modal); - } - }, ActionLink.ActionType.VIEW, StandardEntitlement.RESOURCE_HISTORY_LIST); - - // -- restore - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3369924994540304232L; - - @Override - public void onClick(final AjaxRequestTarget target, final ResourceHistoryConfTO modelObject) { - try { - restClient.restore(modelObject.getKey()); - ResourceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - target.add(container); - } catch (SyncopeClientException e) { - LOG.error("While restoring {}", resHistoryConfTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }, ActionLink.ActionType.RESTORE, StandardEntitlement.RESOURCE_HISTORY_RESTORE); - - // -- delete - panel.add(new ActionLink() { - - private static final long serialVersionUID = -3369924994540304232L; - - @Override - public void onClick(final AjaxRequestTarget target, final ResourceHistoryConfTO modelObject) { - try { - restClient.delete(modelObject.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - target.add(container); - ResourceHistoryConfDirectoryPanel.this.getTogglePanel().close(target); - } catch (SyncopeClientException e) { - LOG.error("While deleting {}", resHistoryConfTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }, ActionLink.ActionType.DELETE, StandardEntitlement.RESOURCE_HISTORY_DELETE, true); - - return panel; - } - - @Override - protected Collection getBulkActions() { - return Collections.emptyList(); - } - - @Override - protected String paginatorRowsKey() { - return Constants.PREF_RESOURCE_HISTORY_CONF_PAGINATOR_ROWS; - } - - protected abstract void viewConfiguration(ResourceHistoryConfTO resHistoryTO, - final AjaxRequestTarget target); - - @Override - protected void resultTableCustomChanges( - final AjaxDataTablePanel.Builder resultTableBuilder) { - resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef); - } - - @Override - protected RHConfProvider dataProvider() { - return new RHConfProvider(rows); - } - - @Override - public void onEvent(final IEvent event) { - super.onEvent(event); - if (event.getPayload() instanceof WizardMgtPanel.ExitEvent && modal != null) { - final AjaxRequestTarget target = WizardMgtPanel.ExitEvent.class.cast(event.getPayload()).getTarget(); - baseModal.show(false); - baseModal.close(target); - } - } - - protected class RHConfProvider extends DirectoryDataProvider { - - private static final long serialVersionUID = -5244315453787001412L; - - private final SortableDataProviderComparator comparator; - - public RHConfProvider(final int paginatorRows) { - super(paginatorRows); - - setSort("creation", SortOrder.ASCENDING); // sort by 'creation' property - comparator = new SortableDataProviderComparator<>(this); - } - - @Override - public Iterator iterator(final long first, final long count) { - final List configurations = restClient.list(entityKey); - - Collections.sort(configurations, getComparator()); - return configurations.iterator(); - } - - public SortableDataProviderComparator getComparator() { - return comparator; - } - - @Override - public long size() { - return restClient.list(entityKey).size(); - } - - @Override - public IModel model(final ResourceHistoryConfTO object) { - return new CompoundPropertyModel<>(object); - } - - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java index 1301f0dc073..a38cb67d90a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java @@ -26,13 +26,13 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; import org.apache.syncope.client.console.commons.SortableDataProviderComparator; -import org.apache.syncope.client.console.layout.ConsoleLayoutInfo; -import org.apache.syncope.client.console.layout.FormLayoutInfoUtils; +import org.apache.syncope.client.console.layout.AnyLayout; +import org.apache.syncope.client.console.layout.AnyLayoutWrapper; +import org.apache.syncope.client.console.layout.AnyLayoutUtils; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.RoleDirectoryPanel.RoleDataProvider; import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; @@ -47,6 +47,7 @@ import org.apache.syncope.client.console.wizards.role.RoleWrapper; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyTypeTO; import org.apache.syncope.common.lib.to.RoleTO; import org.apache.syncope.common.lib.to.UserTO; @@ -70,9 +71,11 @@ public class RoleDirectoryPanel extends DirectoryPanel utilityModal = new BaseModal<>("outer"); + protected final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); - protected final BaseModal membersModal = new BaseModal<>("outer"); + protected final BaseModal utilityModal = new BaseModal<>(Constants.OUTER); + + protected final BaseModal membersModal = new BaseModal<>(Constants.OUTER); protected RoleDirectoryPanel(final String id, final Builder builder) { super(id, builder); @@ -109,7 +112,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); columns.add(new PropertyColumn<>( - new ResourceModel("key"), "key", "key")); + new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new ResourceModel("entitlements", "Entitlements"), null, "entitlements")); columns.add(new PropertyColumn<>( @@ -145,7 +148,7 @@ public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { - final RoleTO clone = SerializationUtils.clone(model.getObject()); + RoleTO clone = SerializationUtils.clone(model.getObject()); clone.setKey(null); send(RoleDirectoryPanel.this, Broadcast.EXACT, new AjaxWizard.NewItemActionEvent<>(new RoleWrapper(clone), target)); @@ -158,45 +161,43 @@ public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { - final String query = SyncopeClient.getUserSearchConditionBuilder().and( - SyncopeClient.getUserSearchConditionBuilder().inRoles(model.getObject().getKey()), - SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query(); - - final AnyTypeRestClient typeRestClient = new AnyTypeRestClient(); - final AnyTypeClassRestClient classRestClient = new AnyTypeClassRestClient(); - - final AnyTypeTO anyTypeTO = typeRestClient.read(AnyTypeKind.USER.name()); - - ModalPanel panel = new AnyPanel(BaseModal.CONTENT_ID, anyTypeTO, null, null, false, pageRef) { - - private static final long serialVersionUID = -7514498203393023415L; - - @Override - protected Panel getDirectoryPanel(final String id) { - final Panel panel = new UserDirectoryPanel.Builder( - classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). - setRealm("/"). - setFiltered(true). - setFiql(query). - disableCheckBoxes(). - addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate( - new UserTO(), - anyTypeTO.getClasses(), - FormLayoutInfoUtils.fetch(typeRestClient.list()).getLeft(), - pageRef), false). - setWizardInModal(false).build(id); - - MetaDataRoleAuthorizationStrategy.authorize( - panel, - WebPage.RENDER, - StandardEntitlement.USER_SEARCH); - - return panel; - } - }; + AnyTypeClassRestClient classRestClient = new AnyTypeClassRestClient(); + AnyTypeTO userType = anyTypeRestClient.read(AnyTypeKind.USER.name()); + + AnyLayout layout = AnyLayoutUtils.fetch(anyTypeRestClient.list()); + ModalPanel anyPanel = AnyLayoutUtils.newAnyPanel( + layout.getAnyPanelClass(), + BaseModal.CONTENT_ID, userType, null, layout, false, + (id, anyTypeTO, realmTO, anyLayout, pageRef) -> { + String query = SyncopeClient.getUserSearchConditionBuilder(). + inRoles(model.getObject().getKey()). + and(Constants.KEY_FIELD_NAME).notNullValue(). + query(); + + Panel panel = new UserDirectoryPanel.Builder( + classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). + setRealm(SyncopeConstants.ROOT_REALM). + setFiltered(true). + setFiql(query). + disableCheckBoxes(). + addNewItemPanelBuilder(AnyLayoutUtils.newLayoutInfo( + new UserTO(), + anyTypeTO.getClasses(), + anyLayout.getUser(), + pageRef), false). + setWizardInModal(false).build(id); + + MetaDataRoleAuthorizationStrategy.authorize( + panel, + WebPage.RENDER, + StandardEntitlement.USER_SEARCH); + + return panel; + }, + pageRef); membersModal.header(new StringResourceModel("role.members", RoleDirectoryPanel.this, model)); - membersModal.setContent(panel); + membersModal.setContent(anyPanel); membersModal.show(true); target.add(membersModal); } @@ -208,26 +209,27 @@ protected Panel getDirectoryPanel(final String id) { @Override public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { - final ConsoleLayoutInfo info = new ConsoleLayoutInfo(model.getObject().getKey()); - info.setContent(restClient.readConsoleLayoutInfo(model.getObject().getKey())); + AnyLayoutWrapper wrapper = new AnyLayoutWrapper( + model.getObject().getKey(), + AnyLayoutUtils.defaultIfEmpty( + restClient.readAnyLayout(model.getObject().getKey()), anyTypeRestClient.list())); utilityModal.header(new ResourceModel("console.layout.info", "JSON Content")); utilityModal.setContent(new JsonEditorPanel( - utilityModal, new PropertyModel(info, "content"), false, pageRef) { + utilityModal, new PropertyModel(wrapper, "content"), false, pageRef) { private static final long serialVersionUID = -8927036362466990179L; @Override public void onSubmit(final AjaxRequestTarget target) { try { - restClient.setConsoleLayoutInfo(info.getKey(), info.getContent()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + restClient.setAnyLayout(wrapper.getKey(), wrapper.getContent()); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.show(false); modal.close(target); } catch (Exception e) { - LOG.error("While updating console layout info for role {}", info.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + LOG.error("While updating console layout for role {}", wrapper.getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -244,12 +246,11 @@ public void onSubmit(final AjaxRequestTarget target) { public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -259,7 +260,7 @@ public void onClick(final AjaxRequestTarget target, final RoleTO ignore) { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.singletonList(ActionLink.ActionType.DELETE); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java index 1ced1aaf394..36aa70bc24d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java @@ -69,12 +69,12 @@ public class SchemaTypePanel extends TypesDirectoryPanel getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -131,7 +131,7 @@ protected List> getColumns() { for (String field : COL_NAMES.get(schemaType)) { Field clazzField = ReflectionUtils.findField(schemaType.getToClass(), field); - if (clazzField != null) { + if (clazzField != null && !clazzField.isSynthetic()) { if (clazzField.getType().equals(Boolean.class) || clazzField.getType().equals(boolean.class)) { columns.add(new BooleanPropertyColumn<>(new ResourceModel(field), field, field)); } else { @@ -143,7 +143,7 @@ protected List> getColumns() { @Override public String getCssClass() { String css = super.getCssClass(); - if ("key".equals(field)) { + if (Constants.KEY_FIELD_NAME.equals(field)) { css = StringUtils.isBlank(css) ? "col-xs-1" : css + " col-xs-1"; @@ -161,15 +161,15 @@ public String getCssClass() { @Override public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); + ActionsPanel panel = super.getActions(model); panel.add(new ActionLink() { private static final long serialVersionUID = -3722207913631435501L; @Override public void onClick(final AjaxRequestTarget target, final SchemaTO ignore) { - send(SchemaTypePanel.this, Broadcast.EXACT, - new AjaxWizard.EditItemActionEvent<>(model.getObject(), target)); + send(SchemaTypePanel.this, Broadcast.EXACT, new AjaxWizard.EditItemActionEvent<>( + restClient.read(schemaType, model.getObject().getKey()), target)); } }, ActionLink.ActionType.EDIT, StandardEntitlement.SCHEMA_UPDATE); panel.add(new ActionLink() { @@ -179,26 +179,13 @@ public void onClick(final AjaxRequestTarget target, final SchemaTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final SchemaTO ignore) { try { - switch (schemaType) { - case DERIVED: - restClient.deleteDerSchema(model.getObject().getKey()); - break; + restClient.delete(schemaType, model.getObject().getKey()); - case VIRTUAL: - restClient.deleteVirSchema(model.getObject().getKey()); - break; - - default: - restClient.deletePlainSchema(model.getObject().getKey()); - break; - } - - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -221,7 +208,7 @@ private SchemaProvider(final int paginatorRows, final SchemaType schemaType) { super(paginatorRows); this.schemaType = schemaType; - setSort("key", SortOrder.ASCENDING); + setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING); comparator = new SortableDataProviderComparator<>(this); } @@ -264,11 +251,13 @@ public void onEvent(final IEvent event) { AjaxRequestTarget target = payload.getTarget(); keyword = payload.getKeyword(); - if (!keyword.startsWith("*")) { - keyword = "*" + keyword; - } - if (!keyword.endsWith("*")) { - keyword = keyword + "*"; + if (StringUtils.isNotBlank(keyword)) { + if (!StringUtils.startsWith(keyword, "*")) { + keyword = "*" + keyword; + } + if (!StringUtils.endsWith(keyword, "*")) { + keyword = keyword + "*"; + } } updateResultTable(target); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java index 099cb257a9f..72f4a18fde8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java @@ -74,9 +74,9 @@ protected Serializable onApplyInternal(final SchemaTO modelObject) { modelObject.getLabels().clear(); modelObject.getLabels().putAll(translations.getObject().stream(). filter(Objects::nonNull). - filter(translation -> translation.getKey() != null). - filter(translation -> translation.getValue() != null). - collect(Collectors.toMap(MutablePair::getKey, MutablePair::getValue))); + filter(translation -> translation.getLeft() != null). + filter(translation -> translation.getRight() != null). + collect(Collectors.toMap(pair -> SchemaTO.toString(pair.getLeft()), MutablePair::getRight))); if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey())) { restClient.create(schemaType, modelObject); @@ -136,7 +136,7 @@ public Labels(final SchemaTO modelObject) { translations.getObject().clear(); modelObject.getLabels().forEach((locale, display) -> { - translations.getObject().add(MutablePair.of(locale, display)); + translations.getObject().add(MutablePair.of(SchemaTO.toLocale(locale), display)); }); ListView> labels = diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemasPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemasPanel.java index e45a58a6750..2bcebfacd68 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemasPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemasPanel.java @@ -24,14 +24,9 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.types.SchemaType; -import org.apache.wicket.AttributeModifier; -import org.apache.wicket.Component; import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.attributes.AjaxCallListener; -import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.event.Broadcast; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.extensions.markup.html.tabs.ITab; @@ -44,71 +39,37 @@ public class SchemasPanel extends Panel { private static final long serialVersionUID = -1140213992451232279L; - private static final String SEARCH_SUBMIT_LINK = "searchSubmitLink"; - - private final PageReference pageReference; + private final PageReference pageRef; public SchemasPanel(final String id, final PageReference pageRef) { super(id); - this.pageReference = pageRef; + this.pageRef = pageRef; - final Model keywordModel = new Model<>(StringUtils.EMPTY); + Model keywordModel = new Model<>(StringUtils.EMPTY); WebMarkupContainer searchBoxContainer = new WebMarkupContainer("searchBox"); add(searchBoxContainer); - final Form form = new Form<>("form"); + Form form = new Form<>("form"); searchBoxContainer.add(form); - final AjaxTextFieldPanel searchPanel = new AjaxTextFieldPanel( - "filter", "filter", keywordModel, true); - form.add(searchPanel.hideLabel().setOutputMarkupId(true)); + AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", keywordModel, true); + form.add(filter.hideLabel().setOutputMarkupId(true)); - final AjaxSubmitLink submitLink = new AjaxSubmitLink("search") { + AjaxButton search = new AjaxButton("search") { - private static final long serialVersionUID = -1765773642975892072L; + private static final long serialVersionUID = 8390605330558248736L; @Override - protected void onAfterSubmit(final AjaxRequestTarget target) { - super.onAfterSubmit(target); - - send(SchemasPanel.this, Broadcast.DEPTH, + protected void onSubmit(final AjaxRequestTarget target) { + send(SchemasPanel.this, Broadcast.DEPTH, new SchemaTypePanel.SchemaSearchEvent(target, keywordModel.getObject())); } }; - submitLink.setOutputMarkupId(true); - submitLink.setMarkupId(SEARCH_SUBMIT_LINK); - form.add(submitLink); - - searchPanel.getField().add(AttributeModifier.replace( - "onkeydown", - Model.of("if(event.keyCode == 13) {event.preventDefault();}"))); - - searchPanel.getField().add(new AjaxEventBehavior("onkeydown") { - - private static final long serialVersionUID = -7133385027739964990L; - - @Override - protected void onEvent(final AjaxRequestTarget target) { - target.appendJavaScript("$('#" + SEARCH_SUBMIT_LINK + "').click();"); - } - - @Override - protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { - super.updateAjaxAttributes(attributes); - - attributes.getAjaxCallListeners().add(new AjaxCallListener() { - - private static final long serialVersionUID = 7160235486520935153L; - - @Override - public CharSequence getPrecondition(final Component component) { - return "if (Wicket.Event.keyCode(attrs.event) == 13) { return true; } else { return false; }"; - } - }); - } - }); + search.setOutputMarkupId(true); + form.add(search); + form.setDefaultButton(search); Accordion accordion = new Accordion("accordionPanel", buildTabList()); accordion.setOutputMarkupId(true); @@ -125,7 +86,7 @@ private List buildTabList() { @Override public Panel getPanel(final String panelId) { - return new SchemaTypePanel(panelId, schemaType, pageReference); + return new SchemaTypePanel(panelId, schemaType, pageRef); } }); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsModalPanel.java index 8043a01f19c..197255be988 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsModalPanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.panels; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -60,12 +59,11 @@ public void onSubmit(final AjaxRequestTarget target) { restClient.update(securityQuestionTO); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.close(target); } catch (Exception e) { LOG.error("While creating or updating {}", securityQuestionTO, e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java index 7660153788d..a84bf25c3bd 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -44,7 +43,6 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.model.CompoundPropertyModel; @@ -68,16 +66,13 @@ protected WizardMgtPanel newInstance(final String id, final } }.disableCheckBoxes()); - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - modal.show(false); - target.add(container); - } + modal.addSubmitButton(); + modal.size(Modal.Size.Large); + modal.setWindowClosedCallback(target -> { + modal.show(false); + target.add(container); }); + setFooterVisibility(true); this.addNewItemPanelBuilder( new AbstractModalPanelBuilder(new SecurityQuestionTO(), pageRef) { @@ -92,22 +87,11 @@ public WizardModalPanel build( } }, true); - setFooterVisibility(true); - modal.addSubmitButton(); - modal.size(Modal.Size.Large); initResultTable(); MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.SECURITY_QUESTION_CREATE); } - private SecurityQuestionsPanel( - final String id, - final Builder builder) { - - super(id, builder); - setOutputMarkupId(true); - } - @Override protected SecurityQuestionsProvider dataProvider() { return new SecurityQuestionsProvider(rows); @@ -119,8 +103,8 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - return Collections.emptyList(); + protected Collection getBatches() { + return Collections.emptyList(); } @Override @@ -128,7 +112,7 @@ protected List> getColumns() { List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("content", this), "content", "content")); @@ -137,7 +121,7 @@ protected List> getColumns() { @Override public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); + ActionsPanel panel = super.getActions(model); panel.add(new ActionLink() { @@ -157,16 +141,15 @@ public void onClick(final AjaxRequestTarget target, final SecurityQuestionTO ign public void onClick(final AjaxRequestTarget target, final SecurityQuestionTO ignore) { try { restClient.delete(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While deleting {}", model.getObject(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionLink.ActionType.DELETE, StandardEntitlement.TASK_DELETE, true); + }, ActionLink.ActionType.DELETE, StandardEntitlement.SECURITY_QUESTION_DELETE, true); return panel; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/StartAtTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/StartAtTogglePanel.java index 1faf3e10d6e..d94b48c8220 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/StartAtTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/StartAtTogglePanel.java @@ -20,7 +20,6 @@ import java.io.Serializable; import java.util.Date; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -82,13 +81,12 @@ protected void onUpdate(final AjaxRequestTarget target) { protected void onSubmit(final AjaxRequestTarget target) { try { getRestClient().startExecution(key, startAtDateModel.getObject()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); toggle(target, false); target.add(container); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); LOG.error("While running task {}", key, e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/TogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/TogglePanel.java index 862747a4d18..1432e7b3f9d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/TogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/TogglePanel.java @@ -39,11 +39,9 @@ import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ReportTO; -import org.apache.syncope.common.lib.to.ProvisioningTaskTO; +import org.apache.syncope.common.lib.to.SchedTaskTO; import org.apache.syncope.common.lib.to.SecurityQuestionTO; import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.PageReference; @@ -193,7 +191,7 @@ private String getTargetKey(final Serializable modelObject) { } else if (modelObject instanceof ReportTO) { key = ((ReportTO) modelObject).getKey(); } else if (modelObject instanceof AttrTO) { - key = ((AttrTO) modelObject).getSchemaInfo().getKey(); + key = ((AttrTO) modelObject).getSchema(); } else if (modelObject instanceof PolicyTO) { key = ((PolicyTO) modelObject).getKey(); } else if (modelObject instanceof SecurityQuestionTO) { @@ -202,12 +200,8 @@ private String getTargetKey(final Serializable modelObject) { key = ((AccessTokenTO) modelObject).getKey(); } else if (modelObject instanceof ExecTO) { key = ((ExecTO) modelObject).getKey(); - } else if (modelObject instanceof WorkflowDefinitionTO) { - key = ((WorkflowDefinitionTO) modelObject).getKey(); - } else if (modelObject instanceof ProvisioningTaskTO) { - key = ((ProvisioningTaskTO) modelObject).getKey(); - } else if (modelObject instanceof WorkflowFormTO) { - key = ((WorkflowFormTO) modelObject).getKey(); + } else if (modelObject instanceof SchedTaskTO) { + key = ((SchedTaskTO) modelObject).getKey(); } else if (modelObject instanceof EntityTO) { key = ((EntityTO) modelObject).getKey(); } else if (modelObject instanceof StatusBean) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java index 511fbfcac5a..f9e98bed758 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java @@ -95,11 +95,10 @@ public void onSubmit(final AjaxRequestTarget target) { this.baseModal.show(false); this.baseModal.close(target); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("Group update failure", e); - SyncopeConsoleSession.get().error(getString(Constants.ERROR) + ": " + e.getMessage()); - + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -163,7 +162,7 @@ public void onClick(final AjaxRequestTarget target, final TypeExtensionTO ignore } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/TypesDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/TypesDirectoryPanel.java index 8aa4b80224c..8e4a0f48da0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/TypesDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/TypesDirectoryPanel.java @@ -21,11 +21,11 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import org.apache.syncope.client.console.commons.DirectoryDataProvider; import org.apache.syncope.client.console.rest.RestClient; -import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.to.EntityTO; import org.apache.wicket.PageReference; public abstract class TypesDirectoryPanel< - T extends AbstractBaseBean, DP extends DirectoryDataProvider, E extends RestClient> + T extends EntityTO, DP extends DirectoryDataProvider, E extends RestClient> extends DirectoryPanel { private static final long serialVersionUID = 7890071604330629259L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java index 54528539a20..72f2f0cbad0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java @@ -18,13 +18,15 @@ */ package org.apache.syncope.client.console.panels; +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.notifications.NotificationTasks; import org.apache.syncope.client.console.pages.BasePage; @@ -32,17 +34,25 @@ import org.apache.syncope.client.console.status.AnyStatusModal; import org.apache.syncope.client.console.status.ChangePasswordModal; import org.apache.syncope.client.console.tasks.AnyPropagationTasks; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.client.console.wizards.WizardMgtPanel; import org.apache.syncope.client.console.wizards.any.AnyWrapper; +import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsWizardBuilder; import org.apache.syncope.client.console.wizards.any.UserWrapper; +import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.patch.UserPatch; import org.apache.syncope.common.lib.to.AnyTypeClassTO; +import org.apache.syncope.common.lib.to.DerSchemaTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.rest.api.service.UserSelfService; import org.apache.wicket.PageReference; @@ -59,6 +69,17 @@ public class UserDirectoryPanel extends AnyDirectoryPanel wizardWrapperModal = new BaseModal(Constants.OUTER) { + + private static final long serialVersionUID = 389935548143327858L; + + @Override + protected void onConfigure() { + super.onConfigure(); + } + + }; + protected UserDirectoryPanel(final String id, final Builder builder) { this(id, builder, true); } @@ -76,6 +97,20 @@ public void onClose(final AjaxRequestTarget target) { modal.show(false); } }); + + wizardWrapperModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { + + private static final long serialVersionUID = -6109847349558471532L; + + @Override + public void onClose(final AjaxRequestTarget target) { + updateResultTable(target); + modal.show(false); + } + }); + + wizardWrapperModal.size(Modal.Size.Large); + addOuterObject(wizardWrapperModal); } @Override @@ -89,15 +124,13 @@ protected String[] getDefaultAttributeSelection() { } @Override - protected Collection getBulkActions() { - List bulkActions = new ArrayList<>(); - - bulkActions.add(ActionType.MUSTCHANGEPASSWORD); - bulkActions.add(ActionType.DELETE); - bulkActions.add(ActionType.SUSPEND); - bulkActions.add(ActionType.REACTIVATE); - - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionType.MUSTCHANGEPASSWORD); + batches.add(ActionType.DELETE); + batches.add(ActionType.SUSPEND); + batches.add(ActionType.REACTIVATE); + return batches; } @Override @@ -111,7 +144,10 @@ public ActionsPanel getHeader(final String componentId) { @Override public void onClick(final AjaxRequestTarget target, final Serializable ignore) { target.add(displayAttributeModal.setContent(new UserDisplayAttributesModalPanel<>( - displayAttributeModal, page.getPageReference(), pSchemaNames, dSchemaNames))); + displayAttributeModal, + page.getPageReference(), + plainSchemas.stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()), + derSchemas.stream().map(DerSchemaTO::getKey).collect(Collectors.toList())))); displayAttributeModal.header(new ResourceModel("any.attr.display")); displayAttributeModal.addSubmitButton(); @@ -138,31 +174,12 @@ public ActionsPanel getActions(final IModel model) { public void onClick(final AjaxRequestTarget target, final UserTO ignore) { send(UserDirectoryPanel.this, Broadcast.EXACT, new AjaxWizard.EditItemActionEvent<>( - new UserWrapper(new UserRestClient().read(model.getObject().getKey())), + new UserWrapper(restClient.read(model.getObject().getKey())), target)); } }, ActionType.EDIT, String.format("%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE)). - setRealm(realm); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -7978723352517770644L; - - @Override - public void onClick(final AjaxRequestTarget target, final UserTO ignore) { - UserTO clone = SerializationUtils.clone(model.getObject()); - clone.setKey(null); - clone.setUsername(model.getObject().getUsername() + "_clone"); - send(UserDirectoryPanel.this, Broadcast.EXACT, - new AjaxWizard.NewItemActionEvent<>(new UserWrapper(clone), target)); - } - - @Override - protected boolean statusCondition(final UserTO modelObject) { - return addAjaxLink.isVisibleInHierarchy() && realm.startsWith(SyncopeConstants.ROOT_REALM); - } - }, ActionType.CLONE, StandardEntitlement.USER_CREATE).setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -171,21 +188,22 @@ protected boolean statusCondition(final UserTO modelObject) { @Override public void onClick(final AjaxRequestTarget target, final UserTO ignore) { try { - UserRestClient.class.cast(restClient).mustChangePassword( + model.setObject(restClient.read(model.getObject().getKey())); + restClient.mustChangePassword( model.getObject().getETagValue(), !model.getObject().isMustChangePassword(), model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While actioning object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionType.MUSTCHANGEPASSWORD, StandardEntitlement.USER_UPDATE).setRealm(realm); + }, ActionType.MUSTCHANGEPASSWORD, StandardEntitlement.USER_UPDATE). + setRealms(realm, model.getObject().getDynRealms()); if (wizardInModal) { panel.add(new ActionLink() { @@ -194,6 +212,7 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); IModel> formModel = new CompoundPropertyModel<>( new AnyWrapper<>(model.getObject())); displayAttributeModal.setFormModel(formModel); @@ -208,7 +227,8 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { displayAttributeModal.show(true); } - }, ActionType.PASSWORD_MANAGEMENT, StandardEntitlement.USER_UPDATE).setRealm(realm); + }, ActionType.PASSWORD_MANAGEMENT, StandardEntitlement.USER_UPDATE). + setRealms(realm, model.getObject().getDynRealms()); if (SyncopeConsoleSession.get().getPlatformInfo().isPwdResetAllowed() && !SyncopeConsoleSession.get().getPlatformInfo().isPwdResetRequiringSecurityQuestions()) { @@ -223,16 +243,16 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { SyncopeConsoleSession.get().getAnonymousClient().getService(UserSelfService.class). requestPasswordReset(model.getObject().getUsername(), null); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { LOG.error("While actioning object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionType.REQUEST_PASSWORD_RESET, StandardEntitlement.USER_UPDATE).setRealm(realm); + }, ActionType.REQUEST_PASSWORD_RESET, StandardEntitlement.USER_UPDATE). + setRealms(realm, model.getObject().getDynRealms()); } panel.add(new ActionLink() { @@ -257,7 +277,8 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { altDefaultModal.show(true); } - }, ActionType.ENABLE, StandardEntitlement.USER_UPDATE).setRealm(realm); + }, ActionType.ENABLE, StandardEntitlement.USER_UPDATE). + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -265,6 +286,7 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); IModel> formModel = new CompoundPropertyModel<>( new AnyWrapper<>(model.getObject())); altDefaultModal.setFormModel(formModel); @@ -283,7 +305,7 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { } }, ActionType.MANAGE_RESOURCES, String.format("%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE)). - setRealm(realm); + setRealms(realm, model.getObject().getDynRealms()); panel.add(new ActionLink() { @@ -307,13 +329,125 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { public void onClick(final AjaxRequestTarget target, final UserTO ignore) { target.add(utilityModal.setContent( new NotificationTasks(AnyTypeKind.USER, model.getObject().getKey(), pageRef))); + utilityModal.header(new StringResourceModel("any.notification.tasks", model)); utilityModal.show(true); target.add(utilityModal); } }, ActionType.NOTIFICATION_TASKS, StandardEntitlement.TASK_LIST); + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 8011039414597736111L; + + @Override + public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); + target.add(wizardWrapperModal.setContent( + new LinkedAccountModalPanel(wizardWrapperModal, model, pageRef, false))); + wizardWrapperModal.header(new ResourceModel("linkedAccounts.title")); + wizardWrapperModal.show(true); + } + }, ActionType.MANAGE_ACCOUNTS, + String.format("%s,%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE, + StandardEntitlement.RESOURCE_GET_CONNOBJECT)); + + if (wizardInModal) { + panel.add(new ActionLink() { + private static final long serialVersionUID = 8011039414597736111L; + + @Override + public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + model.setObject(UserRestClient.class.cast(restClient).read(model.getObject().getKey())); + MergeLinkedAccountsWizardBuilder builder = + new MergeLinkedAccountsWizardBuilder(model, pageRef, UserDirectoryPanel.this, modal); + builder.setEventSink(builder); + target.add(modal.setContent(builder.build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE))); + modal.header(new StringResourceModel("mergeLinkedAccounts.title", model)); + modal.show(true); + } + }, ActionType.MERGE_ACCOUNTS, + String.format("%s,%s,%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE, + StandardEntitlement.USER_DELETE, StandardEntitlement.RESOURCE_GET_CONNOBJECT)); + } + } + + if (wizardInModal) { + panel.add(new ActionLink() { + private static final long serialVersionUID = -1978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + model.setObject(restClient.read(model.getObject().getKey())); + target.add(altDefaultModal.setContent(new AuditHistoryModal( + altDefaultModal, + AuditElements.EventCategoryType.LOGIC, + "UserLogic", + model.getObject(), + StandardEntitlement.USER_UPDATE, + pageRef) { + + private static final long serialVersionUID = 959378158400669867L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + // The original audit record masks the password and the security + // answer; so we cannot use the audit record to resurrect the entry + // based on mask data. + // + // The method behavior below will reset the audit record such + // that the current security answer and the password for the object + // are always maintained, and such properties for the + // user cannot be restored using audit records. + UserTO original = model.getObject(); + try { + UserTO updated = MAPPER.readValue(json, UserTO.class); + UserPatch userPatch = AnyOperations.diff(updated, original, false); + userPatch.setPassword(null); + userPatch.setSecurityAnswer(null); + ProvisioningResult result = + restClient.update(original.getETagValue(), userPatch); + model.getObject().setLastChangeDate(result.getEntity().getLastChangeDate()); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + target.add(container); + } catch (Exception e) { + LOG.error("While restoring user {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); + + altDefaultModal.header(new Model<>( + getString("auditHistory.title", new Model<>(new AnyWrapper<>(model.getObject()))))); + + altDefaultModal.show(true); + } + }, ActionType.VIEW_AUDIT_HISTORY, + String.format("%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.AUDIT_LIST)). + setRealms(realm, model.getObject().getDynRealms()); } + panel.add(new ActionLink() { + + private static final long serialVersionUID = -7978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target, final UserTO ignore) { + UserTO clone = SerializationUtils.clone(model.getObject()); + clone.setKey(null); + clone.setUsername(model.getObject().getUsername() + "_clone"); + send(UserDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.NewItemActionEvent<>(new UserWrapper(clone), target)); + } + + @Override + protected boolean statusCondition(final UserTO modelObject) { + return addAjaxLink.isVisibleInHierarchy() && realm.startsWith(SyncopeConstants.ROOT_REALM); + } + }, ActionType.CLONE, StandardEntitlement.USER_CREATE).setRealm(realm); + panel.add(new ActionLink() { private static final long serialVersionUID = -7978723352517770644L; @@ -323,12 +457,11 @@ public void onClick(final AjaxRequestTarget target, final UserTO ignore) { try { restClient.delete(model.getObject().getETagValue(), model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { - LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + LOG.error("While deleting user {}", model.getObject().getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/VirSchemaDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/VirSchemaDetails.java index 5b27c83aeeb..8068d64d7e0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/VirSchemaDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/VirSchemaDetails.java @@ -19,11 +19,9 @@ package org.apache.syncope.client.console.panels; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -33,8 +31,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; -import org.apache.syncope.common.lib.to.ConnIdObjectClassTO; -import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.to.VirSchemaTO; @@ -159,16 +156,10 @@ private void populateAnyTypes(final String resourceKey) { } private List getExtAttrNames() { - ConnInstanceTO connInstanceTO = new ConnInstanceTO(); - connInstanceTO.setKey(selectedResource.getConnector()); - connInstanceTO.getConf().addAll(selectedResource.getConfOverride()); - - Optional connIdObjectClass = connRestClient.buildObjectClassInfo(connInstanceTO, false). - stream().filter(object -> object.getType().equals(anyTypes.get(anyType.getModelObject()))). - findAny(); - - return connIdObjectClass.isPresent() - ? connIdObjectClass.get().getAttributes() - : Collections.emptyList(); + return connectorRestClient.getExtAttrNames( + SyncopeConstants.ROOT_REALM, + anyTypes.get(anyType.getModelObject()), + selectedResource.getConnector(), + selectedResource.getConfOverride()); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/WorkflowDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/WorkflowDirectoryPanel.java deleted file mode 100644 index a8303110f2b..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/WorkflowDirectoryPanel.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * 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.client.console.panels; - -import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import javax.ws.rs.core.MediaType; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleApplication; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; -import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.pages.ModelerPopupPage; -import org.apache.syncope.client.console.panels.WorkflowDirectoryPanel.WorkflowDefinitionDataProvider; -import org.apache.syncope.client.console.rest.WorkflowRestClient; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; -import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; -import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; -import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.ImageModalPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel; -import org.apache.syncope.client.console.wizards.AjaxWizardBuilder; -import org.apache.syncope.client.console.wizards.WizardMgtPanel; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.Page; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; -import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; -import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.extensions.wizard.WizardModel; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.model.CompoundPropertyModel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.ResourceModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.apache.wicket.util.io.IOUtils; - -public class WorkflowDirectoryPanel extends DirectoryPanel< - WorkflowDefinitionTO, WorkflowDefinitionTO, WorkflowDefinitionDataProvider, WorkflowRestClient> { - - private static final long serialVersionUID = 2705668831139984998L; - - private final BaseModal utility; - - private String modelerCtx; - - protected WorkflowDirectoryPanel(final String id, final Builder builder) { - super(id, builder); - - this.addNewItemPanelBuilder(new AjaxWizardBuilder(new WorkflowDefinitionTO(), pageRef) { - - private static final long serialVersionUID = 1633859795677053912L; - - @Override - protected WizardModel buildModelSteps( - final WorkflowDefinitionTO modelObject, final WizardModel wizardModel) { - - return wizardModel; - } - }, false); - final NewWorkflowProcess newWorkflowProcess = new NewWorkflowProcess("newWorkflowProcess", container, pageRef); - addInnerObject(newWorkflowProcess); - AjaxLink newWorkflowProcessLink = new AjaxLink("add") { - - private static final long serialVersionUID = -7978723352517770644L; - - @Override - public void onClick(final AjaxRequestTarget target) { - newWorkflowProcess.toggle(target, true); - } - }; - ((WebMarkupContainer) get("container:content")).addOrReplace(newWorkflowProcessLink); - - setShowResultPage(true); - - modal.size(Modal.Size.Large); - - utility = new BaseModal<>("outer"); - addOuterObject(utility); - utility.size(Modal.Size.Large); - AjaxSubmitLink xmlEditorSubmit = utility.addSubmitButton(); - MetaDataRoleAuthorizationStrategy.authorize(xmlEditorSubmit, RENDER, StandardEntitlement.WORKFLOW_DEF_SET); - utility.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - utility.show(false); - utility.close(target); - } - }); - initResultTable(); - - // Check if Flowable Modeler directory is found - modelerCtx = null; - try { - if (SyncopeConsoleApplication.get().getFlowableModelerDirectory() != null) { - File baseDir = new File(SyncopeConsoleApplication.get().getFlowableModelerDirectory()); - if (baseDir.exists() && baseDir.canRead() && baseDir.isDirectory()) { - modelerCtx = Constants.FLOWABLE_MODELER_CONTEXT; - } - } - } catch (Exception e) { - LOG.error("Could not check for Modeler directory", e); - } - } - - @Override - protected WorkflowDefinitionDataProvider dataProvider() { - return new WorkflowDefinitionDataProvider(rows); - } - - @Override - protected String paginatorRowsKey() { - return Constants.PREF_WORKFLOW_PAGINATOR_ROWS; - } - - @Override - protected List> getColumns() { - List> columns = new ArrayList<>(); - - columns.add(new KeyPropertyColumn<>(new ResourceModel("key"), "key")); - columns.add(new PropertyColumn<>(new ResourceModel("name"), "name", "name")); - columns.add(new BooleanPropertyColumn<>(new ResourceModel("main"), null, "main")); - - return columns; - } - - @Override - public ActionsPanel getActions(final IModel model) { - final ActionsPanel panel = super.getActions(model); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -184018732772021627L; - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowDefinitionTO ignore) { - final IModel wfDefinition = new Model<>(); - try { - wfDefinition.setObject(IOUtils.toString(restClient.getDefinition( - MediaType.APPLICATION_XML_TYPE, model.getObject().getKey()))); - } catch (IOException e) { - LOG.error("Could not get workflow definition", e); - } - - utility.header(Model.of(model.getObject().getKey())); - utility.setContent(new XMLEditorPanel(utility, wfDefinition, false, pageRef) { - - private static final long serialVersionUID = -7688359318035249200L; - - @Override - public void onSubmit(final AjaxRequestTarget target) { - if (StringUtils.isNotBlank(wfDefinition.getObject())) { - try { - restClient.setDefinition(MediaType.APPLICATION_XML_TYPE, - model.getObject().getKey(), wfDefinition.getObject()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - - target.add(container); - utility.show(false); - utility.close(target); - } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - } - }); - utility.show(target); - target.add(utility); - } - }, ActionLink.ActionType.EDIT, StandardEntitlement.WORKFLOW_DEF_SET); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = 3109256773218160485L; - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowDefinitionTO ignore) { - modal.header(Model.of(model.getObject().getKey())); - modal.setContent(new ImageModalPanel<>( - modal, restClient.getDiagram(model.getObject().getKey()), pageRef)); - modal.show(target); - target.add(modal); - } - }, ActionLink.ActionType.VIEW, StandardEntitlement.WORKFLOW_DEF_GET); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -184018732772021627L; - - @Override - public Class getPageClass() { - return ModelerPopupPage.class; - } - - @Override - public PageParameters getPageParameters() { - PageParameters parameters = new PageParameters(); - if (modelerCtx != null) { - parameters.add(Constants.MODELER_CONTEXT, modelerCtx); - } - parameters.add(Constants.MODEL_ID_PARAM, model.getObject().getModelId()); - - return parameters; - } - - @Override - protected boolean statusCondition(final WorkflowDefinitionTO modelObject) { - return modelerCtx != null; - } - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowDefinitionTO ignore) { - // do nothing - } - }, ActionLink.ActionType.WORKFLOW_MODELER, StandardEntitlement.WORKFLOW_DEF_SET); - - panel.add(new ActionLink() { - - private static final long serialVersionUID = -7978723352517770644L; - - @Override - protected boolean statusCondition(final WorkflowDefinitionTO modelObject) { - return !modelObject.isMain(); - } - - @Override - public void onClick(final AjaxRequestTarget target, final WorkflowDefinitionTO ignore) { - try { - restClient.deleteDefinition(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - target.add(container); - } catch (SyncopeClientException e) { - LOG.error("While deleting workflow definition {}", model.getObject().getName(), e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); - } - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - }, ActionLink.ActionType.DELETE, StandardEntitlement.WORKFLOW_DEF_DELETE, true); - - return panel; - } - - @Override - protected Collection getBulkActions() { - return Collections.emptyList(); - } - - public abstract static class Builder - extends DirectoryPanel.Builder { - - private static final long serialVersionUID = 5088962796986706805L; - - public Builder(final PageReference pageRef) { - super(new WorkflowRestClient(), pageRef); - } - - @Override - protected WizardMgtPanel newInstance(final String id, final boolean wizardInModal) { - return new WorkflowDirectoryPanel(id, this); - } - } - - protected class WorkflowDefinitionDataProvider extends DirectoryDataProvider { - - private static final long serialVersionUID = 1764153405387687592L; - - private final SortableDataProviderComparator comparator; - - private final WorkflowRestClient restClient = new WorkflowRestClient(); - - public WorkflowDefinitionDataProvider(final int paginatorRows) { - super(paginatorRows); - this.comparator = new SortableDataProviderComparator<>(this); - setSort("main", SortOrder.DESCENDING); - } - - @Override - public Iterator iterator(final long first, final long count) { - List result = restClient.getDefinitions(); - Collections.sort(result, comparator); - return result.subList((int) first, (int) first + (int) count).iterator(); - } - - @Override - public long size() { - return restClient.getDefinitions().size(); - } - - @Override - public IModel model(final WorkflowDefinitionTO object) { - return new CompoundPropertyModel<>(object); - } - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java index 9103ba6d252..6364e0c859c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java @@ -19,23 +19,24 @@ package org.apache.syncope.client.console.panels.search; import java.io.Serializable; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.rest.AnyTypeRestClient; import org.apache.syncope.client.console.rest.GroupRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; import org.apache.syncope.client.console.rest.SchemaRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.search.SearchableFields; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.syncope.common.lib.types.SchemaType; +import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.event.IEventSink; -import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; @@ -56,7 +57,7 @@ public abstract class AbstractSearchPanel extends Panel { protected ResourceRestClient resourceRestClient = new ResourceRestClient(); - protected IModel> dnames; + protected IModel> dnames; protected IModel> anames; @@ -64,7 +65,7 @@ public abstract class AbstractSearchPanel extends Panel { protected IModel> types; - protected IModel> groupNames; + protected IModel> groupNames; protected IModel> roleNames; @@ -94,6 +95,12 @@ public abstract static class Builder implements S protected boolean enableSearch = false; + protected SearchClausePanel.Customizer customizer = new SearchClausePanel.Customizer() { + + private static final long serialVersionUID = 4449199405807453441L; + + }; + protected IEventSink resultContainer; public Builder(final IModel> model) { @@ -110,12 +117,17 @@ public Builder enableSearch() { return this; } + public Builder customizer(final SearchClausePanel.Customizer customizer) { + this.customizer = customizer; + return this; + } + public Builder required(final boolean required) { this.required = required; return this; } - public abstract T build(final String id); + public abstract T build(String id); } protected AbstractSearchPanel(final String id, final AnyTypeKind kind, final Builder builder) { @@ -127,12 +139,6 @@ protected AbstractSearchPanel( super(id); populate(); - Pair>, Integer> groupInfo = - Pair.of(groupNames, groupRestClient.search("/", - null, - 1, - 1, - new SortParam<>("name", true)).getTotalCount()); this.model = builder.model; this.typeKind = kind; @@ -146,16 +152,30 @@ protected AbstractSearchPanel( searchFormContainer.setOutputMarkupId(true); add(searchFormContainer); - final SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel", + Pair>, IModel> groupInfo = + typeKind != AnyTypeKind.GROUP && SyncopeConsoleSession.get().owns(StandardEntitlement.GROUP_SEARCH) + ? Pair.of(groupNames, new LoadableDetachableModel() { + + private static final long serialVersionUID = 7362833782319137329L; + + @Override + protected Integer load() { + return groupRestClient.count(SyncopeConstants.ROOT_REALM, null, null); + } + }) + : Pair.of(groupNames, Model.of(0)); + SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel", Model.of(new SearchClause()), required, - types, anames, dnames, groupInfo, roleNames, privilegeNames, resourceNames); + types, + builder.customizer, + anames, dnames, groupInfo, roleNames, privilegeNames, resourceNames); if (enableSearch) { searchClausePanel.enableSearch(builder.resultContainer); } - final MultiFieldPanel.Builder searchView = new MultiFieldPanel.Builder(model) { + MultiFieldPanel.Builder searchView = new MultiFieldPanel.Builder(model) { private static final long serialVersionUID = 1343431509987473047L; @@ -169,26 +189,19 @@ protected SearchClause newModelObject() { } protected void populate() { - dnames = new LoadableDetachableModel>() { - - private static final long serialVersionUID = 5275935387613157437L; - - @Override - protected List load() { - return SearchableFields.get(typeKind.getTOClass()); - } - }; - - anames = new LoadableDetachableModel>() { + dnames = new LoadableDetachableModel>() { private static final long serialVersionUID = 5275935387613157437L; @Override protected Map load() { - final List schemas = schemaRestClient.getSchemas( - SchemaType.PLAIN, null, anyTypeRestClient.read(type).getClasses().toArray(new String[] {})); - - return schemas.stream().collect(Collectors.toMap(schema -> schema.getKey(), Function.identity())); + Map dSchemaNames = new HashMap<>(); + SearchableFields.get(typeKind.getTOClass()).forEach((key, type) -> { + PlainSchemaTO plain = new PlainSchemaTO(); + plain.setType(type); + dSchemaNames.put(key, plain); + }); + return dSchemaNames; } }; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java index 0a941109760..f18a6cf5339 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java @@ -19,11 +19,16 @@ package org.apache.syncope.client.console.panels.search; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; @@ -32,8 +37,6 @@ public class AnyObjectSearchPanel extends AbstractSearchPanel { private static final long serialVersionUID = -1769527800450203738L; - public static final int MAX_GROUP_LIST_CARDINALITY = 30; - public static class Builder extends AbstractSearchPanel.Builder { private static final long serialVersionUID = 6308997285778809578L; @@ -73,25 +76,31 @@ protected List load() { } }; - this.groupNames = new LoadableDetachableModel>() { + this.groupNames = new LoadableDetachableModel>() { private static final long serialVersionUID = 5275935387613157437L; @Override - protected Map load() { - List res = groupRestClient.search("/", + protected List load() { + return groupRestClient.search( + SyncopeConstants.ROOT_REALM, null, 1, - MAX_GROUP_LIST_CARDINALITY, + Constants.MAX_GROUP_LIST_SIZE, new SortParam<>("name", true), - null); + null).stream().map(GroupTO::getName).collect(Collectors.toList()); + } + }; - final Map result = new HashMap<>(res.size()); - for (GroupTO group : res) { - result.put(group.getKey(), group.getName()); - } + this.anames = new LoadableDetachableModel>() { - return result; + private static final long serialVersionUID = 5275935387613157437L; + + @Override + protected Map load() { + return schemaRestClient.getSchemas( + SchemaType.PLAIN, null, anyTypeRestClient.read(type).getClasses().toArray(new String[] {})). + stream().collect(Collectors.toMap(schema -> schema.getKey(), Function.identity())); } }; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnySelectionDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnySelectionDirectoryPanel.java index 3ce4143d5b6..da5341821f7 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnySelectionDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnySelectionDirectoryPanel.java @@ -46,6 +46,11 @@ protected AnySelectionDirectoryPanel( final boolean wizardInModal) { super(id, builder, wizardInModal); + + csvPushLink.setEnabled(false); + csvPushLink.setVisible(false); + csvPullLink.setEnabled(false); + csvPullLink.setVisible(false); } @Override @@ -67,7 +72,7 @@ public void onClick(final AjaxRequestTarget target, final A ignore) { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java new file mode 100644 index 00000000000..b078a409047 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java @@ -0,0 +1,117 @@ +/* + * 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.client.console.panels.search; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.rest.ConnectorRestClient; +import org.apache.syncope.common.lib.to.ConnIdObjectClassTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; + +public class ConnObjectSearchPanel extends AbstractSearchPanel { + + private static final long serialVersionUID = 21020550706646L; + + protected final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + + protected final ResourceTO resource; + + public static class Builder extends AbstractSearchPanel.Builder { + + private static final long serialVersionUID = 6308997285778809578L; + + private final ResourceTO resource; + + private final AnyTypeKind anyType; + + private final String typeName; + + public Builder( + final ResourceTO resource, + final AnyTypeKind anyType, + final String type, + final IModel> model) { + + super(model); + this.resource = resource; + this.anyType = anyType; + this.typeName = type; + } + + @Override + public ConnObjectSearchPanel build(final String id) { + return new ConnObjectSearchPanel(id, anyType, typeName, this); + } + } + + protected ConnObjectSearchPanel(final String id, final AnyTypeKind kind, final Builder builder) { + super(id, kind, builder); + this.resource = builder.resource; + } + + protected ConnObjectSearchPanel(final String id, final AnyTypeKind kind, final String type, final Builder builder) { + super(id, kind, type, builder); + this.resource = builder.resource; + } + + @Override + protected void populate() { + this.types = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 22668815812716L; + + @Override + protected List load() { + return Collections.singletonList(SearchClause.Type.ATTRIBUTE); + } + }; + + this.dnames = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 2989042618372L; + + @Override + protected Map load() { + return Collections.emptyMap(); + } + }; + + this.anames = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 3002350300761L; + + @Override + protected Map load() { + return connectorRestClient.buildObjectClassInfo( + connectorRestClient.read(resource.getConnector()), false).stream(). + map(ConnIdObjectClassTO::getAttributes). + flatMap(List::stream). + collect(Collectors.toMap(PlainSchemaTO::getKey, Function.identity(), + (schema1, schema2) -> schema1)); + } + }; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java index 91ed3189a49..253b58919aa 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java @@ -22,11 +22,15 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; -public final class GroupSearchPanel extends AbstractSearchPanel { +public class GroupSearchPanel extends AbstractSearchPanel { private static final long serialVersionUID = 5757183539269316263L; @@ -44,7 +48,7 @@ public GroupSearchPanel build(final String id) { } } - private GroupSearchPanel(final String id, final GroupSearchPanel.Builder builder) { + protected GroupSearchPanel(final String id, final GroupSearchPanel.Builder builder) { super(id, AnyTypeKind.GROUP, builder); } @@ -66,13 +70,25 @@ protected List load() { } }; - this.groupNames = new LoadableDetachableModel>() { + this.groupNames = new LoadableDetachableModel>() { private static final long serialVersionUID = 5275935387613157437L; @Override - protected Map load() { - return Collections.emptyMap(); + protected List load() { + return Collections.emptyList(); + } + }; + + this.anames = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 5275935387613157437L; + + @Override + protected Map load() { + return schemaRestClient.getSchemas( + SchemaType.PLAIN, null, anyTypeRestClient.read(type).getClasses().toArray(new String[] {})). + stream().collect(Collectors.toMap(schema -> schema.getKey(), Function.identity())); } }; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java index 8d20abc2654..69f2d153c5c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java @@ -21,8 +21,7 @@ import java.io.Serializable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.commons.lang3.builder.ToStringBuilder; public final class SearchClause implements Serializable { @@ -43,7 +42,8 @@ public enum Type { ROLE_MEMBERSHIP, PRIVILEGE, RESOURCE, - RELATIONSHIP; + RELATIONSHIP, + CUSTOM; } @@ -116,17 +116,45 @@ public void setValue(final String value) { } @Override - public boolean equals(final Object obj) { - return EqualsBuilder.reflectionEquals(this, obj); + public int hashCode() { + return new HashCodeBuilder(). + append(operator). + append(type). + append(property). + append(comparator). + append(value). + build(); } @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final SearchClause other = (SearchClause) obj; + return new EqualsBuilder(). + append(operator, other.operator). + append(type, other.type). + append(property, other.property). + append(comparator, other.comparator). + append(value, other.value). + build(); } @Override public String toString() { - return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + return new ToStringBuilder(this). + append(operator). + append(type). + append(property). + append(comparator). + append(value). + build(); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java index 01566191fe1..c79169bb155 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java @@ -21,15 +21,22 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle; import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig; import java.io.Serializable; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.FastDateFormat; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.SchemaUtils; import org.apache.syncope.client.console.panels.search.SearchClause.Comparator; import org.apache.syncope.client.console.panels.search.SearchClause.Operator; import org.apache.syncope.client.console.panels.search.SearchClause.Type; @@ -37,13 +44,17 @@ import org.apache.syncope.client.console.rest.RelationshipTypeRestClient; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateTimeFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSpinnerFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.to.RelationshipTypeTO; +import org.apache.syncope.common.lib.types.AttrSchemaType; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; @@ -51,12 +62,13 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.AjaxCallListener; import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.event.Broadcast; import org.apache.wicket.event.IEventSink; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.list.ListItem; @@ -70,15 +82,69 @@ public class SearchClausePanel extends FieldPanel { private static final long serialVersionUID = -527351923968737757L; + protected static final AttributeModifier PREVENT_DEFAULT_RETURN = AttributeModifier.replace( + "onkeydown", + Model.of("if (event.keyCode == 13) { event.preventDefault(); }")); + + protected static final Consumer AJAX_SUBMIT_ON_RETURN = + attributes -> attributes.getAjaxCallListeners().add(new AjaxCallListener() { + + private static final long serialVersionUID = 7160235486520935153L; + + @Override + public CharSequence getPrecondition(final Component component) { + return "return (Wicket.Event.keyCode(attrs.event) == 13);"; + } + }); + + public interface Customizer extends Serializable { + + default IChoiceRenderer typeRenderer() { + return new ChoiceRenderer<>(); + } + + default List comparators() { + return Collections.emptyList(); + } + + default String comparatorDisplayValue(Comparator object) { + return object.toString(); + } + + default Optional comparatorGetObject(String id) { + return Optional.empty(); + } + + default List properties() { + return Collections.emptyList(); + } + + default void setFieldAccess( + FieldPanel value, + AjaxTextFieldPanel property, + LoadableDetachableModel> properties) { + + value.setEnabled(true); + value.setModelObject(StringUtils.EMPTY); + property.setEnabled(true); + + // reload properties list + properties.detach(); + property.setChoices(properties.getObject()); + } + } + private final boolean required; private final IModel> types; + private final Customizer customizer; + private final IModel> anames; - private final IModel> dnames; + private final IModel> dnames; - private final Pair>, Integer> groupInfo; + private final Pair>, IModel> groupInfo; private final IModel> roleNames; @@ -96,10 +162,13 @@ public class SearchClausePanel extends FieldPanel { private final Fragment searchButtonFragment; - private final AjaxSubmitLink searchButton; + private final AjaxLink searchButton; private IEventSink resultContainer; + @SuppressWarnings({ "rawtypes", "unchecked" }) + private FieldPanel value; + private final GroupRestClient groupRestClient = new GroupRestClient(); public SearchClausePanel( @@ -108,9 +177,10 @@ public SearchClausePanel( final Model clause, final boolean required, final IModel> types, + final Customizer customizer, final IModel> anames, - final IModel> dnames, - final Pair>, Integer> groupInfo, + final IModel> dnames, + final Pair>, IModel> groupInfo, final IModel> roleNames, final IModel> privilegeNames, final IModel> resourceNames) { @@ -121,6 +191,7 @@ public SearchClausePanel( this.required = required; this.types = types; + this.customizer = customizer; this.anames = anames; this.dnames = dnames; this.groupInfo = groupInfo; @@ -128,14 +199,14 @@ public SearchClausePanel( this.privilegeNames = privilegeNames; this.resourceNames = resourceNames; - searchButton = new AjaxSubmitLink("search") { + searchButton = new AjaxLink("search") { private static final long serialVersionUID = 5538299138211283825L; @Override - protected void onSubmit(final AjaxRequestTarget target) { + public void onClick(final AjaxRequestTarget target) { if (resultContainer == null) { - send(this, Broadcast.BUBBLE, new SearchEvent(target)); + send(SearchClausePanel.this, Broadcast.BUBBLE, new SearchEvent(target)); } else { send(resultContainer, Broadcast.EXACT, new SearchEvent(target)); } @@ -152,7 +223,6 @@ protected void onSubmit(final AjaxRequestTarget target) { private static final long serialVersionUID = -8204140666393922700L; }; - add(field); comparators = new LoadableDetachableModel>() { @@ -162,7 +232,7 @@ protected void onSubmit(final AjaxRequestTarget target) { @Override protected List load() { if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) { - return Collections.emptyList(); + return Collections.emptyList(); } switch (field.getModel().getObject().getType()) { @@ -184,8 +254,12 @@ protected List load() { SearchClause.Comparator.IS_NULL, SearchClause.Comparator.EQUALS, SearchClause.Comparator.NOT_EQUALS); + + case CUSTOM: + return customizer.comparators(); + default: - return Collections.emptyList(); + return Collections.emptyList(); } } }; @@ -197,46 +271,41 @@ protected List load() { @Override protected List load() { if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) { - return Collections.emptyList(); + return Collections.emptyList(); } switch (field.getModel().getObject().getType()) { case ATTRIBUTE: - final List names = new ArrayList<>(dnames.getObject()); + List names = new ArrayList<>(dnames.getObject().keySet()); if (anames != null && anames.getObject() != null && !anames.getObject().isEmpty()) { names.addAll(anames.getObject().keySet()); } - Collections.sort(names); - return names; + return names.stream().sorted().collect(Collectors.toList()); case GROUP_MEMBERSHIP: - final List groups = groupInfo.getLeft().getObject().values(). - stream().collect(Collectors.toList()); - Collections.sort(groups); - return groups; + return groupInfo.getLeft().getObject(); case ROLE_MEMBERSHIP: - final List roles = new ArrayList<>(roleNames.getObject()); - Collections.sort(roles); - return roles; + return roleNames.getObject().stream(). + sorted().collect(Collectors.toList()); case PRIVILEGE: - final List privileges = new ArrayList<>(privilegeNames.getObject()); - Collections.sort(privileges); - return privileges; + return privilegeNames.getObject().stream(). + sorted().collect(Collectors.toList()); case RESOURCE: - final List resources = new ArrayList<>(resourceNames.getObject()); - Collections.sort(resources); - return resources; + return resourceNames.getObject().stream(). + sorted().collect(Collectors.toList()); case RELATIONSHIP: - final List relations = new RelationshipTypeRestClient().list().stream(). - map(RelationshipTypeTO::getKey).collect(Collectors.toList()); - return relations; + return new RelationshipTypeRestClient().list().stream(). + map(RelationshipTypeTO::getKey).sorted().collect(Collectors.toList()); + + case CUSTOM: + return customizer.properties(); default: - return Collections.emptyList(); + return Collections.emptyList(); } } }; @@ -247,11 +316,8 @@ public void enableSearch(final IEventSink resultContainer) { this.searchButton.setEnabled(true); this.searchButton.setVisible(true); - field.add(AttributeModifier.replace( - "onkeydown", - Model.of("if(event.keyCode == 13) {event.preventDefault();}"))); - - field.add(new AjaxEventBehavior("keydown") { + field.add(PREVENT_DEFAULT_RETURN); + field.add(new AjaxEventBehavior(Constants.ON_KEYDOWN) { private static final long serialVersionUID = -7133385027739964990L; @@ -267,16 +333,7 @@ protected void onEvent(final AjaxRequestTarget target) { @Override protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { super.updateAjaxAttributes(attributes); - - attributes.getAjaxCallListeners().add(new AjaxCallListener() { - - private static final long serialVersionUID = 7160235486520935153L; - - @Override - public CharSequence getPrecondition(final Component component) { - return "if (Wicket.Event.keyCode(attrs.event) == 13) { return true; } else { return false; }"; - } - }); + AJAX_SUBMIT_ON_RETURN.accept(attributes); } }); } @@ -311,14 +368,15 @@ public final MarkupContainer add(final Component... childs) { } @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public FieldPanel settingsDependingComponents() { - final SearchClause searchClause = this.clause.getObject(); + SearchClause searchClause = this.clause.getObject(); - final WebMarkupContainer operatorContainer = new WebMarkupContainer("operatorContainer"); + WebMarkupContainer operatorContainer = new WebMarkupContainer("operatorContainer"); operatorContainer.setOutputMarkupId(true); field.add(operatorContainer); - final BootstrapToggleConfig config = new BootstrapToggleConfig(). + BootstrapToggleConfig config = new BootstrapToggleConfig(). withOnStyle(BootstrapToggleConfig.Style.info). withOffStyle(BootstrapToggleConfig.Style.warning). withSize(BootstrapToggleConfig.Size.mini); @@ -352,10 +410,10 @@ protected IModel getOnLabel() { @Override protected CheckBox newCheckBox(final String id, final IModel model) { - final CheckBox checkBox = super.newCheckBox(id, model); + CheckBox checkBox = super.newCheckBox(id, model); checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 18266219802290L; @Override protected void onUpdate(final AjaxRequestTarget target) { @@ -371,80 +429,71 @@ protected void onUpdate(final AjaxRequestTarget target) { operatorContainer.add(searchButtonFragment); } - final AjaxTextFieldPanel property = new AjaxTextFieldPanel( - "property", - "property", - new PropertyModel<>(searchClause, "property"), - false); + AjaxTextFieldPanel property = new AjaxTextFieldPanel( + "property", "property", new PropertyModel<>(searchClause, "property"), true); property.hideLabel().setOutputMarkupId(true).setEnabled(true); property.setChoices(properties.getObject()); field.add(property); - property.getField().add(AttributeModifier.replace( - "onkeydown", - Model.of("if(event.keyCode == 13) { event.preventDefault(); }"))); - - property.getField().add(new IndicatorAjaxEventBehavior("onkeyup") { + property.getField().add(PREVENT_DEFAULT_RETURN); + property.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYUP) { - private static final long serialVersionUID = -7866120562087857309L; + private static final long serialVersionUID = -957948639666058749L; @Override protected void onEvent(final AjaxRequestTarget target) { - if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) { - return; - } + if (field.getModel().getObject() != null + && field.getModel().getObject().getType() == Type.GROUP_MEMBERSHIP) { - if (field.getModel().getObject().getType() == Type.GROUP_MEMBERSHIP) { String[] inputAsArray = property.getField().getInputAsArray(); - - if (StringUtils.isBlank(property.getField().getInput()) - || inputAsArray.length == 0) { + if (ArrayUtils.isEmpty(inputAsArray)) { property.setChoices(properties.getObject()); - } else { - String inputValue = (inputAsArray.length > 1 && inputAsArray[1] != null) + } else if (groupInfo.getRight().getObject() > Constants.MAX_GROUP_LIST_SIZE) { + String inputValue = inputAsArray.length > 1 && inputAsArray[1] != null ? inputAsArray[1] : property.getField().getInput(); - inputValue = (inputValue.startsWith("*") && !inputValue.endsWith("*")) - ? inputValue + "*" - : (!inputValue.startsWith("*") && inputValue.endsWith("*")) - ? "*" + inputValue - : (inputValue.startsWith("*") && inputValue.endsWith("*") - ? inputValue : "*" + inputValue + "*"); - - if (groupInfo.getRight() > AnyObjectSearchPanel.MAX_GROUP_LIST_CARDINALITY) { - List filteredGroups = groupRestClient.search("/", - SyncopeClient.getGroupSearchConditionBuilder(). - is("name").equalToIgnoreCase(inputValue). - query(), - 1, - AnyObjectSearchPanel.MAX_GROUP_LIST_CARDINALITY, - new SortParam<>("name", true), - null); - List names = filteredGroups.stream(). - map(GroupTO::getName).collect(Collectors.toList()); - Collections.sort(names); - property.setChoices(names); + if (!inputValue.startsWith("*")) { + inputValue = "*" + inputValue; + } + if (!inputValue.endsWith("*")) { + inputValue += "*"; } + property.setChoices(groupRestClient.search( + SyncopeConstants.ROOT_REALM, + SyncopeClient.getGroupSearchConditionBuilder(). + is("name").equalToIgnoreCase(inputValue). + query(), + 1, + Constants.MAX_GROUP_LIST_SIZE, + new SortParam<>("name", true), + null).stream().map(GroupTO::getName).collect(Collectors.toList())); } } } + }); + property.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) { + + private static final long serialVersionUID = -7133385027739964990L; @Override - protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { - super.updateAjaxAttributes(attributes); - attributes.getAjaxCallListeners().clear(); + protected void onEvent(final AjaxRequestTarget target) { + target.focusComponent(null); + property.getField().inputChanged(); + property.getField().validate(); + if (property.getField().isValid()) { + property.getField().valid(); + property.getField().updateModel(); + } } - }, new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - - private static final long serialVersionUID = -1107858522700306810L; @Override - protected void onUpdate(final AjaxRequestTarget target) { - + protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { + super.updateAjaxAttributes(attributes); + AJAX_SUBMIT_ON_RETURN.accept(attributes); } }); - final AjaxDropDownChoicePanel comparator = new AjaxDropDownChoicePanel<>( + AjaxDropDownChoicePanel comparator = new AjaxDropDownChoicePanel<>( "comparator", "comparator", new PropertyModel<>(searchClause, "comparator")); comparator.setChoices(comparators); comparator.setNullValid(false).hideLabel().setOutputMarkupId(true); @@ -452,49 +501,26 @@ protected void onUpdate(final AjaxRequestTarget target) { comparator.setChoiceRenderer(getComparatorRender(field.getModel())); field.add(comparator); - final AjaxTextFieldPanel value = new AjaxTextFieldPanel( - "value", "value", new PropertyModel<>(searchClause, "value"), false); - value.hideLabel().setOutputMarkupId(true); - field.add(value); - - value.getField().add(AttributeModifier.replace( - "onkeydown", - Model.of("if(event.keyCode == 13) {event.preventDefault();}"))); + renderSearchValueField(searchClause, property); + field.addOrReplace(value); - value.getField().add(new IndicatorAjaxEventBehavior("keydown") { - - private static final long serialVersionUID = -7133385027739964990L; + property.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - @Override - protected void onEvent(final AjaxRequestTarget target) { - target.focusComponent(null); - value.getField().inputChanged(); - value.getField().validate(); - if (value.getField().isValid()) { - value.getField().valid(); - value.getField().updateModel(); - } - } + private static final long serialVersionUID = -1107858522700306810L; @Override - protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { - super.updateAjaxAttributes(attributes); - - attributes.getAjaxCallListeners().add(new AjaxCallListener() { - - private static final long serialVersionUID = 7160235486520935153L; - - @Override - public CharSequence getPrecondition(final Component component) { - return "if (Wicket.Event.keyCode(attrs.event) == 13) { return true; } else { return false; }"; - } - }); + protected void onUpdate(final AjaxRequestTarget target) { + renderSearchValueField(searchClause, property); + field.addOrReplace(value); + target.add(value); } }); - final AjaxDropDownChoicePanel type = new AjaxDropDownChoicePanel<>( + AjaxDropDownChoicePanel type = new AjaxDropDownChoicePanel<>( "type", "type", new PropertyModel<>(searchClause, "type")); - type.setChoices(types).hideLabel().setRequired(required).setOutputMarkupId(true); + + type.setChoices(types).setChoiceRenderer(customizer.typeRenderer()). + hideLabel().setRequired(required).setOutputMarkupId(true); type.setNullValid(false); type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { @@ -528,8 +554,10 @@ protected void onUpdate(final AjaxRequestTarget target) { protected void onUpdate(final AjaxRequestTarget target) { if (type.getModelObject() == SearchClause.Type.ATTRIBUTE || type.getModelObject() == SearchClause.Type.RELATIONSHIP) { + if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) { + value.setModelObject(StringUtils.EMPTY); value.setEnabled(false); } else { @@ -541,7 +569,7 @@ protected void onUpdate(final AjaxRequestTarget target) { if (type.getModelObject() == SearchClause.Type.RELATIONSHIP) { property.setEnabled(true); - final SearchClause searchClause = new SearchClause(); + SearchClause searchClause = new SearchClause(); searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString())); searchClause.setComparator(comparator.getModelObject()); SearchClausePanel.this.clause.setObject(searchClause); @@ -556,11 +584,12 @@ protected void onUpdate(final AjaxRequestTarget target) { return this; } + @SuppressWarnings({ "rawtypes", "unchecked" }) private void setFieldAccess( final Type type, final AjaxTextFieldPanel property, final FieldPanel comparator, - final FieldPanel value) { + final FieldPanel value) { if (type != null) { property.setEnabled(true); @@ -637,6 +666,10 @@ private void setFieldAccess( property.setChoices(properties.getObject()); break; + case CUSTOM: + customizer.setFieldAccess(value, property, properties); + break; + default: break; } @@ -650,7 +683,6 @@ private IChoiceRenderer getComparatorRender(final IMode @Override public Object getDisplayValue(final SearchClause.Comparator object) { - if (clause == null || clause.getObject() == null || clause.getObject().getType() == null) { return object.toString(); } @@ -696,6 +728,7 @@ public Object getDisplayValue(final SearchClause.Comparator object) { display = StringUtils.EMPTY; } break; + case GROUP_MEMBERSHIP: switch (object) { case EQUALS: @@ -710,6 +743,7 @@ public Object getDisplayValue(final SearchClause.Comparator object) { display = StringUtils.EMPTY; } break; + case GROUP_MEMBER: switch (object) { case EQUALS: @@ -724,6 +758,7 @@ public Object getDisplayValue(final SearchClause.Comparator object) { display = StringUtils.EMPTY; } break; + case ROLE_MEMBERSHIP: case PRIVILEGE: case RESOURCE: @@ -740,6 +775,7 @@ public Object getDisplayValue(final SearchClause.Comparator object) { display = StringUtils.EMPTY; } break; + case RELATIONSHIP: switch (object) { case IS_NOT_NULL: @@ -762,6 +798,11 @@ public Object getDisplayValue(final SearchClause.Comparator object) { display = StringUtils.EMPTY; } break; + + case CUSTOM: + display = customizer.comparatorDisplayValue(object); + break; + default: display = object.toString(); } @@ -781,105 +822,218 @@ public SearchClause.Comparator getObject( return SearchClause.Comparator.EQUALS; } - final SearchClause.Comparator res; + final SearchClause.Comparator comparator; switch (id) { case "HAS": case "IN": case "WITH": - res = SearchClause.Comparator.EQUALS; + comparator = SearchClause.Comparator.EQUALS; break; + case "HAS NOT": case "NOT IN": case "WITHOUT": - res = SearchClause.Comparator.NOT_EQUALS; + comparator = SearchClause.Comparator.NOT_EQUALS; break; + case "NULL": case "NOT EXIST": - res = SearchClause.Comparator.IS_NULL; + comparator = SearchClause.Comparator.IS_NULL; break; + case "NOT NULL": case "EXIST": - res = SearchClause.Comparator.IS_NOT_NULL; + comparator = SearchClause.Comparator.IS_NOT_NULL; break; + case "==": - res = SearchClause.Comparator.EQUALS; + comparator = SearchClause.Comparator.EQUALS; break; + case "!=": - res = SearchClause.Comparator.NOT_EQUALS; + comparator = SearchClause.Comparator.NOT_EQUALS; break; + case "<": - res = SearchClause.Comparator.LESS_THAN; + comparator = SearchClause.Comparator.LESS_THAN; break; + case "<=": - res = SearchClause.Comparator.LESS_OR_EQUALS; + comparator = SearchClause.Comparator.LESS_OR_EQUALS; break; + case ">": - res = SearchClause.Comparator.GREATER_THAN; + comparator = SearchClause.Comparator.GREATER_THAN; break; + case ">=": - res = SearchClause.Comparator.GREATER_OR_EQUALS; + comparator = SearchClause.Comparator.GREATER_OR_EQUALS; break; + default: // EQUALS to be used as default value - res = SearchClause.Comparator.EQUALS; + comparator = customizer.comparatorGetObject(id).orElse(SearchClause.Comparator.EQUALS); break; } - return res; + + return comparator; } }; } - @Override - public FieldPanel clone() { - final SearchClausePanel panel = new SearchClausePanel( - getId(), name, null, required, types, anames, dnames, groupInfo, - roleNames, privilegeNames, resourceNames); - panel.setReadOnly(this.isReadOnly()); - panel.setRequired(this.isRequired()); - if (searchButton.isEnabled()) { - panel.enableSearch(resultContainer); + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void renderSearchValueField( + final SearchClause searchClause, + final AjaxTextFieldPanel property) { + + PlainSchemaTO plainSchemaTO = anames.getObject().get(property.getModelObject()); + if (plainSchemaTO == null) { + PlainSchemaTO defaultPlainTO = new PlainSchemaTO(); + defaultPlainTO.setType(AttrSchemaType.String); + plainSchemaTO = dnames.getObject().getOrDefault(property.getModelObject(), defaultPlainTO); } - return panel; - } - private class DefaultChoiceRender implements IChoiceRenderer { + switch (plainSchemaTO.getType()) { + case Boolean: + value = new AjaxTextFieldPanel( + "value", + "value", + new PropertyModel<>(searchClause, "value"), + true); + ((AjaxTextFieldPanel) value).setChoices(Arrays.asList("true", "false")); - private static final long serialVersionUID = -8034248752951761058L; + break; - @Override - public Object getDisplayValue(final String object) { - return object; - } + case Date: + FastDateFormat fdf = FastDateFormat.getInstance( + plainSchemaTO.getConversionPattern() == null + ? SyncopeConstants.DEFAULT_DATE_PATTERN + : plainSchemaTO.getConversionPattern()); - @Override - public String getIdValue(final String object, final int index) { - return object; - } + value = new AjaxDateTimeFieldPanel( + "value", + "value", + new PropertyModel(searchClause, "value") { - @Override - public String getObject(final String id, final IModel> choices) { - return id; - } - } + private static final long serialVersionUID = 1177692285167186690L; + + @Override + public Object getObject() { + String date = (String) super.getObject(); + try { + return date != null ? fdf.parse(date) : null; + } catch (ParseException ex) { + LOG.error("Date parse error {}", date, ex); + } + return null; + } - private class GroupChoiceRender extends DefaultChoiceRender { + @Override + public void setObject(final Object object) { + if (object instanceof Date) { + String valueDate = fdf.format(object); + super.setObject(valueDate); + } else { + super.setObject(object); + } + } + }, SyncopeConstants.DEFAULT_DATE_PATTERN); + break; + + case Enum: + value = new AjaxDropDownChoicePanel<>( + "value", + "value", + new PropertyModel(searchClause, "value"), + true); + ((AjaxDropDownChoicePanel) value).setChoices(SchemaUtils.getEnumeratedValues(plainSchemaTO)); + + if (StringUtils.isNotBlank(plainSchemaTO.getEnumerationKeys())) { + Map valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchemaTO); + ((AjaxDropDownChoicePanel) value).setChoiceRenderer(new IChoiceRenderer() { + + private static final long serialVersionUID = -3724971416312135885L; + + @Override + public String getDisplayValue(final String value) { + return valueMap.get(value) == null ? value : valueMap.get(value); + } - private static final long serialVersionUID = -8034248752951761058L; + @Override + public String getIdValue(final String value, final int i) { + return value; + } - @Override - public String getIdValue(final String object, final int index) { - return object; + @Override + public String getObject( + final String id, final IModel> choices) { + return id; + } + }); + } + break; + + case Long: + value = new AjaxSpinnerFieldPanel.Builder().enableOnChange().build( + "value", + "Value", + Long.class, + new PropertyModel(searchClause, "value")); + + value.add(new AttributeModifier("class", "field value search-spinner")); + break; + + case Double: + value = new AjaxSpinnerFieldPanel.Builder().enableOnChange().step(0.1).build( + "value", + "value", + Double.class, + new PropertyModel(searchClause, "value")); + value.add(new AttributeModifier("class", "field value search-spinner")); + break; + + default: + value = new AjaxTextFieldPanel( + "value", "value", new PropertyModel<>(searchClause, "value"), true); + break; } - @Override - public String getObject(final String id, final IModel> choices) { - return id; - } + value.hideLabel().setOutputMarkupId(true); + value.getField().add(PREVENT_DEFAULT_RETURN); + value.getField().add(new IndicatorAjaxEventBehavior(Constants.ON_KEYDOWN) { + + private static final long serialVersionUID = -7133385027739964990L; + + @Override + protected void onEvent(final AjaxRequestTarget target) { + target.focusComponent(null); + value.getField().inputChanged(); + value.getField().validate(); + if (value.getField().isValid()) { + value.getField().valid(); + value.getField().updateModel(); + } + } + + @Override + protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { + super.updateAjaxAttributes(attributes); + AJAX_SUBMIT_ON_RETURN.accept(attributes); + } + }); + } - @Override - public Object getDisplayValue(final String object) { - return groupInfo.getLeft().getObject().get(object); + @Override + public FieldPanel clone() { + SearchClausePanel panel = new SearchClausePanel( + getId(), name, null, required, types, customizer, anames, dnames, groupInfo, + roleNames, privilegeNames, resourceNames); + panel.setReadOnly(this.isReadOnly()); + panel.setRequired(this.isRequired()); + if (searchButton.isEnabled()) { + panel.enableSearch(resultContainer); } + return panel; } public static class SearchEvent implements Serializable { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java index 9bef61cb4ac..399d5f799ca 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java @@ -21,10 +21,14 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.cxf.jaxrs.ext.search.ConditionType; @@ -40,7 +44,6 @@ import org.apache.syncope.common.lib.search.SyncopeProperty; import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder; import org.apache.syncope.common.lib.to.PlainSchemaTO; -import org.apache.syncope.common.lib.types.AttrSchemaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,19 +51,29 @@ public final class SearchUtils implements Serializable { private static final long serialVersionUID = 398381905376547084L; + public static final Function NO_CUSTOM_CONDITION = clause -> null; + private static final Logger LOG = LoggerFactory.getLogger(SearchUtils.class); + private static final BidiMap ENCODINGS = new DualHashBidiMap() { + + private static final long serialVersionUID = 5636572627689425575L; + + { + put(",", "%252C"); + put(";", "%253B"); + put("+", "%252B"); + } + }; + private static Pattern getTypeConditionPattern(final String type) { return Pattern.compile(String.format(";\\$type==%s|\\$type==%s;", type, type)); } public static Map> getSearchClauses(final Map fiql) { - Map> clauses = new HashMap<>(); - for (Map.Entry entry : fiql.entrySet()) { - clauses.put(entry.getKey(), getSearchClauses( - entry.getValue().replaceAll(getTypeConditionPattern(entry.getKey()).pattern(), ""))); - } - return clauses; + return fiql.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> getSearchClauses(e.getValue().replaceAll(getTypeConditionPattern(e.getKey()).pattern(), "")))); } public static List getSearchClauses(final String fiql) { @@ -92,7 +105,7 @@ private static List getSearchClauses(final SearchCondition getCompoundSearchClauses(final SearchCondition sc) { List clauses = new ArrayList<>(); - for (SearchCondition searchCondition : sc.getSearchConditions()) { + sc.getSearchConditions().forEach(searchCondition -> { if (searchCondition.getStatement() == null) { clauses.addAll(getCompoundSearchClauses(searchCondition)); } else { @@ -105,7 +118,7 @@ private static List getCompoundSearchClauses(final SearchCondition } clauses.add(clause); } - } + }); return clauses; } @@ -115,7 +128,9 @@ private static SearchClause getPrimitiveSearchClause(final SearchCondition s.replace(v, ENCODINGS.getKey(v))); clause.setValue(value); LOG.debug("Condition: " + sc.getCondition()); @@ -141,6 +156,9 @@ private static SearchClause getPrimitiveSearchClause(final SearchCondition clauses, final AbstractFiqlSearchConditionBuilder builder) { - return buildFIQL(clauses, builder, Collections.emptyMap()); + public static String buildFIQL( + final List clauses, + final AbstractFiqlSearchConditionBuilder builder) { + + return buildFIQL(clauses, builder, Collections.emptyMap(), NO_CUSTOM_CONDITION); } public static String buildFIQL( final List clauses, - final AbstractFiqlSearchConditionBuilder builder, - final Map availableSchemaTypes) { - LOG.debug("Generating FIQL from List: {}", clauses); + final AbstractFiqlSearchConditionBuilder builder, + final Map availableSchemaTypes, + final Function customCondition) { + + LOG.debug("Generating FIQL from {}", clauses); CompleteCondition prevCondition; CompleteCondition condition = null; @@ -219,20 +242,23 @@ public static String buildFIQL( if (clause.getType() != null) { String value = clause.getValue() == null ? null - : clause.getValue().replace(",", "%252C").replace(";", "%253B"); + : ENCODINGS.keySet().stream(). + reduce(clause.getValue(), (s, k) -> s.replace(k, ENCODINGS.get(k))); switch (clause.getType()) { case GROUP_MEMBER: - switch (clause.getComparator()) { - case EQUALS: - condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value); - break; + if (builder instanceof GroupFiqlSearchConditionBuilder) { + switch (clause.getComparator()) { + case EQUALS: + condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value); + break; - case NOT_EQUALS: - condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value); - break; + case NOT_EQUALS: + condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value); + break; - default: + default: + } } break; @@ -264,14 +290,26 @@ public static String buildFIQL( if (StringUtils.isNotBlank(clause.getProperty())) { boolean isLong = false; boolean isDouble = false; + boolean isBoolean = false; if (availableSchemaTypes.get(clause.getProperty()) != null) { - isLong = availableSchemaTypes.get(clause.getProperty()).getType() - == AttrSchemaType.Long; - isDouble = availableSchemaTypes.get(clause.getProperty()).getType() - == AttrSchemaType.Double; + switch (availableSchemaTypes.get(clause.getProperty()).getType()) { + case Long: + isLong = true; + break; + + case Double: + isDouble = true; + break; + + case Boolean: + isBoolean = true; + break; + + default: + } } - SyncopeProperty property = builder.is(clause.getProperty()); + SyncopeProperty property = builder.is(clause.getProperty()); switch (clause.getComparator()) { case IS_NULL: condition = builder.isNull(clause.getProperty()); @@ -314,7 +352,14 @@ public static String buildFIQL( break; case NOT_EQUALS: - condition = property.notEqualTolIgnoreCase(value); + condition = isLong + ? property.notEqualTo(NumberUtils.toLong(value)) + : isDouble + ? property.notEqualTo(NumberUtils.toDouble(value)) + : isBoolean + ? property.notEqualTo(BooleanUtils.toStringTrueFalse( + BooleanUtils.toBoolean(value))) + : property.notEqualTolIgnoreCase(value); break; case EQUALS: @@ -322,8 +367,12 @@ public static String buildFIQL( ? property.equalTo(NumberUtils.toLong(value)) : isDouble ? property.equalTo(NumberUtils.toDouble(value)) - : property.equalToIgnoreCase(value); + : isBoolean + ? property.equalTo(BooleanUtils.toStringTrueFalse( + BooleanUtils.toBoolean(value))) + : property.equalToIgnoreCase(value); break; + default: condition = property.equalToIgnoreCase(value); break; @@ -332,7 +381,9 @@ public static String buildFIQL( break; case ROLE_MEMBERSHIP: - if (StringUtils.isNotBlank(clause.getProperty())) { + if (StringUtils.isNotBlank(clause.getProperty()) + && builder instanceof UserFiqlSearchConditionBuilder) { + switch (clause.getComparator()) { case EQUALS: condition = ((UserFiqlSearchConditionBuilder) builder). @@ -349,7 +400,9 @@ public static String buildFIQL( break; case PRIVILEGE: - if (StringUtils.isNotBlank(clause.getProperty())) { + if (StringUtils.isNotBlank(clause.getProperty()) + && builder instanceof UserFiqlSearchConditionBuilder) { + switch (clause.getComparator()) { case EQUALS: condition = ((UserFiqlSearchConditionBuilder) builder). @@ -413,6 +466,10 @@ public static String buildFIQL( } break; + case CUSTOM: + condition = customCondition.apply(clause); + break; + default: break; } @@ -420,10 +477,10 @@ public static String buildFIQL( if (notTheFirst) { if (clause.getOperator() == SearchClause.Operator.AND) { - condition = builder.and(prevCondition, condition); + condition = builder.and(condition, prevCondition); } if (clause.getOperator() == SearchClause.Operator.OR) { - condition = builder.or(prevCondition, condition); + condition = builder.or(condition, prevCondition); } } @@ -439,5 +496,4 @@ public static String buildFIQL( private SearchUtils() { // private constructor for static utility class } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java index 7dd81b3816b..89cc62ead3b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java @@ -28,13 +28,13 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; -public final class UserSearchPanel extends AnyObjectSearchPanel { +public class UserSearchPanel extends AnyObjectSearchPanel { private static final long serialVersionUID = -1769527800450203738L; - private final RoleRestClient roleRestClient = new RoleRestClient(); + protected final RoleRestClient roleRestClient = new RoleRestClient(); - private final ApplicationRestClient applicationRestClient = new ApplicationRestClient(); + protected final ApplicationRestClient applicationRestClient = new ApplicationRestClient(); public static class Builder extends AnyObjectSearchPanel.Builder { @@ -50,7 +50,7 @@ public UserSearchPanel build(final String id) { } } - private UserSearchPanel(final String id, final Builder builder) { + protected UserSearchPanel(final String id, final Builder builder) { super(id, AnyTypeKind.USER, builder); } @@ -92,5 +92,4 @@ protected List getAvailableTypes() { result.add(SearchClause.Type.RELATIONSHIP); return result; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java index aae91147d3c..2817a4d2fc8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java @@ -25,7 +25,6 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -47,7 +46,6 @@ import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; -import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; @@ -65,7 +63,7 @@ public abstract class PolicyDirectoryPanel private static final long serialVersionUID = 4984337552918213290L; - protected final BaseModal ruleCompositionModal = new BaseModal("outer") { + protected final BaseModal ruleCompositionModal = new BaseModal(Constants.OUTER) { private static final long serialVersionUID = 389935548143327858L; @@ -77,7 +75,7 @@ protected void onConfigure() { }; - protected final BaseModal policySpecModal = new BaseModal<>("outer"); + protected final BaseModal policySpecModal = new BaseModal<>(Constants.OUTER); private final PolicyType type; @@ -95,20 +93,13 @@ public PolicyDirectoryPanel(final String id, final PolicyType policyType, final setWindowClosedReloadCallback(policySpecModal); addOuterObject(policySpecModal); - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487129L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - modal.show(false); - } - }); - - setFooterVisibility(true); modal.addSubmitButton(); modal.size(Modal.Size.Large); + modal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); + }); + setFooterVisibility(true); disableCheckBoxes(); } @@ -118,7 +109,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("description", this), "description", "description")); columns.add(new CollectionPropertyColumn<>( @@ -173,12 +164,11 @@ public void onClick(final AjaxRequestTarget target, final PolicyTO ignore) { final T policyTO = model.getObject(); try { restClient.delete(type, policyTO.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting {}", policyTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -194,7 +184,7 @@ protected void addCustomActions(final ActionsPanel panel, final IModel mod } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java index 6bad2d495b2..393bb9660ab 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -31,6 +30,7 @@ import org.apache.syncope.client.console.panels.WizardModalPanel; import org.apache.syncope.client.console.rest.PolicyRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; @@ -43,8 +43,10 @@ import org.apache.syncope.common.lib.policy.PolicyTO; import org.apache.syncope.common.lib.policy.AccountPolicyTO; import org.apache.syncope.common.lib.policy.PasswordPolicyTO; +import org.apache.syncope.common.lib.policy.PropagationPolicyTO; import org.apache.syncope.common.lib.policy.ProvisioningPolicyTO; import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.types.BackOffStrategy; import org.apache.syncope.common.lib.types.ConflictResolutionAction; import org.apache.syncope.common.lib.types.PolicyType; import org.apache.wicket.Component; @@ -52,6 +54,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.util.ListModel; @@ -60,6 +63,42 @@ public class PolicyModalPanelBuilder extends AbstractModalPa private static final long serialVersionUID = 5945391813567245081L; + private static class BackOffParamsModel implements IModel { + + private static final long serialVersionUID = 28839546672164L; + + private final PropertyModel backOffParamsModel; + + private final int index; + + BackOffParamsModel(final PropertyModel backOffParamsModel, final int index) { + this.backOffParamsModel = backOffParamsModel; + this.index = index; + } + + @SuppressWarnings("unchecked") + @Override + public N getObject() { + String[] split = backOffParamsModel.getObject().split(";"); + if (index >= split.length) { + return null; + } + + return index == 2 + ? (N) Double.valueOf(backOffParamsModel.getObject().split(";")[index]) + : (N) Long.valueOf(backOffParamsModel.getObject().split(";")[index]); + } + + @Override + public void setObject(final N object) { + String[] split = backOffParamsModel.getObject().split(";"); + if (index < split.length) { + split[index] = object.toString(); + backOffParamsModel.setObject(Arrays.stream(split).collect(Collectors.joining(";"))); + } + } + } + private final BaseModal modal; private final PolicyType type; @@ -101,7 +140,7 @@ public Profile(final T policyTO, final BaseModal modal, final PageReference p this.policyTO = policyTO; - final List fields = new ArrayList<>(); + List fields = new ArrayList<>(); FieldPanel description = new AjaxTextFieldPanel("field", "description", new PropertyModel<>(policyTO, "description"), false); @@ -109,7 +148,7 @@ public Profile(final T policyTO, final BaseModal modal, final PageReference p fields.add(description); if (policyTO instanceof AccountPolicyTO) { - fields.add(new AjaxSpinnerFieldPanel.Builder().build( + fields.add(new AjaxSpinnerFieldPanel.Builder().min(1).build( "field", "maxAuthenticationAttempts", Integer.class, @@ -126,7 +165,7 @@ public Profile(final T policyTO, final BaseModal modal, final PageReference p new PropertyModel<>(policyTO, "passthroughResources"), new ListModel<>(resources.getObject()))); } else if (policyTO instanceof PasswordPolicyTO) { - fields.add(new AjaxSpinnerFieldPanel.Builder().build( + fields.add(new AjaxSpinnerFieldPanel.Builder().min(1).build( "field", "historyLength", Integer.class, @@ -137,6 +176,61 @@ public Profile(final T policyTO, final BaseModal modal, final PageReference p "allowNullPassword", new PropertyModel<>(policyTO, "allowNullPassword"), false)); + } else if (policyTO instanceof PropagationPolicyTO) { + fields.add(new AjaxSpinnerFieldPanel.Builder().min(1).build( + "field", + "maxAttempts", + Integer.class, + new PropertyModel<>(policyTO, "maxAttempts"))); + AjaxDropDownChoicePanel backOffStrategy = new AjaxDropDownChoicePanel<>( + "field", + "backOffStrategy", + new PropertyModel<>(policyTO, "backOffStrategy")). + setChoices(Arrays.asList((Serializable[]) BackOffStrategy.values())); + fields.add(backOffStrategy); + + PropertyModel backOffParamsModel = new PropertyModel<>(policyTO, "backOffParams"); + + AjaxSpinnerFieldPanel initialInterval = new AjaxSpinnerFieldPanel.Builder(). + min(1L).build( + "field", + "initialInterval", + Long.class, + new BackOffParamsModel<>(backOffParamsModel, 0)); + fields.add(initialInterval.setOutputMarkupPlaceholderTag(true)); + AjaxSpinnerFieldPanel maxInterval = new AjaxSpinnerFieldPanel.Builder(). + min(1L).build( + "field", + "maxInterval", + Long.class, + new BackOffParamsModel<>(backOffParamsModel, 1)); + fields.add(maxInterval.setOutputMarkupPlaceholderTag(true).setVisible(false)); + AjaxSpinnerFieldPanel multiplier = new AjaxSpinnerFieldPanel.Builder(). + min(1D).build( + "field", + "multiplier", + Double.class, + new BackOffParamsModel<>(backOffParamsModel, 2)); + fields.add(multiplier.setOutputMarkupPlaceholderTag(true).setVisible(false)); + + showHide(backOffStrategy, initialInterval, maxInterval, multiplier); + + backOffStrategy.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject(); + backOffParamsModel.setObject(strategy.getDefaultBackOffParams()); + + showHide(backOffStrategy, initialInterval, maxInterval, multiplier); + + target.add(initialInterval); + target.add(maxInterval); + target.add(multiplier); + } + }); } else if (policyTO instanceof ProvisioningPolicyTO) { fields.add(new AjaxDropDownChoicePanel<>( "field", @@ -153,10 +247,38 @@ public Profile(final T policyTO, final BaseModal modal, final PageReference p protected void populateItem(final ListItem item) { item.add(item.getModelObject()); } - }); } + private void showHide( + final AjaxDropDownChoicePanel backOffStrategy, + final AjaxSpinnerFieldPanel initialInterval, + final AjaxSpinnerFieldPanel maxInterval, + final AjaxSpinnerFieldPanel multiplier) { + + BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject(); + + switch (strategy) { + case EXPONENTIAL: + initialInterval.addLabel("initialInterval"); + maxInterval.setVisible(true); + multiplier.setVisible(true); + break; + + case RANDOM: + initialInterval.addLabel("initialInterval"); + maxInterval.setVisible(true); + multiplier.setVisible(true); + break; + + case FIXED: + default: + initialInterval.addLabel("period"); + maxInterval.setVisible(false); + multiplier.setVisible(false); + } + } + @Override public void onSubmit(final AjaxRequestTarget target) { try { @@ -165,15 +287,13 @@ public void onSubmit(final AjaxRequestTarget target) { } else { restClient.updatePolicy(type, policyTO); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); Profile.this.modal.close(target); } catch (Exception e) { LOG.error("While creating/updating policy", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleDirectoryPanel.java index 2b89ee4a669..064050ea6ae 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleDirectoryPanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.policies; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -26,7 +25,6 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -77,8 +75,6 @@ public class PolicyRuleDirectoryPanel extends DirectoryPanel private static final long serialVersionUID = 4984337552918213290L; - private static final ObjectMapper MAPPER = new ObjectMapper(); - private final BaseModal baseModal; private final PolicyType type; @@ -171,13 +167,12 @@ public void onClick(final AjaxRequestTarget target, final PolicyRuleWrapper igno ((ComposablePolicy) actual).getRules().remove(model.getObject().getImplementationKey()); restClient.updatePolicy(type, actual); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); customActionOnFinishCallback(target); } } catch (SyncopeClientException e) { LOG.error("While deleting {}", rule.getName(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -205,10 +200,8 @@ public void onClick(final AjaxRequestTarget target, final Serializable ignore) { } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.DELETE); - return bulkActions; + protected Collection getBatches() { + return Collections.emptyList(); } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleWizardBuilder.java index 855837ea62d..982a4960347 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleWizardBuilder.java @@ -38,6 +38,7 @@ import org.apache.syncope.common.lib.types.ImplementationType; import org.apache.syncope.common.lib.types.PolicyType; import org.apache.wicket.PageReference; +import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.wizard.WizardModel; @@ -93,7 +94,7 @@ protected Serializable onApplyInternal(final PolicyRuleWrapper modelObject) { rule.setBody(MAPPER.writeValueAsString(modelObject.getConf())); implementationClient.update(rule); } catch (Exception e) { - throw new RuntimeException(e); + throw new WicketRuntimeException(e); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java new file mode 100644 index 00000000000..4489f22975e --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java @@ -0,0 +1,51 @@ +/* + * 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.client.console.policies; + +import java.util.List; +import org.apache.syncope.common.lib.policy.PropagationPolicyTO; +import org.apache.syncope.common.lib.types.PolicyType; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.StringResourceModel; + +public class PropagationPolicyDirectoryPanel extends PolicyDirectoryPanel { + + private static final long serialVersionUID = 25188602686577L; + + public PropagationPolicyDirectoryPanel(final String id, final PageReference pageRef) { + super(id, PolicyType.PROPAGATION, pageRef); + + PropagationPolicyTO defaultItem = new PropagationPolicyTO(); + + this.addNewItemPanelBuilder( + new PolicyModalPanelBuilder<>(PolicyType.PROPAGATION, defaultItem, modal, pageRef), true); + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.POLICY_CREATE); + + initResultTable(); + } + + @Override + protected void addCustomColumnFields(final List> columns) { + columns.add(new PropertyColumn<>(new StringResourceModel("maxAttempts", this), "maxAttempts", "maxAttempts")); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java index 96176b64d79..6c98a2b5d23 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java @@ -54,6 +54,7 @@ import org.apache.syncope.common.lib.types.PolicyType; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.PageReference; +import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.panel.Panel; @@ -156,18 +157,17 @@ public void onSubmit(final AjaxRequestTarget target) { try { implRestClient.update(rule.getImpl()); } catch (Exception e) { - throw new RuntimeException(e); + throw new WicketRuntimeException(e); } } }); restClient.updatePolicy(getItem() instanceof PullPolicyTO ? PolicyType.PULL : PolicyType.PUSH, getItem()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); this.modal.close(target); } catch (Exception e) { LOG.error("While creating/updating policy", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -325,7 +325,7 @@ private List getSchemas(final CorrelationRule rule) { ? AnyTypeKind.GROUP : AnyTypeKind.ANY_OBJECT).stream().map(EntityTO::getKey). collect(Collectors.toList()); - choices.add("key"); + choices.add(Constants.KEY_FIELD_NAME); choices.add(rule.getAnyType().equals(AnyTypeKind.USER.name()) ? "username" : "name"); Collections.sort(choices); return choices; @@ -382,8 +382,7 @@ public final void setImpl(final ImplementationTO impl) { try { this.defaultRuleConf = OBJECT_MAPPER.readValue(impl.getBody(), ruleConfClass); } catch (Exception e) { - LOG.debug("Could not deserialize {} as {}", - impl.getBody(), ruleConfClass.getName()); + LOG.debug("Could not deserialize {} as {}", impl.getBody(), ruleConfClass.getName()); } } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java index 5109da34100..c72b1635951 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java @@ -25,7 +25,6 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -47,6 +46,7 @@ import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ReportTO; +import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; @@ -58,6 +58,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; @@ -104,7 +105,8 @@ protected void onTimer(final AjaxRequestTarget target) { protected List> getColumns() { final List> columns = new ArrayList<>(); - columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key")); + columns.add(new KeyPropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>(new StringResourceModel("name", this), "name", "name")); columns.add(new DatePropertyColumn<>( @@ -112,7 +114,7 @@ protected List> getColumns() { columns.add(new DatePropertyColumn<>( new StringResourceModel("nextExec", this), null, "nextExec")); - + columns.add(new DatePropertyColumn<>( new StringResourceModel("start", this), "start", "start")); @@ -135,13 +137,18 @@ public void populateItem( final String componentId, final IModel rowModel) { - JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); - JobActionPanel panel = new JobActionPanel( - componentId, jobTO, false, ReportDirectoryPanel.this, pageRef); - MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, - String.format("%s,%s", - StandardEntitlement.TASK_EXECUTE, - StandardEntitlement.TASK_UPDATE)); + Component panel; + try { + JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); + panel = new JobActionPanel(componentId, jobTO, false, ReportDirectoryPanel.this, pageRef); + MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, + String.format("%s,%s", + StandardEntitlement.REPORT_EXECUTE, + StandardEntitlement.REPORT_UPDATE)); + } catch (Exception e) { + LOG.error("Could not get job for report {}", rowModel.getObject().getKey(), e); + panel = new Label(componentId, Model.of()); + } cellItem.add(panel); } @@ -206,7 +213,7 @@ public void onClick(final AjaxRequestTarget target, final ReportTO ignore) { "reportlet.conf", ReportDirectoryPanel.this, Model.of(model.getObject()))); MetaDataRoleAuthorizationStrategy.authorize( - modal.getForm(), ENABLE, StandardEntitlement.RESOURCE_UPDATE); + modal.getForm(), ENABLE, StandardEntitlement.REPORT_UPDATE); modal.show(true); } @@ -218,9 +225,9 @@ public void onClick(final AjaxRequestTarget target, final ReportTO ignore) { @Override public void onClick(final AjaxRequestTarget target, final ReportTO ignore) { - viewTask(model.getObject(), target); + viewReport(model.getObject(), target); } - }, ActionLink.ActionType.VIEW, StandardEntitlement.REPORT_READ); + }, ActionLink.ActionType.VIEW_EXECUTIONS, StandardEntitlement.REPORT_READ); panel.add(new ActionLink() { @@ -243,12 +250,11 @@ public void onClick(final AjaxRequestTarget target, final ReportTO ignore) { final ReportTO reportTO = model.getObject(); try { restClient.delete(reportTO.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting {}", reportTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -257,11 +263,11 @@ public void onClick(final AjaxRequestTarget target, final ReportTO ignore) { } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.EXECUTE); - bulkActions.add(ActionType.DELETE); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionType.EXECUTE); + batches.add(ActionType.DELETE); + return batches; } @Override @@ -274,7 +280,7 @@ protected String paginatorRowsKey() { return Constants.PREF_REPORT_TASKS_PAGINATOR_ROWS; } - protected abstract void viewTask(final ReportTO reportTO, final AjaxRequestTarget target); + protected abstract void viewReport(ReportTO reportTO, AjaxRequestTarget target); protected class ReportDataProvider extends DirectoryDataProvider { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java index 4311a1bd6a1..b08765809b9 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportExecutionDetails.java @@ -18,12 +18,12 @@ */ package org.apache.syncope.client.console.reports; -import org.apache.syncope.client.console.commons.HttpResourceStream; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.ExecutionRestClient; import org.apache.syncope.client.console.rest.ReportRestClient; +import org.apache.syncope.client.console.rest.ResponseHolder; import org.apache.syncope.client.console.tasks.ExecutionsDirectoryPanel; -import org.apache.syncope.client.console.wicket.ajax.form.AbstractAjaxDownloadBehavior; +import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.common.lib.to.ExecTO; @@ -33,7 +33,6 @@ import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.model.IModel; -import org.apache.wicket.util.resource.IResourceStream; /** * Modal window with report executions. @@ -55,9 +54,11 @@ private static class ReportExecutionDirectoryPanel extends ExecutionsDirectoryPa private static final long serialVersionUID = 5691719817252887541L; + private final ReportRestClient reportRestClient = new ReportRestClient(); + private final MultilevelPanel mlp; - private final AjaxExportDownloadBehavior downloadBehavior; + private final AjaxDownloadBehavior downloadBehavior; ReportExecutionDirectoryPanel( final MultilevelPanel multiLevelPanelRef, @@ -67,7 +68,7 @@ private static class ReportExecutionDirectoryPanel extends ExecutionsDirectoryPa super(multiLevelPanelRef, key, executionRestClient, pageRef); this.mlp = multiLevelPanelRef; - this.downloadBehavior = new AjaxExportDownloadBehavior(); + this.downloadBehavior = new AjaxDownloadBehavior(); this.add(downloadBehavior); } @@ -87,7 +88,8 @@ protected void addFurtherAcions(final ActionsPanel panel, final IModel utilityModal = new BaseModal<>("outer"); + protected final BaseModal utilityModal = new BaseModal<>(Constants.OUTER); public ReportTemplateDirectoryPanel(final String id, final PageReference pageReference) { super(id, pageReference, true); @@ -71,18 +70,11 @@ public ReportTemplateDirectoryPanel(final String id, final PageReference pageRef modal.size(Modal.Size.Small); modal.addSubmitButton(); - setFooterVisibility(true); - - modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { - - private static final long serialVersionUID = 8804221891699487139L; - - @Override - public void onClose(final AjaxRequestTarget target) { - updateResultTable(target); - modal.show(false); - } + modal.setWindowClosedCallback(target -> { + updateResultTable(target); + modal.show(false); }); + setFooterVisibility(true); addOuterObject(utilityModal); setWindowClosedReloadCallback(utilityModal); @@ -104,13 +96,15 @@ public WizardModalPanel build( initResultTable(); - MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.MAIL_TEMPLATE_CREATE); + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.REPORT_TEMPLATE_CREATE); } @Override protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key")); + columns.add(new PropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), + Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); return columns; } @@ -134,7 +128,7 @@ public void onClick(final AjaxRequestTarget target, final ReportTemplateTO ignor utilityModal.show(true); target.add(utilityModal); } - }, ActionLink.ActionType.FO_EDIT, StandardEntitlement.MAIL_TEMPLATE_UPDATE); + }, ActionLink.ActionType.FO_EDIT, StandardEntitlement.REPORT_TEMPLATE_UPDATE); panel.add(new ActionLink() { @@ -152,7 +146,7 @@ public void onClick(final AjaxRequestTarget target, final ReportTemplateTO ignor utilityModal.show(true); target.add(utilityModal); } - }, ActionLink.ActionType.HTML, StandardEntitlement.MAIL_TEMPLATE_UPDATE); + }, ActionLink.ActionType.HTML, StandardEntitlement.REPORT_TEMPLATE_UPDATE); panel.add(new ActionLink() { @@ -170,7 +164,7 @@ public void onClick(final AjaxRequestTarget target, final ReportTemplateTO ignor utilityModal.show(true); target.add(utilityModal); } - }, ActionLink.ActionType.TEXT, StandardEntitlement.MAIL_TEMPLATE_UPDATE); + }, ActionLink.ActionType.TEXT, StandardEntitlement.REPORT_TEMPLATE_UPDATE); panel.add(new ActionLink() { @@ -180,16 +174,15 @@ public void onClick(final AjaxRequestTarget target, final ReportTemplateTO ignor public void onClick(final AjaxRequestTarget target, final ReportTemplateTO ignore) { try { restClient.deleteTemplate(model.getObject().getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting object {}", model.getObject().getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionLink.ActionType.DELETE, StandardEntitlement.MAIL_TEMPLATE_DELETE, true); + }, ActionLink.ActionType.DELETE, StandardEntitlement.REPORT_TEMPLATE_DELETE, true); return panel; } @@ -201,11 +194,11 @@ protected ReportTemplateProvider dataProvider() { @Override protected String paginatorRowsKey() { - return Constants.PREF_MAIL_TEMPLATE_PAGINATOR_ROWS; + return Constants.PREF_REPORT_TEMPLATE_PAGINATOR_ROWS; } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -218,7 +211,7 @@ protected final class ReportTemplateProvider extends DirectoryDataProvider(this); } @@ -270,13 +263,12 @@ public void onSubmit(final AjaxRequestTarget target) { try { restClient.updateTemplateFormat( content.getKey(), content.getContent(), content.getFormat()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.show(false); modal.close(target); } catch (Exception e) { LOG.error("While updating template for {}", content.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletDirectoryPanel.java index 6ac03e2c20a..ea387259163 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletDirectoryPanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.reports; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -26,7 +25,6 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -73,8 +71,6 @@ public class ReportletDirectoryPanel extends DirectoryPanel< private static final long serialVersionUID = 4984337552918213290L; - private static final ObjectMapper MAPPER = new ObjectMapper(); - private final BaseModal baseModal; private final String report; @@ -158,16 +154,15 @@ public void onClick(final AjaxRequestTarget target, final ReportletWrapper ignor actual.getReportlets().remove(model.getObject().getImplementationKey()); restClient.update(actual); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); customActionOnFinishCallback(target); } catch (SyncopeClientException e) { LOG.error("While deleting {}", reportlet.getName(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionLink.ActionType.DELETE, StandardEntitlement.REPORT_DELETE, true); + }, ActionLink.ActionType.DELETE, StandardEntitlement.REPORT_UPDATE); return panel; } @@ -191,10 +186,8 @@ public void onClick(final AjaxRequestTarget target, final Serializable ignore) { } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.DELETE); - return bulkActions; + protected Collection getBatches() { + return Collections.emptyList(); } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWizardBuilder.java index 17785b0e875..fa2ffba4e31 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWizardBuilder.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.BeanPanel; +import org.apache.syncope.client.console.panels.search.SearchUtils; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.rest.ReportRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; @@ -34,12 +35,15 @@ import org.apache.syncope.common.lib.types.ImplementationEngine; import org.apache.syncope.common.lib.types.ImplementationType; import org.apache.wicket.PageReference; +import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.wizard.WizardModel; import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.LoadableDetachableModel; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.PropertyAccessorFactory; public class ReportletWizardBuilder extends AjaxWizardBuilder { @@ -64,13 +68,18 @@ public ReportletWizardBuilder( @Override protected Serializable onApplyInternal(final ReportletWrapper modelObject) { if (modelObject.getImplementationEngine() == ImplementationEngine.JAVA) { + BeanWrapper confWrapper = PropertyAccessorFactory.forBeanPropertyAccess(modelObject.getConf()); + modelObject.getSCondWrapper().forEach((fieldName, pair) -> { + confWrapper.setPropertyValue(fieldName, SearchUtils.buildFIQL(pair.getRight(), pair.getLeft())); + }); + ImplementationTO reportlet = implementationClient.read( ImplementationType.REPORTLET, modelObject.getImplementationKey()); try { reportlet.setBody(MAPPER.writeValueAsString(modelObject.getConf())); implementationClient.update(reportlet); } catch (Exception e) { - throw new RuntimeException(e); + throw new WicketRuntimeException(e); } } @@ -84,8 +93,7 @@ protected Serializable onApplyInternal(final ReportletWrapper modelObject) { } @Override - protected WizardModel buildModelSteps( - final ReportletWrapper modelObject, final WizardModel wizardModel) { + protected WizardModel buildModelSteps(final ReportletWrapper modelObject, final WizardModel wizardModel) { wizardModel.add(new Profile(modelObject)); wizardModel.add(new Configuration(modelObject)); return wizardModel; @@ -96,8 +104,8 @@ public class Profile extends WizardStep { private static final long serialVersionUID = -3043839139187792810L; public Profile(final ReportletWrapper reportlet) { - final AjaxDropDownChoicePanel conf = new AjaxDropDownChoicePanel<>( - "reportlet", getString("reportlet"), new PropertyModel(reportlet, "implementationKey")); + AjaxDropDownChoicePanel conf = new AjaxDropDownChoicePanel<>( + "reportlet", getString("reportlet"), new PropertyModel<>(reportlet, "implementationKey")); conf.setChoices(implementationClient.list(ImplementationType.REPORTLET).stream(). map(EntityTO::getKey).sorted().collect(Collectors.toList())); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWrapper.java index be628a7e6fe..574804ab73a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletWrapper.java @@ -40,7 +40,7 @@ public class ReportletWrapper implements Serializable { private ReportletConf conf; - private final Map>> scondWrapper; + private final Map, List>> scondWrapper; public ReportletWrapper(final boolean isNew) { this.isNew = isNew; @@ -78,7 +78,7 @@ public ReportletWrapper setConf(final ReportletConf conf) { return this; } - public Map>> getSCondWrapper() { + public Map, List>> getSCondWrapper() { return scondWrapper; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/resources/AbstractWorkflowResource.java b/client/console/src/main/java/org/apache/syncope/client/console/resources/AbstractWorkflowResource.java deleted file mode 100644 index b76bd9e161b..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/resources/AbstractWorkflowResource.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.client.console.resources; - -import javax.ws.rs.NotFoundException; -import org.apache.syncope.client.console.commons.Constants; -import org.apache.syncope.client.console.rest.WorkflowRestClient; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.wicket.request.resource.AbstractResource; -import org.apache.wicket.util.string.StringValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -abstract class AbstractWorkflowResource extends AbstractResource { - - private static final long serialVersionUID = 5163553843196539019L; - - protected static final Logger LOG = LoggerFactory.getLogger(AbstractWorkflowResource.class); - - protected final WorkflowRestClient restClient = new WorkflowRestClient(); - - protected WorkflowDefinitionTO getWorkflowDefinition(final Attributes attributes) { - final StringValue modelId = - attributes.getRequest().getQueryParameters().getParameterValue(Constants.MODEL_ID_PARAM); - - WorkflowDefinitionTO workflowDefinition = modelId == null || modelId.isNull() ? null - : restClient.getDefinitions().stream(). - filter(object -> modelId.toString().equals(object.getModelId())).findAny().orElse(null); - if (workflowDefinition == null) { - throw new NotFoundException("Workflow definition with modelId " + modelId); - } - - return workflowDefinition; - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/resources/FilesystemResource.java b/client/console/src/main/java/org/apache/syncope/client/console/resources/FilesystemResource.java deleted file mode 100644 index f8dc13df20e..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/resources/FilesystemResource.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.client.console.resources; - -import java.io.File; -import java.io.InputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import org.apache.wicket.request.resource.AbstractResource; -import org.apache.wicket.util.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Mounts directory on local filesystem as subcontext. - */ -public class FilesystemResource extends AbstractResource { - - private static final long serialVersionUID = -4791087117785935198L; - - private static final Logger LOG = LoggerFactory.getLogger(FilesystemResource.class); - - private final String baseCtx; - - private final String basePath; - - public FilesystemResource(final String baseCtx, final String basePath) { - this.baseCtx = baseCtx; - this.basePath = basePath; - } - - @Override - protected ResourceResponse newResourceResponse(final Attributes attributes) { - ResourceResponse response = new ResourceResponse(); - - final File baseDir = new File(basePath); - if (baseDir.exists() && baseDir.canRead() && baseDir.isDirectory()) { - String reqPath = attributes.getRequest().getUrl().getPath(); - final String subPath = reqPath.substring(reqPath.indexOf(baseCtx) + baseCtx.length()). - replace('/', File.separatorChar); - LOG.debug("Request for {}", subPath); - - response.setTextEncoding(StandardCharsets.UTF_8.name()); - response.setWriteCallback(new WriteCallback() { - - @Override - public void writeData(final Attributes attributes) throws IOException { - try (InputStream resourceIS = Files.newInputStream(new File(baseDir, subPath).toPath())) { - IOUtils.copy(resourceIS, attributes.getResponse().getOutputStream()); - } catch (IOException e) { - LOG.error("Could not read from {}", baseDir.getAbsolutePath() + subPath, e); - } - } - }); - } else { - LOG.error("{} not found, not readable or not a directory", basePath); - } - - return response; - } - -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefGETResource.java b/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefGETResource.java deleted file mode 100644 index 46fb22a5dbe..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefGETResource.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.client.console.resources; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import javax.ws.rs.core.MediaType; -import org.apache.syncope.client.console.rest.WorkflowRestClient; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.wicket.util.io.IOUtils; - -/** - * Mirror REST resource for obtaining user workflow definition in JSON (used by Flowable Modeler). - * - * @see org.apache.syncope.common.rest.api.service.WorkflowService#get - */ -public class WorkflowDefGETResource extends AbstractWorkflowResource { - - private static final long serialVersionUID = 4637304868056148970L; - - @Override - protected ResourceResponse newResourceResponse(final Attributes attributes) { - final WorkflowDefinitionTO toGet = getWorkflowDefinition(attributes); - - ResourceResponse response = new ResourceResponse(); - response.disableCaching(); - response.setContentType(MediaType.APPLICATION_JSON); - response.setTextEncoding(StandardCharsets.UTF_8.name()); - response.setWriteCallback(new WriteCallback() { - - @Override - public void writeData(final Attributes attributes) throws IOException { - IOUtils.copy( - new WorkflowRestClient().getDefinition(MediaType.APPLICATION_JSON_TYPE, toGet.getKey()), - attributes.getResponse().getOutputStream()); - } - }); - - return response; - } - -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefPUTResource.java b/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefPUTResource.java deleted file mode 100644 index 84ec3e53a22..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/resources/WorkflowDefPUTResource.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.client.console.resources; - -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import org.apache.cxf.common.util.UrlUtils; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.wicket.util.io.IOUtils; - -/** - * Mirror REST resource for putting user workflow definition in JSON (used by Flowable Modeler). - * - * @see org.apache.syncope.common.rest.api.service.WorkflowService#set - */ -public class WorkflowDefPUTResource extends AbstractWorkflowResource { - - private static final long serialVersionUID = 2964542005207297944L; - - @Override - protected ResourceResponse newResourceResponse(final Attributes attributes) { - String definition = null; - try { - HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); - String requestBody = IOUtils.toString(request.getInputStream()); - String[] split = requestBody.split("&"); - for (int i = 0; i < split.length && definition == null; i++) { - String keyValue = split[i]; - if (keyValue.startsWith("json_xml=")) { - definition = UrlUtils.urlDecode(keyValue.split("=")[1]); - } - } - } catch (IOException e) { - LOG.error("Could not extract workflow definition", e); - } - - WorkflowDefinitionTO toSet = getWorkflowDefinition(attributes); - - if (definition == null || toSet == null) { - return new ResourceResponse().setStatusCode(Response.Status.BAD_REQUEST.getStatusCode()). - setError(Response.Status.BAD_REQUEST.getStatusCode(), - "Could not extract workflow model id and / or definition"); - } - - try { - restClient.setDefinition(MediaType.APPLICATION_JSON_TYPE, toSet.getKey(), definition); - return new ResourceResponse().setStatusCode(Response.Status.NO_CONTENT.getStatusCode()); - } catch (Exception e) { - LOG.error("While updating workflow definition", e); - return new ResourceResponse().setStatusCode(Response.Status.BAD_REQUEST.getStatusCode()). - setError(Response.Status.BAD_REQUEST.getStatusCode(), - "While updating workflow definition: " + e.getMessage()); - } - } - -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AbstractAnyRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AbstractAnyRestClient.java index 639a4eb51ce..0aa2a2f4d65 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AbstractAnyRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AbstractAnyRestClient.java @@ -18,19 +18,32 @@ */ package org.apache.syncope.client.console.rest; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.jaxrs.client.Client; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.console.commons.status.StatusBean; import org.apache.syncope.client.console.commons.status.StatusUtils; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.lib.patch.AssociationPatch; import org.apache.syncope.common.lib.patch.DeassociationPatch; import org.apache.syncope.common.lib.patch.StatusPatch; import org.apache.syncope.common.lib.to.AnyTO; -import org.apache.syncope.common.lib.to.BulkAction; -import org.apache.syncope.common.lib.to.BulkActionResult; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.types.ResourceAssociationAction; import org.apache.syncope.common.lib.types.ResourceDeassociationAction; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.batch.BatchPayloadParser; +import org.apache.syncope.common.rest.api.batch.BatchRequestItem; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; import org.apache.syncope.common.rest.api.service.AnyService; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; @@ -59,112 +72,103 @@ public ProvisioningResult delete(final String etag, final String key) { return result; } - public BulkActionResult unlink(final String etag, final String key, final List statuses) { - BulkActionResult result; - synchronized (this) { - AnyService service = getService(etag, getAnyServiceClass()); - - DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key). - action(ResourceDeassociationAction.UNLINK). - resources(StatusUtils.buildStatusPatch(statuses).getResources()).build(); - - result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class); - - resetClient(getAnyServiceClass()); - } - return result; + private List parseBatchResponse(final Response response) throws IOException { + return BatchPayloadParser.parse( + (InputStream) response.getEntity(), response.getMediaType(), new BatchResponseItem()); } - public BulkActionResult link(final String etag, final String key, final List statuses) { - BulkActionResult result; - synchronized (this) { - AnyService service = getService(etag, getAnyServiceClass()); - - StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses); + public Map associate( + final ResourceAssociationAction action, + final String etag, + final String key, + final List statuses) { - AssociationPatch associationPatch = new AssociationPatch.Builder().key(key). - action(ResourceAssociationAction.LINK). - onSyncope(statusPatch.isOnSyncope()). - resources(statusPatch.getResources()).build(); - - result = service.associate(associationPatch).readEntity(BulkActionResult.class); - - resetClient(getAnyServiceClass()); - } - return result; - } - - public BulkActionResult deprovision(final String etag, final String key, final List statuses) { - BulkActionResult result; + Map result = new LinkedHashMap<>(); synchronized (this) { AnyService service = getService(etag, getAnyServiceClass()); + Client client = WebClient.client(service); + List accept = client.getHeaders().get(HttpHeaders.ACCEPT); + if (!accept.contains(RESTHeaders.MULTIPART_MIXED)) { + client.accept(RESTHeaders.MULTIPART_MIXED); + } - DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key). - action(ResourceDeassociationAction.DEPROVISION). - resources(StatusUtils.buildStatusPatch(statuses).getResources()).build(); - - result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class); - - resetClient(getAnyServiceClass()); - } - return result; - } - - public BulkActionResult provision(final String etag, final String key, final List statuses) { - BulkActionResult result; - synchronized (this) { - AnyService service = getService(etag, getAnyServiceClass()); - - StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses); + StatusPatch statusPatch = StatusUtils.statusPatch(statuses).build(); AssociationPatch associationPatch = new AssociationPatch.Builder().key(key). - action(ResourceAssociationAction.PROVISION). + action(action). onSyncope(statusPatch.isOnSyncope()). resources(statusPatch.getResources()).build(); - - result = service.associate(associationPatch).readEntity(BulkActionResult.class); + try { + List items = parseBatchResponse(service.associate(associationPatch)); + for (int i = 0; i < items.size(); i++) { + result.put( + associationPatch.getResources().get(i), + getStatus(items.get(i).getStatus())); + } + } catch (IOException e) { + LOG.error("While processing Batch response", e); + } resetClient(getAnyServiceClass()); } return result; } - public BulkActionResult unassign(final String etag, final String key, final List statuses) { - BulkActionResult result; + public Map deassociate( + final ResourceDeassociationAction action, + final String etag, + final String key, + final List statuses) { + + Map result = new LinkedHashMap<>(); synchronized (this) { AnyService service = getService(etag, getAnyServiceClass()); + Client client = WebClient.client(service); + List accept = client.getHeaders().get(HttpHeaders.ACCEPT); + if (!accept.contains(RESTHeaders.MULTIPART_MIXED)) { + client.accept(RESTHeaders.MULTIPART_MIXED); + } DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key). - action(ResourceDeassociationAction.UNASSIGN). - resources(StatusUtils.buildStatusPatch(statuses).getResources()).build(); - - result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class); + action(action). + resources(StatusUtils.statusPatch(statuses).build().getResources()).build(); + try { + List items = parseBatchResponse(service.deassociate(deassociationPatch)); + for (int i = 0; i < items.size(); i++) { + result.put( + deassociationPatch.getResources().get(i), + getStatus(items.get(i).getStatus())); + } + } catch (IOException e) { + LOG.error("While processing Batch response", e); + } resetClient(getAnyServiceClass()); } return result; } - public BulkActionResult assign(final String etag, final String key, final List statuses) { - BulkActionResult result; - synchronized (this) { - AnyService service = getService(etag, getAnyServiceClass()); - - StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses); - - AssociationPatch associationPatch = new AssociationPatch.Builder().key(key). - action(ResourceAssociationAction.ASSIGN). - onSyncope(statusPatch.isOnSyncope()). - resources(statusPatch.getResources()).build(); - - result = service.associate(associationPatch).readEntity(BulkActionResult.class); - - resetClient(getAnyServiceClass()); + public Map batch(final BatchRequest batchRequest) { + List batchRequestItems = new ArrayList<>(batchRequest.getItems()); + + Map result = new LinkedHashMap<>(); + try { + List batchResponseItems = batchRequest.commit().getItems(); + for (int i = 0; i < batchResponseItems.size(); i++) { + String status = getStatus(batchResponseItems.get(i).getStatus()); + if (batchRequestItems.get(i).getRequestURI().endsWith("/status")) { + result.put(StringUtils.substringAfterLast( + StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/status"), "/"), + status); + } else { + result.put(StringUtils.substringAfterLast( + batchRequestItems.get(i).getRequestURI(), "/"), status); + } + } + } catch (IOException e) { + LOG.error("While processing Batch response", e); } - return result; - } - public BulkActionResult bulkAction(final BulkAction action) { - return getService(getAnyServiceClass()).bulk(action).readEntity(BulkActionResult.class); + return result; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AccessTokenRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AccessTokenRestClient.java index 50b917c1439..a643d5583ad 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AccessTokenRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AccessTokenRestClient.java @@ -37,7 +37,7 @@ public void delete(final String key) { public int count() { return getService(AccessTokenService.class).list( - new AccessTokenQuery.Builder().page(1).size(1).build()). + new AccessTokenQuery.Builder().page(1).size(0).build()). getTotalCount(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java index 86a9e29f07e..358f8374b0b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyObjectRestClient.java @@ -61,7 +61,7 @@ public ProvisioningResult update(final String etag, final AnyObject @Override public int count(final String realm, final String fiql, final String type) { return getService(AnyObjectService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(1).build()). + search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(0).build()). getTotalCount(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java index b1e55a6330f..f4e0ed67d19 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java @@ -54,7 +54,7 @@ public List listAnyTypes() { try { types = getService(AnyTypeService.class).list(); - Collections.sort(types, new AnyTypeComparator()); + types.sort(COMPARATOR); } catch (SyncopeClientException e) { LOG.error("While reading all any types", e); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java index 0c765f8957e..e70b90d1e50 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java @@ -45,5 +45,4 @@ public void create(final ApplicationTO applicationTO) { public List list() { return getService(ApplicationService.class).list(); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java index 4a780a69474..1d259c0c138 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java @@ -19,13 +19,13 @@ package org.apache.syncope.client.console.rest; import java.net.URI; - import javax.ws.rs.core.HttpHeaders; - import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.search.OrderByClauseBuilder; +import org.apache.syncope.common.lib.types.ExecStatus; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.JAXRSService; import org.apache.syncope.common.rest.api.service.SyncopeService; @@ -55,7 +55,7 @@ protected static void resetClient(final Class serviceClass) { SyncopeConsoleSession.get().resetClient(serviceClass); } - protected static String toOrderBy(final SortParam sort) { + public static String toOrderBy(final SortParam sort) { OrderByClauseBuilder builder = SyncopeClient.getOrderByClauseBuilder(); String property = sort.getProperty(); @@ -82,4 +82,11 @@ protected static T getObject( header(HttpHeaders.AUTHORIZATION, "Bearer " + SyncopeConsoleSession.get().getJWT()). get(resultClass); } + + protected static String getStatus(final int httpStatus) { + ExecStatus execStatus = ExecStatus.fromHttpStatus(httpStatus); + return execStatus == null + ? Constants.UNKNOWN + : execStatus.name(); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorHistoryRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorHistoryRestClient.java deleted file mode 100644 index 9178569ef42..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorHistoryRestClient.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.client.console.rest; - -import java.util.Collections; -import java.util.List; -import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO; -import org.apache.syncope.common.rest.api.service.ConnectorHistoryService; - -/** - * Console client for invoking Rest Connector configuration history services. - */ -public class ConnectorHistoryRestClient extends BaseRestClient { - - private static final long serialVersionUID = -1917949374689773018L; - - public List list(final String key) { - List connHistoryConfs = Collections.emptyList(); - try { - connHistoryConfs = getService(ConnectorHistoryService.class).list(key); - } catch (Exception e) { - LOG.error("While reading connector history configuration instances", e); - } - return connHistoryConfs; - } - - public void delete(final String key) { - getService(ConnectorHistoryService.class).delete(key); - } - - public void restore(final String key) { - getService(ConnectorHistoryService.class).restore(key); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java index d8b3fdfd5d1..db00c504888 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java @@ -18,20 +18,29 @@ */ package org.apache.syncope.client.console.rest; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.ConnBundleTO; import org.apache.syncope.common.lib.to.ConnIdObjectClassTO; import org.apache.syncope.common.lib.to.ConnInstanceTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.types.ConnConfProperty; +import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.ConnectorService; import org.springframework.beans.BeanUtils; @@ -43,7 +52,7 @@ public class ConnectorRestClient extends BaseRestClient { private static final long serialVersionUID = -6870366819966266617L; public List getAllConnectors() { - List connectors = Collections.emptyList(); + List connectors = Collections.emptyList(); try { connectors = getService(ConnectorService.class).list(SyncopeConsoleSession.get().getLocale().toString()); } catch (Exception e) { @@ -92,7 +101,10 @@ public List getExtAttrNames( filter(object -> object.getType().equalsIgnoreCase(objectClass)). findAny(); - return connIdObjectClass.isPresent() ? connIdObjectClass.get().getAttributes() : new ArrayList<>(); + return connIdObjectClass.isPresent() + ? connIdObjectClass.get().getAttributes().stream(). + map(PlainSchemaTO::getKey).collect(Collectors.toList()) + : Collections.emptyList(); } /** @@ -163,6 +175,23 @@ private List filterProperties(final Collection check(final ConnInstanceTO connectorTO) { ConnInstanceTO toBeChecked = new ConnInstanceTO(); BeanUtils.copyProperties(connectorTO, toBeChecked, new String[] { "configuration", "configurationMap" }); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java new file mode 100644 index 00000000000..3928a24247e --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java @@ -0,0 +1,48 @@ +/* + * 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.client.console.rest; + +import java.util.List; +import org.apache.syncope.common.lib.to.DelegationTO; +import org.apache.syncope.common.rest.api.service.DelegationService; + +public class DelegationRestClient extends BaseRestClient { + + private static final long serialVersionUID = -381814125643246243L; + + public void delete(final String key) { + getService(DelegationService.class).delete(key); + } + + public DelegationTO read(final String key) { + return getService(DelegationService.class).read(key); + } + + public void update(final DelegationTO applicationTO) { + getService(DelegationService.class).update(applicationTO); + } + + public void create(final DelegationTO applicationTO) { + getService(DelegationService.class).create(applicationTO); + } + + public List list() { + return getService(DelegationService.class).list(); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ExecutionRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ExecutionRestClient.java index 8cde6922e6a..d8c9ff995c7 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ExecutionRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ExecutionRestClient.java @@ -20,6 +20,8 @@ import java.util.Date; import java.util.List; +import java.util.Map; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; @@ -34,4 +36,6 @@ public interface ExecutionRestClient extends RestClient { List listExecutions(String taskKey, int page, int size, SortParam sort); int countExecutions(String taskKey); + + Map batch(BatchRequest batchRequest); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java index 9ecd9ca403c..fc217844805 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java @@ -18,15 +18,13 @@ */ package org.apache.syncope.client.console.rest; -import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; import org.apache.syncope.common.lib.patch.GroupPatch; import org.apache.syncope.common.lib.to.GroupTO; -import org.apache.syncope.common.lib.to.PagedResult; import org.apache.syncope.common.lib.to.ProvisioningResult; -import org.apache.syncope.common.lib.types.BulkMembersActionType; +import org.apache.syncope.common.lib.types.ProvisionAction; import org.apache.syncope.common.rest.api.beans.AnyQuery; import org.apache.syncope.common.rest.api.service.AnyService; import org.apache.syncope.common.rest.api.service.GroupService; @@ -64,7 +62,7 @@ public ProvisioningResult update(final String etag, final GroupPatch pa @Override public int count(final String realm, final String fiql, final String type) { return getService(GroupService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(1).build()). + search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(0).details(false).build()). getTotalCount(); } @@ -77,36 +75,12 @@ public List search( final SortParam sort, final String type) { - List result = new ArrayList<>(); - PagedResult res; - do { - res = getService(GroupService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size). - orderBy(toOrderBy(sort)).details(false).build()); - result.addAll(res.getResult()); - } while (page == -1 && size == -1 && res.getNext() != null); - - return result; - } - - public PagedResult search( - final String realm, - final String fiql, - final int page, - final int size, - final SortParam sort) { - - PagedResult res; - do { - res = getService(GroupService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size). - orderBy(toOrderBy(sort)).details(false).build()); - } while (page == -1 && size == -1 && res.getNext() != null); - - return res; + return getService(GroupService.class). + search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size).details(false). + orderBy(toOrderBy(sort)).details(false).build()).getResult(); } - public void bulkMembersAction(final String key, final BulkMembersActionType actionType) { - getService(GroupService.class).bulkMembersAction(key, actionType); + public void provisionMembers(final String key, final ProvisionAction actionType) { + getService(GroupService.class).provisionMembers(key, actionType); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java index a4c7825af94..e070d5251a4 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java @@ -20,22 +20,26 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.syncope.common.lib.log.EventCategoryTO; +import org.apache.syncope.common.lib.log.AuditEntry; +import org.apache.syncope.common.lib.log.EventCategory; import org.apache.syncope.common.lib.log.LogAppender; -import org.apache.syncope.common.lib.log.LogStatementTO; +import org.apache.syncope.common.lib.log.LogStatement; import org.apache.syncope.common.lib.log.LoggerTO; +import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditLoggerName; import org.apache.syncope.common.lib.types.LoggerLevel; import org.apache.syncope.common.lib.types.LoggerType; import org.apache.syncope.common.rest.api.LoggerWrapper; +import org.apache.syncope.common.rest.api.beans.AuditQuery; import org.apache.syncope.common.rest.api.service.LoggerService; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; public class LoggerRestClient extends BaseRestClient { @@ -46,8 +50,8 @@ public List listMemoryAppenders() { map(LogAppender::getName).collect(Collectors.toList()); } - public List getLastLogStatements(final String appender, final long lastStatementTime) { - List result = new ArrayList<>(); + public List getLastLogStatements(final String appender, final long lastStatementTime) { + List result = new ArrayList<>(); getService(LoggerService.class).getLastLogStatements(appender).stream(). filter(statement -> statement.getTimeMillis() > lastStatementTime). forEachOrdered(statement -> { @@ -59,7 +63,7 @@ public List getLastLogStatements(final String appender, final lo public List listLogs() { List logs = getService(LoggerService.class).list(LoggerType.LOG); - Collections.sort(logs, (o1, o2) -> ObjectUtils.compare(o1.getKey(), o2.getKey())); + logs.sort(Comparator.comparing(LoggerTO::getKey)); return logs; } @@ -100,11 +104,54 @@ public void disableAudit(final AuditLoggerName auditLoggerName) { getService(LoggerService.class).delete(LoggerType.AUDIT, auditLoggerName.toLoggerName()); } - public List listEvents() { + public List listEvents() { try { return getService(LoggerService.class).events(); } catch (Exception e) { - return Collections.emptyList(); + return Collections.emptyList(); } } + + public List search( + final String key, + final int page, + final int size, + final AuditElements.EventCategoryType type, + final String category, + final List events, + final AuditElements.Result result, + final SortParam sort) { + + AuditQuery query = new AuditQuery.Builder(). + entityKey(key). + size(size). + page(page). + type(type). + category(category). + events(events). + result(result). + orderBy(toOrderBy(sort)). + build(); + + return getService(LoggerService.class).search(query).getResult(); + } + + public int count( + final String key, + final AuditElements.EventCategoryType type, + final String category, + final List events, + final AuditElements.Result result) { + + AuditQuery query = new AuditQuery.Builder(). + entityKey(key). + page(1). + size(0). + type(type). + category(category). + events(events). + result(result). + build(); + return getService(LoggerService.class).search(query).getTotalCount(); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java index 2d23df64403..70ebcc7f0cc 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java @@ -22,8 +22,10 @@ import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; import org.apache.syncope.common.lib.to.DynRealmTO; +import org.apache.syncope.common.lib.to.PagedResult; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.RealmTO; +import org.apache.syncope.common.rest.api.beans.RealmQuery; import org.apache.syncope.common.rest.api.service.DynRealmService; import org.apache.syncope.common.rest.api.service.RealmService; @@ -34,15 +36,19 @@ public class RealmRestClient extends BaseRestClient { private static final long serialVersionUID = -8549081557283519638L; - public List list() { - return getService(RealmService.class).list(); + public PagedResult search(final RealmQuery query) { + return getService(RealmService.class).search(query); } - public List listDynReams() { + public List list(final String fullpath) { + return getService(RealmService.class).list(fullpath); + } + + public List listDynRealms() { return getService(DynRealmService.class).list(); } - public DynRealmTO readDynReams(final String key) { + public DynRealmTO readDynRealm(final String key) { return getService(DynRealmService.class).read(key); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java index 16960def311..b9618e57764 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java @@ -18,35 +18,62 @@ */ package org.apache.syncope.client.console.rest; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.stream.Collectors; +import javax.ws.rs.core.Response; +import org.apache.cxf.jaxrs.client.Client; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.common.lib.to.PullTaskTO; import org.apache.syncope.common.lib.to.PushTaskTO; import org.apache.syncope.common.lib.to.ReconStatus; -import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.beans.AnyQuery; +import org.apache.syncope.common.rest.api.beans.CSVPullSpec; +import org.apache.syncope.common.rest.api.beans.CSVPushSpec; +import org.apache.syncope.common.rest.api.beans.ReconQuery; import org.apache.syncope.common.rest.api.service.ReconciliationService; public class ReconciliationRestClient extends BaseRestClient { private static final long serialVersionUID = -3161863874876938094L; - public ReconStatus status(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) { - return getService(ReconciliationService.class).status(anyTypeKind, anyKey, resourceKey); + public ReconStatus status(final ReconQuery reconQuery) { + return getService(ReconciliationService.class).status(reconQuery); } - public void push( - final AnyTypeKind anyTypeKind, - final String anyKey, - final String resourceKey, - final PushTaskTO pushTask) { + public void push(final ReconQuery reconQuery, final PushTaskTO pushTask) { + getService(ReconciliationService.class).push(reconQuery, pushTask); + } + + public void pull(final ReconQuery reconQuery, final PullTaskTO pullTask) { + getService(ReconciliationService.class).pull(reconQuery, pullTask); + } + + public Response push(final AnyQuery anyQuery, final CSVPushSpec spec) { + ReconciliationService service = getService(ReconciliationService.class); + Client client = WebClient.client(service); + client.accept(RESTHeaders.TEXT_CSV); - getService(ReconciliationService.class).push(anyTypeKind, anyKey, resourceKey, pushTask); + Response response = service.push(anyQuery, spec); + + SyncopeConsoleSession.get().resetClient(ReconciliationService.class); + + return response; } - public void pull( - final AnyTypeKind anyTypeKind, - final String anyKey, - final String resourceKey, - final PullTaskTO pullTask) { + public ArrayList pull(final CSVPullSpec spec, final InputStream csv) { + ReconciliationService service = getService(ReconciliationService.class); + Client client = WebClient.client(service); + client.type(RESTHeaders.TEXT_CSV); + + ArrayList result = service.pull(spec, csv).stream(). + collect(Collectors.toCollection(ArrayList::new)); + + SyncopeConsoleSession.get().resetClient(ReconciliationService.class); - getService(ReconciliationService.class).pull(anyTypeKind, anyKey, resourceKey, pullTask); + return result; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/RemediationRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/RemediationRestClient.java index a5023e5941b..bfc66597d54 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/RemediationRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/RemediationRestClient.java @@ -25,14 +25,24 @@ import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.RemediationTO; +import org.apache.syncope.common.rest.api.beans.RemediationQuery; import org.apache.syncope.common.rest.api.service.RemediationService; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; public class RemediationRestClient extends BaseRestClient { private static final long serialVersionUID = -7033745375669316378L; - public List getRemediations() { - return getService(RemediationService.class).list(); + public int countRemediations() { + return getService(RemediationService.class). + list(new RemediationQuery.Builder().page(1).size(0).build()). + getTotalCount(); + } + + public List getRemediations(final int page, final int size, final SortParam sort) { + return getService(RemediationService.class). + list(new RemediationQuery.Builder().page(page).size(size).orderBy(toOrderBy(sort)).build()). + getResult(); } public RemediationTO getRemediation(final String key) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java index e65ce7e3b3a..a2ea0dad180 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java @@ -18,16 +18,18 @@ */ package org.apache.syncope.client.console.rest; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; -import javax.ws.rs.NotSupportedException; +import java.util.Map; import javax.ws.rs.core.Response; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.common.lib.to.BulkAction; -import org.apache.syncope.common.lib.to.BulkActionResult; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.ReportTO; @@ -35,8 +37,10 @@ import org.apache.syncope.common.lib.types.JobAction; import org.apache.syncope.common.lib.types.ReportExecExportFormat; import org.apache.syncope.common.lib.types.ReportTemplateFormat; +import org.apache.syncope.common.rest.api.batch.BatchRequestItem; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; import org.apache.syncope.common.rest.api.beans.ExecQuery; -import org.apache.syncope.common.rest.api.beans.ExecuteQuery; +import org.apache.syncope.common.rest.api.beans.ExecSpecs; import org.apache.syncope.common.rest.api.service.ReportService; import org.apache.syncope.common.rest.api.service.ReportTemplateService; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; @@ -85,7 +89,7 @@ public void delete(final String reportKey) { @Override public void startExecution(final String reportKey, final Date start) { - getService(ReportService.class).execute(new ExecuteQuery.Builder().key(reportKey).startAt(start).build()); + getService(ReportService.class).execute(new ExecSpecs.Builder().key(reportKey).startAt(start).build()); } @Override @@ -106,15 +110,14 @@ public Response exportExecutionResult(final String executionKey, final ReportExe public List listExecutions( final String taskKey, final int page, final int size, final SortParam sort) { - return getService(ReportService.class). - listExecutions(new ExecQuery.Builder().key(taskKey).page(page).size(size). - orderBy(toOrderBy(sort)).build()).getResult(); + return getService(ReportService.class).listExecutions(new ExecQuery.Builder(). + key(taskKey).page(page).size(size).orderBy(toOrderBy(sort)).build()).getResult(); } @Override public int countExecutions(final String taskKey) { return getService(ReportService.class). - listExecutions(new ExecQuery.Builder().key(taskKey).page(1).size(1).build()).getTotalCount(); + listExecutions(new ExecQuery.Builder().key(taskKey).page(1).size(0).build()).getTotalCount(); } @Override @@ -144,7 +147,7 @@ public String readTemplateFormat(final String key, final ReportTemplateFormat fo getService(ReportTemplateService.class).getFormat(key, format).getEntity()), StandardCharsets.UTF_8); } catch (Exception e) { - LOG.error("Error retrieving mail template {} as {}", key, format, e); + LOG.error("Error retrieving report template {} as {}", key, format, e); return StringUtils.EMPTY; } } @@ -155,25 +158,29 @@ public void updateTemplateFormat(final String key, final String content, final R key, format, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); } - public BulkActionResult bulkAction(final BulkAction action) { - BulkActionResult result = new BulkActionResult(); + @Override + public Map batch(final BatchRequest batchRequest) { + List batchRequestItems = new ArrayList<>(batchRequest.getItems()); - switch (action.getType()) { - case DELETE: - for (String target : action.getTargets()) { - delete(target); - result.getResults().put(target, BulkActionResult.Status.SUCCESS); - } - break; - case EXECUTE: - for (String target : action.getTargets()) { - startExecution(target, null); - result.getResults().put(target, BulkActionResult.Status.SUCCESS); + Map result = new LinkedHashMap<>(); + try { + List batchResponseItems = batchRequest.commit().getItems(); + for (int i = 0; i < batchResponseItems.size(); i++) { + String status = getStatus(batchResponseItems.get(i).getStatus()); + + if (batchRequestItems.get(i).getRequestURI().contains("/execute")) { + result.put(StringUtils.substringAfterLast( + StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/execute"), "/"), + status); + } else { + result.put(StringUtils.substringAfterLast( + batchRequestItems.get(i).getRequestURI(), "/"), status); } - break; - default: - throw new NotSupportedException(action.getType().name()); + } + } catch (IOException e) { + LOG.error("While processing Batch response", e); } + return result; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceHistoryRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceHistoryRestClient.java deleted file mode 100644 index 9efb783245d..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceHistoryRestClient.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.client.console.rest; - -import java.util.Collections; -import java.util.List; -import org.apache.syncope.common.lib.to.ResourceHistoryConfTO; -import org.apache.syncope.common.rest.api.service.ResourceHistoryService; - -/** - * Console client for invoking Rest Resource configuration history services. - */ -public class ResourceHistoryRestClient extends BaseRestClient { - - private static final long serialVersionUID = -5722829010510310887L; - - public List list(final String key) { - List resHistoryConfs = Collections.emptyList(); - try { - resHistoryConfs = getService(ResourceHistoryService.class).list(key); - } catch (Exception e) { - LOG.error("While reading resource history configuration instances", e); - } - return resHistoryConfs; - } - - public void delete(final String key) { - getService(ResourceHistoryService.class).delete(key); - } - - public void restore(final String key) { - getService(ResourceHistoryService.class).restore(key); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java index e1f8f787cef..015c7bb81b5 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java @@ -18,19 +18,24 @@ */ package org.apache.syncope.client.console.rest; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; -import org.apache.syncope.common.lib.patch.ResourceDeassociationPatch; -import org.apache.syncope.common.lib.to.BulkActionResult; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.PagedConnObjectTOResult; import org.apache.syncope.common.lib.to.ResourceTO; -import org.apache.syncope.common.lib.types.ResourceDeassociationAction; -import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery; import org.apache.syncope.common.rest.api.service.ResourceService; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; @@ -41,6 +46,23 @@ public class ResourceRestClient extends BaseRestClient { private static final long serialVersionUID = -6898907679835668987L; + public static boolean check(final String coreAddress, final String domain, final String jwt, final String key) + throws IOException { + + WebClient client = WebClient.create(coreAddress).path("resources"). + accept(MediaType.APPLICATION_JSON_TYPE). + type(MediaType.APPLICATION_JSON_TYPE). + header(RESTHeaders.DOMAIN, domain). + header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt); + Response response = client.path(key).get(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + response = client.back(false).path("check"). + post(IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + return response.getStatus() == Response.Status.NO_CONTENT.getStatusCode(); + } + return false; + } + public Pair check(final ResourceTO resourceTO) { boolean check = false; String errorMessage = null; @@ -59,24 +81,35 @@ public ConnObjectTO readConnObject(final String resource, final String anyTypeKe return getService(ResourceService.class).readConnObject(resource, anyTypeKey, anyKey); } - public Pair> listConnObjects( + public String getConnObjectKeyValue(final String resource, final String anyTypeKey, final String anyKey) { + try { + Response response = getService(ResourceService.class).getConnObjectKeyValue(resource, anyTypeKey, anyKey); + if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { + return response.getHeaderString(RESTHeaders.CONNOBJECT_KEY); + } + } catch (Exception e) { + LOG.debug("Error fetching connector object key", e); + } + LOG.error("Unable to determine connector object key value for resource {}, {} and {}", + resource, anyTypeKey, anyKey); + return null; + } + + public Pair> searchConnObjects( final String resource, final String anyTypeKey, - final int size, - final String pagedResultCookie, - final SortParam sort) { - - ConnObjectTOListQuery.Builder builder = new ConnObjectTOListQuery.Builder(). - pagedResultsCookie(pagedResultCookie). - size(size). - orderBy(toOrderBy(sort)); + final ConnObjectTOQuery.Builder queryBuilder, + final SortParam sortParam) { final List result = new ArrayList<>(); String nextPageResultCookie = null; PagedConnObjectTOResult list; try { - list = getService(ResourceService.class).listConnObjects(resource, anyTypeKey, builder.build()); + if (sortParam != null) { + queryBuilder.orderBy(toOrderBy(sortParam)); + } + list = getService(ResourceService.class).searchConnObjects(resource, anyTypeKey, queryBuilder.build()); result.addAll(list.getResult()); nextPageResultCookie = list.getPagedResultsCookie(); } catch (Exception e) { @@ -94,7 +127,7 @@ public List list() { List resources = Collections.emptyList(); try { resources = getService(ResourceService.class).list(); - Collections.sort(resources, (o1, o2) -> ObjectUtils.compare(o1.getKey(), o2.getKey())); + resources.sort(Comparator.comparing(ResourceTO::getKey)); } catch (Exception e) { LOG.error("Could not fetch the Resource list", e); } @@ -116,19 +149,6 @@ public void delete(final String name) { getService(ResourceService.class).delete(name); } - public BulkActionResult bulkAssociationAction( - final String resourceName, final String anyTypeName, - final ResourceDeassociationAction action, final List anyKeys) { - - ResourceDeassociationPatch patch = new ResourceDeassociationPatch(); - patch.setKey(resourceName); - patch.setAnyTypeKey(anyTypeName); - patch.setAction(action); - patch.getAnyKyes().addAll(anyKeys); - - return getService(ResourceService.class).bulkDeassociation(patch); - } - public void setLatestSyncToken(final String key, final String anyType) { getService(ResourceService.class).setLatestSyncToken(key, anyType); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java new file mode 100644 index 00000000000..2311a598746 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResponseHolder.java @@ -0,0 +1,72 @@ +/* + * 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.client.console.rest; + +import java.io.InputStream; +import java.io.Serializable; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; + +public class ResponseHolder implements Serializable { + + private static final long serialVersionUID = 2627155013246805827L; + + private transient InputStream inputStream; + + private String contentType; + + private String location; + + private String filename; + + public ResponseHolder(final Response response) { + Object entity = response.getEntity(); + if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode() + && (entity instanceof InputStream)) { + + this.inputStream = (InputStream) entity; + this.contentType = response.getHeaderString(HttpHeaders.CONTENT_TYPE); + this.location = response.getLocation() == null ? null : response.getLocation().toASCIIString(); + String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION); + if (StringUtils.isNotBlank(contentDisposition)) { + String[] splitted = contentDisposition.split("="); + if (splitted != null && splitted.length > 1) { + this.filename = splitted[1].trim(); + } + } + } + } + + public InputStream getInputStream() { + return inputStream; + } + + public String getContentType() { + return contentType; + } + + public String getLocation() { + return location; + } + + public String getFilename() { + return filename; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java index 743bfb56666..b7ce9c45ae1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java @@ -20,9 +20,8 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.common.lib.to.RoleTO; @@ -55,10 +54,10 @@ public List list() { return getService(RoleService.class).list(); } - public String readConsoleLayoutInfo(final String roleKey) { + public String readAnyLayout(final String roleKey) { try { return IOUtils.toString(InputStream.class.cast( - getService(RoleService.class).getConsoleLayoutInfo(roleKey).getEntity()), + getService(RoleService.class).getAnyLayout(roleKey).getEntity()), StandardCharsets.UTF_8); } catch (Exception e) { LOG.error("Error retrieving console layout info for role {}", roleKey, e); @@ -66,18 +65,16 @@ public String readConsoleLayoutInfo(final String roleKey) { } } - public void setConsoleLayoutInfo(final String roleKey, final String content) { - getService(RoleService.class).setConsoleLayoutInfo( + public void setAnyLayout(final String roleKey, final String content) { + getService(RoleService.class).setAnyLayout( roleKey, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); } - public void removeConsoleLayoutInfo(final String roleKey) { - getService(RoleService.class).removeConsoleLayoutInfo(roleKey); + public void removeAnyLayout(final String roleKey) { + getService(RoleService.class).removeAnyLayout(roleKey); } public List getAllAvailableEntitlements() { - List entitlements = new ArrayList<>(getSyncopeService().platform().getEntitlements()); - Collections.sort(entitlements); - return entitlements; + return getSyncopeService().platform().getEntitlements().stream().sorted().collect(Collectors.toList()); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java index 6e75036ed6c..1462a6d611b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/SchemaRestClient.java @@ -26,10 +26,6 @@ import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.SchemaTO; import org.apache.syncope.common.lib.to.AnyTypeTO; -import org.apache.syncope.common.lib.to.DerSchemaTO; -import org.apache.syncope.common.lib.to.EntityTO; -import org.apache.syncope.common.lib.to.PlainSchemaTO; -import org.apache.syncope.common.lib.to.VirSchemaTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.syncope.common.rest.api.beans.SchemaQuery; @@ -44,14 +40,14 @@ public class SchemaRestClient extends BaseRestClient { private static final long serialVersionUID = -2479730152700312373L; public List getSchemas(final SchemaType schemaType, final AnyTypeKind kind) { - final AnyTypeService client = getService(AnyTypeService.class); + AnyTypeService client = getService(AnyTypeService.class); - final List classes = new ArrayList<>(); + List classes = new ArrayList<>(); switch (kind) { case USER: case GROUP: - final AnyTypeTO type = client.read(kind.name()); + AnyTypeTO type = client.read(kind.name()); if (type != null) { classes.addAll(type.getClasses()); } @@ -59,12 +55,11 @@ public List getSchemas(final SchemaType schemaType, fina default: new AnyTypeRestClient().listAnyTypes().stream().filter( - anyTypeTO -> (anyTypeTO.getKind() != AnyTypeKind.USER - && anyTypeTO.getKind() != AnyTypeKind.GROUP)). - forEach((anyTypeTO) -> { - classes.addAll(anyTypeTO.getClasses()); - }); + anyType -> anyType.getKind() != AnyTypeKind.USER && anyType.getKind() != AnyTypeKind.GROUP). + forEach(anyType -> classes.addAll(anyType.getClasses())); + } + return getSchemas(schemaType, null, classes.toArray(new String[] {})); } @@ -93,7 +88,7 @@ public List getSchemaNames(final SchemaType schemaType) { try { schemaNames = getSchemas(schemaType, null, new String[0]).stream(). - map(EntityTO::getKey).collect(Collectors.toList()); + map(SchemaTO::getKey).collect(Collectors.toList()); } catch (SyncopeClientException e) { LOG.error("While getting all user schema names", e); } @@ -101,19 +96,7 @@ public List getSchemaNames(final SchemaType schemaType) { return schemaNames; } - public List getPlainSchemaNames() { - return getSchemaNames(SchemaType.PLAIN); - } - - public List getDerSchemaNames() { - return getSchemaNames(SchemaType.DERIVED); - } - - public List getVirSchemaNames() { - return getSchemaNames(SchemaType.VIRTUAL); - } - - public PlainSchemaTO read(final SchemaType schemaType, final String key) { + public T read(final SchemaType schemaType, final String key) { return getService(SchemaService.class).read(schemaType, key); } @@ -126,21 +109,7 @@ public void update(final SchemaType schemaType, final SchemaTO modelObject) { getService(SchemaService.class).update(schemaType, modelObject); } - public PlainSchemaTO deletePlainSchema(final String name) { - PlainSchemaTO response = getService(SchemaService.class).read(SchemaType.PLAIN, name); - getService(SchemaService.class).delete(SchemaType.PLAIN, name); - return response; - } - - public DerSchemaTO deleteDerSchema(final String name) { - DerSchemaTO schemaTO = getService(SchemaService.class).read(SchemaType.DERIVED, name); - getService(SchemaService.class).delete(SchemaType.DERIVED, name); - return schemaTO; - } - - public VirSchemaTO deleteVirSchema(final String name) { - VirSchemaTO schemaTO = getService(SchemaService.class).read(SchemaType.VIRTUAL, name); - getService(SchemaService.class).delete(SchemaType.VIRTUAL, name); - return schemaTO; + public void delete(final SchemaType schemaType, final String key) { + getService(SchemaService.class).delete(schemaType, key); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java index b479894cee0..b68a935c4b8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java @@ -18,8 +18,10 @@ */ package org.apache.syncope.client.console.rest; +import java.util.List; import org.apache.syncope.common.lib.info.NumbersInfo; import org.apache.syncope.common.lib.info.SystemInfo; +import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.rest.api.service.SyncopeService; public class SyncopeRestClient extends BaseRestClient { @@ -34,4 +36,12 @@ public SystemInfo system() { return getService(SyncopeService.class).system(); } + public List searchAssignableGroups( + final String realm, + final String term, + final int page, + final int size) { + + return getService(SyncopeService.class).searchAssignableGroups(realm, term, page, size).getResult(); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java index 516ef7c5d60..ed1f0781033 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java @@ -18,11 +18,15 @@ */ package org.apache.syncope.client.console.rest; +import java.io.IOException; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.lib.batch.BatchRequest; import org.apache.syncope.common.lib.to.TaskTO; -import org.apache.syncope.common.lib.to.BulkAction; -import org.apache.syncope.common.lib.to.BulkActionResult; import org.apache.syncope.common.lib.to.NotificationTaskTO; import org.apache.syncope.common.lib.to.PropagationTaskTO; import org.apache.syncope.common.lib.to.PushTaskTO; @@ -34,7 +38,9 @@ import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.JobAction; import org.apache.syncope.common.lib.types.TaskType; -import org.apache.syncope.common.rest.api.beans.ExecuteQuery; +import org.apache.syncope.common.rest.api.batch.BatchRequestItem; +import org.apache.syncope.common.rest.api.batch.BatchResponseItem; +import org.apache.syncope.common.rest.api.beans.ExecSpecs; import org.apache.syncope.common.rest.api.beans.ExecQuery; import org.apache.syncope.common.rest.api.beans.TaskQuery; import org.apache.syncope.common.rest.api.service.TaskService; @@ -61,31 +67,31 @@ public void actionJob(final String refKey, final JobAction jobAction) { public int count(final TaskType kind) { return getService(TaskService.class).search( - new TaskQuery.Builder(kind).page(1).size(1).build()).getTotalCount(); + new TaskQuery.Builder(kind).page(1).size(0).build()).getTotalCount(); } public int count(final String resource, final TaskType kind) { return getService(TaskService.class).search( - new TaskQuery.Builder(kind).resource(resource).page(1).size(1).build()).getTotalCount(); + new TaskQuery.Builder(kind).resource(resource).page(1).size(0).build()).getTotalCount(); } public int count(final AnyTypeKind anyTypeKind, final String entityKey, final TaskType kind) { return getService(TaskService.class).search( - new TaskQuery.Builder(kind).anyTypeKind(anyTypeKind).entityKey(entityKey).page(1).size(1).build()). + new TaskQuery.Builder(kind).anyTypeKind(anyTypeKind).entityKey(entityKey).page(1).size(0).build()). getTotalCount(); } public int count(final AnyTypeKind anyTypeKind, final String entityKey, final String notification) { return getService(TaskService.class).search( new TaskQuery.Builder(TaskType.NOTIFICATION).notification(notification). - anyTypeKind(anyTypeKind).entityKey(entityKey).page(1).size(1).build()). + anyTypeKind(anyTypeKind).entityKey(entityKey).page(1).size(0).build()). getTotalCount(); } @Override public int countExecutions(final String taskKey) { return getService(TaskService.class). - listExecutions(new ExecQuery.Builder().key(taskKey).page(1).size(1).build()).getTotalCount(); + listExecutions(new ExecQuery.Builder().key(taskKey).page(1).size(0).build()).getTotalCount(); } public List listPropagationTasks( @@ -207,7 +213,7 @@ public void startExecution(final String taskKey, final Date start) { public void startExecution(final String taskKey, final Date start, final boolean dryRun) { getService(TaskService.class).execute( - new ExecuteQuery.Builder().key(taskKey).startAt(start).dryRun(dryRun).build()); + new ExecSpecs.Builder().key(taskKey).startAt(start).dryRun(dryRun).build()); } @Override @@ -228,7 +234,29 @@ public void update(final TaskType type, final SchedTaskTO taskTO) { getService(TaskService.class).update(type, taskTO); } - public BulkActionResult bulkAction(final BulkAction action) { - return getService(TaskService.class).bulk(action); + @Override + public Map batch(final BatchRequest batchRequest) { + List batchRequestItems = new ArrayList<>(batchRequest.getItems()); + + Map result = new LinkedHashMap<>(); + try { + List batchResponseItems = batchRequest.commit().getItems(); + for (int i = 0; i < batchResponseItems.size(); i++) { + String status = getStatus(batchResponseItems.get(i).getStatus()); + + if (batchRequestItems.get(i).getRequestURI().contains("/execute")) { + result.put(StringUtils.substringAfterLast( + StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/execute"), "/"), + status); + } else { + result.put(StringUtils.substringAfterLast( + batchRequestItems.get(i).getRequestURI(), "/"), status); + } + } + } catch (IOException e) { + LOG.error("While processing Batch response", e); + } + + return result; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/TemplateRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/TemplateRestClient.java index d3d5c355734..356d4802baa 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/TemplateRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/TemplateRestClient.java @@ -24,13 +24,13 @@ public interface TemplateRestClient extends RestClient { List listTemplates(); - void createTemplate(final T templateTO); + void createTemplate(T templateTO); - void deleteTemplate(final String key); + void deleteTemplate(String key); - T readTemplate(final String key); + T readTemplate(String key); - String readTemplateFormat(final String key, final F format); + String readTemplateFormat(String key, F format); - void updateTemplateFormat(final String key, final String content, final F format); + void updateTemplateFormat(String key, String content, F format); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java index 514d970e9de..a270da1c793 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java @@ -18,20 +18,21 @@ */ package org.apache.syncope.client.console.rest; +import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.status.Status; import org.apache.syncope.client.console.commons.status.StatusBean; import org.apache.syncope.client.console.commons.status.StatusUtils; import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem; import org.apache.syncope.common.lib.patch.StatusPatch; import org.apache.syncope.common.lib.patch.UserPatch; -import org.apache.syncope.common.lib.to.BulkActionResult; -import org.apache.syncope.common.lib.to.PropagationStatus; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.ExecStatus; import org.apache.syncope.common.lib.types.StatusPatchType; import org.apache.syncope.common.rest.api.beans.AnyQuery; import org.apache.syncope.common.rest.api.service.AnyService; @@ -70,7 +71,7 @@ public ProvisioningResult update(final String etag, final UserPatch patc @Override public int count(final String realm, final String fiql, final String type) { return getService(UserService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(1).build()). + search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(1).size(0).details(false).build()). getTotalCount(); } @@ -80,74 +81,75 @@ public List search( final String type) { return getService(UserService.class). - search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size). + search(new AnyQuery.Builder().realm(realm).fiql(fiql).page(page).size(size).details(false). orderBy(toOrderBy(sort)).details(false).build()).getResult(); } public ProvisioningResult mustChangePassword(final String etag, final boolean value, final String key) { - final UserPatch userPatch = new UserPatch(); + UserPatch userPatch = new UserPatch(); userPatch.setKey(key); userPatch.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(value).build()); return update(etag, userPatch); } - public BulkActionResult suspend(final String etag, final String userKey, final List statuses) { - StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses, false); - statusPatch.setKey(userKey); - statusPatch.setType(StatusPatchType.SUSPEND); + private Map status( + final StatusPatchType type, final String etag, final String userKey, final List statuses) { - BulkActionResult bulkActionResult; + StatusPatch statusPatch = StatusUtils.statusPatch(statuses).key(userKey).type(type).build(); + + Map results; synchronized (this) { - bulkActionResult = new BulkActionResult(); - Map results = bulkActionResult.getResults(); - UserService service = getService(etag, UserService.class); + ProvisioningResult provisioningResult = getService(etag, UserService.class).status(statusPatch). + readEntity(new GenericType>() { + }); - ProvisioningResult provisioningResult = service.status(statusPatch).readEntity( - new GenericType>() { + statuses.forEach(statusBean -> statusBean.setStatus(Status.UNDEFINED)); + + results = new HashMap<>(); + provisioningResult.getPropagationStatuses().forEach(propagationStatus -> { + results.put(propagationStatus.getResource(), propagationStatus.getStatus().name()); + + if (propagationStatus.getAfterObj() != null) { + Boolean enabled = StatusUtils.isEnabled(propagationStatus.getAfterObj()); + if (enabled != null) { + statuses.stream(). + filter(statusBean -> propagationStatus.getResource().equals(statusBean.getResource())). + findFirst(). + ifPresent(statusBean -> statusBean.setStatus( + enabled ? Status.ACTIVE : Status.SUSPENDED)); + } + } }); - + statuses.stream(). + filter(statusBean -> Constants.SYNCOPE.equals(statusBean.getResource())). + findFirst(). + ifPresent(statusBean -> statusBean.setStatus( + "suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus()) + ? Status.SUSPENDED : Status.ACTIVE)); if (statusPatch.isOnSyncope()) { results.put(Constants.SYNCOPE, - "suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus()) - ? BulkActionResult.Status.SUCCESS - : BulkActionResult.Status.FAILURE); + ("suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus()) + && type == StatusPatchType.SUSPEND) + || ("active".equalsIgnoreCase(provisioningResult.getEntity().getStatus()) + && type == StatusPatchType.REACTIVATE) + ? ExecStatus.SUCCESS.name() + : ExecStatus.FAILURE.name()); } - for (PropagationStatus status : provisioningResult.getPropagationStatuses()) { - results.put(status.getResource(), BulkActionResult.Status.valueOf(status.getStatus().name())); - } resetClient(UserService.class); } - return bulkActionResult; + return results; } - public BulkActionResult reactivate(final String etag, final String userKey, final List statuses) { - StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses, true); - statusPatch.setKey(userKey); - statusPatch.setType(StatusPatchType.REACTIVATE); - - BulkActionResult bulkActionResult; - synchronized (this) { - bulkActionResult = new BulkActionResult(); - Map results = bulkActionResult.getResults(); - UserService service = getService(etag, UserService.class); + public Map suspend( + final String etag, final String userKey, final List statuses) { - ProvisioningResult provisioningResult = service.status(statusPatch).readEntity( - new GenericType>() { - }); + return status(StatusPatchType.SUSPEND, etag, userKey, statuses); + } - if (statusPatch.isOnSyncope()) { - results.put(Constants.SYNCOPE, - "active".equalsIgnoreCase(provisioningResult.getEntity().getStatus()) - ? BulkActionResult.Status.SUCCESS - : BulkActionResult.Status.FAILURE); - } + public Map reactivate( + final String etag, final String userKey, final List statuses) { - for (PropagationStatus status : provisioningResult.getPropagationStatuses()) { - results.put(status.getResource(), BulkActionResult.Status.valueOf(status.getStatus().name())); - } - resetClient(UserService.class); - } - return bulkActionResult; + return status(StatusPatchType.REACTIVATE, etag, userKey, statuses); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserWorkflowRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserWorkflowRestClient.java deleted file mode 100644 index fc42f9bb879..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserWorkflowRestClient.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.client.console.rest; - -import java.util.List; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.syncope.common.rest.api.service.UserWorkflowService; - -public class UserWorkflowRestClient extends BaseRestClient { - - private static final long serialVersionUID = -4785231164900813921L; - - public List getForms() { - return getService(UserWorkflowService.class).getForms(); - } - - public WorkflowFormTO getFormForUser(final String userKey) { - return getService(UserWorkflowService.class).getFormForUser(userKey); - } - - public WorkflowFormTO claimForm(final String taskKey) { - return getService(UserWorkflowService.class).claimForm(taskKey); - } - - public void submitForm(final WorkflowFormTO form) { - getService(UserWorkflowService.class).submitForm(form); - } - - public UserTO executeTask(final String taskId, final UserTO form) { - return getService(UserWorkflowService.class).executeTask(taskId, form); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/WorkflowRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/WorkflowRestClient.java deleted file mode 100644 index 14711a4a6ef..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/rest/WorkflowRestClient.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.client.console.rest; - -import java.io.InputStream; -import java.util.List; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import org.apache.cxf.helpers.IOUtils; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; -import org.apache.syncope.common.rest.api.RESTHeaders; -import org.apache.syncope.common.rest.api.service.WorkflowService; - -public class WorkflowRestClient extends BaseRestClient { - - private static final long serialVersionUID = 5049285686167071017L; - - private WorkflowService getService(final MediaType mediaType) { - return SyncopeConsoleSession.get().getService(mediaType, WorkflowService.class); - } - - public List getDefinitions() { - return getService(WorkflowService.class).list(AnyTypeKind.USER.name()); - } - - public InputStream getDefinition(final MediaType mediaType, final String key) { - Response response = getService(mediaType).get(AnyTypeKind.USER.name(), key); - - return (InputStream) response.getEntity(); - } - - public byte[] getDiagram(final String key) { - WorkflowService service = getService(WorkflowService.class); - WebClient.client(service).accept(RESTHeaders.MEDIATYPE_IMAGE_PNG); - Response response = service.exportDiagram(AnyTypeKind.USER.name(), key); - - byte[] diagram; - try { - diagram = IOUtils.readBytesFromStream((InputStream) response.getEntity()); - } catch (Exception e) { - LOG.error("Could not get workflow diagram", e); - diagram = new byte[0]; - } - return diagram; - } - - public void setDefinition(final MediaType mediaType, final String key, final String definition) { - getService(mediaType).set(AnyTypeKind.USER.name(), key, definition); - } - - public void deleteDefinition(final String key) { - getService(WorkflowService.class).delete(AnyTypeKind.USER.name(), key); - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java index bd54f000866..5f80e1940e8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; @@ -34,6 +33,7 @@ import org.apache.syncope.client.console.commons.status.StatusUtils; import org.apache.syncope.client.console.panels.DirectoryPanel; import org.apache.syncope.client.console.panels.AjaxDataTablePanel; +import org.apache.syncope.client.console.panels.LinkedAccountsStatusModalPanel; import org.apache.syncope.client.console.panels.ModalPanel; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.AbstractAnyRestClient; @@ -64,6 +64,7 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; public class AnyStatusDirectoryPanel @@ -193,7 +194,7 @@ public ActionsPanel getActions(final IModel model) { @Override public void onClick(final AjaxRequestTarget target, final StatusBean bean) { multiLevelPanelRef.next(bean.getResource(), - new ReconStatusPanel(bean.getResource(), anyTypeKind, anyTO.getKey()), + new ReconStatusPanel(bean.getResource(), anyTO.getType(), anyTO.getKey()), target); target.add(multiLevelPanelRef); AnyStatusDirectoryPanel.this.getTogglePanel().close(target); @@ -212,8 +213,9 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { new ReconTaskPanel( bean.getResource(), new PushTaskTO(), - anyTypeKind, + anyTO.getType(), anyTO.getKey(), + true, multiLevelPanelRef, pageRef), target); @@ -232,8 +234,9 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { new ReconTaskPanel( bean.getResource(), new PullTaskTO(), - anyTypeKind, + anyTO.getType(), anyTO.getKey(), + true, multiLevelPanelRef, pageRef), target); @@ -243,42 +246,69 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { }, ActionLink.ActionType.RECONCILIATION_PULL, StandardEntitlement.TASK_EXECUTE); } + if (anyTO instanceof UserTO && !UserTO.class.cast(anyTO).getLinkedAccounts().isEmpty()) { + UserTO userTO = UserTO.class.cast(anyTO); + + if (!userTO.getLinkedAccounts().isEmpty() + && userTO.getLinkedAccounts().stream().anyMatch(linkedAccountTO -> { + return linkedAccountTO.getResource().equals(model.getObject().getResource()); + })) { + + panel.add(new ActionLink() { + + private static final long serialVersionUID = 5168094747477174155L; + + @Override + public void onClick(final AjaxRequestTarget target, final StatusBean bean) { + multiLevelPanelRef.next("ACCOUNTS", + new LinkedAccountsStatusModalPanel( + baseModal, Model.of(UserTO.class.cast(anyTO)), pageRef), + target); + target.add(multiLevelPanelRef); + AnyStatusDirectoryPanel.this.getTogglePanel().close(target); + } + }, ActionLink.ActionType.MANAGE_ACCOUNTS, + String.format("%s,%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE, + StandardEntitlement.RESOURCE_GET_CONNOBJECT)); + } + } + return panel; } @Override - protected Collection getBulkActions() { - List bulkActions = new ArrayList<>(); + protected Collection getBatches() { + List batches = new ArrayList<>(); if (statusOnly) { - bulkActions.add(ActionLink.ActionType.SUSPEND); - bulkActions.add(ActionLink.ActionType.REACTIVATE); + batches.add(ActionLink.ActionType.SUSPEND); + batches.add(ActionLink.ActionType.REACTIVATE); } else { - bulkActions.add(ActionLink.ActionType.UNLINK); - bulkActions.add(ActionLink.ActionType.LINK); - bulkActions.add(ActionLink.ActionType.DEPROVISION); - bulkActions.add(ActionLink.ActionType.PROVISION); - bulkActions.add(ActionLink.ActionType.ASSIGN); - bulkActions.add(ActionLink.ActionType.UNASSIGN); + batches.add(ActionLink.ActionType.UNLINK); + batches.add(ActionLink.ActionType.LINK); + batches.add(ActionLink.ActionType.DEPROVISION); + batches.add(ActionLink.ActionType.PROVISION); + batches.add(ActionLink.ActionType.ASSIGN); + batches.add(ActionLink.ActionType.UNASSIGN); } - return bulkActions; + return batches; } @Override protected AnyStatusProvider dataProvider() { - return new AnyStatusProvider(); + return new AnyStatusProvider(rows); } @Override protected String paginatorRowsKey() { - return StringUtils.EMPTY; + return Constants.PREF_RESOURCE_STATUS_PAGINATOR_ROWS; } protected class AnyStatusProvider extends AbstractStatusBeanProvider { private static final long serialVersionUID = 4586969457669796621L; - AnyStatusProvider() { - super("resource"); + AnyStatusProvider(final int paginatorRows) { + super(paginatorRows, "resource"); } @Override @@ -289,8 +319,7 @@ protected List getStatusBeans(final long first, final long count) { List statusBeans = actual.getResources().stream().map(resource -> { List> statuses = Collections.emptyList(); if (statusOnly) { - statuses = StatusUtils. - getReconStatuses(anyTypeKind, anyTO.getKey(), Arrays.asList(resource)); + statuses = StatusUtils.getReconStatuses(anyTO.getType(), anyTO.getKey(), Arrays.asList(resource)); } return StatusUtils.getStatusBean( @@ -332,7 +361,7 @@ protected List getStatusBeans(final long first, final long count) { statusBeans.add(0, syncope); } else { statusBeans.addAll(resources.stream(). - filter(resource -> !anyTO.getResources().contains(resource)). + filter(resource -> !actual.getResources().contains(resource)). map(resource -> { StatusBean statusBean = StatusUtils.getStatusBean( actual, diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java b/client/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java index 55e3381f26f..fbb6528938a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java @@ -94,15 +94,13 @@ public void onSubmit(final AjaxRequestTarget target) { patch.setPassword(passwordPatch); userRestClient.update(inner.getETagValue(), patch); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.show(false); modal.close(target); } } catch (Exception e) { LOG.error("While updating password for user {}", inner, e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } super.onSubmit(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/LinkedAccountStatusPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/LinkedAccountStatusPanel.java new file mode 100644 index 00000000000..959e3cb50cd --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/LinkedAccountStatusPanel.java @@ -0,0 +1,67 @@ +/* + * 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.client.console.status; + +import java.util.Optional; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.status.StatusUtils; +import org.apache.syncope.client.console.panels.RemoteObjectPanel; +import org.apache.syncope.client.console.wizards.any.ConnObjectPanel; +import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.common.lib.to.ReconStatus; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; + +public class LinkedAccountStatusPanel extends RemoteObjectPanel { + + private static final long serialVersionUID = 7662852503618434902L; + + private final String resource; + + private final String anyTypeKey; + + private final String connObjectKeyValue; + + public LinkedAccountStatusPanel( + final String resource, + final String anyTypeKey, + final String connObjectKeyValue) { + + this.resource = resource; + this.anyTypeKey = anyTypeKey; + this.connObjectKeyValue = connObjectKeyValue; + + add(new ConnObjectPanel( + REMOTE_OBJECT_PANEL_ID, + Pair., IModel>of(Model.of(Constants.SYNCOPE), new ResourceModel("resource")), + getConnObjectTOs(), + false)); + } + + @Override + protected Pair getConnObjectTOs() { + Optional status = StatusUtils.getReconStatus(anyTypeKey, connObjectKeyValue, resource); + + return status.isPresent() + ? Pair.of(status.get().getOnSyncope(), status.get().getOnResource()) + : null; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconStatusPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconStatusPanel.java index 2f05ce77cf5..9d2eee67556 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconStatusPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconStatusPanel.java @@ -27,7 +27,6 @@ import org.apache.syncope.client.console.wizards.any.ConnObjectPanel; import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.ReconStatus; -import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; @@ -38,17 +37,17 @@ public class ReconStatusPanel extends RemoteObjectPanel { private final String resource; - private final AnyTypeKind anyTypeKind; + private final String anyTypeKey; private final String anyKey; public ReconStatusPanel( final String resource, - final AnyTypeKind anyTypeKind, + final String anyTypeKey, final String anyKey) { this.resource = resource; - this.anyTypeKind = anyTypeKind; + this.anyTypeKey = anyTypeKey; this.anyKey = anyKey; add(new ConnObjectPanel( @@ -61,7 +60,7 @@ public ReconStatusPanel( @Override protected Pair getConnObjectTOs() { List> statuses = - StatusUtils.getReconStatuses(anyTypeKind, anyKey, Arrays.asList(resource)); + StatusUtils.getReconStatuses(anyTypeKey, anyKey, Arrays.asList(resource)); return statuses.isEmpty() ? null diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java index ba08b8d5fb0..32c6b2fd6cc 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java @@ -19,29 +19,39 @@ package org.apache.syncope.client.console.status; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.ImplementationRestClient; +import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.rest.ReconciliationRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.ProvisioningTaskTO; import org.apache.syncope.common.lib.to.PullTaskTO; import org.apache.syncope.common.lib.to.PushTaskTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.lib.types.ImplementationType; import org.apache.syncope.common.lib.types.MatchingRule; import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.common.rest.api.beans.ReconQuery; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; @@ -57,10 +67,12 @@ public class ReconTaskPanel extends MultilevelPanel.SecondLevel { protected static final Logger LOG = LoggerFactory.getLogger(ReconTaskPanel.class); - private final ReconciliationRestClient restClient = new ReconciliationRestClient(); - private final ImplementationRestClient implRestClient = new ImplementationRestClient(); + private final RealmRestClient realmRestClient = new RealmRestClient(); + + private final ReconciliationRestClient reconRestClient = new ReconciliationRestClient(); + private final IModel> pullActions = new LoadableDetachableModel>() { private static final long serialVersionUID = 5275935387613157437L; @@ -86,52 +98,107 @@ protected List load() { public ReconTaskPanel( final String resource, final ProvisioningTaskTO taskTO, - final AnyTypeKind anyTypeKind, + final String anyType, final String anyKey, + final boolean isOnSyncope, + final MultilevelPanel multiLevelPanelRef, + final PageReference pageRef) { + + this(resource, taskTO, anyType, anyKey, null, isOnSyncope, multiLevelPanelRef, pageRef); + } + + public ReconTaskPanel( + final String resource, + final ProvisioningTaskTO taskTO, + final String anyType, + final String anyKey, + final String fiql, + final boolean isOnSyncope, final MultilevelPanel multiLevelPanelRef, final PageReference pageRef) { Form form = new Form<>("form", new CompoundPropertyModel<>(taskTO)); add(form); + if (taskTO instanceof PushTaskTO) { + form.add(new Label("realm", "")); + form.add(new Label("remediation", "")); + } else { + boolean isSearchEnabled = RealmsUtils.isSearchEnabled(); + AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(!isSearchEnabled); + settings.setShowListOnEmptyInput(!isSearchEnabled); + + AjaxSearchFieldPanel realm = new AjaxSearchFieldPanel( + "realm", "destinationRealm", new PropertyModel(taskTO, "destinationRealm"), settings) { + + private static final long serialVersionUID = -6390474600233486704L; + + @Override + protected Iterator getChoices(final String input) { + return (RealmsUtils.checkInput(input) + ? (isSearchEnabled + ? realmRestClient.search(RealmsUtils.buildQuery(input)).getResult() + : realmRestClient.list(SyncopeConstants.ROOT_REALM)) + : Collections.emptyList()).stream(). + sorted(Comparator.comparing(RealmTO::getName)). + map(RealmTO::getFullPath).collect(Collectors.toList()).iterator(); + } + }; + + realm.addRequiredLabel(); + realm.setOutputMarkupId(true); + // add a default destination realm if missing in the task + if (StringUtils.isBlank(PullTaskTO.class.cast(taskTO).getDestinationRealm())) { + realm.getField().setModelObject(SyncopeConstants.ROOT_REALM); + } + if (isOnSyncope) { + realm.setEnabled(false); + } + form.add(realm); + + form.add(new AjaxCheckBoxPanel( + "remediation", "remediation", new PropertyModel<>(taskTO, "remediation"), false)); + } + AjaxPalettePanel actions = new AjaxPalettePanel.Builder(). setAllowMoveAll(true).setAllowOrder(true). build("actions", - new PropertyModel>(taskTO, "actions"), + new PropertyModel<>(taskTO, "actions"), new ListModel<>(taskTO instanceof PushTaskTO ? pushActions.getObject() : pullActions.getObject())); actions.setOutputMarkupId(true); form.add(actions); AjaxDropDownChoicePanel matchingRule = new AjaxDropDownChoicePanel<>( - "matchingRule", "matchingRule", new PropertyModel(taskTO, "matchingRule"), false); + "matchingRule", "matchingRule", new PropertyModel<>(taskTO, "matchingRule"), false); matchingRule.setChoices(Arrays.asList(MatchingRule.values())); form.add(matchingRule); AjaxDropDownChoicePanel unmatchingRule = new AjaxDropDownChoicePanel<>( - "unmatchingRule", "unmatchingRule", new PropertyModel(taskTO, "unmatchingRule"), + "unmatchingRule", "unmatchingRule", new PropertyModel<>(taskTO, "unmatchingRule"), false); unmatchingRule.setChoices(Arrays.asList(UnmatchingRule.values())); form.add(unmatchingRule); taskTO.setPerformCreate(true); AjaxCheckBoxPanel performCreate = new AjaxCheckBoxPanel( - "performCreate", "performCreate", new PropertyModel(taskTO, "performCreate"), false); + "performCreate", "performCreate", new PropertyModel<>(taskTO, "performCreate"), false); form.add(performCreate); taskTO.setPerformUpdate(true); AjaxCheckBoxPanel performUpdate = new AjaxCheckBoxPanel( - "performUpdate", "performUpdate", new PropertyModel(taskTO, "performUpdate"), false); + "performUpdate", "performUpdate", new PropertyModel<>(taskTO, "performUpdate"), false); form.add(performUpdate); taskTO.setPerformDelete(true); AjaxCheckBoxPanel performDelete = new AjaxCheckBoxPanel( - "performDelete", "performDelete", new PropertyModel(taskTO, "performDelete"), false); + "performDelete", "performDelete", new PropertyModel<>(taskTO, "performDelete"), false); form.add(performDelete); taskTO.setSyncStatus(true); AjaxCheckBoxPanel syncStatus = new AjaxCheckBoxPanel( - "syncStatus", "syncStatus", new PropertyModel(taskTO, "syncStatus"), false); + "syncStatus", "syncStatus", new PropertyModel<>(taskTO, "syncStatus"), false); form.add(syncStatus); form.add(new AjaxSubmitLink("reconcile") { @@ -140,19 +207,19 @@ public ReconTaskPanel( @Override protected void onSubmit(final AjaxRequestTarget target) { + ReconQuery reconQuery = new ReconQuery.Builder(anyType, resource).anyKey(anyKey).fiql(fiql).build(); try { if (taskTO instanceof PushTaskTO) { - restClient.push(anyTypeKind, anyKey, resource, (PushTaskTO) form.getModelObject()); + reconRestClient.push(reconQuery, (PushTaskTO) form.getModelObject()); } else { - restClient.pull(anyTypeKind, anyKey, resource, (PullTaskTO) form.getModelObject()); + reconRestClient.pull(reconQuery, (PullTaskTO) form.getModelObject()); } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { - LOG.error("While attempting reconciliation on {} {} {} {}", - anyTypeKind, anyKey, resource, form.getModelObject(), e); - SyncopeConsoleSession.get().error(resource + ": " - + (StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage())); + LOG.error("While attempting reconciliation using query {} on {}", + reconQuery, form.getModelObject(), e); + SyncopeConsoleSession.get().onException(e); } multiLevelPanelRef.prev(target); ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java index 8b621bc4ce5..39c1770f6f0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; import org.apache.syncope.client.console.commons.status.AbstractStatusBeanProvider; import org.apache.syncope.client.console.commons.status.StatusBean; @@ -100,23 +101,6 @@ protected List> getColumns() { return columns; } - private AnyTypeKind getAnyTypeKind() { - if (StringUtils.isBlank(type)) { - return null; - } - - switch (type) { - case "USER": - return AnyTypeKind.USER; - - case "GROUP": - return AnyTypeKind.GROUP; - - default: - return AnyTypeKind.ANY_OBJECT; - } - } - @Override public ActionsPanel getActions(final IModel model) { final ActionsPanel panel = super.getActions(model); @@ -127,13 +111,13 @@ public ActionsPanel getActions(final IModel model) { @Override protected boolean statusCondition(final StatusBean bean) { - return getAnyTypeKind() != null; + return StringUtils.isNotBlank(type); } @Override public void onClick(final AjaxRequestTarget target, final StatusBean bean) { multiLevelPanelRef.next(bean.getResource(), - new ReconStatusPanel(bean.getResource(), getAnyTypeKind(), bean.getKey()), + new ReconStatusPanel(bean.getResource(), type, bean.getKey()), target); target.add(multiLevelPanelRef); getTogglePanel().close(target); @@ -146,7 +130,7 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { @Override protected boolean statusCondition(final StatusBean bean) { - return getAnyTypeKind() != null; + return StringUtils.isNotBlank(type); } @Override @@ -155,8 +139,9 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { new ReconTaskPanel( bean.getResource(), new PushTaskTO(), - getAnyTypeKind(), + type, bean.getKey(), + true, multiLevelPanelRef, pageRef), target); @@ -171,7 +156,7 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { @Override protected boolean statusCondition(final StatusBean bean) { - return getAnyTypeKind() != null; + return StringUtils.isNotBlank(type); } @Override @@ -180,8 +165,9 @@ public void onClick(final AjaxRequestTarget target, final StatusBean bean) { new ReconTaskPanel( bean.getResource(), new PullTaskTO(), - getAnyTypeKind(), + type, bean.getKey(), + true, multiLevelPanelRef, pageRef), target); @@ -215,25 +201,25 @@ public void updateResultTable(final String type, final AjaxRequestTarget target) } @Override - protected Collection getBulkActions() { - List bulkActions = new ArrayList<>(); - bulkActions.add(ActionLink.ActionType.UNLINK); - bulkActions.add(ActionLink.ActionType.LINK); - bulkActions.add(ActionLink.ActionType.DEPROVISION); - bulkActions.add(ActionLink.ActionType.PROVISION); - bulkActions.add(ActionLink.ActionType.ASSIGN); - bulkActions.add(ActionLink.ActionType.UNASSIGN); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionLink.ActionType.UNLINK); + batches.add(ActionLink.ActionType.LINK); + batches.add(ActionLink.ActionType.DEPROVISION); + batches.add(ActionLink.ActionType.PROVISION); + batches.add(ActionLink.ActionType.ASSIGN); + batches.add(ActionLink.ActionType.UNASSIGN); + return batches; } @Override protected ResourceStatusDataProvider dataProvider() { - return new ResourceStatusDataProvider(); + return new ResourceStatusDataProvider(rows); } @Override protected String paginatorRowsKey() { - return StringUtils.EMPTY; + return Constants.PREF_RECONCILIATION_PAGINATOR_ROWS; } protected class ResourceStatusDataProvider extends AbstractStatusBeanProvider { @@ -244,14 +230,14 @@ protected class ResourceStatusDataProvider extends AbstractStatusBeanProvider { private final AbstractAnyRestClient restClient; - public ResourceStatusDataProvider() { - super(AnyTypeKind.USER.name().equals(type) ? "username" : "name"); + public ResourceStatusDataProvider(final int paginatorRows) { + super(paginatorRows, AnyTypeKind.USER.name().equals(type) ? "username" : "name"); if (StringUtils.isEmpty(type)) { fiql = null; restClient = null; } else { - AbstractFiqlSearchConditionBuilder bld; + AbstractFiqlSearchConditionBuilder bld; switch (type) { case "USER": bld = SyncopeClient.getUserSearchConditionBuilder(); @@ -267,7 +253,7 @@ public ResourceStatusDataProvider() { bld = SyncopeClient.getAnyObjectSearchConditionBuilder(type); restClient = new AnyObjectRestClient(); } - fiql = bld.isNotNull("key").query(); + fiql = bld.isNotNull(Constants.KEY_FIELD_NAME).query(); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/StatusModal.java b/client/console/src/main/java/org/apache/syncope/client/console/status/StatusModal.java index d1387e05afb..f7d6dfd1f63 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/status/StatusModal.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/status/StatusModal.java @@ -25,11 +25,11 @@ import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.AbstractAnyRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; -import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.to.EntityTO; import org.apache.wicket.PageReference; import org.apache.wicket.markup.html.panel.Panel; -public abstract class StatusModal extends Panel implements ModalPanel { +public abstract class StatusModal extends Panel implements ModalPanel { private static final long serialVersionUID = 1066124171682570080L; @@ -53,10 +53,10 @@ public StatusModal( protected abstract DirectoryPanel< StatusBean, StatusBean, DirectoryDataProvider, AbstractAnyRestClient> getStatusDirectoryPanel( - final MultilevelPanel mlp, - final BaseModal baseModal, - final PageReference pageReference, - final T entity, - final String itemKeyFieldName, - final boolean statusOnly); + MultilevelPanel mlp, + BaseModal baseModal, + PageReference pageReference, + T entity, + String itemKeyFieldName, + boolean statusOnly); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/CrontabPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/CrontabPanel.java index a44b6de27e6..f5f84b9d30a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/CrontabPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/CrontabPanel.java @@ -161,20 +161,20 @@ private String getCronField(final String cron, final int field) { public String getCronExpression() { String cronExpression = null; - if (seconds != null && seconds.getField().getInput() != null - && minutes != null && minutes.getField().getInput() != null - && hours != null && hours.getField().getInput() != null - && daysOfMonth != null && daysOfMonth.getField().getInput() != null - && months != null && months.getField().getInput() != null - && daysOfWeek != null && daysOfWeek.getField().getInput() != null) { + if (seconds != null && seconds.getModelObject() != null + && minutes != null && minutes.getModelObject() != null + && hours != null && hours.getModelObject() != null + && daysOfMonth != null && daysOfMonth.getModelObject() != null + && months != null && months.getModelObject() != null + && daysOfWeek != null && daysOfWeek.getModelObject() != null) { cronExpression = new StringBuilder(). - append(seconds.getField().getInput().trim()).append(" "). - append(minutes.getField().getInput().trim()).append(" "). - append(hours.getField().getInput().trim()).append(" "). - append(daysOfMonth.getField().getInput().trim()).append(" "). - append(months.getField().getInput().trim()).append(" "). - append(daysOfWeek.getField().getInput().trim()).toString(); + append(seconds.getModelObject().trim()).append(" "). + append(minutes.getModelObject().trim()).append(" "). + append(hours.getModelObject().trim()).append(" "). + append(daysOfMonth.getModelObject().trim()).append(" "). + append(months.getModelObject().trim()).append(" "). + append(daysOfWeek.getModelObject().trim()).toString(); } return StringUtils.isNotBlank(cronExpression) ? cronExpression : null; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java index f9203811c7e..f9647425692 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java @@ -25,7 +25,6 @@ import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.panels.DirectoryPanel; import org.apache.syncope.client.console.panels.AjaxDataTablePanel; @@ -95,7 +94,9 @@ protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder> getColumns() { final List> columns = new ArrayList<>(); - columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key", "key")); + columns.add(new KeyPropertyColumn<>( + new StringResourceModel(Constants.KEY_FIELD_NAME, this), + Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME)); columns.add(new DatePropertyColumn<>(new StringResourceModel("start", this), "start", "start")); @@ -130,10 +131,10 @@ public void onClick(final AjaxRequestTarget target, final ExecTO ignore) { ExecutionsDirectoryPanel.this.getTogglePanel().close(target); try { restClient.deleteExecution(taskExecutionTO.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); - } catch (SyncopeClientException scce) { - SyncopeConsoleSession.get().error(scce.getMessage()); + } catch (SyncopeClientException e) { + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -158,31 +159,25 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionLink.ActionType.DELETE); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionLink.ActionType.DELETE); + return batches; } protected class ExecProvider extends DirectoryDataProvider { private static final long serialVersionUID = 8943636537120648961L; - private final SortableDataProviderComparator comparator; - private final String taskKey; public ExecProvider(final String taskKey, final int paginatorRows) { super(paginatorRows); + this.taskKey = taskKey; - comparator = new SortableDataProviderComparator<>(this); setSort("end", SortOrder.DESCENDING); } - public SortableDataProviderComparator getComparator() { - return comparator; - } - @Override public Iterator iterator(final long first, final long count) { int page = ((int) first / paginatorRows); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java index 656b707d493..168c8bf17ca 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.TaskDataProvider; @@ -80,7 +79,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("sender", this), "sender", "sender")); @@ -142,12 +141,11 @@ public void onClick(final AjaxRequestTarget target, final NotificationTaskTO mod public void onClick(final AjaxRequestTarget target, final NotificationTaskTO modelObject) { try { restClient.startExecution(taskTO.getKey(), null); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); LOG.error("While running {}", taskTO.getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -161,12 +159,11 @@ public void onClick(final AjaxRequestTarget target, final NotificationTaskTO mod try { restClient.delete(TaskType.NOTIFICATION, taskTO.getKey()); updateResultTable(target); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { LOG.error("While deleting {}", taskTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -176,11 +173,11 @@ public void onClick(final AjaxRequestTarget target, final NotificationTaskTO mod } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.DELETE); - bulkActions.add(ActionType.EXECUTE); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionType.DELETE); + batches.add(ActionType.EXECUTE); + return batches; } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java index 5298b22bbc7..1b3282b77f9 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.TaskDataProvider; @@ -70,7 +69,7 @@ protected List> getColumns() { final List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("operation", this), "operation", "operation")); @@ -148,12 +147,11 @@ public void onClick(final AjaxRequestTarget target, final PropagationTaskTO mode public void onClick(final AjaxRequestTarget target, final PropagationTaskTO modelObject) { try { restClient.startExecution(taskTO.getKey(), null); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); LOG.error("While running {}", taskTO.getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -167,13 +165,12 @@ public void onClick(final AjaxRequestTarget target, final PropagationTaskTO mode public void onClick(final AjaxRequestTarget target, final PropagationTaskTO modelObject) { try { restClient.delete(TaskType.PROPAGATION, taskTO.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); PropagationTaskDirectoryPanel.this.getTogglePanel().close(target); } catch (SyncopeClientException e) { LOG.error("While deleting {}", taskTO.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -183,11 +180,11 @@ public void onClick(final AjaxRequestTarget target, final PropagationTaskTO mode } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.DELETE); - bulkActions.add(ActionType.EXECUTE); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionType.DELETE); + batches.add(ActionType.EXECUTE); + return batches; } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java index 0577433f1a2..17367337ba1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; @@ -34,6 +35,7 @@ import org.apache.syncope.common.lib.to.PushTaskTO; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.lib.types.TaskType; +import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; @@ -43,6 +45,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; @@ -99,7 +102,7 @@ protected List> getFieldColumns() { List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("name", this), "name", "name")); @@ -137,13 +140,17 @@ public void populateItem( final String componentId, final IModel rowModel) { - JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); - JobActionPanel panel = new JobActionPanel( - componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef); - MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, - String.format("%s,%s", - StandardEntitlement.TASK_EXECUTE, - StandardEntitlement.TASK_UPDATE)); + Component panel; + try { + JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); + panel = new JobActionPanel(componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef); + MetaDataRoleAuthorizationStrategy.authorize( + panel, WebPage.ENABLE, + String.format("%s,%s", StandardEntitlement.TASK_EXECUTE, StandardEntitlement.TASK_UPDATE)); + } catch (Exception e) { + LOG.error("Could not get job for task {}", rowModel.getObject().getKey(), e); + panel = new Label(componentId, Model.of()); + } cellItem.add(panel); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/PushTaskWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/PushTaskWrapper.java index 0bded4b2722..69e3e170faf 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/PushTaskWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/PushTaskWrapper.java @@ -57,7 +57,7 @@ public Map getFilters() { for (Map.Entry> entry : getFilterClauses().entrySet()) { if (!entry.getValue().isEmpty()) { - AbstractFiqlSearchConditionBuilder bld; + AbstractFiqlSearchConditionBuilder bld; switch (entry.getKey()) { case "USER": bld = SyncopeClient.getUserSearchConditionBuilder(); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java index 70170c2cf26..e629141fd2b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java @@ -24,7 +24,6 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -33,6 +32,7 @@ import org.apache.syncope.client.console.panels.ModalPanel; import org.apache.syncope.client.console.panels.MultilevelPanel; import org.apache.syncope.client.console.rest.TaskRestClient; +import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; @@ -40,26 +40,32 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.client.console.widgets.JobActionPanel; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.common.lib.types.TaskType; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.JobTO; import org.apache.syncope.common.lib.to.SchedTaskTO; import org.apache.syncope.common.lib.to.TemplatableTO; +import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.event.Broadcast; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.util.time.Duration; /** * Tasks page. @@ -95,7 +101,7 @@ protected SchedTaskDirectoryPanel( try { schedTaskTO = reference.getDeclaredConstructor().newInstance(); } catch (Exception e) { - LOG.error("Falure instantiating task", e); + LOG.error("Failure instantiating task", e); } this.addNewItemPanelBuilder(new SchedTaskWizardBuilder<>(taskType, schedTaskTO, pageRef), true); @@ -107,6 +113,17 @@ protected SchedTaskDirectoryPanel( initResultTable(); + container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) { + + private static final long serialVersionUID = -4661303265651934868L; + + @Override + protected void onTimer(final AjaxRequestTarget target) { + container.modelChanged(); + target.add(container); + } + }); + startAt = new TaskStartAtTogglePanel(container, pageRef); addInnerObject(startAt); @@ -123,15 +140,127 @@ protected Serializable onApplyInternal( return targetObject; } }; + addInnerObject(templates); + } + + protected SchedTaskDirectoryPanel( + final BaseModal baseModal, + final MultilevelPanel multiLevelPanelRef, + final TaskType taskType, + final Class reference, + final PageReference pageRef, + final boolean wizardInModal) { + + super(baseModal, multiLevelPanelRef, pageRef, wizardInModal); + this.taskType = taskType; + this.reference = reference; + + try { + schedTaskTO = reference.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + LOG.error("Failure instantiating task", e); + } + + this.addNewItemPanelBuilder(new SchedTaskWizardBuilder<>(taskType, schedTaskTO, pageRef), true); + + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.TASK_CREATE); + + enableExitButton(); + setFooterVisibility(false); + initResultTable(); + + container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) { + + private static final long serialVersionUID = -4661303265651934868L; + + @Override + protected void onTimer(final AjaxRequestTarget target) { + container.modelChanged(); + target.add(container); + } + }); + + startAt = new TaskStartAtTogglePanel(container, pageRef); + addInnerObject(startAt); + + templates = new TemplatesTogglePanel(getActualId(), this, pageRef) { + + private static final long serialVersionUID = -8765794727538618705L; + + @Override + protected Serializable onApplyInternal( + final TemplatableTO targetObject, final String type, final AnyTO anyTO) { + + targetObject.getTemplates().put(type, anyTO); + new TaskRestClient().update(taskType, SchedTaskTO.class.cast(targetObject)); + return targetObject; + } + }; addInnerObject(templates); } + protected SchedTaskDirectoryPanel( + final BaseModal baseModal, + final MultilevelPanel multiLevelPanelRef, + final TaskType taskType, + final Class reference, + final PageReference pageRef, + final String id) { + + super(baseModal, multiLevelPanelRef, pageRef, id); + this.taskType = taskType; + this.reference = reference; + + try { + schedTaskTO = reference.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + LOG.error("Failure instantiating task", e); + } + + this.addNewItemPanelBuilder(new SchedTaskWizardBuilder<>(taskType, schedTaskTO, pageRef), true); + + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.TASK_CREATE); + + setFooterVisibility(false); + + initResultTable(); + + container.add(new IndicatorAjaxTimerBehavior(Duration.seconds(10)) { + + private static final long serialVersionUID = 3780176123730676296L; + + @Override + protected void onTimer(final AjaxRequestTarget target) { + container.modelChanged(); + target.add(container); + } + }); + + startAt = new TaskStartAtTogglePanel(container, pageRef); + addOrReplaceInnerObject(startAt); + + templates = new TemplatesTogglePanel(getActualId(), this, pageRef) { + + private static final long serialVersionUID = 5463425091347504892L; + + @Override + protected Serializable onApplyInternal( + final TemplatableTO targetObject, final String type, final AnyTO anyTO) { + + targetObject.getTemplates().put(type, anyTO); + new TaskRestClient().update(taskType, SchedTaskTO.class.cast(targetObject)); + return targetObject; + } + }; + addOrReplaceInnerObject(templates); + } + protected List> getFieldColumns() { - final List> columns = new ArrayList<>(); + List> columns = new ArrayList<>(); columns.add(new KeyPropertyColumn<>( - new StringResourceModel("key", this), "key")); + new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME)); columns.add(new PropertyColumn<>( new StringResourceModel("name", this), "name", "name")); @@ -143,8 +272,11 @@ protected List> getFieldColumns() { @Override public void populateItem( - final Item> item, final String componentId, final IModel rowModel) { - final IModel model = getDataModel(rowModel); + final Item> item, + final String componentId, + final IModel rowModel) { + + IModel model = getDataModel(rowModel); if (model != null && model.getObject() instanceof String) { String value = String.class.cast(model.getObject()); if (value.length() > 20) { @@ -156,7 +288,6 @@ public void populateItem( super.populateItem(item, componentId, rowModel); } } - }); columns.add(new DatePropertyColumn<>( @@ -171,6 +302,36 @@ public void populateItem( columns.add(new BooleanPropertyColumn<>( new StringResourceModel("active", this), "active", "active")); + columns.add(new AbstractColumn(new Model<>(""), "running") { + + private static final long serialVersionUID = -4008579357070833846L; + + @Override + public void populateItem( + final Item> cellItem, + final String componentId, + final IModel rowModel) { + + Component panel; + try { + JobTO jobTO = restClient.getJob(rowModel.getObject().getKey()); + panel = new JobActionPanel(componentId, jobTO, false, SchedTaskDirectoryPanel.this, pageRef); + MetaDataRoleAuthorizationStrategy.authorize( + panel, WebPage.ENABLE, + String.format("%s,%s", StandardEntitlement.TASK_EXECUTE, StandardEntitlement.TASK_UPDATE)); + } catch (Exception e) { + LOG.error("Could not get job for task {}", rowModel.getObject().getKey(), e); + panel = new Label(componentId, Model.of()); + } + cellItem.add(panel); + } + + @Override + public String getCssClass() { + return "col-xs-1"; + } + }); + return columns; } @@ -196,7 +357,7 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { SchedTaskDirectoryPanel.this.getTogglePanel().close(target); viewTask(taskTO, target); } - }, ActionLink.ActionType.VIEW, StandardEntitlement.TASK_READ); + }, ActionLink.ActionType.VIEW_EXECUTIONS, StandardEntitlement.TASK_READ); panel.add(new ActionLink() { @@ -211,8 +372,7 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { target).setResourceModel( new StringResourceModel("inner.task.edit", SchedTaskDirectoryPanel.this, - Model.of(Pair.of( - ActionLink.ActionType.EDIT, model.getObject()))))); + Model.of(Pair.of(ActionLink.ActionType.EDIT, model.getObject()))))); } }, ActionLink.ActionType.EDIT, StandardEntitlement.TASK_UPDATE); @@ -229,8 +389,7 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { new AjaxWizard.EditItemActionEvent<>(clone, target).setResourceModel( new StringResourceModel("inner.task.clone", SchedTaskDirectoryPanel.this, - Model.of(Pair.of( - ActionLink.ActionType.CLONE, model.getObject()))))); + Model.of(Pair.of(ActionLink.ActionType.CLONE, model.getObject()))))); } }, ActionLink.ActionType.CLONE, StandardEntitlement.TASK_CREATE); @@ -241,8 +400,7 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { @Override public void onClick(final AjaxRequestTarget target, final T ignore) { SchedTaskDirectoryPanel.this.getTogglePanel().close(target); - startAt.setExecutionDetail( - model.getObject().getKey(), model.getObject().getName(), target); + startAt.setExecutionDetail(model.getObject().getKey(), model.getObject().getName(), target); startAt.toggle(target, true); } }, ActionLink.ActionType.EXECUTE, StandardEntitlement.TASK_EXECUTE); @@ -257,13 +415,12 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { public void onClick(final AjaxRequestTarget target, final T ignore) { try { restClient.delete(taskType, taskTO.getKey()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); SchedTaskDirectoryPanel.this.getTogglePanel().close(target); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); LOG.error("While deleting propagation task {}", taskTO.getKey(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -281,12 +438,12 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { - final List bulkActions = new ArrayList<>(); - bulkActions.add(ActionType.DELETE); - bulkActions.add(ActionType.EXECUTE); - bulkActions.add(ActionType.DRYRUN); - return bulkActions; + protected Collection getBatches() { + List batches = new ArrayList<>(); + batches.add(ActionType.DELETE); + batches.add(ActionType.EXECUTE); + batches.add(ActionType.DRYRUN); + return batches; } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java index a23f39bec62..937bd2ba97c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java @@ -21,9 +21,12 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.rest.TaskRestClient; @@ -31,8 +34,10 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wizards.AjaxWizardBuilder; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.ProvisioningTaskTO; import org.apache.syncope.common.lib.to.SchedTaskTO; @@ -46,6 +51,7 @@ import org.apache.syncope.common.lib.types.UnmatchingRule; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; import org.apache.wicket.extensions.wizard.WizardModel; import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -60,6 +66,8 @@ public class SchedTaskWizardBuilder extends AjaxWizardBui private final TaskRestClient taskRestClient = new TaskRestClient(); + private final RealmRestClient realmRestClient = new RealmRestClient(); + private final ImplementationRestClient implRestClient = new ImplementationRestClient(); private final TaskType type; @@ -68,23 +76,12 @@ public class SchedTaskWizardBuilder extends AjaxWizardBui private CrontabPanel crontabPanel; - private final LoadableDetachableModel> realms = new LoadableDetachableModel>() { - - private static final long serialVersionUID = 5275935387613157437L; - - @Override - protected List load() { - List result = new RealmRestClient().list().stream(). - map(RealmTO::getFullPath).collect(Collectors.toList()); - Collections.sort(result); - - return result; - } - }; + private final boolean isSearchEnabled; public SchedTaskWizardBuilder(final TaskType type, final T taskTO, final PageReference pageRef) { super(taskTO, pageRef); this.type = type; + this.isSearchEnabled = RealmsUtils.isSearchEnabled(); } @Override @@ -113,6 +110,12 @@ protected WizardModel buildModelSteps(final SchedTaskTO modelObject, final Wizar return wizardModel; } + private List searchRealms(final String realmQuery) { + return isSearchEnabled + ? realmRestClient.search(RealmsUtils.buildQuery(realmQuery)).getResult() + : realmRestClient.list(SyncopeConstants.ROOT_REALM); + } + public class Profile extends WizardStep { private static final long serialVersionUID = -3043839139187792810L; @@ -230,14 +233,31 @@ protected void onUpdate(final AjaxRequestTarget target) { } }); - AjaxDropDownChoicePanel destinationRealm = new AjaxDropDownChoicePanel<>( - "destinationRealm", "destinationRealm", - new PropertyModel(taskTO, "destinationRealm"), false). - setChoices(realms); + final AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(!isSearchEnabled); + settings.setShowListOnEmptyInput(!isSearchEnabled); + + final AjaxSearchFieldPanel destinationRealm = + new AjaxSearchFieldPanel("destinationRealm", "destinationRealm", + new PropertyModel(taskTO, "destinationRealm"), settings) { + + private static final long serialVersionUID = -6390474600233486704L; + + @Override + protected Iterator getChoices(final String input) { + return (RealmsUtils.checkInput(input) + ? searchRealms(input).stream().map(RealmTO::getFullPath).collect(Collectors.toList()) + : Collections.emptyList()).iterator(); + } + }; + if (taskTO instanceof PullTaskTO) { destinationRealm.addRequiredLabel(); + if (StringUtils.isBlank(PullTaskTO.class.cast(taskTO).getDestinationRealm())) { + // add a default destination realm if missing in the task + destinationRealm.setModelObject(SyncopeConstants.ROOT_REALM); + } } - destinationRealm.setNullValid(!(taskTO instanceof PullTaskTO)); pullTaskSpecifics.add(destinationRealm); AjaxCheckBoxPanel remediation = new AjaxCheckBoxPanel( @@ -254,14 +274,22 @@ protected void onUpdate(final AjaxRequestTarget target) { pushTaskSpecifics.setEnabled(false).setVisible(false); } - AjaxDropDownChoicePanel sourceRealm = new AjaxDropDownChoicePanel<>( - "sourceRealm", "sourceRealm", - new PropertyModel(taskTO, "sourceRealm"), false). - setChoices(realms); + final AjaxSearchFieldPanel sourceRealm = new AjaxSearchFieldPanel("sourceRealm", "sourceRealm", + new PropertyModel(taskTO, "sourceRealm"), settings) { + + private static final long serialVersionUID = -6390474600233486704L; + + @Override + protected Iterator getChoices(final String input) { + return (RealmsUtils.checkInput(input) + ? searchRealms(input).stream().map(RealmTO::getFullPath).collect(Collectors.toList()) + : Collections.emptyList()).iterator(); + } + }; + if (taskTO instanceof PushTaskTO) { sourceRealm.addRequiredLabel(); } - sourceRealm.setNullValid(!(taskTO instanceof PushTaskTO)); pushTaskSpecifics.add(sourceRealm); // ------------------------------ @@ -279,7 +307,7 @@ protected void onUpdate(final AjaxRequestTarget target) { AjaxPalettePanel actions = new AjaxPalettePanel.Builder(). setAllowMoveAll(true).setAllowOrder(true). build("actions", - new PropertyModel>(taskTO, "actions"), + new PropertyModel<>(taskTO, "actions"), new ListModel<>(taskTO instanceof PushTaskTO ? pushActions.getObject() : pullActions.getObject())); actions.setOutputMarkupId(true); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java index 562d6326659..f173c65810c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java @@ -52,4 +52,25 @@ protected void viewTask(final SchedTaskTO taskTO, final AjaxRequestTarget target } }); } + + public SchedTasks(final BaseModal baseModal, final PageReference pageReference, + final boolean wizardInModal, final String id) { + super(id); + + final MultilevelPanel mlp = new MultilevelPanel("tasks"); + add(mlp); + + mlp.setFirstLevel(new SchedTaskDirectoryPanel( + baseModal, mlp, TaskType.SCHEDULED, SchedTaskTO.class, pageReference, wizardInModal) { + + private static final long serialVersionUID = -2195387360323687302L; + + @Override + protected void viewTask(final SchedTaskTO taskTO, final AjaxRequestTarget target) { + mlp.next( + new StringResourceModel("task.view", this, new Model<>(Pair.of(null, taskTO))).getObject(), + new TaskExecutionDetails<>(baseModal, taskTO, pageReference), target); + } + }); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java index 56a17a9d1cf..e9d79b06a48 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java @@ -18,8 +18,8 @@ */ package org.apache.syncope.client.console.tasks; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.commons.DirectoryDataProvider; -import org.apache.syncope.client.console.commons.SortableDataProviderComparator; import org.apache.syncope.client.console.commons.TaskDataProvider; import org.apache.syncope.client.console.panels.DirectoryPanel; import org.apache.syncope.client.console.panels.AjaxDataTablePanel; @@ -59,6 +59,26 @@ protected TaskDirectoryPanel( setShowResultPage(false); } + protected TaskDirectoryPanel( + final BaseModal baseModal, final MultilevelPanel multiLevelPanelRef, final PageReference pageRef, + final boolean wizardInModal) { + super(MultilevelPanel.FIRST_LEVEL_ID, pageRef, wizardInModal); + this.baseModal = baseModal; + this.multiLevelPanelRef = multiLevelPanelRef; + restClient = new TaskRestClient(); + setShowResultPage(false); + } + + protected TaskDirectoryPanel( + final BaseModal baseModal, final MultilevelPanel multiLevelPanelRef, final PageReference pageRef, + final String id) { + super(id, pageRef, false); + this.baseModal = baseModal; + this.multiLevelPanelRef = multiLevelPanelRef; + restClient = new TaskRestClient(); + setShowResultPage(false); + } + @Override protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder resultTableBuilder) { resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef); @@ -70,22 +90,15 @@ protected abstract class TasksProvider extends DirectoryDataPr private static final long serialVersionUID = -20112718133295756L; - private final SortableDataProviderComparator comparator; - private final TaskType id; public TasksProvider(final int paginatorRows, final TaskType id) { super(paginatorRows); - setSort("key", SortOrder.ASCENDING); - this.comparator = new SortableDataProviderComparator<>(this); + setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING); this.id = id; } - public SortableDataProviderComparator getComparator() { - return comparator; - } - @Override public long size() { return restClient.count(id); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskStartAtTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskStartAtTogglePanel.java index 9440e196f05..d9b114d4759 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskStartAtTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TaskStartAtTogglePanel.java @@ -18,7 +18,6 @@ */ package org.apache.syncope.client.console.tasks; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -45,13 +44,11 @@ public TaskStartAtTogglePanel(final WebMarkupContainer container, final PageRefe protected void onSubmit(final AjaxRequestTarget target) { try { getRestClient().startExecution(key, startAtDateModel.getObject(), true); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); toggle(target, false); target.add(container); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); LOG.error("While running task {}", key, e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); @@ -61,7 +58,6 @@ protected void onSubmit(final AjaxRequestTarget target) { protected void onError(final AjaxRequestTarget target) { ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }); } @@ -69,5 +65,4 @@ protected void onError(final AjaxRequestTarget target) { protected TaskRestClient getRestClient() { return new TaskRestClient(); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TemplatesTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TemplatesTogglePanel.java index 802b34c6978..8f78b94b568 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/TemplatesTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/TemplatesTogglePanel.java @@ -20,7 +20,6 @@ import java.io.Serializable; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; @@ -161,10 +160,8 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject send(container, Broadcast.EXACT, payload); toggle(target, false); } catch (SyncopeClientException e) { - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() - : e.getMessage()); LOG.error("While editing template for {}", typeModel.getObject(), e); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -176,8 +173,7 @@ protected void onError(final AjaxRequestTarget target) { }); } - protected abstract Serializable onApplyInternal( - final TemplatableTO targetObject, final String type, final AnyTO anyTO); + protected abstract Serializable onApplyInternal(TemplatableTO targetObject, String type, AnyTO anyTO); public void setTargetObject(final TemplatableTO targetObject) { this.targetObject = targetObject; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java new file mode 100644 index 00000000000..46f3234814f --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java @@ -0,0 +1,113 @@ +/* + * 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.client.console.topology; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.pages.Connectors; +import org.apache.syncope.client.console.pages.Resources; +import org.apache.syncope.client.console.panels.ConnidLocations; +import org.apache.syncope.client.console.tasks.SchedTasks; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; +import org.apache.wicket.extensions.markup.html.tabs.ITab; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class TabularTopology extends BasePage { + + private static final long serialVersionUID = -4434385801124981824L; + + public TabularTopology() { + TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior(); + body.add(websocket); + + WebMarkupContainer content = new WebMarkupContainer("content"); + content.setOutputMarkupId(true); + content.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList())); + body.add(content); + } + + protected List buildTabList() { + final List tabs = new ArrayList<>(); + + tabs.add(new AbstractTab(new Model<>("CustomTasks")) { + + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + BaseModal schedTaskModal = new BaseModal(Constants.OUTER) { + + private static final long serialVersionUID = -1673561782333149836L; + + @Override + protected void onConfigure() { + super.onConfigure(); + setFooterVisible(false); + } + }; + schedTaskModal.size(Modal.Size.Large); + return new SchedTasks(schedTaskModal, getPageReference(), true, panelId); + } + }); + + tabs.add(new AbstractTab(new Model<>("Resources")) { + + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + return new Resources(panelId, getPageReference()); + } + }); + + tabs.add(new AbstractTab(new Model<>("Connectors")) { + + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + return new Connectors(panelId, getPageReference()); + } + }); + + tabs.add(new AbstractTab(new Model<>("ConnectorServers")) { + + private static final long serialVersionUID = -6815067322125799251L; + + @Override + public Panel getPanel(final String panelId) { + return new ConnidLocations.Builder(getPageReference()) { + + private static final long serialVersionUID = -2555113973787214723L; + + }.build(panelId); + } + }); + + return tabs; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java index 070085e4928..e24ac1c87e4 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java @@ -40,18 +40,17 @@ import org.apache.syncope.client.console.rest.BaseRestClient; import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.wicket.markup.html.WebMarkupContainerNoVeil; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.common.lib.to.ConnInstanceTO; -import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.Component; import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.IAjaxIndicatorAware; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.event.IEvent; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; @@ -69,8 +68,6 @@ public class Topology extends BasePage { public static final String CONNECTOR_SERVER_LOCATION_PREFIX = "connid://"; - public static final String ROOT_NAME = "Syncope"; - private final ResourceRestClient resourceRestClient = new ResourceRestClient(); private final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); @@ -107,7 +104,7 @@ protected Map> load() { final Map> res = new HashMap<>(); connectorRestClient.getAllConnectors().forEach(conn -> { - final List conns; + List conns; if (res.containsKey(conn.getLocation())) { conns = res.get(conn.getLocation()); } else { @@ -164,7 +161,7 @@ public void onClose(final AjaxRequestTarget target) { } }); - final TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior(); + TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior(); body.add(websocket); togglePanel = new TopologyTogglePanel("toggle", getPageReference()); @@ -173,7 +170,7 @@ public void onClose(final AjaxRequestTarget target) { // ----------------------------------------- // Add Zoom panel // ----------------------------------------- - final ActionsPanel zoomActionPanel = new ActionsPanel<>("zoom", null); + ActionsPanel zoomActionPanel = new ActionsPanel<>("zoom", null); zoomActionPanel.add(new ActionLink() { @@ -200,29 +197,30 @@ public void onClick(final AjaxRequestTarget target, final Serializable ignore) { // ----------------------------------------- // Add Syncope (root topologynode) // ----------------------------------------- - final TopologyNode syncopeTopologyNode = new TopologyNode(ROOT_NAME, ROOT_NAME, TopologyNode.Kind.SYNCOPE); + String rootName = StringUtils.capitalize(Constants.SYNCOPE); + final TopologyNode syncopeTopologyNode = new TopologyNode(rootName, rootName, TopologyNode.Kind.SYNCOPE); syncopeTopologyNode.setX(origX); syncopeTopologyNode.setY(origY); - final URI uri = WebClient.client(BaseRestClient.getSyncopeService()).getBaseURI(); + URI uri = WebClient.client(BaseRestClient.getSyncopeService()).getBaseURI(); syncopeTopologyNode.setHost(uri.getHost()); syncopeTopologyNode.setPort(uri.getPort()); - body.add(topologyNodePanel("syncope", syncopeTopologyNode)); + body.add(topologyNodePanel(Constants.SYNCOPE, syncopeTopologyNode, false)); - final Map> connections = new HashMap<>(); - final Map syncopeConnections = new HashMap<>(); + Map> connections = new HashMap<>(); + Map syncopeConnections = new HashMap<>(); connections.put(syncopeTopologyNode.getKey(), syncopeConnections); // required to retrieve parent positions - final Map servers = new HashMap<>(); - final Map connectors = new HashMap<>(); + Map servers = new HashMap<>(); + Map connectors = new HashMap<>(); // ----------------------------------------- // ----------------------------------------- // Add Connector Servers // ----------------------------------------- - final ListView connectorServers = new ListView("connectorServers", csModel.getObject().getLeft()) { + ListView connectorServers = new ListView("connectorServers", csModel.getObject().getLeft()) { private static final long serialVersionUID = 6978621871488360380L; @@ -235,10 +233,10 @@ protected void populateItem(final ListItem item) { int x = (int) Math.round(origX + kx * Math.cos(Math.PI + Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round(origY + 100 * Math.sin(Math.PI + Math.PI * (item.getIndex() + 1) / size)); - final URI location = item.getModelObject(); - final String url = location.toASCIIString(); + URI location = item.getModelObject(); + String url = location.toASCIIString(); - final TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.CONNECTOR_SERVER); + TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.CONNECTOR_SERVER); topologynode.setHost(location.getHost()); topologynode.setPort(location.getPort()); @@ -247,7 +245,7 @@ protected void populateItem(final ListItem item) { servers.put(String.class.cast(topologynode.getKey()), topologynode); - item.add(topologyNodePanel("cs", topologynode)); + item.add(topologyNodePanel("cs", topologynode, false)); syncopeConnections.put(url, topologynode); connections.put(url, new HashMap<>()); @@ -261,7 +259,7 @@ protected void populateItem(final ListItem item) { // ----------------------------------------- // Add File Paths // ----------------------------------------- - final ListView filePaths = new ListView("filePaths", csModel.getObject().getRight()) { + ListView filePaths = new ListView("filePaths", csModel.getObject().getRight()) { private static final long serialVersionUID = 6978621871488360380L; @@ -274,10 +272,10 @@ protected void populateItem(final ListItem item) { int x = (int) Math.round(origX + kx * Math.cos(Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round(origY + 100 * Math.sin(Math.PI * (item.getIndex() + 1) / size)); - final URI location = item.getModelObject(); - final String url = location.toASCIIString(); + URI location = item.getModelObject(); + String url = location.toASCIIString(); - final TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.FS_PATH); + TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.FS_PATH); topologynode.setHost(location.getHost()); topologynode.setPort(location.getPort()); @@ -286,7 +284,7 @@ protected void populateItem(final ListItem item) { servers.put(String.class.cast(topologynode.getKey()), topologynode); - item.add(topologyNodePanel("fp", topologynode)); + item.add(topologyNodePanel("fp", topologynode, false)); syncopeConnections.put(url, topologynode); connections.put(url, new HashMap<>()); @@ -300,43 +298,36 @@ protected void populateItem(final ListItem item) { // ----------------------------------------- // Add Connector Intances // ----------------------------------------- - final List> allConns = new ArrayList<>(connModel.getObject().values()); - - final ListView> conns = new ListView>("conns", allConns) { + ListView> conns = + new ListView>("conns", new ArrayList<>(connModel.getObject().values())) { private static final long serialVersionUID = 697862187148836036L; @Override protected void populateItem(final ListItem> item) { + int size = item.getModelObject().size() + 1; - final int size = item.getModelObject().size() + 1; - - final ListView conns = new ListView("conns", item.getModelObject()) { + ListView conns = new ListView("conns", item.getModelObject()) { private static final long serialVersionUID = 6978621871488360381L; @Override protected void populateItem(final ListItem item) { - final ConnInstanceTO conn = item.getModelObject(); + ConnInstanceTO conn = item.getModelObject(); - final TopologyNode topologynode = new TopologyNode( + TopologyNode topologynode = new TopologyNode( conn.getKey(), StringUtils.isBlank(conn.getDisplayName()) // [SYNCOPE-1233] ? conn.getBundleName() : conn.getDisplayName(), TopologyNode.Kind.CONNECTOR); // Define the parent note - final TopologyNode parent = servers.get(conn.getLocation()); + TopologyNode parent = servers.get(conn.getLocation()); // Set the position int kx = size >= 6 ? 800 : (130 * size); - final double hpos; - if (conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) { - hpos = Math.PI; - } else { - hpos = 0.0; - } + double hpos = conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX) ? Math.PI : 0.0; int x = (int) Math.round((parent == null ? origX : parent.getX()) + kx * Math.cos(hpos + Math.PI * (item.getIndex() + 1) / size)); @@ -348,11 +339,10 @@ protected void populateItem(final ListItem item) { topologynode.setY(y); connectors.put(String.class.cast(topologynode.getKey()), topologynode); - item.add(topologyNodePanel("conn", topologynode)); + item.add(topologyNodePanel("conn", topologynode, conn.isErrored())); // Update connections - final Map remoteConnections; - + Map remoteConnections; if (connections.containsKey(conn.getLocation())) { remoteConnections = connections.get(conn.getLocation()); } else { @@ -375,19 +365,19 @@ protected void populateItem(final ListItem item) { // ----------------------------------------- // Add Resources // ----------------------------------------- - final Collection adminConns = new HashSet<>(); - connModel.getObject().values().forEach(connInstances -> { - adminConns.addAll(connInstances.stream().map(EntityTO::getKey).collect(Collectors.toList())); - }); + Collection adminConns = new HashSet<>(); + connModel.getObject().values().forEach(connInstances -> adminConns.addAll( + connInstances.stream().map(ConnInstanceTO::getKey).collect(Collectors.toList()))); - final Set adminRes = new HashSet<>(); - final List connToBeProcessed = new ArrayList<>(); + Set adminRes = new HashSet<>(); + List connToBeProcessed = new ArrayList<>(); resModel.getObject().stream(). - filter((resourceTO) -> (adminConns.contains(resourceTO.getConnector()))). - forEachOrdered(resourceTO -> { - final TopologyNode topologynode = new TopologyNode( + filter(resourceTO -> adminConns.contains(resourceTO.getConnector())). + forEach(resourceTO -> { + TopologyNode topologynode = new TopologyNode( resourceTO.getKey(), resourceTO.getKey(), TopologyNode.Kind.RESOURCE); - final Map remoteConnections; + + Map remoteConnections; if (connections.containsKey(resourceTO.getConnector())) { remoteConnections = connections.get(resourceTO.getConnector()); } else { @@ -403,15 +393,15 @@ protected void populateItem(final ListItem item) { } }); - final ListView resources = new ListView("resources", connToBeProcessed) { + ListView resources = new ListView("resources", connToBeProcessed) { private static final long serialVersionUID = 697862187148836038L; @Override protected void populateItem(final ListItem item) { - final String connectorKey = item.getModelObject(); + String connectorKey = item.getModelObject(); - final ListView innerListView = new ListView("resources", + ListView innerListView = new ListView("resources", new ArrayList<>(connections.get(connectorKey).values())) { private static final long serialVersionUID = -3447760771863754342L; @@ -420,19 +410,14 @@ protected void populateItem(final ListItem item) { @Override protected void populateItem(final ListItem item) { - final TopologyNode topologynode = item.getModelObject(); - final TopologyNode parent = connectors.get(connectorKey); + TopologyNode topologynode = item.getModelObject(); + TopologyNode parent = connectors.get(connectorKey); // Set position int kx = size >= 16 ? 800 : (48 * size); int ky = size < 4 ? 100 : size < 6 ? 350 : 750; - final double hpos; - if (parent == null || parent.getY() < syncopeTopologyNode.getY()) { - hpos = Math.PI; - } else { - hpos = 0.0; - } + double hpos = (parent == null || parent.getY() < syncopeTopologyNode.getY()) ? Math.PI : 0.0; int x = (int) Math.round((parent == null ? origX : parent.getX()) + kx * Math.cos(hpos + Math.PI * (item.getIndex() + 1) / size)); @@ -442,7 +427,7 @@ protected void populateItem(final ListItem item) { topologynode.setX(x); topologynode.setY(y); - item.add(topologyNodePanel("res", topologynode)); + item.add(topologyNodePanel("res", topologynode, false)); } }; @@ -458,7 +443,7 @@ protected void populateItem(final ListItem item) { // ----------------------------------------- // Create connections // ----------------------------------------- - final WebMarkupContainer jsPlace = new WebMarkupContainerNoVeil("jsPlace"); + WebMarkupContainer jsPlace = new WebMarkupContainerNoVeil("jsPlace"); jsPlace.setOutputMarkupId(true); body.add(jsPlace); @@ -518,7 +503,7 @@ protected void onTimer(final AjaxRequestTarget target) { @Override protected void populateItem(final ListItem item) { - item.add(topologyNodePanel("el", item.getModelObject())); + item.add(topologyNodePanel("el", item.getModelObject(), false)); } }; newlyCreated.setOutputMarkupId(true); @@ -530,24 +515,18 @@ protected void populateItem(final ListItem item) { private List createConnections(final Map> targets) { List list = new ArrayList<>(); - targets.forEach((key, value) -> { - value.forEach((label, node) -> { - list.add(String.format("connect('%s','%s','%s');", - key, - label, - node.getKind())); - }); - }); + targets.forEach((key, value) -> value.forEach((label, node) -> list.add( + String.format("connect('%s','%s','%s');", key, label, node.getKind())))); + return list; } - private TopologyNodePanel topologyNodePanel(final String id, final TopologyNode node) { - - final TopologyNodePanel panel = new TopologyNodePanel(id, node); + private TopologyNodePanel topologyNodePanel(final String id, final TopologyNode node, final boolean errored) { + TopologyNodePanel panel = new TopologyNodePanel(id, node, errored); panel.setMarkupId(String.valueOf(node.getKey())); panel.setOutputMarkupId(true); - final List behaviors = new ArrayList<>(); + List behaviors = new ArrayList<>(); behaviors.add(new Behavior() { @@ -590,9 +569,9 @@ public void onEvent(final IEvent event) { super.onEvent(event); if (event.getPayload() instanceof CreateEvent) { - final CreateEvent resourceCreateEvent = CreateEvent.class.cast(event.getPayload()); + CreateEvent resourceCreateEvent = CreateEvent.class.cast(event.getPayload()); - final TopologyNode node = new TopologyNode( + TopologyNode node = new TopologyNode( resourceCreateEvent.getKey(), resourceCreateEvent.getDisplayName(), resourceCreateEvent.getKind()); @@ -610,18 +589,4 @@ public void onEvent(final IEvent event) { resourceCreateEvent.getKind())); } } - - private static class WebMarkupContainerNoVeil extends WebMarkupContainer implements IAjaxIndicatorAware { - - private static final long serialVersionUID = 6883930486048460708L; - - WebMarkupContainerNoVeil(final String id) { - super(id); - } - - @Override - public String getAjaxIndicatorMarkupId() { - return StringUtils.EMPTY; - } - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNodePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNodePanel.java index ad0960ba9a7..f8a45d38916 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNodePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNodePanel.java @@ -25,12 +25,8 @@ import org.apache.syncope.client.console.topology.TopologyTogglePanel.UpdateEvent; import org.apache.syncope.common.lib.to.ConnInstanceTO; import org.apache.wicket.AttributeModifier; -import org.apache.wicket.Component; -import org.apache.wicket.MarkupContainer; -import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.IAjaxIndicatorAware; import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.behavior.Behavior; import org.apache.wicket.event.IEvent; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; @@ -49,38 +45,42 @@ protected enum Status { } - public TopologyNodePanel(final String id, final TopologyNode node) { + public TopologyNodePanel(final String id, final TopologyNode node, final boolean errored) { super(id); this.node = node; - final String resourceName = node.getDisplayName().length() > 14 - ? node.getDisplayName().subSequence(0, 10) + "..." - : node.getDisplayName(); - - label = new Label("label", resourceName); + label = new Label("label", StringUtils.abbreviate(node.getDisplayName(), 10)); label.setOutputMarkupId(true); add(label); - final String title; - + String title; switch (node.getKind()) { case SYNCOPE: title = ""; add(new AttributeAppender("class", "topology_root", " ")); break; + case CONNECTOR_SERVER: title = node.getDisplayName(); add(new AttributeAppender("class", "topology_cs", " ")); break; + case FS_PATH: title = node.getDisplayName(); add(new AttributeAppender("class", "topology_cs", " ")); break; + case CONNECTOR: title = (StringUtils.isBlank(node.getConnectionDisplayName()) ? "" : node.getConnectionDisplayName() + ":") + node.getDisplayName(); - add(new AttributeAppender("class", "topology_conn", " ")); + if (errored) { + add(new AttributeAppender("class", "topology_conn_errored", " ")); + } else { + add(new AttributeAppender("class", "topology_conn", " ")); + } break; + + case RESOURCE: default: title = node.getDisplayName().length() > 14 ? node.getDisplayName() : ""; add(new AttributeAppender("class", "topology_res", " ")); @@ -93,21 +93,6 @@ public TopologyNodePanel(final String id, final TopologyNode node) { this.setMarkupId(node.getDisplayName()); } - @Override - public final MarkupContainer add(final Component... childs) { - return super.add(childs); - } - - @Override - public final Component add(final Behavior... behaviors) { - return super.add(behaviors); - } - - @Override - public final Component setMarkupId(final String markupId) { - return super.setMarkupId(markupId); - } - @Override public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; @@ -116,26 +101,20 @@ public String getAjaxIndicatorMarkupId() { @Override public void onEvent(final IEvent event) { if (event.getPayload() instanceof UpdateEvent) { - final UpdateEvent updateEvent = UpdateEvent.class.cast(event.getPayload()); - final String key = updateEvent.getKey(); - final AjaxRequestTarget target = updateEvent.getTarget(); + UpdateEvent updateEvent = UpdateEvent.class.cast(event.getPayload()); + String key = updateEvent.getKey(); if (node.getKind() == Kind.CONNECTOR && key.equalsIgnoreCase(node.getKey())) { ConnInstanceTO conn = new ConnectorRestClient().read(key); - String displayName = - // [SYNCOPE-1233] - StringUtils.isBlank(conn.getDisplayName()) ? conn.getBundleName() : conn.getDisplayName(); + // [SYNCOPE-1233] + String displayName = StringUtils.isBlank(conn.getDisplayName()) + ? conn.getBundleName() : conn.getDisplayName(); - final String resourceName = displayName.length() > 14 - ? displayName.subSequence(0, 10) + "..." - : displayName; - - label.setDefaultModelObject(resourceName); - target.add(label); + label.setDefaultModelObject(StringUtils.abbreviate(displayName, 10)); + updateEvent.getTarget().add(label); node.setDisplayName(displayName); } } } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java index 310aa130a4b..7462f22e412 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java @@ -18,14 +18,16 @@ */ package org.apache.syncope.client.console.topology; +import com.fasterxml.jackson.databind.ObjectMapper; import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.text.MessageFormat; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.audit.AuditHistoryModal; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; -import org.apache.syncope.client.console.panels.HistoryConfList; import org.apache.syncope.client.console.panels.ConnObjects; import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder; import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder; @@ -40,13 +42,11 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink; import org.apache.syncope.client.console.wizards.AjaxWizard; -import org.apache.syncope.client.console.wizards.resources.AbstractResourceWizardBuilder; import org.apache.syncope.client.console.wizards.resources.ResourceProvisionPanel; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.ConnInstanceTO; -import org.apache.syncope.common.lib.to.ItemTO; -import org.apache.syncope.common.lib.to.ProvisionTO; import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -68,6 +68,8 @@ public class TopologyTogglePanel extends TogglePanel { private static final long serialVersionUID = -2025535531121434056L; + private static final ObjectMapper MAPPER = new ObjectMapper(); + private final ResourceRestClient resourceRestClient = new ResourceRestClient(); private final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); @@ -88,11 +90,11 @@ public TopologyTogglePanel(final String id, final PageReference pageRef) { modal.size(Modal.Size.Large); setFooterVisibility(false); - propTaskModal = new BaseModal<>("outer"); + propTaskModal = new BaseModal<>(Constants.OUTER); propTaskModal.size(Modal.Size.Large); addOuterObject(propTaskModal); - schedTaskModal = new BaseModal("outer") { + schedTaskModal = new BaseModal(Constants.OUTER) { private static final long serialVersionUID = 389935548143327858L; @@ -105,12 +107,12 @@ protected void onConfigure() { schedTaskModal.size(Modal.Size.Large); addOuterObject(schedTaskModal); - provisionModal = new BaseModal<>("outer"); + provisionModal = new BaseModal<>(Constants.OUTER); provisionModal.size(Modal.Size.Large); provisionModal.addSubmitButton(); addOuterObject(provisionModal); - historyModal = new BaseModal<>("outer"); + historyModal = new BaseModal<>(Constants.OUTER); historyModal.size(Modal.Size.Large); addOuterObject(historyModal); @@ -175,11 +177,10 @@ private Fragment getSyncopeFragment(final PageReference pageRef) { public void onClick(final AjaxRequestTarget target) { try { connectorRestClient.reload(); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While reloading all connectors", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -235,7 +236,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; fragment.add(create); MetaDataRoleAuthorizationStrategy.authorize(create, RENDER, StandardEntitlement.CONNECTOR_CREATE); @@ -255,12 +255,11 @@ public void onClick(final AjaxRequestTarget target) { try { connectorRestClient.delete(String.class.cast(node.getKey())); target.appendJavaScript(String.format("jsPlumb.remove('%s');", node.getKey())); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); toggle(target, false); } catch (SyncopeClientException e) { LOG.error("While deleting resource {}", node.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -324,7 +323,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(edit, RENDER, StandardEntitlement.CONNECTOR_READ); fragment.add(edit); @@ -335,11 +333,33 @@ public String getAjaxIndicatorMarkupId() { @Override public void onClick(final AjaxRequestTarget target) { - String connKey = String.class.cast(node.getKey()); - ConnInstanceTO connInstance = connectorRestClient.read(connKey); - - target.add(historyModal.setContent( - new HistoryConfList<>(historyModal, connKey, pageRef, connInstance))); + ConnInstanceTO modelObject = connectorRestClient.read(node.getKey()); + + target.add(historyModal.setContent(new AuditHistoryModal( + historyModal, + AuditElements.EventCategoryType.LOGIC, + "ConnectorLogic", + modelObject, + StandardEntitlement.CONNECTOR_UPDATE, + pageRef) { + + private static final long serialVersionUID = -3225348282675513648L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + try { + ConnInstanceTO updated = MAPPER.readValue(json, ConnInstanceTO.class); + connectorRestClient.update(updated); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + toggle(target, false); + } catch (Exception e) { + LOG.error("While restoring connector {}", node.getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); historyModal.header( new Model<>(MessageFormat.format(getString("connector.menu.history"), node.getDisplayName()))); @@ -351,9 +371,9 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; - MetaDataRoleAuthorizationStrategy.authorize(history, RENDER, StandardEntitlement.CONNECTOR_HISTORY_LIST); + MetaDataRoleAuthorizationStrategy.authorize(history, RENDER, + String.format("%s,%s", StandardEntitlement.CONNECTOR_READ, StandardEntitlement.AUDIT_LIST)); fragment.add(history); return fragment; @@ -371,12 +391,11 @@ public void onClick(final AjaxRequestTarget target) { try { resourceRestClient.delete(node.getKey()); target.appendJavaScript(String.format("jsPlumb.remove('%s');", node.getKey())); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); toggle(target, false); } catch (SyncopeClientException e) { LOG.error("While deleting resource {}", node.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -434,7 +453,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(status, RENDER, StandardEntitlement.USER_UPDATE); fragment.add(status); @@ -470,7 +488,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(edit, RENDER, StandardEntitlement.RESOURCE_READ); fragment.add(provision); @@ -492,7 +509,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(explore, RENDER, StandardEntitlement.RESOURCE_LIST_CONNOBJECT); fragment.add(explore); @@ -515,7 +531,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(propagation, RENDER, StandardEntitlement.TASK_LIST); fragment.add(propagation); @@ -535,7 +550,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(pull, RENDER, StandardEntitlement.TASK_LIST); fragment.add(pull); @@ -555,7 +569,6 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; MetaDataRoleAuthorizationStrategy.authorize(push, RENDER, StandardEntitlement.TASK_LIST); fragment.add(push); @@ -566,11 +579,33 @@ public String getAjaxIndicatorMarkupId() { @Override public void onClick(final AjaxRequestTarget target) { - String resourceKey = String.class.cast(node.getKey()); - final ResourceTO modelObject = resourceRestClient.read(String.class.cast(node.getKey())); + ResourceTO modelObject = resourceRestClient.read(node.getKey()); - target.add(historyModal.setContent( - new HistoryConfList<>(historyModal, resourceKey, pageRef, modelObject))); + target.add(historyModal.setContent(new AuditHistoryModal( + historyModal, + AuditElements.EventCategoryType.LOGIC, + "ResourceLogic", + modelObject, + StandardEntitlement.RESOURCE_UPDATE, + pageRef) { + + private static final long serialVersionUID = -3712506022627033811L; + + @Override + protected void restore(final String json, final AjaxRequestTarget target) { + try { + ResourceTO updated = MAPPER.readValue(json, ResourceTO.class); + resourceRestClient.update(updated); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + toggle(target, false); + } catch (Exception e) { + LOG.error("While restoring resource {}", node.getKey(), e); + SyncopeConsoleSession.get().onException(e); + } + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + })); historyModal.header( new Model<>(MessageFormat.format(getString("resource.menu.history"), node.getDisplayName()))); @@ -582,13 +617,13 @@ public void onClick(final AjaxRequestTarget target) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - }; - MetaDataRoleAuthorizationStrategy.authorize(history, RENDER, StandardEntitlement.RESOURCE_HISTORY_LIST); + MetaDataRoleAuthorizationStrategy.authorize(history, RENDER, + String.format("%s,%s", StandardEntitlement.RESOURCE_READ, StandardEntitlement.AUDIT_LIST)); fragment.add(history); // [SYNCOPE-1161] - Option to clone a resource - AjaxLink clone = new IndicatingOnConfirmAjaxLink("clone", "confirmClone", true) { + AjaxLink clone = new IndicatingAjaxLink("clone") { private static final long serialVersionUID = -7978723352517770644L; @@ -600,36 +635,25 @@ public void onClick(final AjaxRequestTarget target) { // reset some resource objects keys if (resource.getOrgUnit() != null) { resource.getOrgUnit().setKey(null); - for (ItemTO item : resource.getOrgUnit().getItems()) { - item.setKey(null); - } + resource.getOrgUnit().getItems().forEach(item -> item.setKey(null)); } - for (ProvisionTO provision : resource.getProvisions()) { + resource.getProvisions().forEach(provision -> { provision.setKey(null); if (provision.getMapping() != null) { - for (ItemTO item : provision.getMapping().getItems()) { - item.setKey(null); - } + provision.getMapping().getItems().forEach(item -> item.setKey(null)); provision.getMapping().getLinkingItems().clear(); } provision.getVirSchemas().clear(); - } - resourceRestClient.create(resource); - - // refresh Topology - send(pageRef.getPage(), Broadcast.DEPTH, new AbstractResourceWizardBuilder.CreateEvent( - resource.getKey(), - resource.getKey(), - TopologyNode.Kind.RESOURCE, - resource.getConnector(), - target)); + }); + target.add(modal.setContent(new ResourceWizardBuilder(resource, pageRef). + build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE))); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - toggle(target, false); + modal.header(new Model<>(MessageFormat.format(getString("resource.clone"), node.getKey()))); + modal.show(true); + } catch (SyncopeClientException e) { LOG.error("While cloning resource {}", node.getKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -646,13 +670,13 @@ public void onEvent(final IEvent event) { super.onEvent(event); if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { - final AjaxWizard.NewItemFinishEvent item = AjaxWizard.NewItemFinishEvent.class.cast(event.getPayload()); + final AjaxWizard.NewItemFinishEvent item = AjaxWizard.NewItemFinishEvent.class.cast(event.getPayload()); final Serializable result = item.getResult(); - final AjaxRequestTarget target = item.getTarget(); - if (result != null && result instanceof ConnInstanceTO) { + final Optional target = item.getTarget(); + if (result != null && result instanceof ConnInstanceTO && target.isPresent()) { // update Toggle Panel header ConnInstanceTO conn = ConnInstanceTO.class.cast(result); - setHeader(target, StringUtils.abbreviate(conn.getDisplayName(), HEADER_FIRST_ABBREVIATION)); + setHeader(target.get(), StringUtils.abbreviate(conn.getDisplayName(), HEADER_FIRST_ABBREVIATION)); } } } @@ -675,6 +699,5 @@ public AjaxRequestTarget getTarget() { public String getKey() { return key; } - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java index 1134a004058..4af5b249648 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java @@ -27,14 +27,14 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.rest.ConfRestClient; import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; -import org.apache.syncope.common.lib.to.ConnInstanceTO; -import org.apache.syncope.common.lib.to.ResourceTO; -import org.apache.wicket.Application; -import org.apache.wicket.Session; -import org.apache.wicket.ThreadContext; import org.apache.wicket.protocol.ws.api.WebSocketBehavior; import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler; import org.apache.wicket.protocol.ws.api.message.TextMessage; @@ -49,28 +49,85 @@ public class TopologyWebSocketBehavior extends WebSocketBehavior { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final Map resources = - Collections.synchronizedMap(new HashMap()); + private static final String CONNECTOR_TEST_TIMEOUT_PARAMETER = "connector.test.timeout"; - private final Set runningResCheck = Collections.synchronizedSet(new HashSet()); + private static final String RESOURCE_TEST_TIMEOUT_PARAMETER = "resource.test.timeout"; - private final Map connectors = - Collections.synchronizedMap(new HashMap()); + private static void timeoutHandlingConnectionChecker( + final Checker checker, + final Integer timeout, + final Map responses, + final Set running) { - private final Set runningConnCheck = Collections.synchronizedSet(new HashSet()); + String response = null; + try { + if (timeout == null || timeout < 0) { + LOG.debug("No timeouts for resource connection checking ... "); + response = checker.call(); + } else if (timeout > 0) { + LOG.debug("Timeouts provided for resource connection checking ... "); + response = SyncopeConsoleSession.get().execute(checker).get(timeout, TimeUnit.SECONDS); + } + } catch (InterruptedException | TimeoutException e) { + LOG.warn("Connection with {} timed out", checker.key); + response = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + TopologyNode.Status.UNREACHABLE, checker.key); + } catch (Exception e) { + LOG.error("Unexpected exception conneting to {}", checker.key, e); + response = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + TopologyNode.Status.FAILURE, checker.key); + } + + if (response != null) { + responses.put(checker.key, response); + } + + running.remove(checker.key); + } + + private final Map connectors = Collections.synchronizedMap(new HashMap<>()); + + private final Set runningConnCheck = Collections.synchronizedSet(new HashSet<>()); - private final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + private final Map resources = Collections.synchronizedMap(new HashMap<>()); - private final ResourceRestClient resourceRestClient = new ResourceRestClient(); + private final Set runningResCheck = Collections.synchronizedSet(new HashSet<>()); + + private String coreAddress; + + private String domain; + + private String jwt; + + private Integer connectorTestTimeout = null; + + private Integer resourceTestTimeout = null; + + public TopologyWebSocketBehavior() { + coreAddress = SyncopeConsoleApplication.get().newClientFactory().getAddress(); + domain = SyncopeConsoleSession.get().getDomain(); + jwt = SyncopeConsoleSession.get().getJWT(); + + // Handling with timeout as per SYNCOPE-1379 + ConfRestClient confRestClient = new ConfRestClient(); + try { + connectorTestTimeout = Integer.parseInt( + confRestClient.get(CONNECTOR_TEST_TIMEOUT_PARAMETER).getValues().get(0)); + resourceTestTimeout = Integer.parseInt( + confRestClient.get(RESOURCE_TEST_TIMEOUT_PARAMETER).getValues().get(0)); + } catch (Exception e) { + LOG.debug("No {} or {} conf parameters found", + CONNECTOR_TEST_TIMEOUT_PARAMETER, RESOURCE_TEST_TIMEOUT_PARAMETER, e); + } + } @Override protected void onMessage(final WebSocketRequestHandler handler, final TextMessage message) { try { JsonNode obj = OBJECT_MAPPER.readTree(message.getText()); - switch (Topology.SupportedOperation.valueOf(obj.get("kind").asText())) { case CHECK_CONNECTOR: - final String ckey = obj.get("target").asText(); + String ckey = obj.get("target").asText(); if (connectors.containsKey(ckey)) { handler.push(connectors.get(ckey)); @@ -82,14 +139,19 @@ protected void onMessage(final WebSocketRequestHandler handler, final TextMessag if (runningConnCheck.contains(ckey)) { LOG.debug("Running connection check for connector {}", ckey); } else { - runningConnCheck.add(ckey); + try { + SyncopeConsoleSession.get().execute(() -> timeoutHandlingConnectionChecker( + new ConnectorChecker(ckey), connectorTestTimeout, connectors, runningConnCheck)); + + runningConnCheck.add(ckey); + } catch (Exception e) { + LOG.error("Unexpected error", e); + } } - - SyncopeConsoleSession.get().execute(new ConnCheck(ckey)); - break; + case CHECK_RESOURCE: - final String rkey = obj.get("target").asText(); + String rkey = obj.get("target").asText(); if (resources.containsKey(rkey)) { handler.push(resources.get(rkey)); @@ -101,18 +163,24 @@ protected void onMessage(final WebSocketRequestHandler handler, final TextMessag if (runningResCheck.contains(rkey)) { LOG.debug("Running connection check for resource {}", rkey); } else { - runningResCheck.add(rkey); + try { + SyncopeConsoleSession.get().execute(() -> timeoutHandlingConnectionChecker( + new ResourceChecker(rkey), resourceTestTimeout, resources, runningResCheck)); + + runningResCheck.add(rkey); + } catch (Exception e) { + LOG.error("Unexpected error", e); + } } - - SyncopeConsoleSession.get().execute(new ResCheck(rkey)); - break; + case ADD_ENDPOINT: handler.appendJavaScript(String.format("addEndpoint('%s', '%s', '%s');", obj.get("source").asText(), obj.get("target").asText(), obj.get("scope").asText())); break; + default: } } catch (IOException e) { @@ -128,80 +196,51 @@ public boolean resCheckDone(final Collection resources) { return this.resources.keySet().containsAll(resources); } - class ConnCheck implements Runnable { + private abstract class Checker implements Callable { - private final String key; + protected final String key; - private final Application application; + Checker(final String key) { + this.key = key; + } + } - private final Session session; + private class ConnectorChecker extends Checker { - ConnCheck(final String key) { - this.key = key; - this.application = Application.get(); - this.session = Session.exists() ? Session.get() : null; + ConnectorChecker(final String key) { + super(key); } @Override - public void run() { + public String call() throws Exception { try { - ThreadContext.setApplication(application); - ThreadContext.setSession(session); - - String res; - try { - final ConnInstanceTO connector = connectorRestClient.read(key); - res = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", - connectorRestClient.check(connector).getLeft() - ? TopologyNode.Status.REACHABLE : TopologyNode.Status.UNREACHABLE, key); - } catch (Exception e) { - LOG.warn("Error checking connection for {}", key, e); - res = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", TopologyNode.Status.FAILURE, key); - } - - connectors.put(key, res); - runningConnCheck.remove(key); - } finally { - ThreadContext.detach(); + return String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + ConnectorRestClient.check(coreAddress, domain, jwt, key) + ? TopologyNode.Status.REACHABLE : TopologyNode.Status.UNREACHABLE, key); + } catch (Exception e) { + LOG.warn("Error checking connection for {}", key, e); + return String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + TopologyNode.Status.FAILURE, key); } } } - class ResCheck implements Runnable { + private class ResourceChecker extends Checker { - private final String key; - - private final Application application; - - private final Session session; - - ResCheck(final String key) { - this.key = key; - this.application = Application.get(); - this.session = Session.exists() ? Session.get() : null; + ResourceChecker(final String key) { + super(key); } @Override - public void run() { + public String call() throws Exception { try { - ThreadContext.setApplication(application); - ThreadContext.setSession(session); - - String res; - try { - final ResourceTO resource = resourceRestClient.read(key); - res = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", - resourceRestClient.check(resource).getLeft() - ? TopologyNode.Status.REACHABLE : TopologyNode.Status.UNREACHABLE, key); - } catch (Exception e) { - LOG.warn("Error checking connection for {}", key, e); - res = String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", TopologyNode.Status.FAILURE, key); - } - - resources.put(key, res); - runningResCheck.remove(key); - } finally { - ThreadContext.detach(); + return String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + ResourceRestClient.check(coreAddress, domain, jwt, key) + ? TopologyNode.Status.REACHABLE : TopologyNode.Status.UNREACHABLE, key); + } catch (Exception e) { + LOG.warn("Error checking connection for {}", key, e); + return String.format("{ \"status\": \"%s\", \"target\": \"%s\"}", + TopologyNode.Status.FAILURE, key); } } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AbstractAjaxDownloadBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AbstractAjaxDownloadBehavior.java deleted file mode 100644 index 004f7c8629e..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AbstractAjaxDownloadBehavior.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.client.console.wicket.ajax.form; - -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.behavior.AbstractAjaxBehavior; -import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; -import org.apache.wicket.util.resource.IResourceStream; -import org.apache.wicket.util.time.Duration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class AbstractAjaxDownloadBehavior extends AbstractAjaxBehavior { - - private static final long serialVersionUID = 6833760760338614245L; - - protected static final Logger LOG = LoggerFactory.getLogger(AbstractAjaxDownloadBehavior.class); - - /** - * Call this method to initiate the download. - * - * @param target request target. - */ - public void initiate(final AjaxRequestTarget target) { - CharSequence url = getCallbackUrl(); - target.appendJavaScript("window.location.href='" + url + "'"); - } - - @Override - public void onRequest() { - try { - getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent( - new ResourceStreamRequestHandler( - getResourceStream(), getFileName()).setCacheDuration(Duration.NONE)); - } catch (Exception e) { - // cannot be notifies beacause the use of scheduleRequestHandlerAfterCurrent - LOG.error("Error downloading file", e); - } - } - - protected abstract String getFileName(); - - protected abstract IResourceStream getResourceStream(); -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java new file mode 100644 index 00000000000..acf6045835a --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/form/AjaxDownloadBehavior.java @@ -0,0 +1,77 @@ +/* + * 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.client.console.wicket.ajax.form; + +import org.apache.syncope.client.console.commons.HttpResourceStream; +import org.apache.syncope.client.console.rest.ResponseHolder; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AbstractAjaxBehavior; +import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; +import org.apache.wicket.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AjaxDownloadBehavior extends AbstractAjaxBehavior { + + private static final long serialVersionUID = 6833760760338614245L; + + protected static final Logger LOG = LoggerFactory.getLogger(AjaxDownloadBehavior.class); + + protected ResponseHolder responseHolder; + + public void setResponse(final ResponseHolder response) { + this.responseHolder = response; + } + + /** + * Call this method to initiate the download. + * + * @param target request target. + */ + public void initiate(final AjaxRequestTarget target) { + CharSequence url = getCallbackUrl(); + target.appendJavaScript("window.location.href='" + url + "'"); + } + + protected HttpResourceStream getResourceStream() { + HttpResourceStream stream = null; + + if (responseHolder != null) { + stream = new HttpResourceStream(responseHolder); + responseHolder = null; + } + + return stream; + } + + @Override + public void onRequest() { + try { + HttpResourceStream resourceStream = getResourceStream(); + if (resourceStream != null) { + getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent( + new ResourceStreamRequestHandler( + resourceStream, resourceStream.getFilename()).setCacheDuration(Duration.NONE)); + } + } catch (Exception e) { + // cannot be notifies beacause the use of scheduleRequestHandlerAfterCurrent + LOG.error("Error downloading file", e); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java index 9cfa76904c3..74829989ed6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java @@ -46,13 +46,19 @@ public class AjaxFallbackDataTable extends DataTable { - private static final long serialVersionUID = 6861105496141602937L; + private static final long serialVersionUID = 18103848201505L; private ActionLinksTogglePanel togglePanel; - public AjaxFallbackDataTable(final String id, final List> columns, - final ISortableDataProvider dataProvider, final int rowsPerPage, final WebMarkupContainer container) { + public AjaxFallbackDataTable( + final String id, + final List> columns, + final ISortableDataProvider dataProvider, + final int rowsPerPage, + final WebMarkupContainer container) { + super(id, columns, dataProvider, rowsPerPage); + setOutputMarkupId(true); setVersioned(false); @@ -67,7 +73,7 @@ protected WebMarkupContainer newSortableHeader( final String borderId, final S property, final ISortStateLocator locator) { return new AjaxFallbackOrderByBorder(borderId, property, locator) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -5729452929468028615L; @Override protected void onAjaxClick(final AjaxRequestTarget target) { @@ -89,7 +95,7 @@ protected WebMarkupContainer newSortableHeader( final String borderId, final S property, final ISortStateLocator locator) { return new AjaxFallbackOrderByBorder(borderId, property, locator) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 18100257555728L; @Override protected void onAjaxClick(final AjaxRequestTarget target) { @@ -124,7 +130,7 @@ protected Item newRowItem(final String id, final int index, final IModel m item.add(new AttributeModifier("style", "cursor: pointer;")); item.add(new AjaxEventBehavior(Constants.ON_CLICK) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -4609215765213990763L; @Override protected String findIndicatorId() { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AttrColumn.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AttrColumn.java index 0aa49fd5d61..c4c51b1129f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AttrColumn.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AttrColumn.java @@ -37,10 +37,10 @@ public class AttrColumn extends AbstractColumn extends AbstractColumn { + + private static final long serialVersionUID = 7955560320949560716L; + + private static final Logger LOG = LoggerFactory.getLogger(BatchResponseColumn.class); + + private final Map results; + + private final String keyFieldName; + + public BatchResponseColumn(final Map results, final String keyFieldName) { + super(new Model()); + this.results = results; + this.keyFieldName = keyFieldName; + } + + @Override + public Component getHeader(final String componentId) { + Label label = new Label(componentId, new Model<>()); + label.setDefaultModel(new StringResourceModel("batch.result.header", label, new Model<>("Result"))); + return label; + } + + @Override + public void populateItem(final Item> item, final String componentId, final IModel rowModel) { + try { + Object key = BeanUtils.getPropertyDescriptor(rowModel.getObject().getClass(), keyFieldName). + getReadMethod().invoke(rowModel.getObject(), new Object[0]); + String status = results.containsKey(key.toString()) + ? results.get(key.toString()) + : ExecStatus.NOT_ATTEMPTED.name(); + + item.add(new Label(componentId, new StringResourceModel(status, item, new Model<>(status)))); + } catch (BeansException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + LOG.error("Errore retrieving target key value", e); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BooleanPropertyColumn.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BooleanPropertyColumn.java index c069470f203..bfe7a36dcd4 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BooleanPropertyColumn.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BooleanPropertyColumn.java @@ -47,7 +47,7 @@ public void populateItem(final Item> item, final String compon Object obj = bwi.getPropertyValue(getPropertyExpression()); item.add(new Label(componentId, StringUtils.EMPTY)); - if (Boolean.valueOf(obj.toString())) { + if (obj != null && Boolean.valueOf(obj.toString())) { item.add(new AttributeModifier("class", "glyphicon glyphicon-ok")); item.add(new AttributeModifier("style", "display: table-cell; text-align: center;")); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BulkActionResultColumn.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BulkActionResultColumn.java deleted file mode 100644 index 73abd092d48..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BulkActionResultColumn.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.client.console.wicket.extensions.markup.html.repeater.data.table; - -import java.lang.reflect.InvocationTargetException; -import org.apache.syncope.common.lib.to.BulkActionResult; -import org.apache.syncope.common.lib.to.BulkActionResult.Status; -import org.apache.wicket.Component; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.StringResourceModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.BeansException; - -public class BulkActionResultColumn extends AbstractColumn { - - private static final long serialVersionUID = 7955560320949560716L; - - private static final Logger LOG = LoggerFactory.getLogger(BulkActionResultColumn.class); - - private final BulkActionResult results; - - private final String keyFieldName; - - public BulkActionResultColumn(final BulkActionResult results, final String keyFieldName) { - super(new Model()); - this.results = results; - this.keyFieldName = keyFieldName; - } - - @Override - public String getCssClass() { - return "bulkResultColumn"; - } - - @Override - public Component getHeader(final String componentId) { - final Label label = new Label(componentId, new Model<>()); - label.setDefaultModel(new StringResourceModel("bulk.action.result.header", label, new Model<>("Result"))); - return label; - } - - @Override - public void populateItem(final Item> item, final String componentId, final IModel rowModel) { - try { - final Object id = BeanUtils.getPropertyDescriptor(rowModel.getObject().getClass(), keyFieldName). - getReadMethod().invoke(rowModel.getObject(), new Object[0]); - final Status status = results.getResults().containsKey(id.toString()) - ? results.getResults().get(id.toString()) - : Status.NOT_ATTEMPTED; - - item.add(new Label(componentId, new StringResourceModel(status.name(), item, new Model<>(status.name())))); - - } catch (BeansException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - LOG.error("Errore retrieving target id value", e); - } - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/WebMarkupContainerNoVeil.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/WebMarkupContainerNoVeil.java new file mode 100644 index 00000000000..9faf87e56a9 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/WebMarkupContainerNoVeil.java @@ -0,0 +1,42 @@ +/* + * 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.client.console.wicket.markup.html; + +import org.apache.commons.lang3.StringUtils; +import org.apache.wicket.ajax.IAjaxIndicatorAware; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.IModel; + +public class WebMarkupContainerNoVeil extends WebMarkupContainer implements IAjaxIndicatorAware { + + private static final long serialVersionUID = 6883930486048460708L; + + public WebMarkupContainerNoVeil(final String id) { + super(id); + } + + public WebMarkupContainerNoVeil(final String id, final IModel model) { + super(id, model); + } + + @Override + public String getAjaxIndicatorMarkupId() { + return StringUtils.EMPTY; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java index 69ede942618..76cc9620859 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java @@ -52,7 +52,7 @@ public class BaseModal extends Modal { - private static final long serialVersionUID = -6142277554912316095L; + private static final long serialVersionUID = 18135548644909L; protected static final Logger LOG = LoggerFactory.getLogger(BaseModal.class); @@ -90,7 +90,7 @@ public BaseModal(final String id) { content = new AbstractModalPanel(this, null) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -6142277554912316095L; }; @@ -127,6 +127,11 @@ public BaseModal setFormModel(final IModel model) { return this; } + public BaseModal setFormAsMultipart(final boolean multipart) { + form.setMultiPart(multipart); + return this; + } + public T getFormModel() { return form.getModelObject(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractFieldPanel.java index 115349b968f..d19d2d3b304 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractFieldPanel.java @@ -118,4 +118,10 @@ protected String externalActionIcon() { } public abstract AbstractFieldPanel setModelObject(T object); + + public String getName() { + return this.name; + } + + public abstract AbstractFieldPanel setReadOnly(boolean readOnly); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java index 6d3be218172..20eba2067cc 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java @@ -73,6 +73,12 @@ public AbstractMultiPanel( } } + // SYNCOPE-1476 + public AbstractMultiPanel setFormAsMultipart(final boolean multipart) { + form.setMultiPart(multipart); + return this; + } + private Fragment getNoDataFragment(final IModel> model, final String label) { final Fragment fragment = new Fragment("content", "noDataFragment", AbstractMultiPanel.this); fragment.add(new Label("field-label", new ResourceModel(label, label))); @@ -188,14 +194,20 @@ protected void onError(final AjaxRequestTarget target) { } } - protected abstract Panel getItemPanel(final ListItem item); + protected abstract Panel getItemPanel(ListItem item); protected void clearInput(final Panel panel) { // do nothing by default } + @Override public AbstractFieldPanel> setReadOnly(final boolean readOnly) { container.setEnabled(!readOnly); return this; } + + public AbstractFieldPanel> setFormReadOnly(final boolean readOnly) { + form.setEnabled(!readOnly); + return this; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/Action.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/Action.java index 98d25565248..f01a9841066 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/Action.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/Action.java @@ -19,6 +19,8 @@ package org.apache.syncope.client.console.wicket.markup.html.form; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.model.Model; @@ -31,7 +33,7 @@ public final class Action implements Serializable { private static final long serialVersionUID = -7989237020377623993L; - private String realm = null; + private final List realms = new ArrayList<>(); private final ActionLink link; @@ -66,12 +68,24 @@ public Action(final ActionLink link, final ActionLink.ActionType type) { this.indicator = true; } - public String getRealm() { - return realm; + public String[] getRealms() { + return realms.toArray(new String[realms.size()]); } public void setRealm(final String realm) { - this.realm = realm; + this.realms.clear(); + + if (realm != null) { + this.realms.add(realm); + } + } + + public void setRealms(final String realm, final List dynRealms) { + setRealm(realm); + + if (dynRealms != null) { + this.realms.addAll(dynRealms); + } } public ActionLink getLink() { @@ -104,7 +118,7 @@ public Action hideLabel() { this.visibleLabel = false; return this; } - + public Action showLabel() { this.visibleLabel = true; return this; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java index d8e8f77a678..80367ac224e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java @@ -33,6 +33,8 @@ public abstract class ActionLink implements Serializable private boolean enabled = true; + private String confirmMessage; + public ActionLink() { } @@ -48,6 +50,8 @@ public enum ActionType { REMOVE_SYNC_TOKEN("update"), CLONE("create"), CREATE("create"), + CREATE_CONNECTOR("create"), + CREATE_RESOURCE("create"), TEMPLATE("read"), EDIT("read"), TYPE_EXTENSIONS("read"), @@ -68,6 +72,7 @@ public enum ActionType { REQUEST_PASSWORD_RESET("update"), DRYRUN("execute"), CLAIM("claim"), + UNCLAIM("unclaim"), SELECT("read"), CLOSE("read"), EXPORT("read"), @@ -76,7 +81,6 @@ public enum ActionType { EXPORT_PDF("read"), EXPORT_RTF("read"), EXPORT_XML("read"), - RESTORE("import"), SUSPEND("update"), REACTIVATE("update"), RELOAD("import"), @@ -91,18 +95,25 @@ public enum ActionType { PROVISION_MEMBERS("update"), RECONCILIATION_PUSH("update"), RECONCILIATION_PULL("update"), + RECONCILIATION_RESOURCE("update"), MANAGE_RESOURCES("update"), + MANAGE_ACCOUNTS("update"), + MERGE_ACCOUNTS("update"), MANAGE_USERS("update"), MANAGE_GROUPS("update"), PROPAGATION_TASKS("read"), NOTIFICATION_TASKS("read"), + PULL_TASKS("read"), + PUSH_TASKS("read"), ZOOM_IN("zoomin"), ZOOM_OUT("zoomout"), - WORKFLOW_MODELER("workflowModeler"), VIEW_EXECUTIONS("read"), VIEW_DETAILS("read"), MANAGE_APPROVAL("edit"), - EDIT_APPROVAL("edit"); + EDIT_APPROVAL("edit"), + VIEW_AUDIT_HISTORY("read"), + EXTERNAL_EDITOR("externalEditor"), + EXPLORE_RESOURCE("search"); private final String actionId; @@ -119,7 +130,7 @@ public T getModelObject() { return modelObject; } - public abstract void onClick(final AjaxRequestTarget target, final T modelObject); + public abstract void onClick(AjaxRequestTarget target, T modelObject); public void postClick() { } @@ -157,4 +168,13 @@ public final boolean isEnabled(final T modelObject) { public boolean isIndicatorEnabled() { return true; } + + public String getConfirmMessage() { + return confirmMessage; + } + + public ActionLink confirmMessage(final String confirmMessage) { + this.confirmMessage = confirmMessage; + return this; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java index 71e5ef4e71c..63d0e839805 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java @@ -41,9 +41,7 @@ import org.apache.syncope.common.lib.to.AccessTokenTO; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.JobTO; -import org.apache.syncope.common.lib.to.ProvisioningTaskTO; -import org.apache.syncope.common.lib.to.WorkflowDefinitionTO; -import org.apache.syncope.common.lib.to.WorkflowFormTO; +import org.apache.syncope.common.lib.to.SchedTaskTO; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.IEvent; @@ -100,12 +98,8 @@ public void updateHeader(final AjaxRequestTarget target, final Serializable mode header = ((AccessTokenTO) modelObject).getOwner(); } else if (modelObject instanceof ExecTO) { header = ((ExecTO) modelObject).getKey(); - } else if (modelObject instanceof WorkflowDefinitionTO) { - header = ((WorkflowDefinitionTO) modelObject).getName(); - } else if (modelObject instanceof ProvisioningTaskTO) { - header = ((ProvisioningTaskTO) modelObject).getName(); - } else if (modelObject instanceof WorkflowFormTO) { - header = ((WorkflowFormTO) modelObject).getKey(); + } else if (modelObject instanceof SchedTaskTO) { + header = ((SchedTaskTO) modelObject).getName(); } else if (modelObject instanceof EntityTO) { header = ((EntityTO) modelObject).getKey(); } else if (modelObject instanceof StatusBean) { @@ -121,6 +115,8 @@ public void updateHeader(final AjaxRequestTarget target, final Serializable mode ? ((JobTO) modelObject).getRefDesc() : ((JobTO) modelObject).getRefKey(); } else if (modelObject instanceof ResourceProvision) { header = ((ResourceProvision) modelObject).getAnyType(); + } else if (modelObject instanceof EntityTO) { + header = ((EntityTO) modelObject).getKey(); } else { header = new ResourceModel("actions", StringUtils.EMPTY).getObject(); } @@ -129,8 +125,12 @@ public void updateHeader(final AjaxRequestTarget target, final Serializable mode } public void toggleWithContent( - final AjaxRequestTarget target, final ActionsPanel actionsPanel, final T modelObject) { + final AjaxRequestTarget target, + final ActionsPanel actionsPanel, + final T modelObject) { + updateHeader(target, modelObject); + modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { private static final long serialVersionUID = 8804221891699487139L; @@ -141,7 +141,7 @@ public void onClose(final AjaxRequestTarget target) { } }); - final Fragment frag = new Fragment("actions", "actionsFragment", this); + Fragment frag = new Fragment("actions", "actionsFragment", this); frag.setOutputMarkupId(true); frag.add(actionsPanel); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java index 91f83b98469..933bff2375e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java @@ -54,7 +54,7 @@ public final class ActionPanel extends Panel { private final Action action; public ActionPanel(final IModel model, final Action action) { - this("action", model, action); + this(Constants.ACTION, model, action); } public ActionPanel(final String componentId, final IModel model, final Action action) { @@ -74,7 +74,7 @@ public ActionPanel(final String componentId, final IModel model, final Action if (action.getLink() == null || action.getType() == ActionType.NOT_FOUND) { enabled = true; - actionLink = new IndicatingAjaxLink("action") { + actionLink = new IndicatingAjaxLink(Constants.ACTION) { private static final long serialVersionUID = -7978723352517770644L; @@ -87,16 +87,20 @@ public boolean isEnabled() { public void onClick(final AjaxRequestTarget target) { } }; - } else if (action.getType() == ActionType.WORKFLOW_MODELER) { + } else if (action.getType() == ActionType.EXTERNAL_EDITOR) { enabled = action.getLink().isEnabled(obj); actionLink = new BookmarkablePageLink<>( - "action", action.getLink().getPageClass(), action.getLink().getPageParameters()). + Constants.ACTION, action.getLink().getPageClass(), action.getLink().getPageParameters()). setPopupSettings(new VeilPopupSettings().setHeight(600).setWidth(800)); } else { enabled = action.getLink().isEnabled(obj); actionLink = action.isOnConfirm() - ? new IndicatingOnConfirmAjaxLink("action", enabled) { + ? new IndicatingOnConfirmAjaxLink( + Constants.ACTION, + StringUtils.isNotBlank(action.getLink().getConfirmMessage()) + ? action.getLink().getConfirmMessage() + : Constants.CONFIRM_DELETE, enabled) { private static final long serialVersionUID = -7978723352517770644L; @@ -112,7 +116,7 @@ public String getAjaxIndicatorMarkupId() { ? StringUtils.EMPTY : Constants.VEIL_INDICATOR_MARKUP_ID; } } - : new IndicatingAjaxLink("action") { + : new IndicatingAjaxLink(Constants.ACTION) { private static final long serialVersionUID = -7978723352517770644L; @@ -130,7 +134,7 @@ public String getAjaxIndicatorMarkupId() { }; } - if (SyncopeConsoleSession.get().owns(action.getEntitlements(), action.getRealm())) { + if (SyncopeConsoleSession.get().owns(action.getEntitlements(), action.getRealms())) { MetaDataRoleAuthorizationStrategy.authorizeAll(actionLink, RENDER); } else { MetaDataRoleAuthorizationStrategy.unauthorizeAll(actionLink, RENDER); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.java index c7086246c97..2d828c4d542 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.java @@ -60,6 +60,7 @@ public Action add( final ActionLink link, final ActionLink.ActionType type, final String entitlements) { + return add(link, type, entitlements, false); } @@ -69,30 +70,48 @@ public Action add( final String entitlements, final boolean onConfirm) { - final Action action = new Action<>(link, type); + Action action = new Action<>(link, type); action.setEntitlements(entitlements); action.setOnConfirm(onConfirm); actions.add(action); return action; } + public Action add(final Action action) { + actions.add(action); + return action; + } + + public Action add(final int index, final Action action) { + actions.add(index, action); + return action; + } + + public Action set(final int index, final Action action) { + actions.set(index, action); + return action; + } + + public List> getActions() { + return actions; + } + public ActionsPanel clone(final String componentId, final IModel model) { - final ActionsPanel panel = new ActionsPanel<>(componentId, model); + ActionsPanel panel = new ActionsPanel<>(componentId, model); panel.actions.addAll(actions); return panel; } /** * Use this with toggle panels. + * * @param componentId Component Id. * @param model Model. * @return Actions panel. */ public ActionsPanel cloneWithLabels(final String componentId, final IModel model) { - final ActionsPanel panel = new ActionsPanel<>(componentId, model); - for (Action action : actions) { - panel.actions.add(action.showLabel()); - } + ActionsPanel panel = new ActionsPanel<>(componentId, model); + actions.forEach(action -> panel.actions.add(action.showLabel())); return panel; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java new file mode 100644 index 00000000000..38b1d27e704 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java @@ -0,0 +1,133 @@ +/* + * 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.client.console.wicket.markup.html.form; + +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAutoCompleteBehavior; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.validation.IValidator; + +public class AjaxCharacterFieldPanel extends FieldPanel implements Cloneable { + + private static final long serialVersionUID = 238940918106696068L; + + private Component questionMarkJexlHelp; + + private List choices = Collections.emptyList(); + + public AjaxCharacterFieldPanel(final String id, final String name, final IModel model) { + this(id, name, model, true); + } + + public AjaxCharacterFieldPanel( + final String id, final String name, final IModel model, final boolean enableOnChange) { + super(id, name, model); + + questionMarkJexlHelp = Constants.getJEXLPopover(this, TooltipConfig.Placement.right); + add(questionMarkJexlHelp.setVisible(false)); + + final AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(true); + settings.setShowListOnEmptyInput(true); + settings.setCssClassName("custom-autocomplete-box"); + + field = new AutoCompleteTextField("textField", model, settings) { + + private static final long serialVersionUID = -6648767303091874219L; + + @Override + protected Iterator getChoices(final String input) { + return AjaxCharacterFieldPanel.this.getChoices(input); + } + + @Override + protected AutoCompleteBehavior newAutoCompleteBehavior( + final IAutoCompleteRenderer renderer, final AutoCompleteSettings settings) { + return new IndicatorAutoCompleteBehavior(renderer, settings) { + + private static final long serialVersionUID = 1070808433195962931L; + + @Override + protected Iterator getChoices(final String input) { + return AjaxCharacterFieldPanel.this.getChoices(input); + } + }; + } + }; + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); + + if (enableOnChange && !isReadOnly()) { + field.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + // nothing to do + } + }); + } + } + + public void addValidator(final IValidator validator) { + this.field.add(validator); + } + + public void setChoices(final List choices) { + if (choices != null) { + this.choices = choices; + } + } + + public FieldPanel enableJexlHelp() { + questionMarkJexlHelp.setVisible(true); + return this; + } + + public FieldPanel enableJexlHelp(final String... jexlExamples) { + questionMarkJexlHelp = Constants.getJEXLPopover(this, TooltipConfig.Placement.bottom, jexlExamples); + addOrReplace(questionMarkJexlHelp.setVisible(true)); + return this; + } + + protected Iterator getChoices(final String input) { + return choices.stream(). + filter(choice -> input != null && input.length() > 0 && input.toCharArray()[0] == choice). + iterator(); + } + + @Override + public FieldPanel clone() { + final AjaxCharacterFieldPanel panel = (AjaxCharacterFieldPanel) super.clone(); + panel.setChoices(choices); + return panel; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCheckBoxPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCheckBoxPanel.java index 2a2d6e9069a..bb2d7ef33e6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCheckBoxPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCheckBoxPanel.java @@ -22,11 +22,13 @@ import java.util.List; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.common.lib.to.AttributableTO; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; public class AjaxCheckBoxPanel extends FieldPanel { @@ -38,10 +40,11 @@ public AjaxCheckBoxPanel(final String id, final String name, final IModel model, final boolean enableOnChange) { + super(id, name, model); field = new CheckBox("checkboxField", model); - add(field.setLabel(new Model<>(name)).setOutputMarkupId(true)); + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); if (enableOnChange && !isReadOnly()) { field.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { @@ -121,4 +124,30 @@ public void setObject(final Boolean object) { return this; } + @SuppressWarnings({ "rawtypes", "unchecked" }) + public FieldPanel setNewModel(final AttributableTO attributableTO, final String schema) { + field.setModel(new Model() { + + private static final long serialVersionUID = -4214654722524358000L; + + @Override + public Serializable getObject() { + if (!attributableTO.getPlainAttr(schema).get().getValues().isEmpty()) { + return Boolean.TRUE.toString().equalsIgnoreCase( + attributableTO.getPlainAttr(schema).get().getValues().get(0)); + } + return null; + } + + @Override + public void setObject(final Serializable object) { + attributableTO.getPlainAttr(schema).get().getValues().clear(); + attributableTO.getPlainAttr(schema).get().getValues().add( + object == null ? Boolean.FALSE.toString() : object.toString()); + } + + }); + + return this; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateFieldPanel.java index 8020b70560e..5faf14e4bd5 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateFieldPanel.java @@ -23,6 +23,7 @@ import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; public class AjaxDateFieldPanel extends DateFieldPanel { @@ -31,8 +32,8 @@ public class AjaxDateFieldPanel extends DateFieldPanel { public AjaxDateFieldPanel(final String id, final String name, final IModel model, final String datePattern) { super(id, name, model, datePattern); - field = new AjaxDatePicker("field", model, SyncopeConsoleSession.get().getDateFormat().getLocale()); - add(field.setLabel(new Model<>(name)).setOutputMarkupId(true)); + field = new AjaxDatePicker("field", model, SyncopeConsoleSession.get().getLocale(), datePattern); + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateTimeFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateTimeFieldPanel.java index 723dd009b3e..485ceecd0f3 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateTimeFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDateTimeFieldPanel.java @@ -19,21 +19,34 @@ package org.apache.syncope.client.console.wicket.markup.html.form; import com.googlecode.wicket.kendo.ui.form.datetime.AjaxDateTimePicker; +import java.text.DateFormat; import java.util.Date; +import java.util.Locale; +import org.apache.commons.lang3.time.FastDateFormat; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; public class AjaxDateTimeFieldPanel extends DateFieldPanel { private static final long serialVersionUID = -428975732068281726L; public AjaxDateTimeFieldPanel( - final String id, final String name, final IModel model, final String datePattern) { - super(id, name, model, datePattern); + final String id, final String name, final IModel model, final String dateTimePattern) { - field = new AjaxDateTimePicker("field", model, SyncopeConsoleSession.get().getDateFormat().getLocale()); - add(field.setLabel(new Model<>(name)).setOutputMarkupId(true)); + super(id, name, model, dateTimePattern); + + // dateTimePattern should be spit into separate date and time pattern strings in order to be passed to the + // AjaxDateTimePicker constructor, but there is no safe way to do that - ignoring + Locale locale = SyncopeConsoleSession.get().getLocale(); + field = new AjaxDateTimePicker( + "field", + model, + locale, + FastDateFormat.getDateInstance(DateFormat.SHORT, locale).getPattern().replace("yy", "yyyy"), + FastDateFormat.getTimeInstance(DateFormat.SHORT, locale).getPattern()); + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); } @Override diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDownload.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDownload.java deleted file mode 100644 index 6ede95f2201..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDownload.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.client.console.wicket.markup.html.form; - -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleApplication; -import org.apache.syncope.client.console.commons.HttpResourceStream; -import org.apache.syncope.client.console.init.ConsoleInitializer; -import org.apache.syncope.client.console.init.MIMETypesLoader; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.behavior.AbstractAjaxBehavior; -import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; -import org.apache.wicket.request.resource.ContentDisposition; -import org.apache.wicket.util.time.Duration; - -public abstract class AjaxDownload extends AbstractAjaxBehavior { - - private static final long serialVersionUID = 7203445884857810583L; - - private static final MIMETypesLoader MIME_TYPES_LOADER = (MIMETypesLoader) SyncopeConsoleApplication.get(). - getServletContext().getAttribute(ConsoleInitializer.MIMETYPES_LOADER); - - private final String name; - - private String fileKey; - - private String mimeType; - - private final boolean addAntiCache; - - public AjaxDownload(final String name, final boolean addAntiCache) { - super(); - this.name = name; - this.addAntiCache = addAntiCache; - } - - public AjaxDownload(final String name, final String fileKey, final String mimeType, final boolean addAntiCache) { - this(name, addAntiCache); - this.fileKey = fileKey; - this.mimeType = mimeType; - } - - public void initiate(final AjaxRequestTarget target) { - - String url = getCallbackUrl().toString(); - if (addAntiCache) { - url = url + (url.contains("?") ? "&" : "?"); - url = url + "antiCache=" + System.currentTimeMillis(); - } - target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);"); - } - - @Override - public void onRequest() { - HttpResourceStream stream = getResourceStream(); - ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(stream); - String key = StringUtils.isNotBlank(fileKey) ? fileKey + "_" : ""; - String ext = ""; - if (StringUtils.isNotBlank(mimeType)) { - String extByMimeType = MIME_TYPES_LOADER.getFileExt(mimeType); - ext = StringUtils.isBlank(extByMimeType) ? ".bin" : ("." + extByMimeType); - } - String fileName = key + (stream.getFilename() == null ? name : stream.getFilename()) + ext; - - handler.setFileName(fileName); - handler.setContentDisposition(ContentDisposition.ATTACHMENT); - handler.setCacheDuration(Duration.NONE); - getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler); - } - - protected abstract HttpResourceStream getResourceStream(); - -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java index 193c0ad4156..7fe282c1783 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java @@ -29,7 +29,7 @@ import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; public class AjaxDropDownChoicePanel extends FieldPanel implements Cloneable { @@ -44,9 +44,8 @@ public AjaxDropDownChoicePanel( super(id, name, model); - field = new BootstrapSelect<>( - "dropDownChoiceField", model, Collections.emptyList(), new ChoiceRenderer<>()); - add(field.setLabel(new Model<>(name)).setOutputMarkupId(true)); + field = new BootstrapSelect<>("dropDownChoiceField", model, Collections.emptyList(), new ChoiceRenderer<>()); + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); if (enableOnBlur) { field.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java deleted file mode 100644 index e18f788985e..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.client.console.wicket.markup.html.form; - -import java.io.Serializable; - -/** - * To be overridden in order to change the default filter option. - */ -public class AjaxPaletteConf implements Serializable { - - private static final long serialVersionUID = -1; - - private static final String DEFAULT_FILTER = "*"; - - public String getDefaultFilter() { - return DEFAULT_FILTER; - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java index 59478b542ec..642b559388c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java @@ -20,18 +20,23 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.extensions.markup.html.form.palette.component.Recorder; import org.apache.wicket.markup.html.basic.Label; @@ -49,14 +54,15 @@ public class AjaxPalettePanel extends AbstractFieldPanel protected Palette palette; - private final Model queryFilter = new Model<>(StringUtils.EMPTY); + protected final Model queryFilter = new Model<>(StringUtils.EMPTY); - private final List availableBefore = new ArrayList<>(); + protected final List availableBefore = new ArrayList<>(); private final LoadableDetachableModel> choicesModel; public AjaxPalettePanel( - final String id, final IModel> model, final Builder.Query choices, final Builder builder) { + final String id, final IModel> model, final Builder.Query query, final Builder builder) { + super(id, builder.name == null ? id : builder.name, model); choicesModel = new PaletteLoadableDetachableModel(builder) { @@ -65,7 +71,7 @@ public AjaxPalettePanel( @Override protected List getChoices() { - return choices.execute(getFilter()); + return query.execute(queryFilter.getObject()); } }; initialize(model, builder); @@ -82,17 +88,46 @@ public AjaxPalettePanel( @Override protected List getChoices() { return builder.filtered - ? getFilteredList(choices.getObject(), getFilter().replaceAll("\\*", "\\.\\*")) + ? getFilteredList(choices.getObject(), queryFilter.getObject().replaceAll("\\*", "\\.\\*")) : choices.getObject(); } }; initialize(model, builder); } - private void initialize(final IModel> model, final Builder builder) { + protected void initialize(final IModel> model, final Builder builder) { setOutputMarkupId(true); - this.palette = new NonI18nPalette( + palette = buildPalette(model, builder); + add(palette.setLabel(new ResourceModel(name)).setOutputMarkupId(true)); + + Form form = new Form<>("form"); + add(form.setEnabled(builder.filtered).setVisible(builder.filtered)); + + queryFilter.setObject(builder.filter); + AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", queryFilter, false); + form.add(filter.hideLabel().setOutputMarkupId(true)); + + AjaxButton search = new AjaxButton("search") { + + private static final long serialVersionUID = 8390605330558248736L; + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + if (builder.warnIfEmptyFilter && StringUtils.isEmpty(queryFilter.getObject())) { + SyncopeConsoleSession.get().info(getString("nomatch")); + ((BasePage) getPage()).getNotificationPanel().refresh(target); + } + + target.add(palette); + } + }; + search.setOutputMarkupId(true); + form.add(search); + } + + protected Palette buildPalette(final IModel> model, final Builder builder) { + return new NonI18nPalette( "paletteField", model, choicesModel, builder.renderer, 8, builder.allowOrder, builder.allowMoveAll) { private static final long serialVersionUID = -3074655279011678437L; @@ -109,20 +144,19 @@ protected Component newSelectedHeader(final String componentId) { @Override protected Recorder newRecorderComponent() { - return new Recorder("recorder", this) { + Recorder recorder = new Recorder("recorder", this) { private static final long serialVersionUID = -9169109967480083523L; @Override public List getUnselectedList() { - final IChoiceRenderer renderer = getPalette().getChoiceRenderer(); - final Collection choices = getPalette().getChoices(); - final List unselected = new ArrayList<>(choices.size()); - final List ids = Arrays.asList(getValue().split(",")); + IChoiceRenderer renderer = getChoiceRenderer(); + Collection choices = getChoices(); + List ids = builder.idExtractor.apply(getValue()).collect(Collectors.toList()); + List unselected = new ArrayList<>(choices.size()); choices.forEach(choice -> { - final String choiceId = renderer.getIdValue(choice, 0); - if (!ids.contains(choiceId)) { + if (!ids.contains(renderer.getIdValue(choice, 0))) { unselected.add(choice); } }); @@ -132,54 +166,50 @@ public List getUnselectedList() { @Override public List getSelectedList() { - final IChoiceRenderer renderer = getPalette().getChoiceRenderer(); - final Collection choices = getPalette().getChoices(); - final List selected = new ArrayList<>(choices.size()); + IChoiceRenderer renderer = getChoiceRenderer(); + Collection choices = getChoices(); // reduce number of method calls by building a lookup table - final Map idForChoice = new HashMap<>(choices.size()); - choices.forEach(choice -> { - idForChoice.put(choice, renderer.getIdValue(choice, 0)); - }); + Map idForChoice = choices.stream().collect(Collectors.toMap( + Function.identity(), choice -> renderer.getIdValue(choice, 0), (c1, c2) -> c1)); - final String value = getValue(); - int start = value.indexOf(';') + 1; - - for (final String id : Strings.split(value.substring(start), ',')) { - for (final T choice : choices) { - final String idValue = idForChoice.get(choice); - if (id.equals(idValue)) { + List selected = new ArrayList<>(choices.size()); + builder.idExtractor.apply(getValue()).forEach(id -> { + for (T choice : choices) { + if (id.equals(idForChoice.get(choice))) { selected.add(choice); break; } } - } + }); return selected; } }; - } - }; - - add(palette.setOutputMarkupId(true)); + recorder.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - final Form form = new Form<>("form"); - add(form.setEnabled(builder.filtered).setVisible(builder.filtered)); - - final AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", queryFilter, false); - filter.hideLabel().setOutputMarkupId(true); - form.add(filter); + private static final long serialVersionUID = -6139318907146065915L; - form.add(new AjaxSubmitLink("search") { + @Override + protected void onUpdate(final AjaxRequestTarget target) { + processInput(); + } + }); - private static final long serialVersionUID = -1765773642975892072L; + return recorder; + } @Override - protected void onAfterSubmit(final AjaxRequestTarget target) { - super.onAfterSubmit(target); - target.add(palette); + protected Map getAdditionalAttributes(final Object choice) { + return builder.additionalAttributes == null + ? super.getAdditionalAttributes(choice) + : builder.additionalAttributes.apply(choice); } - }); + }; + } + + public Recorder getRecorderComponent() { + return palette.getRecorderComponent(); } public LoadableDetachableModel> getChoicesModel() { @@ -200,34 +230,44 @@ public void reload(final AjaxRequestTarget target) { target.add(palette); } + @Override + public AbstractFieldPanel> setReadOnly(final boolean readOnly) { + palette.setEnabled(!readOnly); + return this; + } + + @Override + public AbstractFieldPanel> setRequired(final boolean required) { + palette.setRequired(required); + return super.setRequired(required); + } + public static class Builder implements Serializable { private static final long serialVersionUID = 991248996001040352L; - private IChoiceRenderer renderer; + protected String name; - private boolean allowOrder; + protected IChoiceRenderer renderer = new SelectChoiceRenderer<>(); - private boolean allowMoveAll; + protected boolean allowOrder; - private String selectedLabel; + protected boolean allowMoveAll; - private String availableLabel; + protected String selectedLabel; - private boolean filtered; + protected String availableLabel; - private final AjaxPaletteConf conf = new AjaxPaletteConf(); + protected boolean filtered; - private String filter = conf.getDefaultFilter(); + protected String filter = "*"; - private String name; + protected boolean warnIfEmptyFilter = true; - public Builder() { - this.allowMoveAll = false; - this.allowOrder = false; - this.filtered = false; - this.renderer = new SelectChoiceRenderer<>(); - } + protected Function> idExtractor = + (Function> & Serializable) input -> Stream.of(Strings.split(input, ',')); + + protected Function> additionalAttributes; public Builder setName(final String name) { this.name = name; @@ -270,6 +310,21 @@ public Builder withFilter(final String defaultFilter) { return this; } + public Builder warnIfEmptyFilter(final boolean warnIfEmptyFilter) { + this.warnIfEmptyFilter = warnIfEmptyFilter; + return this; + } + + public Builder idExtractor(final Function> idExtractor) { + this.idExtractor = idExtractor; + return this; + } + + public Builder additionalAttributes(final Function> additionalAttributes) { + this.additionalAttributes = additionalAttributes; + return this; + } + public AjaxPalettePanel build(final String id, final IModel> model, final IModel> choices) { return new AjaxPalettePanel<>(id, model, choices, this); } @@ -282,82 +337,71 @@ public abstract static class Query implements Serializab private static final long serialVersionUID = 3582312993557742858L; - public abstract List execute(final String filter); + public abstract List execute(String filter); } } - private abstract class PaletteLoadableDetachableModel extends LoadableDetachableModel> { + protected abstract class PaletteLoadableDetachableModel extends LoadableDetachableModel> { private static final long serialVersionUID = -7745220313769774616L; - private final Builder builder; + protected final Builder builder; - PaletteLoadableDetachableModel(final Builder builder) { - super(); + public PaletteLoadableDetachableModel(final Builder builder) { this.builder = builder; } protected abstract List getChoices(); - protected String getFilter() { - return StringUtils.isBlank(queryFilter.getObject()) ? builder.filter : queryFilter.getObject(); - } - @Override protected List load() { - final List selected = availableBefore.isEmpty() + List selected = availableBefore.isEmpty() ? new ArrayList<>(palette.getModelCollection()) - : getSelectedList(availableBefore, palette.getRecorderComponent().getValue()); + : getSelectedList(availableBefore); availableBefore.clear(); availableBefore.addAll(ListUtils.sum(selected, getChoices())); return availableBefore; } - private List getSelectedList(final Collection choices, final String selection) { - final IChoiceRenderer renderer = palette.getChoiceRenderer(); - final List selected = new ArrayList<>(); + protected List getSelectedList(final Collection choices) { + IChoiceRenderer renderer = palette.getChoiceRenderer(); - final Map idForChoice = new HashMap<>(); - choices.forEach(choice -> { - idForChoice.put(choice, renderer.getIdValue(choice, 0)); - }); + Map idForChoice = choices.stream().collect(Collectors.toMap( + Function.identity(), choice -> renderer.getIdValue(choice, 0), (c1, c2) -> c1)); - for (final String id : Strings.split(selection, ',')) { - final Iterator iter = choices.iterator(); + List selected = new ArrayList<>(); + builder.idExtractor.apply(palette.getRecorderComponent().getValue()).forEach(id -> { + Iterator iter = choices.iterator(); boolean found = false; while (!found && iter.hasNext()) { - final T choice = iter.next(); - final String idValue = idForChoice.get(choice); - if (id.equals(idValue)) { + T choice = iter.next(); + if (id.equals(idForChoice.get(choice))) { selected.add(choice); found = true; } } - } + }); return selected; } protected List getFilteredList(final Collection choices, final String filter) { - final IChoiceRenderer renderer = palette.getChoiceRenderer(); - final List selected = new ArrayList<>(choices.size()); + IChoiceRenderer renderer = palette.getChoiceRenderer(); - final Map idForChoice = new HashMap<>(); - choices.forEach(choice -> { - idForChoice.put(choice, renderer.getIdValue(choice, 0)); - }); + Map idForChoice = choices.stream().collect(Collectors.toMap( + Function.identity(), choice -> renderer.getIdValue(choice, 0), (c1, c2) -> c1)); - final Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE); + Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE); + List filtered = new ArrayList<>(choices.size()); choices.forEach(choice -> { - final String idValue = idForChoice.get(choice); - if (pattern.matcher(idValue).matches()) { - selected.add(choice); + if (pattern.matcher(idForChoice.get(choice)).matches()) { + filtered.add(choice); } }); - return selected; + return filtered; } } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java new file mode 100644 index 00000000000..a6943af2a58 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java @@ -0,0 +1,133 @@ +/* + * 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.client.console.wicket.markup.html.form; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAutoCompleteBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.validation.IValidator; + +public class AjaxSearchFieldPanel extends FieldPanel implements Cloneable { + + private static final long serialVersionUID = 6890905510177974519L; + + private List choices = Collections.emptyList(); + + private final IAutoCompleteRenderer renderer; + + private final AutoCompleteSettings settings; + + public AjaxSearchFieldPanel(final String id, final String name, final IModel model) { + this(id, name, model, null, null); + } + + public AjaxSearchFieldPanel( + final String id, final String name, + final IModel model, + final AutoCompleteSettings settings) { + this(id, name, model, null, settings); + } + + public AjaxSearchFieldPanel( + final String id, final String name, + final IModel model, + final IAutoCompleteRenderer renderer, + final AutoCompleteSettings settings) { + super(id, name, model); + + this.settings = settings; + this.renderer = renderer; + + field = new AutoCompleteTextField("textField", model, settings) { + + private static final long serialVersionUID = -6648767303091874219L; + + @Override + protected Iterator getChoices(final String input) { + return AjaxSearchFieldPanel.this.getChoices(input); + } + + @Override + protected AutoCompleteBehavior newAutoCompleteBehavior( + final IAutoCompleteRenderer renderer, final AutoCompleteSettings settings) { + + return new IndicatorAutoCompleteBehavior( + AjaxSearchFieldPanel.this.renderer != null ? AjaxSearchFieldPanel.this.renderer : renderer, + AjaxSearchFieldPanel.this.settings != null ? AjaxSearchFieldPanel.this.settings : settings) { + + private static final long serialVersionUID = 1070808433195962931L; + + @Override + protected Iterator getChoices(final String input) { + return AjaxSearchFieldPanel.this.getChoices(input); + } + }; + } + }; + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); + + if (!isReadOnly()) { + field.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -6139318907146065915L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + AjaxSearchFieldPanel.this.onUpdateBehavior(); + } + }); + } + } + + @Override + public FieldPanel setRequired(final boolean required) { + return super.setRequired(required); + } + + public List getChoices() { + return choices; + } + + public void addValidator(final IValidator validator) { + this.field.add(validator); + } + + public void onUpdateBehavior() { + } + + protected Iterator getChoices(final String input) { + return choices.iterator(); + } + + @Override + public FieldPanel clone() { + final AjaxSearchFieldPanel panel = (AjaxSearchFieldPanel) super.clone(); + return panel; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSpinnerFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSpinnerFieldPanel.java index 1621c9e9e8b..684cb8a7486 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSpinnerFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSpinnerFieldPanel.java @@ -29,6 +29,7 @@ import org.apache.commons.lang3.math.NumberUtils; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.common.lib.to.AttributableTO; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.model.IModel; @@ -45,6 +46,8 @@ public final class AjaxSpinnerFieldPanel extends FieldPanel private final Options options; + private SpinnerBehavior behavior; + private AjaxSpinnerFieldPanel( final String id, final String name, @@ -61,7 +64,7 @@ private AjaxSpinnerFieldPanel( @Override public JQueryUIBehavior newWidgetBehavior(final String selector) { - SpinnerBehavior behavior = new SpinnerBehavior(selector, new SpinnerAdapter()); + behavior = new SpinnerBehavior(selector, new SpinnerAdapter()); behavior.setOptions(options); return behavior; } @@ -173,6 +176,44 @@ public void setObject(final T object) { return this; } + @SuppressWarnings("unchecked") + public FieldPanel setNewModel(final AttributableTO attributableTO, final String schema) { + field.setModel(new Model() { + + private static final long serialVersionUID = -4214654722524358000L; + + @Override + public Serializable getObject() { + List values = attributableTO.getPlainAttr(schema).get().getValues(); + if (!values.isEmpty()) { + return reference.equals(Integer.class) + ? reference.cast(NumberUtils.toInt(values.get(0))) + : reference.equals(Long.class) + ? reference.cast(NumberUtils.toLong(values.get(0))) + : reference.equals(Short.class) + ? reference.cast(NumberUtils.toShort(values.get(0))) + : reference.equals(Float.class) + ? reference.cast(NumberUtils.toFloat(values.get(0))) + : reference.equals(byte.class) + ? reference.cast(NumberUtils.toByte(values.get(0))) + : reference.cast(NumberUtils.toDouble(values.get(0))); + } + return null; + } + + @Override + public void setObject(final Serializable object) { + attributableTO.getPlainAttr(schema).get().getValues().clear(); + if (object != null) { + attributableTO.getPlainAttr(schema).get().getValues().add(object.toString()); + } + } + + }); + + return this; + } + @Override public AjaxSpinnerFieldPanel clone() { AjaxSpinnerFieldPanel panel = new AjaxSpinnerFieldPanel<>(getId(), name, reference, model, options, false); @@ -224,4 +265,15 @@ public AjaxSpinnerFieldPanel build( } } + @Override + public FieldPanel setReadOnly(final boolean readOnly) { + super.setReadOnly(readOnly); + AjaxSpinner.class.cast(field).setEnabled(!readOnly); + options.set("disabled", readOnly); + if (behavior != null) { + behavior.setOptions(options); + } + return this; + } + } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxTextFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxTextFieldPanel.java index 8d0c66507e6..4396a7ec89d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxTextFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxTextFieldPanel.java @@ -40,7 +40,7 @@ public class AjaxTextFieldPanel extends FieldPanel implements Cloneable private static final long serialVersionUID = 238940918106696068L; - private final Component questionMarkJexlHelp; + private Component questionMarkJexlHelp; private List choices = Collections.emptyList(); @@ -113,6 +113,12 @@ public FieldPanel enableJexlHelp() { return this; } + public FieldPanel enableJexlHelp(final String... jexlExamples) { + questionMarkJexlHelp = Constants.getJEXLPopover(this, TooltipConfig.Placement.bottom, jexlExamples); + addOrReplace(questionMarkJexlHelp.setVisible(true)); + return this; + } + protected Iterator getChoices(final String input) { Pattern pattern = Pattern.compile(".*" + Pattern.quote(input) + ".*", Pattern.CASE_INSENSITIVE); return choices.stream().filter(choice -> pattern.matcher(choice).matches()).iterator(); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java new file mode 100644 index 00000000000..5570ee085e2 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java @@ -0,0 +1,89 @@ +/* + * 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.client.console.wicket.markup.html.form; + +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.commons.HttpResourceStream; +import org.apache.syncope.client.console.init.ConsoleInitializer; +import org.apache.syncope.client.console.init.MIMETypesLoader; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AbstractAjaxBehavior; +import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; +import org.apache.wicket.request.resource.ContentDisposition; +import org.apache.wicket.util.time.Duration; + +public abstract class BinaryFieldDownload extends AbstractAjaxBehavior { + + private static final long serialVersionUID = 7203445884857810583L; + + private static final MIMETypesLoader MIME_TYPES_LOADER = (MIMETypesLoader) SyncopeConsoleApplication.get(). + getServletContext().getAttribute(ConsoleInitializer.MIMETYPES_LOADER); + + private final String name; + + private String fileKey; + + private String mimeType; + + private final boolean addAntiCache; + + public BinaryFieldDownload(final String name, final boolean addAntiCache) { + super(); + this.name = name; + this.addAntiCache = addAntiCache; + } + + public BinaryFieldDownload( + final String name, final String fileKey, final String mimeType, final boolean addAntiCache) { + + this(name, addAntiCache); + this.fileKey = fileKey; + this.mimeType = mimeType; + } + + public void initiate(final AjaxRequestTarget target) { + String url = getCallbackUrl().toString(); + if (addAntiCache) { + url = url + (url.contains("?") ? "&" : "?"); + url = url + "antiCache=" + System.currentTimeMillis(); + } + target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);"); + } + + @Override + public void onRequest() { + HttpResourceStream stream = getResourceStream(); + ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(stream); + String key = StringUtils.isNotBlank(fileKey) ? fileKey + "_" : ""; + String ext = ""; + if (StringUtils.isNotBlank(mimeType)) { + String extByMimeType = MIME_TYPES_LOADER.getFileExt(mimeType); + ext = StringUtils.isBlank(extByMimeType) ? ".bin" : ("." + extByMimeType); + } + String fileName = key + (stream.getFilename() == null ? name : stream.getFilename()) + ext; + + handler.setFileName(fileName); + handler.setContentDisposition(ContentDisposition.ATTACHMENT); + handler.setCacheDuration(Duration.NONE); + getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler); + } + + protected abstract HttpResourceStream getResourceStream(); +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java index 38676f2f3a5..4ab4eff36f2 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java @@ -28,6 +28,8 @@ import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Base64; +import java.util.Locale; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -37,6 +39,7 @@ import org.apache.syncope.client.console.commons.HttpResourceStream; import org.apache.syncope.client.console.commons.PreviewUtils; import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.rest.ResponseHolder; import org.apache.syncope.client.console.wicket.markup.html.form.preview.AbstractBinaryPreviewer; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -53,7 +56,7 @@ import org.apache.wicket.markup.html.form.upload.FileUpload; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.util.ListModel; import org.apache.wicket.util.lang.Bytes; @@ -75,7 +78,7 @@ public class BinaryFieldPanel extends FieldPanel { private final BootstrapFileInputField fileUpload; - private final AjaxDownload fileDownload; + private final BinaryFieldDownload fileDownload; private final AbstractBinaryPreviewer previewer; @@ -139,19 +142,18 @@ public String build() { uploadForm.add(container); field = new TextField<>("textField", model); - add(field.setLabel(new Model<>(name)).setOutputMarkupId(true)); + add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true)); uploadForm.add(new Label("preview", StringUtils.isBlank(mimeType) ? StringUtils.EMPTY : "(" + mimeType + ")")); - fileDownload = new AjaxDownload(name, fileKey, mimeType, true) { + fileDownload = new BinaryFieldDownload(name, fileKey, mimeType, true) { private static final long serialVersionUID = 7203445884857810583L; @Override protected HttpResourceStream getResourceStream() { - return new HttpResourceStream(buildResponse()); + return new HttpResourceStream(new ResponseHolder(buildResponse())); } - }; add(fileDownload); @@ -165,53 +167,51 @@ public void onClick(final AjaxRequestTarget target) { try { fileDownload.initiate(target); } catch (Exception e) { - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } } }; downloadLink.setOutputMarkupId(true); uploadForm.add(downloadLink); - FileInputConfig config = new FileInputConfig(); - config.showUpload(false); - config.showRemove(false); - config.showPreview(false); - + FileInputConfig config = new FileInputConfig(). + showUpload(false).showRemove(false).showPreview(false); + String language = SyncopeConsoleSession.get().getLocale().getLanguage(); + if (!Locale.ENGLISH.getLanguage().equals(language)) { + config.withLocale(language); + } fileUpload = new BootstrapFileInputField("fileUpload", new ListModel<>(new ArrayList<>()), config); - fileUpload.setOutputMarkupId(true); - fileUpload.add(new AjaxFormSubmitBehavior(Constants.ON_CHANGE) { private static final long serialVersionUID = -1107858522700306810L; @Override protected void onSubmit(final AjaxRequestTarget target) { - final FileUpload uploadedFile = fileUpload.getFileUpload(); - if (uploadedFile != null) { - if (maxUploadSize != null && uploadedFile.getSize() > maxUploadSize.bytes()) { + FileUpload uploaded = fileUpload.getFileUpload(); + if (uploaded != null) { + if (maxUploadSize != null && uploaded.getSize() > maxUploadSize.bytes()) { // SYNCOPE-1213 manage directly max upload file size (if set in properties file) - SyncopeConsoleSession.get().error(getString("tooLargeFile") - .replace("${maxUploadSizeB}", String.valueOf(maxUploadSize.bytes())) - .replace("${maxUploadSizeMB}", String.valueOf(maxUploadSize.bytes() / 1000000L))); + SyncopeConsoleSession.get().error(getString("tooLargeFile"). + replace("${maxUploadSizeB}", String.valueOf(maxUploadSize.bytes())). + replace("${maxUploadSizeMB}", String.valueOf(maxUploadSize.bytes() / 1000000L))); ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target); } else { - final byte[] uploadedBytes = uploadedFile.getBytes(); - final String uploaded = Base64.getEncoder().encodeToString(uploadedBytes); - field.setModelObject(uploaded); + byte[] uploadedBytes = uploaded.getBytes(); + String uploadedEncoded = Base64.getEncoder().encodeToString(uploadedBytes); + field.setModelObject(uploadedEncoded); target.add(field); if (previewer == null) { container.addOrReplace(emptyFragment); } else { - final Component panelPreview = previewer.preview(uploadedBytes); + Component panelPreview = previewer.preview(uploadedBytes); changePreviewer(panelPreview); fileUpload.setModelObject(null); uploadForm.addOrReplace(fileUpload); } - setVisibleFileButtons(StringUtils.isNotBlank(uploaded)); - downloadLink.setEnabled(StringUtils.isNotBlank(uploaded)); + setVisibleFileButtons(StringUtils.isNotBlank(uploadedEncoded)); + downloadLink.setEnabled(StringUtils.isNotBlank(uploadedEncoded)); target.add(uploadForm); } @@ -245,7 +245,9 @@ public String getAjaxIndicatorMarkupId() { private Response buildResponse() { return Response.ok(new ByteArrayInputStream(Base64.getMimeDecoder().decode(getModelObject()))). - type(StringUtils.isBlank(mimeType) ? MediaType.APPLICATION_OCTET_STREAM : mimeType).build(); + type(StringUtils.isBlank(mimeType) ? MediaType.APPLICATION_OCTET_STREAM : mimeType). + header(HttpHeaders.LOCATION, StringUtils.EMPTY). + build(); } private void changePreviewer(final Component panelPreview) { @@ -288,4 +290,11 @@ public FieldPanel setNewModel(final IModel model) { setVisibleFileButtons(StringUtils.isNotBlank(modelObj)); return this; } + + @Override + public FieldPanel setReadOnly(final boolean readOnly) { + super.setReadOnly(readOnly); + fileUpload.setEnabled(!readOnly); + return this; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java new file mode 100644 index 00000000000..19deec2e6d1 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java @@ -0,0 +1,73 @@ +/* + * 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.client.console.wicket.markup.html.form; + +import static de.agilecoders.wicket.jquery.JQuery.$; + +import de.agilecoders.wicket.jquery.function.JavaScriptInlineFunction; +import java.util.ArrayList; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.wicket.Component; +import org.apache.wicket.behavior.Behavior; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.model.ResourceModel; + +public class ConfirmBehavior extends Behavior { + + private static final long serialVersionUID = 2210125898183667592L; + + private final Component parent; + + private final String msg; + + public ConfirmBehavior(final Component parent, final String msg) { + this.parent = parent; + this.msg = msg; + } + + @Override + public void renderHead(final Component component, final IHeaderResponse response) { + super.renderHead(component, response); + + response.render(JavaScriptHeaderItem.forScript("proceed = false;", null)); + response.render($(parent).on("click", + new JavaScriptInlineFunction("" + + "var element = $(this);" + + "evt.preventDefault();" + + "if (proceed == false) {" + + " evt.stopImmediatePropagation();" + + " bootbox.confirm({" + + "message:'" + new ResourceModel(msg).getObject() + "', " + + "locale: '" + SyncopeConsoleSession.get().getLocale().getLanguage() + "'," + + "callback: function(result) {" + + " if (result == true) {" + + " proceed = true;" + + " element.click();" + + " } else {" + + " proceed = false;" + + " }" + + " return true;" + + " }})" + + "} else {" + + " proceed = false;" + + "};", new ArrayList<>() + )).asDomReadyScript()); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/DateFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/DateFieldPanel.java index 806f09d63f0..99d2ad4395b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/DateFieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/DateFieldPanel.java @@ -23,8 +23,10 @@ import java.text.ParseException; import java.util.Date; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.common.lib.to.AttributableTO; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.JavaScriptHeaderItem; import org.apache.wicket.markup.html.list.ListItem; @@ -54,7 +56,6 @@ public FieldPanel setNewModel(final List list) { @Override public Date getObject() { Date date = null; - if (list != null && !list.isEmpty() && StringUtils.hasText(list.get(0).toString())) { try { // Parse string using datePattern @@ -115,7 +116,7 @@ public Date getObject() { @Override @SuppressWarnings("unchecked") public void setObject(final Date object) { - item.setModelObject(object != null ? fmt.format(object) : null); + item.setModelObject(Optional.ofNullable(object).map(fmt::format).orElse(null)); } }; @@ -123,10 +124,40 @@ public void setObject(final Date object) { return this; } + @Override + public FieldPanel setNewModel(final AttributableTO attributable, final String schema) { + field.setModel(new Model() { + + private static final long serialVersionUID = -4214654722524358000L; + + @Override + public Date getObject() { + if (!attributable.getPlainAttr(schema).get().getValues().isEmpty()) { + try { + return fmt.parse(attributable.getPlainAttr(schema).get().getValues().get(0)); + } catch (ParseException ex) { + LOG.error("While parsing date", ex); + } + } + return null; + } + + @Override + public void setObject(final Date object) { + attributable.getPlainAttr(schema).get().getValues().clear(); + if (object != null) { + attributable.getPlainAttr(schema).get().getValues().add(fmt.format(object)); + } + } + }); + + return this; + } + @Override public void renderHead(final IHeaderResponse response) { super.renderHead(response); response.render(JavaScriptHeaderItem.forReference( - new KendoCultureResourceReference(SyncopeConsoleSession.get().getDateFormat().getLocale()))); + new KendoCultureResourceReference(getLocale()))); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java index 636f6858413..1aa721a2963 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java @@ -25,6 +25,7 @@ import java.util.List; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.to.AttributableTO; import org.apache.wicket.AttributeModifier; import org.apache.wicket.PageReference; import org.apache.wicket.markup.html.form.FormComponent; @@ -97,6 +98,7 @@ public FieldPanel setRequired(final boolean required) { return this; } + @Override public FieldPanel setReadOnly(final boolean readOnly) { field.setEnabled(!readOnly); return this; @@ -126,6 +128,31 @@ public FieldPanel setNewModel(final IModel model) { return this; } + @SuppressWarnings({ "rawtypes", "unchecked" }) + public FieldPanel setNewModel(final AttributableTO attributableTO, final String schema) { + field.setModel(new Model() { + + private static final long serialVersionUID = -4214654722524358000L; + + @Override + public Serializable getObject() { + return (!attributableTO.getPlainAttr(schema).get().getValues().isEmpty()) + ? attributableTO.getPlainAttr(schema).get().getValues().get(0) + : null; + } + + @Override + public void setObject(final Serializable object) { + attributableTO.getPlainAttr(schema).get().getValues().clear(); + if (object != null) { + attributableTO.getPlainAttr(schema).get().getValues().add(object.toString()); + } + } + }); + + return this; + } + /** * Used by MultiFieldPanel to attach items (usually strings). * This method has to be overridden in case of type conversion is required. @@ -209,7 +236,7 @@ public FieldPanel clone() { protected PageReference getPageReference() { // SYNCOPE-1213 - // default implementation does not requier to pass page reference, override this method of want otherwise + // default implementation does not require to pass page reference, override this method of want otherwise return null; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java index 931d6797481..fe1802e07cb 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java @@ -18,21 +18,15 @@ */ package org.apache.syncope.client.console.wicket.markup.html.form; -import static de.agilecoders.wicket.jquery.JQuery.$; - -import de.agilecoders.wicket.jquery.function.JavaScriptInlineFunction; -import java.util.ArrayList; import org.apache.syncope.client.console.commons.Constants; import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink; import org.apache.wicket.markup.head.IHeaderResponse; -import org.apache.wicket.markup.head.JavaScriptHeaderItem; -import org.apache.wicket.model.ResourceModel; public abstract class IndicatingOnConfirmAjaxLink extends IndicatingAjaxLink { private static final long serialVersionUID = 2228670850922265663L; - private final String msg; + private final ConfirmBehavior confirmBehavior; private final boolean enabled; @@ -42,7 +36,8 @@ public IndicatingOnConfirmAjaxLink(final String id, final boolean enabled) { public IndicatingOnConfirmAjaxLink(final String id, final String msg, final boolean enabled) { super(id); - this.msg = msg; + + this.confirmBehavior = new ConfirmBehavior(this, msg); this.enabled = enabled; } @@ -51,26 +46,7 @@ public void renderHead(final IHeaderResponse response) { super.renderHead(response); if (enabled) { - response.render(JavaScriptHeaderItem.forScript("proceed = false;", null)); - response.render($(this).on("click", - new JavaScriptInlineFunction("" - + "var element = $(this);" - + "evt.preventDefault();" - + "if (proceed == false) {" - + " evt.stopImmediatePropagation();" - + " bootbox.confirm('" + new ResourceModel(msg).getObject() + "', function(result) {" - + " if (result == true) {" - + " proceed = true;" - + " element.click();" - + " } else {" - + " proceed = false;" - + " }" - + " return true;" - + " })" - + "} else {" - + " proceed = false;" - + "};", new ArrayList<>() - )).asDomReadyScript()); + confirmBehavior.renderHead(this, response); } } @@ -78,5 +54,4 @@ public void renderHead(final IHeaderResponse response) { public String getAjaxIndicatorMarkupId() { return Constants.VEIL_INDICATOR_MARKUP_ID; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java index 8c5b80e15ec..2ebc7bc2665 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java @@ -30,10 +30,6 @@ public class JsonDiffPanel extends AbstractModalPanel { private static final long serialVersionUID = -5110368813584745668L; - private final IModel first; - - private final IModel second; - public JsonDiffPanel( final BaseModal modal, final IModel first, @@ -41,13 +37,13 @@ public JsonDiffPanel( final PageReference pageRef) { super(modal, pageRef); - this.second = second; - this.first = first; - TextArea jsonEditorInfoDefArea1 = new TextArea<>("jsonEditorInfo1", this.first); - TextArea jsonEditorInfoDefArea2 = new TextArea<>("jsonEditorInfo2", this.second); + + TextArea jsonEditorInfoDefArea1 = new TextArea<>("jsonEditorInfo1", first); jsonEditorInfoDefArea1.setMarkupId("jsonEditorInfo1").setOutputMarkupPlaceholderTag(true); - jsonEditorInfoDefArea2.setMarkupId("jsonEditorInfo2").setOutputMarkupPlaceholderTag(true); add(jsonEditorInfoDefArea1); + + TextArea jsonEditorInfoDefArea2 = new TextArea<>("jsonEditorInfo2", second); + jsonEditorInfoDefArea2.setMarkupId("jsonEditorInfo2").setOutputMarkupPlaceholderTag(true); add(jsonEditorInfoDefArea2); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/NonI18nPalette.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/NonI18nPalette.java index c2313a5047a..9527dc8f0c4 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/NonI18nPalette.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/NonI18nPalette.java @@ -19,9 +19,8 @@ package org.apache.syncope.client.console.wicket.markup.html.form; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.Collections; import java.util.Map; -import org.apache.wicket.Component; import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; @@ -44,28 +43,17 @@ protected boolean localizeDisplayValues() { return false; } - @Override - protected Component newAvailableHeader(final String componentId) { - return super.newAvailableHeader(componentId); - } - - @Override - protected Component newSelectedHeader(final String componentId) { - return super.newSelectedHeader(componentId); + protected Map getAdditionalAttributes(final Object choice) { + return Collections.singletonMap("title", choice.toString()); } @Override protected Map getAdditionalAttributesForChoices(final Object choice) { - Map map = new LinkedHashMap<>(); - map.put("title", choice.toString()); - return map; + return getAdditionalAttributes(choice); } @Override protected Map getAdditionalAttributesForSelection(final Object choice) { - Map map = new LinkedHashMap<>(); - map.put("title", choice.toString()); - return map; + return getAdditionalAttributes(choice); } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/AbstractBinaryPreviewer.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/AbstractBinaryPreviewer.java index 9958a69a80e..39450ca7d64 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/AbstractBinaryPreviewer.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/AbstractBinaryPreviewer.java @@ -41,5 +41,5 @@ public Component preview(final String uploaded) { return preview(Base64.getMimeDecoder().decode(uploaded)); } - public abstract Component preview(final byte[] uploadedBytes); + public abstract Component preview(byte[] uploadedBytes); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/BinaryTextPreviewer.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/BinaryTextPreviewer.java index 33558d1e006..b37bd59f00e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/BinaryTextPreviewer.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/preview/BinaryTextPreviewer.java @@ -23,6 +23,7 @@ import java.io.InputStream; import javax.ws.rs.core.MediaType; import org.apache.syncope.client.console.annotations.BinaryPreview; +import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.wicket.Component; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.OnLoadHeaderItem; @@ -32,12 +33,13 @@ import org.apache.wicket.model.Model; import org.apache.wicket.util.io.IOUtils; -@BinaryPreview(mimeTypes = { "application/json", "application/xml" }) +@BinaryPreview(mimeTypes = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, + RESTHeaders.APPLICATION_YAML, "application/x-yaml", "text/x-yaml", "text/yaml" }) public class BinaryTextPreviewer extends AbstractBinaryPreviewer { private static final long serialVersionUID = 3808379310090668773L; - private String jsonEditorInfoId; + private String previewerId; public BinaryTextPreviewer(final String id, final String mimeType) { super(id, mimeType); @@ -51,11 +53,10 @@ public Component preview(final byte[] uploadedBytes) { try { fragment = new Fragment("preview", "previewFragment", this); InputStream stream = new ByteArrayInputStream(uploadedBytes); - TextArea jsonEditor = - new TextArea<>("jsonEditorInfo", new Model<>(IOUtils.toString(stream))); - jsonEditor.setOutputMarkupPlaceholderTag(true); - jsonEditorInfoId = jsonEditor.getMarkupId(); - fragment.add(jsonEditor); + TextArea previewer = new TextArea<>("previewer", Model.of(IOUtils.toString(stream))); + previewer.setOutputMarkupPlaceholderTag(true); + previewerId = previewer.getMarkupId(); + fragment.add(previewer); } catch (IOException e) { LOG.error("Error evaluating text file", e); } @@ -76,21 +77,30 @@ public void renderHead(final IHeaderResponse response) { case MediaType.APPLICATION_JSON: options = "matchBrackets: true, autoCloseBrackets: true,"; break; + case MediaType.APPLICATION_XML: options = "autoCloseTags: true, mode: 'text/html',"; break; + + case RESTHeaders.APPLICATION_YAML: + case "application/x-yaml": + case "text/x-yaml": + case "text/yaml": + options = "mode: 'yaml',"; + break; + default: options = "mode: 'text/html',"; } response.render(OnLoadHeaderItem.forScript( - "var editor = CodeMirror.fromTextArea(document.getElementById('" + jsonEditorInfoId + "'), {" + "var editor = CodeMirror.fromTextArea(document.getElementById('" + previewerId + "'), {" + " readOnly: true, " + " lineNumbers: true, " + " lineWrapping: false, " + options + " autoRefresh: true" + "});" - + "editor.setSize('500', 100)")); + + "editor.setSize('900', 250)")); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java index c85cfda9c9c..c0e8ff0a1f9 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/link/VeilPopupSettings.java @@ -35,7 +35,6 @@ public String getPopupJavaScript() { + " window.location.reload(false);" + " }" + "}, 1000);" - + "return false"; + + "return false;"; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/list/ConnConfPropertyListView.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/list/ConnConfPropertyListView.java index bc4ef018cd8..8dfb7d0a48d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/list/ConnConfPropertyListView.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/list/ConnConfPropertyListView.java @@ -91,7 +91,7 @@ protected void populateItem(final ListItem item) { try { propertySchemaClass = ClassUtils.getClass(property.getSchema().getType()); if (ClassUtils.isPrimitiveOrWrapper(propertySchemaClass)) { - propertySchemaClass = org.apache.commons.lang3.ClassUtils.primitiveToWrapper(propertySchemaClass); + propertySchemaClass = ClassUtils.primitiveToWrapper(propertySchemaClass); } } catch (ClassNotFoundException e) { LOG.error("Error parsing attribute type", e); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java index ac32d17e4b4..f564ac8f02e 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java @@ -25,8 +25,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.AbstractLink; -import org.apache.wicket.markup.html.list.ListItem; -import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; @@ -39,14 +37,14 @@ public abstract class AlertWidget extends Panel { protected static final Logger LOG = LoggerFactory.getLogger(AlertWidget.class); + protected static final int MAX_SIZE = 5; + protected final Label linkAlertsNumber; protected final Label headerAlertsNumber; protected final WebMarkupContainer latestAlertsList; - protected final ListView latestFive; - protected IModel> latestAlerts; public AlertWidget(final String id) { @@ -61,7 +59,7 @@ public AlertWidget(final String id) { @Override protected Integer load() { - return AlertWidget.this.latestAlerts.getObject().size(); + return getLatestAlertsSize(); } }; @@ -72,7 +70,7 @@ protected Integer load() { @Override protected List load() { final List latest = AlertWidget.this.latestAlerts.getObject(); - return latest.subList(0, latest.size() < 6 ? latest.size() : 5); + return latest.subList(0, latest.size() <= MAX_SIZE ? latest.size() : MAX_SIZE); } }; @@ -98,31 +96,20 @@ protected void onComponentTag(final ComponentTag tag) { headerAlertsNumber.setOutputMarkupId(true); add(headerAlertsNumber); + add(getEventsLink("alertsLink")); + latestAlertsList = new WebMarkupContainer("latestAlertsList"); latestAlertsList.setOutputMarkupId(true); add(latestAlertsList); + } - latestFive = new ListView("latestAlerts", items) { - - private static final long serialVersionUID = 4949588177564901031L; - - @Override - protected void populateItem(final ListItem item) { - item.add(getAlertLink("alert", item.getModelObject()).setRenderBodyOnly(true)); - } - }; - latestAlertsList.add(latestFive.setReuseItems(false).setOutputMarkupId(true)); - - add(getEventsLink("alertsLink")); + protected int getLatestAlertsSize() { + return latestAlerts.getObject().size(); } protected abstract IModel> getLatestAlerts(); - protected Panel getAlertLink(final String panelid, final T alert) { - return new AlertLink<>(panelid, alert); - } - - protected abstract AbstractLink getEventsLink(final String linkid); + protected abstract AbstractLink getEventsLink(String linkid); protected abstract Icon getIcon(String iconid); @@ -135,5 +122,4 @@ public AlertLink(final String id, final T alert) { add(new Label("alert", alert.toString())); } } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java deleted file mode 100644 index 9f8ac626127..00000000000 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.client.console.widgets; - -import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon; -import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconTypeBuilder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.pages.Approvals; -import org.apache.syncope.client.console.rest.UserWorkflowRestClient; -import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior; -import org.apache.syncope.common.lib.to.WorkflowFormTO; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.PageReference; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.AjaxLink; -import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.markup.ComponentTag; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.AbstractLink; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.ResourceModel; -import org.apache.wicket.model.util.ListModel; -import org.apache.wicket.util.time.Duration; - -public class ApprovalsWidget extends AlertWidget { - - private static final long serialVersionUID = 7667120094526529934L; - - private final UserWorkflowRestClient restClient = new UserWorkflowRestClient(); - - private final List lastApprovals = new ArrayList<>(); - - public ApprovalsWidget(final String id, final PageReference pageRef) { - super(id); - setOutputMarkupId(true); - - latestAlertsList.add(new IndicatorAjaxTimerBehavior(Duration.seconds(30)) { - - private static final long serialVersionUID = 7298597675929755960L; - - @Override - protected void onTimer(final AjaxRequestTarget target) { - if (!latestAlerts.getObject().equals(lastApprovals)) { - refreshLatestAlerts(target); - } - } - }); - } - - public final void refreshLatestAlerts(final AjaxRequestTarget target) { - latestAlerts.getObject().clear(); - latestAlerts.getObject().addAll(lastApprovals); - - linkAlertsNumber.setDefaultModelObject(latestAlerts.getObject().size()); - target.add(linkAlertsNumber); - - headerAlertsNumber.setDefaultModelObject(latestAlerts.getObject().size()); - target.add(headerAlertsNumber); - - latestFive.removeAll(); - target.add(latestAlertsList); - - lastApprovals.clear(); - lastApprovals.addAll(latestAlerts.getObject()); - } - - @Override - protected IModel> getLatestAlerts() { - return new ListModel() { - - private static final long serialVersionUID = -2583290457773357445L; - - @Override - public List getObject() { - List updatedApprovals; - if (SyncopeConsoleSession.get().owns(StandardEntitlement.WORKFLOW_FORM_LIST) - && SyncopeConsoleSession.get().owns(StandardEntitlement.WORKFLOW_FORM_READ)) { - - updatedApprovals = restClient.getForms().stream(). - sorted(Comparator.comparing(WorkflowFormTO::getCreateTime)). - collect(Collectors.toList()); - } else { - updatedApprovals = Collections.emptyList(); - } - - return updatedApprovals; - } - }; - } - - @Override - protected Panel getAlertLink(final String panelid, final WorkflowFormTO event) { - return new ApprovalsWidget.InnerPanel(panelid, event); - } - - @Override - protected AbstractLink getEventsLink(final String linkid) { - BookmarkablePageLink approvals = BookmarkablePageLinkBuilder.build(linkid, Approvals.class); - MetaDataRoleAuthorizationStrategy.authorize(approvals, WebPage.ENABLE, StandardEntitlement.WORKFLOW_FORM_LIST); - return approvals; - } - - @Override - protected Icon getIcon(final String iconid) { - return new Icon(iconid, - FontAwesomeIconTypeBuilder.on(FontAwesomeIconTypeBuilder.FontAwesomeGraphic.handshake_o).build()); - } - - public static final class InnerPanel extends Panel { - - private static final long serialVersionUID = 3829642687027801451L; - - public InnerPanel(final String id, final WorkflowFormTO alert) { - super(id); - - final AjaxLink approval = new AjaxLink("approval") { - - private static final long serialVersionUID = 7021195294339489084L; - - @Override - public void onClick(final AjaxRequestTarget target) { - // do nothing - } - - @Override - protected void onComponentTag(final ComponentTag tag) { - super.onComponentTag(tag); - if (StringUtils.isNotBlank(alert.getUsername())) { - tag.put("title", alert.getUsername().trim()); - } - } - }; - - add(approval); - - approval.add(new Label("key", new ResourceModel(alert.getKey(), alert.getKey())). - setRenderBodyOnly(true)); - - approval.add(new Label("owner", alert.getOwner())); - - approval.add(new Label("createTime", - SyncopeConsoleSession.get().getDateFormat().format(alert.getCreateTime())). - setRenderBodyOnly(true)); - - WebMarkupContainer dueDateContainer = new WebMarkupContainer("dueDateContainer"); - dueDateContainer.setOutputMarkupId(true); - approval.add(dueDateContainer); - - if (alert.getDueDate() == null) { - dueDateContainer.add(new Label("dueDate")); - dueDateContainer.setVisible(false); - } else { - dueDateContainer.add(new Label("dueDate", - SyncopeConsoleSession.get().getDateFormat().format(alert.getDueDate())). - setRenderBodyOnly(true)); - } - } - - } -} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/CompletenessWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/CompletenessWidget.java index 5f26ffda4f7..927b4e08bc9 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/CompletenessWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/CompletenessWidget.java @@ -22,13 +22,16 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; +import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.chartjs.ChartJSPanel; import org.apache.syncope.client.console.chartjs.Doughnut; import org.apache.syncope.client.console.chartjs.DoughnutChartData; +import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.pages.Notifications; import org.apache.syncope.client.console.pages.Policies; import org.apache.syncope.client.console.pages.Security; import org.apache.syncope.client.console.pages.Types; +import org.apache.syncope.client.console.topology.TabularTopology; import org.apache.syncope.client.console.topology.Topology; import org.apache.syncope.common.lib.info.NumbersInfo; import org.apache.syncope.common.lib.types.StandardEntitlement; @@ -48,7 +51,7 @@ public class CompletenessWidget extends BaseWidget { private final WebMarkupContainer actions; - private final BookmarkablePageLink topology; + private final BookmarkablePageLink topology; private final BookmarkablePageLink policies; @@ -76,7 +79,11 @@ public CompletenessWidget(final String id, final Map confComple add(actions); - topology = BookmarkablePageLinkBuilder.build("topology", Topology.class); + if (SyncopeConsoleApplication.get().getDefaultTopologyClass().contains("TabularTopology")) { + topology = BookmarkablePageLinkBuilder.build("topology", TabularTopology.class); + } else { + topology = BookmarkablePageLinkBuilder.build("topology", Topology.class); + } topology.setOutputMarkupPlaceholderTag(true); actions.add(topology); MetaDataRoleAuthorizationStrategy.authorize(topology, WebPage.ENABLE, @@ -102,7 +109,7 @@ public CompletenessWidget(final String id, final Map confComple types = BookmarkablePageLinkBuilder.build("types", Types.class); types.setOutputMarkupPlaceholderTag(true); - MetaDataRoleAuthorizationStrategy.authorize(types, WebPage.ENABLE, StandardEntitlement.SCHEMA_LIST); + MetaDataRoleAuthorizationStrategy.authorize(types, WebPage.ENABLE, StandardEntitlement.ANYTYPECLASS_LIST); actions.add(types); types.setVisible( !confCompleteness.get(NumbersInfo.ConfItem.VIR_SCHEMA.name()) diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java new file mode 100644 index 00000000000..1453fa9896a --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.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.client.console.widgets; + +import java.io.Serializable; + +public abstract class ExtAlertWidget extends AlertWidget { + + private static final long serialVersionUID = -5622060468533516192L; + + public ExtAlertWidget(final String id) { + super(id); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java index 5e03e2d3174..34993ba2283 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java @@ -95,12 +95,11 @@ public void onClick(final AjaxRequestTarget target) { default: } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); send(container, Broadcast.EXACT, new JobActionPayload(target)); } catch (Exception e) { LOG.error("While stopping {}", jobTO.getRefDesc(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) getPage()).getNotificationPanel().refresh(target); } @@ -129,12 +128,11 @@ public void onClick(final AjaxRequestTarget target) { default: } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); send(container, Broadcast.EXACT, new JobActionPayload(target)); } catch (Exception e) { LOG.error("While starting {}", jobTO.getRefDesc(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() - : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) getPage()).getNotificationPanel().refresh(target); } @@ -160,6 +158,5 @@ public JobActionPayload(final AjaxRequestTarget target) { public AjaxRequestTarget getTarget() { return target; } - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java index efd27b5d5ee..b1dc722a5ad 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -309,8 +310,6 @@ protected WizardMgtPanel newInstance(final String id, final boolean wizar } }.disableCheckBoxes().hidePaginator()); - super.setTogglePanel(actionTogglePanel); - this.reportModal = JobWidget.this.reportModal; setWindowClosedReloadCallback(reportModal); @@ -332,7 +331,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -358,12 +357,29 @@ public void populateItem( JobTO jobTO = rowModel.getObject(); JobActionPanel panel = new JobActionPanel(componentId, jobTO, true, JobWidget.this, pageRef); - MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, - String.format("%s,%s,%s,%s", - StandardEntitlement.TASK_EXECUTE, - StandardEntitlement.REPORT_EXECUTE, - StandardEntitlement.TASK_UPDATE, - StandardEntitlement.REPORT_UPDATE)); + + String roles; + switch (jobTO.getType()) { + case TASK: + roles = String.format("%s,%s", + StandardEntitlement.TASK_EXECUTE, StandardEntitlement.TASK_UPDATE); + break; + + case REPORT: + roles = String.format("%s,%s", + StandardEntitlement.REPORT_EXECUTE, StandardEntitlement.REPORT_UPDATE); + break; + + case NOTIFICATION: + roles = String.format("%s,%s", + StandardEntitlement.NOTIFICATION_EXECUTE, StandardEntitlement.NOTIFICATION_UPDATE); + break; + + default: + roles = "NO_ROLES"; + } + + MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, roles); cellItem.add(panel); } @@ -516,13 +532,12 @@ public void onClick(final AjaxRequestTarget target, final JobTO ignore) { default: break; } - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } } catch (SyncopeClientException e) { LOG.error("While deleting object {}", jobTO.getRefKey(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) ? e.getClass(). - getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -542,11 +557,13 @@ protected boolean statusCondition(final JobTO modelObject) { @SuppressWarnings("unchecked") public void onEvent(final IEvent event) { if (event.getPayload() instanceof AjaxWizard.NewItemEvent) { - final AjaxRequestTarget target = AjaxWizard.NewItemEvent.class.cast(event.getPayload()).getTarget(); + Optional target = ((AjaxWizard.NewItemEvent) event.getPayload()).getTarget(); - if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent + if (target.isPresent() + && event.getPayload() instanceof AjaxWizard.NewItemCancelEvent || event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { - jobModal.close(target); + + jobModal.close(target.get()); } } @@ -614,7 +631,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/NumberWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/NumberWidget.java index 023596b05c6..caac674db79 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/NumberWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/NumberWidget.java @@ -19,8 +19,11 @@ package org.apache.syncope.client.console.widgets; import java.util.List; +import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.topology.TabularTopology; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; @@ -30,7 +33,6 @@ import org.apache.syncope.client.console.pages.Security; import org.apache.syncope.client.console.topology.Topology; import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.mapper.parameter.PageParameters; public class NumberWidget extends BaseWidget { @@ -49,20 +51,27 @@ public NumberWidget(final String id, final String bg, final int number, final St WebMarkupContainer box = new WebMarkupContainer("box"); box.add(new AttributeAppender("class", " " + bg)); + @SuppressWarnings("unchecked") + Class realmsPage = + (Class) SyncopeConsoleApplication.get().getPageClass("realms"); + if (realmsPage == null) { + realmsPage = Realms.class; + } + boolean isAuthorized = true; - final PageParameters pageParameters = new PageParameters(); - final Class responsePage; + PageParameters pageParameters = new PageParameters(); + Class responsePage; List anyTypes = new AnyTypeRestClient().list(); switch (id) { case "totalUsers": - pageParameters.add("selectedIndex", 1); - responsePage = Realms.class; + pageParameters.add(Realms.SELECTED_INDEX, 1); + responsePage = realmsPage; isAuthorized = SyncopeConsoleSession.get().owns(StandardEntitlement.USER_SEARCH); break; case "totalGroups": - pageParameters.add("selectedIndex", 2); - responsePage = Realms.class; + pageParameters.add(Realms.SELECTED_INDEX, 2); + responsePage = realmsPage; isAuthorized = SyncopeConsoleSession.get().owns(StandardEntitlement.GROUP_SEARCH); break; @@ -72,10 +81,10 @@ public NumberWidget(final String id, final String bg, final int number, final St for (int i = 0; i < anyTypes.size() && selectedIndex == null; i++) { if (anyTypes.get(i).equals(label)) { selectedIndex = i + 1; - pageParameters.add("selectedIndex", selectedIndex); + pageParameters.add(Realms.SELECTED_INDEX, selectedIndex); } } - responsePage = Realms.class; + responsePage = realmsPage; isAuthorized = SyncopeConsoleSession.get().owns(label + "_SEARCH"); } else { responsePage = Security.class; @@ -89,21 +98,25 @@ public NumberWidget(final String id, final String bg, final int number, final St for (int i = 0; i < anyTypes.size() && selectedIndex == null; i++) { if (anyTypes.get(i).equals(label)) { selectedIndex = i + 1; - pageParameters.add("selectedIndex", selectedIndex); + pageParameters.add(Realms.SELECTED_INDEX, selectedIndex); } } responsePage = Realms.class; isAuthorized = SyncopeConsoleSession.get().owns(label + "_SEARCH"); } else { - responsePage = Topology.class; + if (SyncopeConsoleApplication.get().getDefaultTopologyClass().contains("TabularTopology")) { + responsePage = TabularTopology.class; + } else { + responsePage = Topology.class; + } isAuthorized = SyncopeConsoleSession.get().owns(StandardEntitlement.CONNECTOR_LIST) && SyncopeConsoleSession.get().owns(StandardEntitlement.RESOURCE_LIST); } break; default: - pageParameters.add("selectedIndex", 0); - responsePage = Realms.class; + pageParameters.add(Realms.SELECTED_INDEX, 0); + responsePage = realmsPage; } AjaxEventBehavior clickToRealms = new AjaxEventBehavior("mousedown") { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java index 7e969f3e07e..b63c627f376 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java @@ -103,7 +103,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -111,7 +111,7 @@ protected Collection getBulkActions() { protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new PropertyColumn<>(new ResourceModel("key"), "name", "name")); + columns.add(new PropertyColumn<>(new ResourceModel(Constants.KEY_FIELD_NAME), "name", "name")); columns.add(new AbstractColumn(Model.of(Constants.SYNCOPE)) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java index 8723a7da358..a64d249fd92 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java @@ -182,7 +182,7 @@ public void onClick(final AjaxRequestTarget target) { setCheckReconciliationJob(true); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While starting reconciliation report", e); SyncopeConsoleSession.get().error("Could not start reconciliation report"); @@ -348,7 +348,7 @@ protected String paginatorRowsKey() { } @Override - protected Collection getBulkActions() { + protected Collection getBatches() { return Collections.emptyList(); } @@ -356,7 +356,7 @@ protected Collection getBulkActions() { protected List> getColumns() { List> columns = new ArrayList<>(); - columns.add(new AbstractColumn(new ResourceModel("reference"), "key") { + columns.add(new AbstractColumn(new ResourceModel("reference"), Constants.KEY_FIELD_NAME) { private static final long serialVersionUID = -1822504503325964706L; @@ -452,7 +452,7 @@ protected final class AnysReconciliationProvider extends DirectoryDataProvider(this); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java index 287fdbca9a1..9e888dba76f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java @@ -22,9 +22,7 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconTypeBuilder; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.pages.Remediations; @@ -34,14 +32,11 @@ import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.AbstractLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; -import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.util.ListModel; import org.apache.wicket.util.time.Duration; @@ -75,19 +70,27 @@ public final void refreshLatestAlerts(final AjaxRequestTarget target) { latestAlerts.getObject().clear(); latestAlerts.getObject().addAll(lastRemediations); - linkAlertsNumber.setDefaultModelObject(latestAlerts.getObject().size()); + int latestAlertSize = getLatestAlertsSize(); + linkAlertsNumber.setDefaultModelObject(latestAlertSize); target.add(linkAlertsNumber); - headerAlertsNumber.setDefaultModelObject(latestAlerts.getObject().size()); + headerAlertsNumber.setDefaultModelObject(latestAlertSize); target.add(headerAlertsNumber); - latestFive.removeAll(); target.add(latestAlertsList); lastRemediations.clear(); lastRemediations.addAll(latestAlerts.getObject()); } + @Override + protected int getLatestAlertsSize() { + return SyncopeConsoleSession.get().owns(StandardEntitlement.REMEDIATION_LIST) + && SyncopeConsoleSession.get().owns(StandardEntitlement.REMEDIATION_READ) + ? restClient.countRemediations() + : 0; + } + @Override protected IModel> getLatestAlerts() { return new ListModel() { @@ -100,9 +103,7 @@ public List getObject() { if (SyncopeConsoleSession.get().owns(StandardEntitlement.REMEDIATION_LIST) && SyncopeConsoleSession.get().owns(StandardEntitlement.REMEDIATION_READ)) { - updatedRemediations = restClient.getRemediations().stream(). - sorted(Comparator.comparing(RemediationTO::getInstant)). - collect(Collectors.toList()); + updatedRemediations = restClient.getRemediations(1, MAX_SIZE, new SortParam<>("instant", true)); } else { updatedRemediations = Collections.emptyList(); } @@ -112,11 +113,6 @@ public List getObject() { }; } - @Override - protected Panel getAlertLink(final String panelid, final RemediationTO event) { - return new RemediationsWidget.InnerPanel(panelid, event); - } - @Override protected AbstractLink getEventsLink(final String linkid) { BookmarkablePageLink remediations = BookmarkablePageLinkBuilder.build(linkid, Remediations.class); @@ -129,39 +125,4 @@ protected Icon getIcon(final String iconid) { return new Icon(iconid, FontAwesomeIconTypeBuilder.on(FontAwesomeIconTypeBuilder.FontAwesomeGraphic.medkit).build()); } - - public static final class InnerPanel extends Panel { - - private static final long serialVersionUID = 8074027899915634928L; - - public InnerPanel(final String id, final RemediationTO alert) { - super(id); - - AjaxLink approval = new AjaxLink("remediation") { - - private static final long serialVersionUID = 7021195294339489084L; - - @Override - public void onClick(final AjaxRequestTarget target) { - // do nothing - } - - @Override - protected void onComponentTag(final ComponentTag tag) { - super.onComponentTag(tag); - tag.put("title", alert.getRemoteName().trim()); - } - }; - - add(approval); - - approval.add(new Label("label", alert.getOperation().name() + " " + alert.getAnyType())); - - approval.add(new Label("resource", alert.getResource())); - - approval.add(new Label("instant", - SyncopeConsoleSession.get().getDateFormat().format(alert.getInstant())). - setRenderBodyOnly(true)); - } - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Any.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Any.java index f0fde29fa6c..8c461dd104d 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Any.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Any.java @@ -18,11 +18,11 @@ */ package org.apache.syncope.client.console.widgets.reconciliation; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import org.apache.syncope.common.lib.AbstractBaseBean; -public class Any extends AbstractBaseBean { +public class Any implements Serializable { private static final long serialVersionUID = 2421645848049271898L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Anys.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Anys.java index 3c58515cc7d..2d35e144b86 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Anys.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Anys.java @@ -18,11 +18,11 @@ */ package org.apache.syncope.client.console.widgets.reconciliation; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import org.apache.syncope.common.lib.AbstractBaseBean; -public class Anys extends AbstractBaseBean { +public class Anys implements Serializable { private static final long serialVersionUID = -2482591351364634179L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Missing.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Missing.java index 2d0254a047d..175d9de4590 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Missing.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/Missing.java @@ -18,9 +18,9 @@ */ package org.apache.syncope.client.console.widgets.reconciliation; -import org.apache.syncope.common.lib.AbstractBaseBean; +import java.io.Serializable; -public class Missing extends AbstractBaseBean { +public class Missing implements Serializable { private static final long serialVersionUID = -4779715117027316991L; @@ -40,5 +40,4 @@ public String getResource() { public String getConnObjectKeyValue() { return connObjectKeyValue; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReport.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReport.java index a1788633091..696d807baa5 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReport.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReport.java @@ -18,12 +18,12 @@ */ package org.apache.syncope.client.console.widgets.reconciliation; +import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; -import org.apache.syncope.common.lib.AbstractBaseBean; -public class ReconciliationReport extends AbstractBaseBean { +public class ReconciliationReport implements Serializable { private static final long serialVersionUID = 931063230006747313L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java index da5e76dff60..00c5f3494e0 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java @@ -28,14 +28,20 @@ import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.common.lib.types.AnyTypeKind; public final class ReconciliationReportParser { - private static final XMLInputFactory INPUT_FACTORY = XMLInputFactory.newInstance(); + private static final XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance(); + + static { + XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + XML_INPUT_FACTORY.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + } public static ReconciliationReport parse(final Date run, final InputStream in) throws XMLStreamException { - XMLStreamReader streamReader = INPUT_FACTORY.createXMLStreamReader(in); + XMLStreamReader streamReader = XML_INPUT_FACTORY.createXMLStreamReader(in); streamReader.nextTag(); // root streamReader.nextTag(); // report streamReader.nextTag(); // reportlet @@ -63,7 +69,7 @@ public static ReconciliationReport parse(final Date run, final InputStream in) t case "user": user = new Any(); user.setType(AnyTypeKind.USER.name()); - user.setKey(streamReader.getAttributeValue("", "key")); + user.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME)); user.setName(streamReader.getAttributeValue("", "username")); report.getUsers().getAnys().add(user); break; @@ -77,7 +83,7 @@ public static ReconciliationReport parse(final Date run, final InputStream in) t case "group": group = new Any(); group.setType(AnyTypeKind.GROUP.name()); - group.setKey(streamReader.getAttributeValue("", "key")); + group.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME)); group.setName(streamReader.getAttributeValue("", "groupName")); report.getGroups().getAnys().add(group); break; @@ -93,7 +99,7 @@ public static ReconciliationReport parse(final Date run, final InputStream in) t case "anyObject": anyObject = new Any(); anyObject.setType(lastAnyType); - anyObject.setKey(streamReader.getAttributeValue("", "key")); + anyObject.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME)); final String anyType = lastAnyType; Optional anyReport = report.getAnyObjects().stream(). filter(anys -> anyType.equals(anys.getAnyType())). diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java index 2552b4c11de..bbe664e247f 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java @@ -22,12 +22,12 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleApplication; import org.apache.syncope.client.console.SyncopeConsoleSession; @@ -51,6 +51,7 @@ import org.apache.wicket.PageReference; import org.apache.wicket.Session; import org.apache.wicket.ThreadContext; +import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; @@ -65,6 +66,7 @@ public abstract class AjaxWizard extends Wizard public enum Mode { CREATE, EDIT, + EDIT_APPROVAL, TEMPLATE, READONLY; @@ -76,6 +78,8 @@ public enum Mode { private final Mode mode; + private final boolean async; + private IEventSink eventSink; private final PageReference pageRef; @@ -83,10 +87,11 @@ public enum Mode { /** * Construct. * - * @param id The component id. - * @param item model object. + * @param id The component id + * @param item model object * @param model wizard model - * @param mode true if edit mode. + * @param mode mode + * @param async should apply go async or not? * @param pageRef caller page reference. */ public AjaxWizard( @@ -94,10 +99,13 @@ public AjaxWizard( final T item, final WizardModel model, final Mode mode, + final boolean async, final PageReference pageRef) { + super(id); this.item = item; this.mode = mode; + this.async = async; this.pageRef = pageRef; if (mode == Mode.READONLY) { @@ -144,7 +152,7 @@ protected void init(final IWizardModel wizardModel) { getForm().remove(FEEDBACK_ID); if (mode == Mode.READONLY) { - final Iterator iter = wizardModel.stepIterator(); + Iterator iter = wizardModel.stepIterator(); while (iter.hasNext()) { WizardStep.class.cast(iter.next()).setEnabled(false); } @@ -164,7 +172,7 @@ protected Component newButtonBar(final String id) { * @param target request target * @return a pair of payload (maybe null) and resulting object. */ - protected abstract Pair onApplyInternal(final AjaxRequestTarget target); + protected abstract Pair onApplyInternal(AjaxRequestTarget target); /** * @see org.apache.wicket.extensions.wizard.Wizard#onCancel() @@ -181,8 +189,7 @@ public final void onCancel() { } } catch (Exception e) { LOG.warn("Wizard error on cancel", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } } @@ -201,7 +208,7 @@ public final void onFinish() { send(eventSink, Broadcast.EXACT, new NewItemFinishEvent<>(item, target).setResult(res)); } } catch (TimeoutException te) { - LOG.warn("Operation applying took to long", te); + LOG.warn("Operation took too long", te); if (eventSink == null) { send(this, Broadcast.BUBBLE, new NewItemCancelEvent<>(item, target)); } else { @@ -211,8 +218,7 @@ public final void onFinish() { ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } catch (Exception e) { LOG.error("Wizard error on finish", e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } } @@ -233,6 +239,45 @@ public AjaxWizard setItem(final T item) { return this; } + @Override + public void onSubmit(final AjaxRequestTarget target) { + try { + onApply(target); + } catch (TimeoutException te) { + LOG.warn("Operation took too long", te); + send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target)); + SyncopeConsoleSession.get().warn(getString("timeout")); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + } + + @Override + public void onError(final AjaxRequestTarget target) { + ((BasePage) getPage()).getNotificationPanel().refresh(target); + } + + private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException { + Pair results; + if (async) { + try { + Future> future = + SyncopeConsoleSession.get().execute(new ApplyFuture(target)); + + results = future.get(SyncopeConsoleApplication.get().getMaxWaitTimeInSeconds(), TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + throw new WicketRuntimeException(e); + } + } else { + results = onApplyInternal(target); + } + + if (results.getLeft() != null) { + send(pageRef.getPage(), Broadcast.BUBBLE, results.getLeft()); + } + + return results.getRight(); + } + public abstract static class NewItemEvent { private final T item; @@ -252,8 +297,8 @@ public T getItem() { return item; } - public AjaxRequestTarget getTarget() { - return target; + public Optional getTarget() { + return Optional.ofNullable(target); } public WizardModalPanel getModalPanel() { @@ -359,41 +404,6 @@ public Serializable getResult() { } } - @Override - public void onSubmit(final AjaxRequestTarget target) { - try { - onApply(target); - } catch (TimeoutException te) { - LOG.warn("Operation applying took to long", te); - send(eventSink, Broadcast.EXACT, new NewItemCancelEvent<>(item, target)); - SyncopeConsoleSession.get().warn(getString("timeout")); - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); - } - } - - @Override - public void onError(final AjaxRequestTarget target) { - ((BasePage) getPage()).getNotificationPanel().refresh(target); - } - - private Serializable onApply(final AjaxRequestTarget target) throws TimeoutException { - try { - Future> executor = - SyncopeConsoleSession.get().execute(new ApplyFuture(target)); - - Pair res = - executor.get(SyncopeConsoleApplication.get().getMaxWaitTimeInSeconds(), TimeUnit.SECONDS); - - if (res.getLeft() != null) { - send(pageRef.getPage(), Broadcast.BUBBLE, res.getLeft()); - } - - return res.getRight(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - private class ApplyFuture implements Callable>, Serializable { private static final long serialVersionUID = -4657123322652656848L; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java index f43b57b43a9..6f1d01fdb86 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java @@ -40,6 +40,8 @@ public abstract class AjaxWizardBuilder extends Abstract private final List outerObjects = new ArrayList<>(); + private boolean async = true; + /** * Construct. * @@ -50,7 +52,12 @@ public AjaxWizardBuilder(final T defaultItem, final PageReference pageRef) { super(defaultItem, pageRef); } - public final AjaxWizardBuilder addOuterObject(final Component... childs) { + public AjaxWizardBuilder setAsync(final boolean async) { + this.async = async; + return this; + } + + public AjaxWizardBuilder addOuterObject(final Component... childs) { outerObjects.addAll(Arrays.asList(childs)); return this; } @@ -60,7 +67,7 @@ public final AjaxWizardBuilder addOuterObject(final Component... childs) { */ @Override public AjaxWizard build(final String id, final int index, final AjaxWizard.Mode mode) { - final AjaxWizard wizard = build(id, mode); + AjaxWizard wizard = build(id, mode); for (int i = 1; i < index; i++) { wizard.getWizardModel().next(); } @@ -88,20 +95,20 @@ public AjaxWizard build(final String id, final AjaxWizard.Mode mode) { this.mode = mode; // get the specified item if available - final T modelObject = newModelObject(); + T modelObj = newModelObject(); - return new AjaxWizard(id, modelObject, buildModelSteps(modelObject, new WizardModel()), mode, this.pageRef) { + return new AjaxWizard(id, modelObj, buildModelSteps(modelObj, new WizardModel()), mode, async, pageRef) { private static final long serialVersionUID = 7770507663760640735L; @Override protected void onCancelInternal() { - AjaxWizardBuilder.this.onCancelInternal(modelObject); + AjaxWizardBuilder.this.onCancelInternal(modelObj); } @Override protected Pair onApplyInternal(final AjaxRequestTarget target) { - Serializable res = AjaxWizardBuilder.this.onApplyInternal(modelObject); + Serializable res = AjaxWizardBuilder.this.onApplyInternal(modelObj); Serializable payload; switch (mode) { @@ -121,7 +128,7 @@ protected Pair onApplyInternal(final AjaxRequestTarg }.setEventSink(eventSink).addOuterObject(outerObjects); } - protected abstract WizardModel buildModelSteps(final T modelObject, final WizardModel wizardModel); + protected abstract WizardModel buildModelSteps(T modelObject, WizardModel wizardModel); /** * Override to send custom events after create. diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardMgtButtonBar.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardMgtButtonBar.java index 0a192d94aa0..a04100049fd 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardMgtButtonBar.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardMgtButtonBar.java @@ -60,7 +60,7 @@ public MarkupContainer add(final Component... childs) { private void ajaxify(final WizardButton button) { button.add(new AjaxFormSubmitBehavior("click") { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 18163421824742L; @Override protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { @@ -109,7 +109,7 @@ protected FinishButton newFinishButton(final String id, final IWizard wizard) { private static final long serialVersionUID = 864248301720764819L; @Override - public final boolean isEnabled() { + public boolean isEnabled() { switch (mode) { case EDIT: case TEMPLATE: diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java new file mode 100644 index 00000000000..45e47457ce2 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java @@ -0,0 +1,295 @@ +/* + * 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.client.console.wizards; + +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.BootstrapFileInputField; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.FileInputConfig; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.CSVConfPanel; +import org.apache.syncope.client.console.rest.ImplementationRestClient; +import org.apache.syncope.client.console.rest.ReconciliationRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.ProvisioningReport; +import org.apache.syncope.common.lib.types.ConflictResolutionAction; +import org.apache.syncope.common.lib.types.ImplementationType; +import org.apache.syncope.common.lib.types.MatchingRule; +import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.common.rest.api.beans.CSVPullSpec; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior; +import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox; +import org.apache.wicket.event.IEventSink; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Radio; +import org.apache.wicket.markup.html.form.RadioGroup; +import org.apache.wicket.markup.html.form.upload.FileUpload; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.util.ListModel; +import org.apache.wicket.util.lang.Bytes; + +public class CSVPullWizardBuilder extends AjaxWizardBuilder { + + private static final long serialVersionUID = -4049787433975145383L; + + public enum LineSeparator { + LF("\n"), + CR("\r"), + CRLF("\r\n"); + + private final String repr; + + LineSeparator(final String repr) { + this.repr = repr; + } + + public String getRepr() { + return repr; + } + + public static LineSeparator byRepr(final String repr) { + for (LineSeparator value : values()) { + if (value.getRepr().equals(repr)) { + return value; + } + } + return null; + } + } + + private final ReconciliationRestClient restClient = new ReconciliationRestClient(); + + private final Model csv = new Model<>(); + + private final Bytes maxUploadSize; + + public CSVPullWizardBuilder(final CSVPullSpec defaultItem, final PageReference pageRef) { + super(defaultItem, pageRef); + + this.maxUploadSize = SyncopeConsoleApplication.get().getMaxUploadFileSizeMB() == null + ? null + : Bytes.megabytes(SyncopeConsoleApplication.get().getMaxUploadFileSizeMB()); + } + + @Override + public CSVPullWizardBuilder setEventSink(final IEventSink eventSink) { + super.setEventSink(eventSink); + return this; + } + + @Override + protected ArrayList onApplyInternal(final CSVPullSpec modelObject) { + return restClient.pull(modelObject, new ByteArrayInputStream(csv.getObject())); + } + + @Override + protected WizardModel buildModelSteps(final CSVPullSpec modelObject, final WizardModel wizardModel) { + wizardModel.add(new Details(modelObject)); + wizardModel.add(new Columns(modelObject)); + wizardModel.add(new PullTask(modelObject)); + return wizardModel; + } + + public class Details extends WizardStep { + + private static final long serialVersionUID = -4736870165235853919L; + + public Details(final CSVPullSpec spec) { + FileInputConfig csvFile = new FileInputConfig(). + showUpload(false).showRemove(false).showPreview(false); + String language = SyncopeConsoleSession.get().getLocale().getLanguage(); + if (!Locale.ENGLISH.getLanguage().equals(language)) { + csvFile.withLocale(language); + } + BootstrapFileInputField csvUpload = + new BootstrapFileInputField("csvUpload", new ListModel<>(new ArrayList<>()), csvFile); + csvUpload.add(new AjaxFormSubmitBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = 5538299138211283825L; + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + FileUpload uploadedFile = csvUpload.getFileUpload(); + if (uploadedFile != null) { + if (maxUploadSize != null && uploadedFile.getSize() > maxUploadSize.bytes()) { + SyncopeConsoleSession.get().error(getString("tooLargeFile"). + replace("${maxUploadSizeB}", String.valueOf(maxUploadSize.bytes())). + replace("${maxUploadSizeMB}", String.valueOf(maxUploadSize.bytes() / 1000000L))); + ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target); + } else { + csv.setObject(uploadedFile.getBytes()); + } + } + } + }); + add(csvUpload.setRequired(true).setOutputMarkupId(true)); + + add(new CSVConfPanel("csvconf", spec)); + } + } + + public class Columns extends WizardStep implements WizardModel.ICondition { + + private static final long serialVersionUID = -4136764210454067728L; + + private final CSVPullSpec spec; + + private final ListModel columnsModel = new ListModel<>(); + + public Columns(final CSVPullSpec spec) { + this.spec = spec; + + RadioGroup keyColumn = new RadioGroup<>("keyColumn", new PropertyModel<>(spec, "keyColumn")); + keyColumn.setOutputMarkupId(true); + keyColumn.setOutputMarkupPlaceholderTag(true); + keyColumn.setRenderBodyOnly(false); + keyColumn.setRequired(true); + add(keyColumn); + + keyColumn.add(new ListView("columns", columnsModel) { + + private static final long serialVersionUID = -9112553137618363167L; + + @Override + protected void populateItem(final ListItem item) { + item.add(new Label("column", item.getModelObject())); + item.add(new Radio<>("key", new Model<>(item.getModelObject()), keyColumn)); + item.add(new AjaxCheckBox("ignore", new Model<>()) { + + private static final long serialVersionUID = -6139318907146065915L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + if (spec.getIgnoreColumns().contains(item.getModelObject())) { + spec.getIgnoreColumns().remove(item.getModelObject()); + } else { + spec.getIgnoreColumns().add(item.getModelObject()); + } + } + }); + } + }.setReuseItems(true)); + } + + @Override + public boolean evaluate() { + if (csv.getObject() != null) { + String header = StringUtils.substringBefore(new String(csv.getObject()), spec.getLineSeparator()); + columnsModel.setObject(Stream.of(StringUtils.split(header, spec.getColumnSeparator())). + map(value -> { + String unquoted = value; + if (unquoted.charAt(0) == spec.getQuoteChar()) { + unquoted = unquoted.substring(1); + } + if (unquoted.charAt(unquoted.length() - 1) == spec.getQuoteChar()) { + unquoted = unquoted.substring(0, unquoted.length() - 1); + } + return unquoted; + }).collect(Collectors.toList())); + } + + return true; + } + } + + public class PullTask extends WizardStep { + + private static final long serialVersionUID = -8954789648303078732L; + + private final ImplementationRestClient implRestClient = new ImplementationRestClient(); + + private final IModel> pullActions = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 4659376149825914247L; + + @Override + protected List load() { + return implRestClient.list(ImplementationType.PULL_ACTIONS).stream(). + map(EntityTO::getKey).sorted().collect(Collectors.toList()); + } + }; + + private final IModel> pullCorrelationRules = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 4659376149825914247L; + + @Override + protected List load() { + return implRestClient.list(ImplementationType.PULL_CORRELATION_RULE).stream(). + map(EntityTO::getKey).sorted().collect(Collectors.toList()); + } + }; + + public PullTask(final CSVPullSpec spec) { + AjaxCheckBoxPanel remediation = new AjaxCheckBoxPanel( + "remediation", "remediation", new PropertyModel<>(spec, "remediation"), false); + add(remediation); + + AjaxDropDownChoicePanel matchingRule = new AjaxDropDownChoicePanel<>( + "matchingRule", "matchingRule", new PropertyModel<>(spec, "matchingRule"), false); + matchingRule.setChoices(Arrays.asList(MatchingRule.values())); + add(matchingRule); + + AjaxDropDownChoicePanel unmatchingRule = new AjaxDropDownChoicePanel<>( + "unmatchingRule", "unmatchingRule", new PropertyModel<>(spec, "unmatchingRule"), + false); + unmatchingRule.setChoices(Arrays.asList(UnmatchingRule.values())); + add(unmatchingRule); + + AjaxPalettePanel provisioningActions = + new AjaxPalettePanel.Builder().build("provisioningActions", + new PropertyModel<>(spec, "provisioningActions"), new ListModel<>(pullActions.getObject())); + add(provisioningActions); + + AjaxDropDownChoicePanel conflictResolutionAction = new AjaxDropDownChoicePanel<>( + "conflictResolutionAction", "conflictResolutionAction", + new PropertyModel<>(spec, "conflictResolutionAction"), false); + conflictResolutionAction.setChoices(Arrays.asList(ConflictResolutionAction.values())); + conflictResolutionAction.setRequired(true); + add(conflictResolutionAction); + + AjaxDropDownChoicePanel pullCorrelationRule = new AjaxDropDownChoicePanel<>( + "pullCorrelationRule", "pullCorrelationRule", new PropertyModel<>(spec, "pullCorrelationRule"), + false); + pullCorrelationRule.setChoices(pullCorrelationRules); + add(pullCorrelationRule); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java new file mode 100644 index 00000000000..8fa1865f061 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java @@ -0,0 +1,164 @@ +/* + * 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.client.console.wizards; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.panels.CSVConfPanel; +import org.apache.syncope.client.console.rest.ImplementationRestClient; +import org.apache.syncope.client.console.rest.ReconciliationRestClient; +import org.apache.syncope.client.console.rest.ResponseHolder; +import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.types.ImplementationType; +import org.apache.syncope.common.lib.types.MatchingRule; +import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.common.rest.api.beans.AnyQuery; +import org.apache.syncope.common.rest.api.beans.CSVPushSpec; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.IEventSink; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.util.ListModel; +import org.apache.wicket.request.cycle.RequestCycle; + +public class CSVPushWizardBuilder extends AjaxWizardBuilder { + + private static final long serialVersionUID = -8890710681835661962L; + + private final ReconciliationRestClient restClient = new ReconciliationRestClient(); + + private final AnyQuery query; + + private final AjaxDownloadBehavior downloadBehavior; + + public CSVPushWizardBuilder( + final CSVPushSpec defaultItem, + final AnyQuery query, + final AjaxDownloadBehavior downloadBehavior, + final PageReference pageRef) { + + super(defaultItem, pageRef); + this.query = query; + this.downloadBehavior = downloadBehavior; + } + + @Override + public CSVPushWizardBuilder setEventSink(final IEventSink eventSink) { + super.setEventSink(eventSink); + return this; + } + + @Override + protected Serializable onApplyInternal(final CSVPushSpec modelObject) { + return RequestCycle.get().find(AjaxRequestTarget.class).map(target -> { + try { + downloadBehavior.setResponse(new ResponseHolder(restClient.push(query, modelObject))); + downloadBehavior.initiate(target); + + return Constants.OPERATION_SUCCEEDED; + } catch (Exception e) { + LOG.error("While dowloading CSV export", e); + return e; + } + }).orElse(Constants.ERROR); + } + + @Override + protected WizardModel buildModelSteps(final CSVPushSpec modelObject, final WizardModel wizardModel) { + wizardModel.add(new Details(modelObject)); + wizardModel.add(new PushTask(modelObject)); + return wizardModel; + } + + public class Details extends WizardStep { + + private static final long serialVersionUID = -2368995286051427297L; + + public Details(final CSVPushSpec spec) { + add(new CSVConfPanel("csvconf", spec)); + } + } + + public class PushTask extends WizardStep { + + private static final long serialVersionUID = -2747583614435078452L; + + private final ImplementationRestClient implRestClient = new ImplementationRestClient(); + + private final IModel> propActions = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 4659376149825914247L; + + @Override + protected List load() { + return implRestClient.list(ImplementationType.PROPAGATION_ACTIONS).stream(). + map(EntityTO::getKey).sorted().collect(Collectors.toList()); + } + }; + + private final IModel> pushActions = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 4659376149825914247L; + + @Override + protected List load() { + return implRestClient.list(ImplementationType.PUSH_ACTIONS).stream(). + map(EntityTO::getKey).sorted().collect(Collectors.toList()); + } + }; + + public PushTask(final CSVPushSpec spec) { + AjaxCheckBoxPanel ignorePaging = new AjaxCheckBoxPanel( + "ignorePaging", "ignorePaging", new PropertyModel<>(spec, "ignorePaging"), true); + add(ignorePaging); + + AjaxPalettePanel propagationActions = + new AjaxPalettePanel.Builder().build("propagationActions", + new PropertyModel<>(spec, "propagationActions"), new ListModel<>(propActions.getObject())); + add(propagationActions); + + AjaxDropDownChoicePanel matchingRule = new AjaxDropDownChoicePanel<>( + "matchingRule", "matchingRule", new PropertyModel<>(spec, "matchingRule"), false); + matchingRule.setChoices(Arrays.asList(MatchingRule.values())); + add(matchingRule); + + AjaxDropDownChoicePanel unmatchingRule = new AjaxDropDownChoicePanel<>( + "unmatchingRule", "unmatchingRule", new PropertyModel<>(spec, "unmatchingRule"), + false); + unmatchingRule.setChoices(Arrays.asList(UnmatchingRule.values())); + add(unmatchingRule); + + AjaxPalettePanel provisioningActions = + new AjaxPalettePanel.Builder().build("provisioningActions", + new PropertyModel<>(spec, "provisioningActions"), new ListModel<>(pushActions.getObject())); + add(provisioningActions); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java new file mode 100644 index 00000000000..3b97f082be3 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java @@ -0,0 +1,211 @@ +/* + * 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.client.console.wizards; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.rest.DelegationRestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateTimeFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.DelegationTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.ResourceModel; + +public class DelegationWizardBuilder extends AjaxWizardBuilder { + + private static final long serialVersionUID = 16656970898539L; + + private final DelegationRestClient restClient = new DelegationRestClient(); + + private final UserRestClient userRestClient = new UserRestClient(); + + public DelegationWizardBuilder(final DelegationTO defaultItem, final PageReference pageRef) { + super(defaultItem, pageRef); + } + + @Override + protected Serializable onApplyInternal(final DelegationTO modelObject) { + if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey())) { + restClient.create(modelObject); + } else { + restClient.update(modelObject); + } + return null; + } + + @Override + protected WizardModel buildModelSteps(final DelegationTO modelObject, final WizardModel wizardModel) { + if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey()) + && SyncopeConsoleSession.get().owns(StandardEntitlement.USER_SEARCH) + && SyncopeConsoleSession.get().owns(StandardEntitlement.DELEGATION_CREATE)) { + + wizardModel.add(new UserSelectionWizardStep( + new ResourceModel("delegating"), new PropertyModel<>(modelObject, "delegating"), pageRef)); + wizardModel.add(new UserSelectionWizardStep( + new ResourceModel("delegated"), new PropertyModel<>(modelObject, "delegated"), pageRef)); + } else { + wizardModel.add(new Users(modelObject)); + } + + wizardModel.add(new StartEnd(modelObject)); + wizardModel.add(new Roles(modelObject)); + + return wizardModel; + } + + private class Users extends WizardStep { + + private static final long serialVersionUID = 33859341441696L; + + Users(final DelegationTO modelObject) { + super(); + + setTitleModel(new ResourceModel("users")); + + IModel delegating = new PropertyModel<>(modelObject, "delegating"); + IModel delegated = new PropertyModel<>(modelObject, "delegated"); + + boolean isNew = getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey()); + if (!isNew) { + if (SyncopeConsoleSession.get().owns(StandardEntitlement.USER_READ)) { + delegating = Model.of(userRestClient.read(delegating.getObject()).getUsername()); + delegated = Model.of(userRestClient.read(delegated.getObject()).getUsername()); + } else { + if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegating.getObject())) { + delegating = Model.of(SyncopeConsoleSession.get().getSelfTO().getUsername()); + } + if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegated.getObject())) { + delegated = Model.of(SyncopeConsoleSession.get().getSelfTO().getUsername()); + } + } + } + + boolean isSelfOnly = !SyncopeConsoleSession.get().owns(StandardEntitlement.DELEGATION_CREATE); + if (isSelfOnly) { + modelObject.setDelegating(SyncopeConsoleSession.get().getSelfTO().getUsername()); + } + + add(new AjaxTextFieldPanel( + "delegating", + "delegating", + delegating, + false).addRequiredLabel(). + setEnabled(isNew && !isSelfOnly)); + add(new AjaxTextFieldPanel( + "delegated", + "delegated", + delegated, + false).addRequiredLabel(). + setEnabled(isNew)); + } + } + + private static class StartEnd extends WizardStep { + + private static final long serialVersionUID = 16957451737824L; + + StartEnd(final DelegationTO modelObject) { + super(); + + setTitleModel(new ResourceModel("validity")); + + add(new AjaxDateTimeFieldPanel( + "start", + "start", + new PropertyModel<>(modelObject, "start"), + SyncopeConstants.DEFAULT_DATE_PATTERN). + addRequiredLabel()); + + add(new AjaxDateTimeFieldPanel( + "end", + "end", + new PropertyModel<>(modelObject, "end"), + SyncopeConstants.DEFAULT_DATE_PATTERN)); + } + } + + private class Roles extends WizardStep implements WizardModel.ICondition { + + private static final long serialVersionUID = 16957451737824L; + + private final List allRoles = new ArrayList<>(); + + private final DelegationTO modelObject; + + Roles(final DelegationTO modelObject) { + super(); + this.modelObject = modelObject; + + setTitleModel(new ResourceModel("roles")); + + add(new AjaxPalettePanel.Builder(). + withFilter(). + setAllowOrder(true). + build("roles", + new PropertyModel<>(modelObject, "roles"), + new AjaxPalettePanel.Builder.Query() { + + private static final long serialVersionUID = 3900199363626636719L; + + @Override + public List execute(final String filter) { + if (StringUtils.isEmpty(filter) || "*".equals(filter)) { + return allRoles.size() > Constants.MAX_ROLE_LIST_SIZE + ? allRoles.subList(0, Constants.MAX_ROLE_LIST_SIZE) + : allRoles; + + } + return allRoles.stream(). + filter(role -> StringUtils.containsIgnoreCase(role, filter)). + collect(Collectors.toList()); + } + }). + hideLabel(). + setOutputMarkupId(true)); + } + + @Override + public boolean evaluate() { + if (modelObject.getDelegating() != null) { + allRoles.clear(); + + if (SyncopeConsoleSession.get().owns(StandardEntitlement.USER_READ)) { + allRoles.addAll(userRestClient.read(modelObject.getDelegating()).getRoles()); + } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(modelObject.getDelegating())) { + allRoles.addAll(SyncopeConsoleSession.get().getSelfTO().getRoles()); + } + } + return true; + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/DynRealmWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/DynRealmWrapper.java index de013a960e0..13caee27d64 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/DynRealmWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/DynRealmWrapper.java @@ -61,7 +61,8 @@ public Map getDynMembershipConds() { this.dynClauses.entrySet().stream(). filter(entry -> (CollectionUtils.isNotEmpty(entry.getValue()))). forEachOrdered(entry -> { - AbstractFiqlSearchConditionBuilder builder = AnyTypeKind.USER.name().equals(entry.getKey()) + AbstractFiqlSearchConditionBuilder builder = + AnyTypeKind.USER.name().equals(entry.getKey()) ? SyncopeClient.getUserSearchConditionBuilder() : AnyTypeKind.GROUP.name().equals(entry.getKey()) ? SyncopeClient.getGroupSearchConditionBuilder() diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java new file mode 100644 index 00000000000..1cfb03694ba --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java @@ -0,0 +1,99 @@ +/* + * 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.client.console.wizards; + +import java.util.ArrayList; +import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel; +import org.apache.syncope.client.console.panels.search.SearchClausePanel; +import org.apache.syncope.client.console.panels.search.SearchUtils; +import org.apache.syncope.client.console.panels.search.UserSearchPanel; +import org.apache.syncope.client.console.panels.search.UserSelectionDirectoryPanel; +import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.to.AnyTypeTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.wicket.Component; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.util.ListModel; + +public class UserSelectionWizardStep extends WizardStep { + + private static final long serialVersionUID = 36221031226727L; + + private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); + + private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + + private final IModel model; + + private final UserSearchPanel userSearchPanel; + + private final UserSelectionDirectoryPanel userDirectoryPanel; + + public UserSelectionWizardStep( + final IModel title, final IModel model, final PageReference pageRef) { + + super(); + setOutputMarkupId(true); + + this.model = model; + setTitleModel(title); + + userSearchPanel = UserSearchPanel.class.cast(new UserSearchPanel.Builder( + new ListModel<>(new ArrayList<>())).required(false).enableSearch(UserSelectionWizardStep.this). + build("usersearch")); + add(userSearchPanel); + + AnyTypeTO anyTypeTO = anyTypeRestClient.read(AnyTypeKind.USER.name()); + userDirectoryPanel = UserSelectionDirectoryPanel.class.cast(new UserSelectionDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). + build("searchResult")); + add(userDirectoryPanel); + } + + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { + AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); + String fiql = SearchUtils.buildFIQL( + userSearchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder()); + userDirectoryPanel.search(fiql, target); + } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) { + @SuppressWarnings("unchecked") + AnySelectionDirectoryPanel.ItemSelection payload = + (AnySelectionDirectoryPanel.ItemSelection) event.getPayload(); + + UserTO selected = payload.getSelection(); + this.model.setObject(selected.getKey()); + + String tableId = ((Component) event.getSource()). + get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable"). + getMarkupId(); + String js = "$('#" + tableId + " tr').removeClass('active');"; + js += "$('#" + tableId + " td[title=" + selected.getKey() + "]').parent().addClass('active');"; + payload.getTarget().prependJavaScript(js); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java index 9cca5ae0243..09534a84419 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; @@ -84,7 +85,7 @@ public abstract class WizardMgtPanel extends Panel imple private final List outerObjects = new ArrayList<>(); - protected final BaseModal modal = new BaseModal("outer") { + protected final BaseModal modal = new BaseModal(Constants.OUTER) { private static final long serialVersionUID = 389935548143327858L; @@ -163,6 +164,18 @@ public String getActualId() { return actualId; } + public boolean isWizardInModal() { + return wizardInModal; + } + + public ModalPanelBuilder getNewItemPanelBuilder() { + return newItemPanelBuilder; + } + + public boolean isNewItemDefaultButtonEnabled() { + return addAjaxLink.isVisible() && addAjaxLink.isEnabled(); + } + @Override @SuppressWarnings("unchecked") public void onEvent(final IEvent event) { @@ -172,7 +185,7 @@ public void onEvent(final IEvent event) { modal.close(target); } else if (event.getPayload() instanceof AjaxWizard.NewItemEvent) { final AjaxWizard.NewItemEvent newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload()); - final AjaxRequestTarget target = newItemEvent.getTarget(); + final Optional target = newItemEvent.getTarget(); final T item = newItemEvent.getItem(); final boolean modalPanelAvailable = newItemEvent.getModalPanel() != null || newItemPanelBuilder != null; @@ -198,7 +211,7 @@ public void onEvent(final IEvent event) { final IModel model = new CompoundPropertyModel<>(item); modal.setFormModel(model); - target.add(modal.setContent(modalPanel)); + target.ifPresent(t -> t.add(modal.setContent(modalPanel))); modal.header(new StringResourceModel( String.format("any.%s", newItemEvent.getEventDescription()), @@ -214,17 +227,25 @@ public void onEvent(final IEvent event) { fragment.add(Component.class.cast(modalPanel)); container.addOrReplace(fragment); } - customActionCallback(target); + if (target.isPresent()) { + customActionCallback(target.get()); + } } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { if (wizardInModal) { - modal.close(target); + if (target.isPresent()) { + modal.close(target.get()); + } } else { container.addOrReplace(initialFragment); } - customActionOnCancelCallback(target); + if (target.isPresent()) { + customActionOnCancelCallback(target.get()); + } } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); - ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + if (target.isPresent()) { + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target.get()); + } if (wizardInModal && showResultPage) { modal.setContent(new ResultPage( @@ -243,17 +264,21 @@ protected Panel customResultBody(final String id, final T item, final Serializab return WizardMgtPanel.this.customResultBody(id, item, result); } }); - target.add(modal.getForm()); + target.ifPresent(t -> t.add(modal.getForm())); } else if (wizardInModal) { - modal.close(target); + if (target.isPresent()) { + modal.close(target.get()); + } } else { container.addOrReplace(initialFragment); } - customActionOnFinishCallback(target); + if (target.isPresent()) { + customActionOnFinishCallback(target.get()); + } } if (containerAutoRefresh) { - target.add(container); + target.ifPresent(t -> t.add(container)); } } super.onEvent(event); @@ -276,7 +301,7 @@ protected Panel customResultBody(final String panelId, final T item, final Seria } /** - * Show exit butto sending ExitEvent paylad. + * Show exit button sending ExitEvent paylad. * * @return the current instance. */ @@ -359,9 +384,7 @@ public WizardMgtPanel setFooterVisibility(final boolean footerVisibility) { * @param modal target modal. */ protected void setWindowClosedReloadCallback(final BaseModal modal) { - modal.setWindowClosedCallback(target -> { - modal.show(false); - }); + modal.setWindowClosedCallback(target -> modal.show(false)); } /** @@ -413,7 +436,7 @@ protected Builder(final PageReference pageRef) { this.pageRef = pageRef; } - protected abstract WizardMgtPanel newInstance(final String id, final boolean wizardInModal); + protected abstract WizardMgtPanel newInstance(String id, boolean wizardInModal); /** * Builds a list view. @@ -442,7 +465,7 @@ public Builder addNewItemPanelBuilder(final ModalPanelBuilder panelBuilder * Adds new item panel builder. * * @param panelBuilder new item panel builder. - * @param newItemDefaultButtonEnabled enable default button to adda new item. + * @param newItemDefaultButtonEnabled enable default button to add a new item. * @return the current builder. */ public Builder addNewItemPanelBuilder( @@ -453,7 +476,7 @@ public Builder addNewItemPanelBuilder( } /** - * Adds new item panel builder and enables default button to adda new item. + * Adds new item panel builder and enables default button to add a new item. * * @param notificationPanel new item panel builder. * @return the current builder. diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java index 7455ffcdbe9..17992ad79ef 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrs.java @@ -18,120 +18,82 @@ */ package org.apache.syncope.client.console.wizards.any; -import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; -import org.apache.cxf.common.util.StringUtils; -import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; +import org.apache.commons.collections4.CollectionUtils; import org.apache.syncope.client.console.rest.GroupRestClient; -import org.apache.syncope.client.console.rest.SchemaRestClient; -import org.apache.syncope.common.lib.to.SchemaTO; -import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.SchemaTO; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.MembershipTO; -import org.apache.syncope.common.lib.types.SchemaType; -import org.apache.wicket.PageReference; +import org.apache.syncope.common.lib.to.TypeExtensionTO; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.core.util.lang.PropertyResolver; -import org.apache.wicket.extensions.wizard.WizardModel.ICondition; -import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.OnDomReadyHeaderItem; -import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.util.ListModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public abstract class AbstractAttrs extends WizardStep implements ICondition { +public abstract class AbstractAttrs extends AbstractAttrsWizardStep { + private static final Logger LOG = LoggerFactory.getLogger(AbstractAttrs.class); private static final long serialVersionUID = -5387344116983102292L; - protected final Comparator attrComparator = new AttrComparator(); - - private final SchemaRestClient schemaRestClient = new SchemaRestClient(); - - private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); - private final GroupRestClient groupRestClient = new GroupRestClient(); - protected final AnyTO anyTO; - - private final List whichAttrs; - - protected final Map schemas = new LinkedHashMap<>(); + protected final IModel> memberships; protected final Map> membershipSchemas = new LinkedHashMap<>(); - protected final IModel> attrTOs; - - protected final IModel> membershipTOs; - - private final List anyTypeClasses; - public AbstractAttrs( final AnyWrapper modelObject, + final AjaxWizard.Mode mode, final List anyTypeClasses, final List whichAttrs) { - super(); - this.anyTypeClasses = anyTypeClasses; - this.attrTOs = new ListModel<>(Collections.emptyList()); - this.membershipTOs = new ListModel<>(Collections.emptyList()); - this.setOutputMarkupId(true); + super(modelObject.getInnerObject(), mode, anyTypeClasses, whichAttrs, null); - this.anyTO = modelObject.getInnerObject(); - this.whichAttrs = whichAttrs; - } + this.memberships = new ListModel<>(Collections.emptyList()); - private List loadAttrTOs() { - List classes = new ArrayList<>(anyTypeClasses); - classes.addAll(anyTypeClassRestClient.list(anyTO.getAuxClasses()).stream(). - map(EntityTO::getKey).collect(Collectors.toList())); - setSchemas(classes); - setAttrs(); - return AbstractAttrs.this.getAttrsFromTO(); + this.setOutputMarkupId(true); } @SuppressWarnings("unchecked") - private List loadMembershipAttrTOs() { - List memberships = new ArrayList<>(); - try { - membershipSchemas.clear(); + private List loadMemberships() { + membershipSchemas.clear(); - for (MembershipTO membership : (List) PropertyResolver.getPropertyField( - "memberships", anyTO).get(anyTO)) { - setSchemas(membership.getGroupKey(), - anyTypeClassRestClient.list(getMembershipAuxClasses(membership, anyTO.getType())). + List membs = new ArrayList<>(); + try { + ((List) PropertyResolver.getPropertyField("memberships", anyTO).get(anyTO)).forEach(memb -> { + setSchemas(memb.getGroupKey(), + anyTypeClassRestClient.list(getMembershipAuxClasses(memb, anyTO.getType())). stream().map(EntityTO::getKey).collect(Collectors.toList())); - setAttrs(membership); - - if (AbstractAttrs.this instanceof PlainAttrs && !membership.getPlainAttrs().isEmpty()) { - memberships.add(membership); - } else if (AbstractAttrs.this instanceof DerAttrs && !membership.getDerAttrs().isEmpty()) { - memberships.add(membership); - } else if (AbstractAttrs.this instanceof VirAttrs && !membership.getVirAttrs().isEmpty()) { - memberships.add(membership); + setAttrs(memb); + + if (this instanceof PlainAttrs && !memb.getPlainAttrs().isEmpty()) { + membs.add(memb); + } else if (this instanceof DerAttrs && !memb.getDerAttrs().isEmpty()) { + membs.add(memb); + } else if (this instanceof VirAttrs && !memb.getVirAttrs().isEmpty()) { + membs.add(memb); } - } + }); } catch (WicketRuntimeException | IllegalArgumentException | IllegalAccessException ex) { // ignore } - return memberships; - } - - protected boolean reoderSchemas() { - return !whichAttrs.isEmpty(); + return membs; } - protected abstract SchemaType getSchemaType(); - private void setSchemas(final String membership, final List anyTypeClasses) { final Map mscs; @@ -144,106 +106,39 @@ private void setSchemas(final String membership, final List anyTypeClass setSchemas(anyTypeClasses, mscs); } - private void setSchemas(final List anyTypeClasses) { - setSchemas(anyTypeClasses, schemas); - } - - private void setSchemas(final List anyTypeClasses, final Map scs) { - final List allSchemas; - if (anyTypeClasses.isEmpty()) { - allSchemas = Collections.emptyList(); - } else { - allSchemas = schemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(new String[] {})); + private List getMembershipAuxClasses(final MembershipTO membershipTO, final String anyType) { + try { + final GroupTO groupTO = groupRestClient.read(membershipTO.getGroupKey()); + Optional typeExtension = groupTO.getTypeExtension(anyType); + if (!typeExtension.isPresent()) { + LOG.trace("Unable to locate type extension for " + anyType); + return Collections.emptyList(); + } + return typeExtension.get().getAuxClasses(); + } catch (Exception e) { + return Collections.emptyList(); } + } - scs.clear(); + protected abstract void setAttrs(MembershipTO membershipTO); - if (reoderSchemas()) { - // 1. remove attributes not selected for display - allSchemas.removeAll(allSchemas.stream(). - filter(schemaTO -> !whichAttrs.contains(schemaTO.getKey())).collect(Collectors.toSet())); - } - - allSchemas.forEach(schemaTO -> { - scs.put(schemaTO.getKey(), schemaTO); - }); - } + protected abstract List getAttrsFromTO(MembershipTO membershipTO); @Override public void renderHead(final IHeaderResponse response) { super.renderHead(response); - if (org.apache.cxf.common.util.CollectionUtils.isEmpty(attrTOs.getObject()) - && org.apache.cxf.common.util.CollectionUtils.isEmpty(membershipTOs.getObject())) { + if (CollectionUtils.isEmpty(attrTOs.getObject()) + && CollectionUtils.isEmpty(memberships.getObject())) { response.render(OnDomReadyHeaderItem.forScript( String.format("$('#emptyPlaceholder').append(\"%s\"); $('#attributes').hide();", getString("attribute.empty.list")))); } } - protected abstract void setAttrs(); - - protected abstract void setAttrs(final MembershipTO membershipTO); - - protected abstract List getAttrsFromTO(); - - protected abstract List getAttrsFromTO(final MembershipTO membershipTO); - - protected List getMembershipAuxClasses(final MembershipTO membershipTO, final String anyType) { - try { - final GroupTO groupTO = groupRestClient.read(membershipTO.getGroupKey()); - return groupTO.getTypeExtension(anyType).get().getAuxClasses(); - } catch (Exception e) { - return Collections.emptyList(); - } - } - @Override public boolean evaluate() { this.attrTOs.setObject(loadAttrTOs()); - this.membershipTOs.setObject(loadMembershipAttrTOs()); - return !attrTOs.getObject().isEmpty() || !membershipTOs.getObject().isEmpty(); - } - - public PageReference getPageReference() { - // SYNCOPE-1213 - // default implementation does not requier to pass page reference, override this method of want otherwise - return null; - } - - private class AttrComparator implements Comparator, Serializable { - - private static final long serialVersionUID = -5105030477767941060L; - - @Override - public int compare(final AttrTO left, final AttrTO right) { - if (left == null || StringUtils.isEmpty(left.getSchema())) { - return -1; - } - if (right == null || StringUtils.isEmpty(right.getSchema())) { - return 1; - } else if (AbstractAttrs.this.reoderSchemas()) { - int leftIndex = AbstractAttrs.this.whichAttrs.indexOf(left.getSchema()); - int rightIndex = AbstractAttrs.this.whichAttrs.indexOf(right.getSchema()); - - if (leftIndex > rightIndex) { - return 1; - } else if (leftIndex < rightIndex) { - return -1; - } else { - return 0; - } - } else { - return left.getSchema().compareTo(right.getSchema()); - } - } - } - - public class Schemas extends Panel { - - private static final long serialVersionUID = -2447602429647965090L; - - public Schemas(final String id) { - super(id); - } + this.memberships.setObject(loadMemberships()); + return !attrTOs.getObject().isEmpty() || !memberships.getObject().isEmpty(); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java new file mode 100644 index 00000000000..c20df709e6e --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java @@ -0,0 +1,453 @@ +/* + * 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.client.console.wizards.any; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.SchemaUtils; +import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; +import org.apache.syncope.client.console.rest.SchemaRestClient; +import org.apache.syncope.client.console.wicket.ajax.markup.html.LabelInfo; +import org.apache.syncope.client.console.wicket.markup.html.form.AbstractFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateTimeFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSpinnerFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.BinaryFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.EncryptedFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.SchemaTO; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.types.AttrSchemaType; +import org.apache.syncope.common.lib.types.SchemaType; +import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.wizard.WizardModel.ICondition; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.OnDomReadyHeaderItem; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.IChoiceRenderer; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.util.ListModel; + +public abstract class AbstractAttrsWizardStep extends WizardStep implements ICondition { + + private static final long serialVersionUID = 8931397230194043674L; + + protected final Comparator attrComparator = new AttrComparator(); + + private final SchemaRestClient schemaRestClient = new SchemaRestClient(); + + protected final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); + + protected final AnyTO anyTO; + + protected AnyTO previousObject; + + private final List whichAttrs; + + protected final Map schemas = new LinkedHashMap<>(); + + protected final IModel> attrTOs; + + private final List anyTypeClasses; + + protected String fileKey = ""; + + protected final AjaxWizard.Mode mode; + + public AbstractAttrsWizardStep( + final AnyTO anyTO, + final AjaxWizard.Mode mode, + final List anyTypeClasses, + final List whichAttrs, + final EntityWrapper modelObject) { + + super(); + this.anyTypeClasses = anyTypeClasses; + this.attrTOs = new ListModel<>(Collections.emptyList()); + + this.setOutputMarkupId(true); + + this.mode = mode; + this.anyTO = anyTO; + this.whichAttrs = whichAttrs; + } + + protected List loadAttrTOs() { + List classes = new ArrayList<>(anyTypeClasses); + classes.addAll(anyTypeClassRestClient.list(anyTO.getAuxClasses()).stream(). + map(EntityTO::getKey).collect(Collectors.toList())); + setSchemas(classes); + setAttrs(); + return getAttrsFromTO(); + } + + protected boolean reoderSchemas() { + return !whichAttrs.isEmpty(); + } + + protected abstract SchemaType getSchemaType(); + + private void setSchemas(final List anyTypeClasses) { + setSchemas(anyTypeClasses, schemas); + } + + protected void setSchemas(final List anyTypeClasses, final Map scs) { + List allSchemas = anyTypeClasses.isEmpty() + ? Collections.emptyList() + : schemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(new String[] {})); + + scs.clear(); + + if (reoderSchemas()) { + // remove attributes not selected for display + allSchemas.removeAll(allSchemas.stream(). + filter(schemaTO -> !whichAttrs.contains(schemaTO.getKey())).collect(Collectors.toSet())); + } + + allSchemas.forEach(schemaTO -> { + scs.put(schemaTO.getKey(), schemaTO); + }); + } + + @Override + public void renderHead(final IHeaderResponse response) { + super.renderHead(response); + if (CollectionUtils.isEmpty(attrTOs.getObject())) { + response.render(OnDomReadyHeaderItem.forScript( + String.format("$('#emptyPlaceholder').append(\"%s\"); $('#attributes').hide();", + getString("attribute.empty.list")))); + } + } + + protected abstract void setAttrs(); + + protected abstract List getAttrsFromTO(); + + @Override + public boolean evaluate() { + this.attrTOs.setObject(loadAttrTOs()); + return !attrTOs.getObject().isEmpty(); + } + + public PageReference getPageReference() { + // SYNCOPE-1213 + // default implementation does not require to pass page reference, override this method of want otherwise + return null; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected FieldPanel getFieldPanel(final PlainSchemaTO plainSchema) { + final boolean required; + final boolean readOnly; + final AttrSchemaType type; + final boolean jexlHelp; + + if (mode == AjaxWizard.Mode.TEMPLATE) { + required = false; + readOnly = false; + type = AttrSchemaType.String; + jexlHelp = true; + } else { + required = plainSchema.getMandatoryCondition().equalsIgnoreCase("true"); + readOnly = plainSchema.isReadonly(); + type = plainSchema.getType(); + jexlHelp = false; + } + + FieldPanel panel; + switch (type) { + case Boolean: + panel = new AjaxCheckBoxPanel( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + new Model<>(), + true); + panel.setRequired(required); + break; + + case Date: + String datePattern = plainSchema.getConversionPattern() == null + ? SyncopeConstants.DEFAULT_DATE_PATTERN + : plainSchema.getConversionPattern(); + + if (datePattern.contains("H")) { + panel = new AjaxDateTimeFieldPanel( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + new Model<>(), + datePattern); + } else { + panel = new AjaxDateFieldPanel( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + new Model<>(), + datePattern); + } + + if (required) { + panel.addRequiredLabel(); + } + + break; + + case Enum: + panel = new AjaxDropDownChoicePanel<>("panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); + ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(plainSchema)); + + if (StringUtils.isNotBlank(plainSchema.getEnumerationKeys())) { + ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer() { + + private static final long serialVersionUID = -3724971416312135885L; + + private final Map valueMap = SchemaUtils.getEnumeratedKeyValues(plainSchema); + + @Override + public String getDisplayValue(final String value) { + return valueMap.get(value) == null ? value : valueMap.get(value); + } + + @Override + public String getIdValue(final String value, final int i) { + return value; + } + + @Override + public String getObject( + final String id, final IModel> choices) { + return id; + } + }); + } + + if (required) { + panel.addRequiredLabel(); + } + break; + + case Long: + panel = new AjaxSpinnerFieldPanel.Builder().enableOnChange().build( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + Long.class, + new Model<>()); + + if (required) { + panel.addRequiredLabel(); + } + break; + + case Double: + panel = new AjaxSpinnerFieldPanel.Builder().enableOnChange().step(0.1).build( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + Double.class, + new Model<>()); + + if (required) { + panel.addRequiredLabel(); + } + break; + + case Binary: + final PageReference pageRef = getPageReference(); + panel = new BinaryFieldPanel( + "panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), + new Model<>(), + plainSchema.getMimeType(), + fileKey) { + + private static final long serialVersionUID = -3268213909514986831L; + + @Override + protected PageReference getPageReference() { + return pageRef; + } + + }; + if (required) { + panel.addRequiredLabel(); + } + break; + + case Encrypted: + panel = SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(plainSchema.getConversionPattern()) + ? new AjaxTextFieldPanel("panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true) + : new EncryptedFieldPanel("panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); + + if (required) { + panel.addRequiredLabel(); + } + break; + + default: + panel = new AjaxTextFieldPanel("panel", + plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); + + if (jexlHelp) { + AjaxTextFieldPanel.class.cast(panel).enableJexlHelp(); + } + + if (required) { + panel.addRequiredLabel(); + } + } + + panel.setReadOnly(readOnly); + + return panel; + } + + private class AttrComparator implements Comparator, Serializable { + + private static final long serialVersionUID = -5105030477767941060L; + + @Override + public int compare(final AttrTO left, final AttrTO right) { + if (left == null || StringUtils.isEmpty(left.getSchema())) { + return -1; + } + if (right == null || StringUtils.isEmpty(right.getSchema())) { + return 1; + } else if (AbstractAttrsWizardStep.this.reoderSchemas()) { + int leftIndex = AbstractAttrsWizardStep.this.whichAttrs.indexOf(left.getSchema()); + int rightIndex = AbstractAttrsWizardStep.this.whichAttrs.indexOf(right.getSchema()); + + if (leftIndex > rightIndex) { + return 1; + } else if (leftIndex < rightIndex) { + return -1; + } else { + return 0; + } + } else { + return left.getSchema().compareTo(right.getSchema()); + } + } + } + + protected FormComponent checkboxToggle( + final AttrTO attrTO, + final AbstractFieldPanel panel, + final boolean isMultivalue) { + + // do nothing + return null; + } + + public class Schemas extends Panel { + + private static final long serialVersionUID = -2447602429647965090L; + + public Schemas(final String id) { + super(id); + } + } + + protected abstract class PlainSchemas extends Schemas { + + private static final long serialVersionUID = 8315035592714180404L; + + public PlainSchemas( + final String id, + final Map schemas, + final IModel attrTOs) { + + super(id); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected AbstractFieldPanel setPanel( + final Map schemas, + final ListItem item, + final boolean setReadOnly) { + + AttrTO attrTO = item.getModelObject(); + final boolean isMultivalue = mode != AjaxWizard.Mode.TEMPLATE + && schemas.get(attrTO.getSchema()).isMultivalue(); + + AbstractFieldPanel panel = getFieldPanel(schemas.get(attrTO.getSchema())); + if (isMultivalue) { + // SYNCOPE-1476 set form as multipart to properly manage membership attributes + panel = new MultiFieldPanel.Builder<>( + new PropertyModel<>(attrTO, "values")).build( + "panel", + attrTO.getSchema(), + FieldPanel.class.cast(panel)).setFormAsMultipart(true); + // SYNCOPE-1215 the entire multifield panel must be readonly, not only its field + MultiFieldPanel.class.cast(panel).setReadOnly(schemas.get(attrTO.getSchema()).isReadonly()); + MultiFieldPanel.class.cast(panel).setFormReadOnly(setReadOnly); + } else { + FieldPanel.class.cast(panel).setNewModel(attrTO.getValues()).setReadOnly(setReadOnly); + } + item.add(panel); + + setExternalAction(attrTO, panel); + + return panel; + } + + protected void setExternalAction(final AttrTO attrTO, final AbstractFieldPanel panel) { + Optional prevAttr = previousObject == null + ? Optional.empty() + : previousObject.getPlainAttr(attrTO.getSchema()); + if (previousObject != null + && ((!prevAttr.isPresent() && attrTO.getValues().stream().anyMatch(StringUtils::isNotBlank)) + || (prevAttr.isPresent() && !ListUtils.isEqualList( + prevAttr.get().getValues().stream(). + filter(StringUtils::isNotBlank).collect(Collectors.toList()), + attrTO.getValues().stream(). + filter(StringUtils::isNotBlank).collect(Collectors.toList()))))) { + + List oldValues = prevAttr.isPresent() + ? prevAttr.get().getValues() + : Collections.emptyList(); + panel.showExternAction(new LabelInfo("externalAction", oldValues)); + } + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractGroups.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractGroups.java new file mode 100644 index 00000000000..0ee9e7fa0e8 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractGroups.java @@ -0,0 +1,78 @@ +/* + * 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.client.console.wizards.any; + +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.wicket.ajax.markup.html.LabelInfo; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.extensions.wizard.WizardModel.ICondition; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; + +public abstract class AbstractGroups extends WizardStep implements ICondition { + + private static final long serialVersionUID = -5211384197382796059L; + + protected final AnyTO anyTO; + + protected WebMarkupContainer dyngroupsContainer; + + protected WebMarkupContainer dynrealmsContainer; + + protected WebMarkupContainer groupsContainer; + + public AbstractGroups(final AnyWrapper modelObject) { + super(); + this.anyTO = modelObject.getInnerObject(); + + setOutputMarkupId(true); + + groupsContainer = new WebMarkupContainer("groupsContainer"); + groupsContainer.setOutputMarkupId(true); + groupsContainer.setOutputMarkupPlaceholderTag(true); + add(groupsContainer); + + // ------------------ + // insert changed label if needed + // ------------------ + if (modelObject instanceof UserWrapper + && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null + && !ListUtils.isEqualList( + UserWrapper.class.cast(modelObject).getInnerObject().getMemberships(), + UserWrapper.class.cast(modelObject).getPreviousUserTO().getMemberships())) { + groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY)); + } else { + groupsContainer.add(new Label("changed", StringUtils.EMPTY)); + } + // ------------------ + } + + protected abstract void addGroupsPanel(); + + protected abstract void addDynamicRealmsContainer(); + + protected abstract void addDynamicGroupsContainer(); + + @Override + public boolean evaluate() { + return true; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectDetails.java index 5dff819f792..e56ca7bd932 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectDetails.java @@ -35,10 +35,8 @@ public AnyObjectDetails( super(wrapper, templateMode, includeStatusPanel, pageRef); - AnyObjectTO anyObjectTO = wrapper.getInnerObject(); - - AjaxTextFieldPanel name = - new AjaxTextFieldPanel("name", "name", new PropertyModel<>(anyObjectTO, "name"), false); + AjaxTextFieldPanel name = new AjaxTextFieldPanel( + "name", "name", new PropertyModel<>(wrapper.getInnerObject(), "name"), false); if (templateMode) { name.enableJexlHelp(); } else { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java index 141aa9de6bf..df75efd6433 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java @@ -19,6 +19,8 @@ package org.apache.syncope.client.console.wizards.any; import java.util.List; +import java.util.Optional; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.to.AnyObjectTO; @@ -48,17 +50,18 @@ public AnyObjectTemplateWizardBuilder( AnyObjectTO anyObjectTO = new AnyObjectTO(); anyObjectTO.setType(anyType); if (templatable instanceof RealmTO) { - anyObjectTO.setRealm(String.format("'%s'", RealmTO.class.cast(templatable).getFullPath())); + anyObjectTO.setRealm( + String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath()))); } setItem(new AnyWrapper<>(anyObjectTO)); } } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - final Details details = super.addOptionalDetailsPanel(modelObject); - if (templatable instanceof RealmTO) { - details.disableRealmSpecification(); + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + Optional> details = super.addOptionalDetailsPanel(modelObject); + if (templatable instanceof RealmTO && details.isPresent()) { + details.get().disableRealmSpecification(); } return details; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java index 184de10d7cc..0c111ff4609 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWizardBuilder.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Optional; import org.apache.syncope.client.console.layout.AnyObjectForm; import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; import org.apache.syncope.client.console.rest.AnyObjectRestClient; @@ -34,7 +35,7 @@ public class AnyObjectWizardBuilder extends AnyWizardBuilder implem private static final long serialVersionUID = -2480279868319546243L; - private final AnyObjectRestClient anyObjectRestClient = new AnyObjectRestClient(); + protected final AnyObjectRestClient anyObjectRestClient = new AnyObjectRestClient(); public AnyObjectWizardBuilder( final AnyObjectTO anyObjectTO, @@ -72,6 +73,7 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject if (inner.getKey() == null) { result = anyObjectRestClient.create(inner); } else { + fixPlainAndVirAttrs(inner, getOriginalItem().getInnerObject()); AnyObjectPatch patch = AnyOperations.diff(inner, getOriginalItem().getInnerObject(), false); // update just if it is changed @@ -87,10 +89,10 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - return new AnyObjectDetails( + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + return Optional.of(new AnyObjectDetails( modelObject, mode == AjaxWizard.Mode.TEMPLATE, - modelObject.getInnerObject().getKey() != null, pageRef); + modelObject.getInnerObject().getKey() != null, pageRef)); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWrapper.java index 3855bd8f862..8611db37957 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectWrapper.java @@ -38,5 +38,4 @@ public AnyObjectWrapper(final AnyObjectTO previousAnyObjectTO, final AnyObjectTO public AnyObjectTO getPreviousAnyObjectTO() { return previousAnyObjectTO; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java index ec3ff78610a..e8a7ab6d4ca 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java @@ -18,9 +18,8 @@ */ package org.apache.syncope.client.console.wizards.any; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import org.apache.syncope.client.console.layout.AbstractAnyFormLayout; import org.apache.syncope.client.console.layout.AnyForm; import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo; @@ -31,7 +30,7 @@ import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.GroupTO; -import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.wicket.PageReference; import org.apache.wicket.extensions.wizard.WizardModel; @@ -84,10 +83,7 @@ public AnyWizardBuilder( @Override protected WizardModel buildModelSteps(final AnyWrapper modelObject, final WizardModel wizardModel) { // optional details panel step - final Details details = addOptionalDetailsPanel(modelObject); - if (details != null) { - wizardModel.add(details); - } + addOptionalDetailsPanel(modelObject).ifPresent(wizardModel::add); if ((this instanceof GroupWizardBuilder) && (modelObject.getInnerObject() instanceof GroupTO) @@ -112,12 +108,8 @@ protected WizardModel buildModelSteps(final AnyWrapper modelObject, final Wiz // attributes panel steps if (formLayoutInfo.isPlainAttrs()) { - wizardModel.add(new PlainAttrs( - modelObject, - null, - mode, - anyTypeClasses, - formLayoutInfo.getWhichPlainAttrs()) { + wizardModel.add( + new PlainAttrs(modelObject, null, mode, anyTypeClasses, formLayoutInfo.getWhichPlainAttrs()) { private static final long serialVersionUID = 8167894751609598306L; @@ -125,31 +117,28 @@ protected WizardModel buildModelSteps(final AnyWrapper modelObject, final Wiz public PageReference getPageReference() { return pageRef; } - }); } if (formLayoutInfo.isDerAttrs() && mode != AjaxWizard.Mode.TEMPLATE) { - wizardModel.add(new DerAttrs( - modelObject, anyTypeClasses, formLayoutInfo.getWhichDerAttrs())); + wizardModel.add(new DerAttrs(modelObject, anyTypeClasses, formLayoutInfo.getWhichDerAttrs())); } if (formLayoutInfo.isVirAttrs()) { - wizardModel.add(new VirAttrs( - modelObject, mode, anyTypeClasses, formLayoutInfo.getWhichVirAttrs())); + wizardModel.add(new VirAttrs(modelObject, mode, anyTypeClasses, formLayoutInfo.getWhichVirAttrs())); } // role panel step (just available for users) if ((this instanceof UserWizardBuilder) - && (modelObject.getInnerObject() instanceof UserTO) - && (formLayoutInfo instanceof UserFormLayoutInfo) + && modelObject instanceof UserWrapper + && formLayoutInfo instanceof UserFormLayoutInfo && UserFormLayoutInfo.class.cast(formLayoutInfo).isRoles()) { - wizardModel.add(new Roles(modelObject)); + wizardModel.add(new Roles((UserWrapper) modelObject)); } // relationship panel step (available for users and any objects) - if (((formLayoutInfo instanceof UserFormLayoutInfo) + if ((formLayoutInfo instanceof UserFormLayoutInfo && UserFormLayoutInfo.class.cast(formLayoutInfo).isRelationships()) - || ((formLayoutInfo instanceof AnyObjectFormLayoutInfo) + || (formLayoutInfo instanceof AnyObjectFormLayoutInfo && AnyObjectFormLayoutInfo.class.cast(formLayoutInfo).isRelationships())) { wizardModel.add(new Relationships(modelObject, pageRef)); @@ -163,22 +152,51 @@ public PageReference getPageReference() { return wizardModel; } - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { if (modelObject.getInnerObject().getKey() == null) { - return null; + return Optional.empty(); } else { - return new Details<>( - modelObject, - mode == AjaxWizard.Mode.TEMPLATE, - true, - pageRef); + return Optional.of(new Details<>(modelObject, mode == AjaxWizard.Mode.TEMPLATE, true, pageRef)); } } - protected Set cleanEmptyPlainAttrs(final Set plainAttrs) { - Set newPlainAttrs = new HashSet<>(plainAttrs); - plainAttrs.clear(); - plainAttrs.removeIf(attr -> attr.getValues().isEmpty()); - return newPlainAttrs; + protected void fixPlainAndVirAttrs(final AnyTO updated, final AnyTO original) { + // re-add to the updated object any missing plain or virtual attribute (compared to original): this to cope with + // form layout, which might have not included some plain or virtual attributes + for (AttrTO plainAttrTO : original.getPlainAttrs()) { + if (!updated.getPlainAttr(plainAttrTO.getSchema()).isPresent()) { + updated.getPlainAttrs().add(plainAttrTO); + } + } + for (AttrTO virAttrTO : original.getVirAttrs()) { + if (!updated.getVirAttr(virAttrTO.getSchema()).isPresent()) { + updated.getVirAttrs().add(virAttrTO); + } + } + + if (updated instanceof GroupableRelatableTO && original instanceof GroupableRelatableTO) { + GroupableRelatableTO.class.cast(original).getMemberships().forEach(oMemb -> { + GroupableRelatableTO.class.cast(updated).getMembership(oMemb.getGroupKey()).ifPresent(uMemb -> { + oMemb.getPlainAttrs().stream(). + filter(attr -> !uMemb.getPlainAttr(attr.getSchema()).isPresent()). + forEach(attr -> uMemb.getPlainAttrs().add(attr)); + oMemb.getVirAttrs().stream(). + filter(attr -> !uMemb.getVirAttr(attr.getSchema()).isPresent()). + forEach(attr -> uMemb.getVirAttrs().add(attr)); + }); + }); + } + + // remove from the updated object any plain or virtual attribute without values, thus triggering for removal in + // the generated patch + updated.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty()); + updated.getVirAttrs().removeIf(attr -> attr.getValues().isEmpty()); + if (updated instanceof GroupableRelatableTO) { + GroupableRelatableTO.class.cast(updated).getMemberships().forEach(memb -> { + memb.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty()); + memb.getVirAttrs().removeIf(attr -> attr.getValues().isEmpty()); + }); + } } + } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWrapper.java index 85698ca674b..095cef52a15 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWrapper.java @@ -34,5 +34,4 @@ public AnyWrapper(final T anyTO) { public T getInnerObject() { return anyTO; } - } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java index d0f8ac95029..7729425e485 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java @@ -51,7 +51,7 @@ public AuxClasses(final AnyWrapper modelObject, final List< } Collections.sort(choices); add(new AjaxPalettePanel.Builder().setAllowOrder(true).build("auxClasses", - new PropertyModel>(modelObject.getInnerObject(), "auxClasses"), + new PropertyModel<>(modelObject.getInnerObject(), "auxClasses"), new ListModel<>(choices)).hideLabel().setOutputMarkupId(true)); // ------------------ diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java index f064402cca2..38ac6f0c444 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/DerAttrs.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -27,10 +28,12 @@ import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.DerSchemaTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; @@ -55,7 +58,7 @@ public DerAttrs( final List anyTypeClasses, final List whichDerAttrs) { - super(modelObject, anyTypeClasses, whichDerAttrs); + super(modelObject, AjaxWizard.Mode.CREATE, anyTypeClasses, whichDerAttrs); setTitleModel(new ResourceModel("attributes.derived")); add(new Accordion("derSchemas", Collections.singletonList(new AbstractTab( @@ -69,7 +72,7 @@ public WebMarkupContainer getPanel(final String panelId) { } }), Model.of(0)).setOutputMarkupId(true)); - add(new ListView("membershipsDerSchemas", membershipTOs) { + add(new ListView("membershipsDerSchemas", memberships) { private static final long serialVersionUID = 6741044372185745296L; @@ -135,7 +138,13 @@ protected void setAttrs() { protected void setAttrs(final MembershipTO membershipTO) { List attrs = new ArrayList<>(); - Map attrMap = EntityTOUtils.buildAttrMap(anyTO.getDerAttrs()); + final Map attrMap; + if (GroupableRelatableTO.class.cast(anyTO).getMembership(membershipTO.getGroupKey()).isPresent()) { + attrMap = EntityTOUtils.buildAttrMap(GroupableRelatableTO.class.cast(anyTO) + .getMembership(membershipTO.getGroupKey()).get().getDerAttrs()); + } else { + attrMap = new HashMap<>(); + } membershipSchemas.get(membershipTO.getGroupKey()).values().forEach(schema -> { AttrTO attrTO = new AttrTO(); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java index 5016e7dc89f..c7411ae647a 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java @@ -19,18 +19,25 @@ package org.apache.syncope.client.console.wizards.any; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.pages.Realms; import org.apache.syncope.client.console.rest.RealmRestClient; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.RealmTO; +import org.apache.wicket.Component; import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.PropertyModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +48,8 @@ public class Details extends WizardStep { protected static final Logger LOG = LoggerFactory.getLogger(Details.class); + private final RealmRestClient realmRestClient = new RealmRestClient(); + protected final PageReference pageRef; private final FieldPanel realm; @@ -52,27 +61,46 @@ public Details( final PageReference pageRef) { this.pageRef = pageRef; - + final List authRealms = SyncopeConsoleSession.get().getAuthRealms(); final T inner = wrapper.getInnerObject(); + final Fragment fragment; if (templateMode) { realm = new AjaxTextFieldPanel( "destinationRealm", "destinationRealm", new PropertyModel<>(inner, "realm"), false); AjaxTextFieldPanel.class.cast(realm).enableJexlHelp(); + fragment = new Fragment("realmsFragment", "realmsTemplateFragment", this); } else { - final List realms = pageRef.getPage() instanceof Realms - ? getRealmsFromLinks(Realms.class.cast(pageRef.getPage()).getRealmChoicePanel().getLinks()) - : new RealmRestClient().list(); - - realm = new AjaxDropDownChoicePanel<>( - "destinationRealm", "destinationRealm", new PropertyModel<>(inner, "realm"), false); - - ((AjaxDropDownChoicePanel) realm).setChoices( - realms.stream().map(RealmTO::getFullPath).collect(Collectors.toList())); + boolean isSearchEnabled = RealmsUtils.isSearchEnabled(); + final AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(!isSearchEnabled); + settings.setShowListOnEmptyInput(!isSearchEnabled); + + realm = new AjaxSearchFieldPanel("destinationRealm", "destinationRealm", + new PropertyModel<>(inner, "realm"), settings) { + + private static final long serialVersionUID = -6390474600233486704L; + + @Override + protected Iterator getChoices(final String input) { + return (isSearchEnabled + ? realmRestClient.search(RealmsUtils.buildQuery(input)).getResult() + : pageRef.getPage() instanceof Realms + ? getRealmsFromLinks(Realms.class.cast(pageRef.getPage()).getRealmChoicePanel().getLinks()) + : realmRestClient.list(SyncopeConstants.ROOT_REALM)). + stream().filter(realm -> authRealms.stream().anyMatch( + authRealm -> realm.getFullPath().startsWith(authRealm))). + map(item -> item.getFullPath()).collect(Collectors.toList()).iterator(); + } + }; + + fragment = new Fragment("realmsFragment", "realmsSearchFragment", this); } - add(realm); + fragment.addOrReplace(realm); + addOrReplace(fragment); add(getGeneralStatusInformation("generalStatusInformation", inner). setEnabled(includeStatusPanel).setVisible(includeStatusPanel).setRenderBodyOnly(true)); + } public Details disableRealmSpecification() { @@ -88,11 +116,9 @@ private List getRealmsFromLinks(final List realmLinks) { List realms = new ArrayList<>(); realmLinks.stream(). - map(link -> link.getDefaultModelObject()). + map(Component::getDefaultModelObject). filter(modelObject -> modelObject instanceof RealmTO). - forEachOrdered(modelObject -> { - realms.add((RealmTO) modelObject); - }); + forEachOrdered(modelObject -> realms.add((RealmTO) modelObject)); return realms; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/EntityWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/EntityWrapper.java new file mode 100644 index 00000000000..faeb8676681 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/EntityWrapper.java @@ -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. + */ +package org.apache.syncope.client.console.wizards.any; + +import org.apache.syncope.common.lib.to.EntityTO; + +public class EntityWrapper { + + protected final T entityTO; + + public EntityWrapper(final T entityTO) { + this.entityTO = entityTO; + } + + public T getInnerObject() { + return entityTO; + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java index 249f4cfe861..00502ca7dec 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java @@ -35,9 +35,8 @@ public GroupDetails( super(wrapper, templateMode, includeStatusPanel, pageRef); - GroupTO groupTO = GroupWrapper.class.cast(wrapper).getInnerObject(); - - AjaxTextFieldPanel name = new AjaxTextFieldPanel("name", "name", new PropertyModel<>(groupTO, "name"), false); + AjaxTextFieldPanel name = new AjaxTextFieldPanel( + "name", "name", new PropertyModel<>(wrapper.getInnerObject(), "name"), false); if (templateMode) { name.enableJexlHelp(); } else { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java index d9199fe72f0..b3511c1b531 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java @@ -19,6 +19,8 @@ package org.apache.syncope.client.console.wizards.any; import java.util.List; +import java.util.Optional; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.layout.GroupFormLayoutInfo; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.to.GroupTO; @@ -46,17 +48,18 @@ public GroupTemplateWizardBuilder( } else { GroupTO groupTO = new GroupTO(); if (templatable instanceof RealmTO) { - groupTO.setRealm(String.format("'%s'", RealmTO.class.cast(templatable).getFullPath())); + groupTO.setRealm( + String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath()))); } setItem(new GroupWrapper(groupTO)); } } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - final Details details = super.addOptionalDetailsPanel(modelObject); - if (templatable instanceof RealmTO) { - details.disableRealmSpecification(); + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + Optional> details = super.addOptionalDetailsPanel(modelObject); + if (templatable instanceof RealmTO && details.isPresent()) { + details.get().disableRealmSpecification(); } return details; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java index ea4c22ef66b..e68087540b9 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.common.util.CollectionUtils; import org.apache.syncope.client.console.layout.GroupForm; @@ -37,7 +38,7 @@ public class GroupWizardBuilder extends AnyWizardBuilder implements Gro private static final long serialVersionUID = 5945391813567245081L; - private final GroupRestClient groupRestClient = new GroupRestClient(); + protected final GroupRestClient groupRestClient = new GroupRestClient(); public GroupWizardBuilder( final GroupTO groupTO, @@ -83,36 +84,36 @@ public AjaxWizardBuilder> setItem(final AnyWrapper @Override protected Serializable onApplyInternal(final AnyWrapper modelObject) { - GroupTO inner = modelObject instanceof GroupWrapper + GroupTO updated = modelObject instanceof GroupWrapper ? GroupWrapper.class.cast(modelObject).fillDynamicConditions() : modelObject.getInnerObject(); ProvisioningResult result; - if (inner.getKey() == null) { - result = groupRestClient.create(inner); + if (updated.getKey() == null) { + result = groupRestClient.create(updated); } else { - inner.getPlainAttrs().clear(); - inner.getPlainAttrs().addAll(cleanEmptyPlainAttrs(inner.getPlainAttrs())); - GroupPatch patch = AnyOperations.diff(inner, getOriginalItem().getInnerObject(), false); - GroupTO originaObj = getOriginalItem().getInnerObject(); + GroupTO original = getOriginalItem().getInnerObject(); + fixPlainAndVirAttrs(updated, original); // SYNCOPE-1170 boolean othersNotEqualsOrBlanks = - !inner.getADynMembershipConds().equals(originaObj.getADynMembershipConds()) - || (StringUtils.isNotBlank(originaObj.getUDynMembershipCond()) && StringUtils.isBlank(inner. - getUDynMembershipCond())) - || (StringUtils.isBlank(originaObj.getUDynMembershipCond()) && StringUtils.isNotBlank(inner. - getUDynMembershipCond())) - || StringUtils.isAllBlank(originaObj.getUDynMembershipCond(), inner.getUDynMembershipCond()) - || !inner.getUDynMembershipCond().equals(originaObj.getUDynMembershipCond()) - || !CollectionUtils.diff(inner.getTypeExtensions(), originaObj.getTypeExtensions()).isEmpty(); + !updated.getADynMembershipConds().equals(original.getADynMembershipConds()) + || (StringUtils.isNotBlank(original.getUDynMembershipCond()) + && StringUtils.isBlank(updated.getUDynMembershipCond())) + || (StringUtils.isBlank(original.getUDynMembershipCond()) + && StringUtils.isNotBlank(updated.getUDynMembershipCond())) + || StringUtils.isAllBlank(original.getUDynMembershipCond(), updated.getUDynMembershipCond()) + || !updated.getUDynMembershipCond().equals(original.getUDynMembershipCond()) + || !CollectionUtils.diff(updated.getTypeExtensions(), original.getTypeExtensions()).isEmpty(); + + GroupPatch patch = AnyOperations.diff(updated, original, false); // update just if it is changed if (patch.isEmpty() && !othersNotEqualsOrBlanks) { result = new ProvisioningResult<>(); - result.setEntity(inner); + result.setEntity(updated); } else { - result = groupRestClient.update(getOriginalItem().getInnerObject().getETagValue(), patch); + result = groupRestClient.update(original.getETagValue(), patch); } } @@ -120,10 +121,10 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - return new GroupDetails( + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + return Optional.of(new GroupDetails( GroupWrapper.class.cast(modelObject), mode == AjaxWizard.Mode.TEMPLATE, - modelObject.getInnerObject().getKey() != null, pageRef); + modelObject.getInnerObject().getKey() != null, pageRef)); } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWrapper.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWrapper.java index 8e82041d7fe..b5e1b086860 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWrapper.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWrapper.java @@ -75,25 +75,23 @@ public void setADynClauses(final Map> aDynClauses) { } public String getUDynMembershipCond() { - if (CollectionUtils.isEmpty(this.uDynClauses)) { - return null; - } else { - return SearchUtils.buildFIQL(this.uDynClauses, SyncopeClient.getUserSearchConditionBuilder()); - } + return CollectionUtils.isEmpty(this.uDynClauses) + ? null + : SearchUtils.buildFIQL(this.uDynClauses, SyncopeClient.getUserSearchConditionBuilder()); } public Map getADynMembershipConds() { - final Map res = new HashMap<>(); + Map res = new HashMap<>(); if (this.aDynClauses != null && !this.aDynClauses.isEmpty()) { - for (Map.Entry> entry : this.aDynClauses.entrySet()) { - if (CollectionUtils.isNotEmpty(entry.getValue())) { - final String fiql = SearchUtils.buildFIQL(entry.getValue(), - SyncopeClient.getAnyObjectSearchConditionBuilder(entry.getKey())); - if (fiql != null) { - res.put(entry.getKey(), fiql); - } - } - } + this.aDynClauses.entrySet().stream(). + filter(entry -> CollectionUtils.isNotEmpty(entry.getValue())). + forEach(entry -> { + String fiql = SearchUtils.buildFIQL(entry.getValue(), + SyncopeClient.getAnyObjectSearchConditionBuilder(entry.getKey())); + if (fiql != null) { + res.put(entry.getKey(), fiql); + } + }); } return res; diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java index eb99715f0c5..8038828dfa7 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java @@ -19,19 +19,24 @@ package org.apache.syncope.client.console.wizards.any; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition; import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.rest.DynRealmRestClient; import org.apache.syncope.client.console.rest.GroupRestClient; -import org.apache.syncope.client.console.wicket.ajax.markup.html.LabelInfo; +import org.apache.syncope.client.console.rest.SyncopeRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.DynRealmTO; @@ -39,40 +44,34 @@ import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; -import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; import org.apache.wicket.model.util.ListModel; import org.apache.syncope.common.lib.to.GroupableRelatableTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.authroles.authorization.strategies.role.metadata.ActionPermissions; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; -import org.apache.wicket.extensions.wizard.WizardModel.ICondition; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.PropertyModel; -public class Groups extends WizardStep implements ICondition { +public class Groups extends AbstractGroups { private static final long serialVersionUID = 552437609667518888L; - private static final int MAX_GROUP_LIST_CARDINALITY = 30; + protected final boolean templateMode; - private final GroupRestClient groupRestClient = new GroupRestClient(); + protected final SyncopeRestClient syncopeRestClient = new SyncopeRestClient(); - private final List allDynRealms = new DynRealmRestClient().list(); + protected final GroupRestClient groupRestClient = new GroupRestClient(); - private GroupsModel groupsModel; + protected final List allDynRealms = new DynRealmRestClient().list(); - private final AnyTO anyTO; - - private boolean templateMode; + protected GroupsModel groupsModel; public Groups(final AnyWrapper modelObject, final boolean templateMode) { - super(); + super(modelObject); this.templateMode = templateMode; - - this.anyTO = modelObject.getInnerObject(); - groupsModel = new GroupsModel(); // ----------------------------------------------------------------- @@ -83,18 +82,17 @@ public Groups(final AnyWrapper modelObject, final boolean t permissions.authorizeAll(RENDER); // ----------------------------------------------------------------- - setOutputMarkupId(true); - - WebMarkupContainer groupsContainer = new WebMarkupContainer("groupsContainer"); - groupsContainer.setOutputMarkupId(true); - groupsContainer.setOutputMarkupPlaceholderTag(true); - add(groupsContainer); + addDynamicGroupsContainer(); + addGroupsPanel(); + addDynamicRealmsContainer(); + } - WebMarkupContainer dyngroupsContainer = new WebMarkupContainer("dyngroupsContainer"); - dyngroupsContainer.setOutputMarkupId(true); - dyngroupsContainer.setOutputMarkupPlaceholderTag(true); - add(dyngroupsContainer); + protected List searchAssignable(final String realm, final String term) { + return syncopeRestClient.searchAssignableGroups(realm, term, 1, Constants.MAX_GROUP_LIST_SIZE); + } + @Override + protected void addGroupsPanel() { if (anyTO instanceof GroupTO) { groupsContainer.add(new Label("groups").setVisible(false)); groupsContainer.setVisible(false); @@ -125,7 +123,7 @@ public MembershipTO getObject( } }); - groupsContainer.add(builder.setAllowOrder(true).withFilter().build("groups", + groupsContainer.add(builder.setAllowOrder(true).withFilter("*").build("groups", new ListModel() { private static final long serialVersionUID = -2583290457773357445L; @@ -134,27 +132,20 @@ public MembershipTO getObject( public List getObject() { return Groups.this.groupsModel.getMemberships(); } - }, new AjaxPalettePanel.Builder.Query() { private static final long serialVersionUID = -7223078772249308813L; @Override public List execute(final String filter) { - return (StringUtils.isEmpty(filter) || "*".equals(filter) - ? groupsModel.getObject() - : groupRestClient.search( - anyTO.getRealm(), - SyncopeClient.getGroupSearchConditionBuilder(). - isAssignable().and().is("name").equalTo(filter).query(), - 1, MAX_GROUP_LIST_CARDINALITY, - new SortParam<>("name", true), - null)).stream().map(input -> { - - return new MembershipTO.Builder(). - group(input.getKey(), input.getName()). - build(); - }).collect(Collectors.toList()); + return StringUtils.isEmpty(filter) + ? Collections.emptyList() + : ("*".equals(filter) + ? groupsModel.getObject() + : searchAssignable(anyTO.getRealm(), filter)).stream(). + map(group -> new MembershipTO.Builder(). + group(group.getKey(), group.getName()).build()). + collect(Collectors.toList()); } }).hideLabel().setOutputMarkupId(true)); @@ -167,31 +158,29 @@ public List execute(final String filter) { public List getObject() { return Groups.this.groupsModel.getDynMemberships(); } - }, new ListModel<>(groupsModel.getObject().stream().map(GroupTO::getName).collect(Collectors.toList()))). hideLabel().setEnabled(false).setOutputMarkupId(true)); - - // --------------------------------- } + } - add(new AjaxPalettePanel.Builder<>().build("dynrealms", + @Override + protected void addDynamicRealmsContainer() { + dynrealmsContainer = new WebMarkupContainer("dynrealmsContainer"); + dynrealmsContainer.setOutputMarkupId(true); + dynrealmsContainer.setOutputMarkupPlaceholderTag(true); + dynrealmsContainer.add(new AjaxPalettePanel.Builder<>().build("dynrealms", new PropertyModel<>(anyTO, "dynRealms"), new ListModel<>(allDynRealms.stream().map(EntityTO::getKey).collect(Collectors.toList()))). hideLabel().setEnabled(false).setOutputMarkupId(true)); + add(dynrealmsContainer); + } - // ------------------ - // insert changed label if needed - // ------------------ - if (modelObject instanceof UserWrapper - && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null - && !ListUtils.isEqualList( - UserWrapper.class.cast(modelObject).getInnerObject().getMemberships(), - UserWrapper.class.cast(modelObject).getPreviousUserTO().getMemberships())) { - groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY)); - } else { - groupsContainer.add(new Label("changed", StringUtils.EMPTY)); - } - // ------------------ + @Override + protected void addDynamicGroupsContainer() { + dyngroupsContainer = new WebMarkupContainer("dyngroupsContainer"); + dyngroupsContainer.setOutputMarkupId(true); + dyngroupsContainer.setOutputMarkupPlaceholderTag(true); + add(dyngroupsContainer); } @Override @@ -203,17 +192,17 @@ public boolean evaluate() { isActionAuthorized(this, RENDER); } - private class GroupsModel extends ListModel { + public class GroupsModel extends ListModel { private static final long serialVersionUID = -4541954630939063927L; - private List groups; + protected List groups; - private List memberships; + protected List memberships; - private List dynMemberships; + protected List dynMemberships; - private String realm; + protected String realm; @Override public List getObject() { @@ -222,16 +211,10 @@ public List getObject() { } /** - * Retrieve the first MAX_GROUP_LIST_CARDINALITY assignable. + * Retrieve the first MAX_GROUP_LIST_SIZE assignable. */ - private void reloadObject() { - groups = groupRestClient.search( - realm, - SyncopeClient.getGroupSearchConditionBuilder().isAssignable().query(), - 1, - MAX_GROUP_LIST_CARDINALITY, - new SortParam<>("name", true), - null); + protected void reloadObject() { + groups = searchAssignable(realm, null); } public List getMemberships() { @@ -242,41 +225,42 @@ public List getMemberships() { /** * Retrieve group memberships. */ - private void reloadMemberships() { + protected void reloadMemberships() { // this is to be sure to have group names (required to see membership details in approval page) - GroupFiqlSearchConditionBuilder searchConditionBuilder = SyncopeClient.getGroupSearchConditionBuilder(); - - List conditions = new ArrayList<>(); - GroupableRelatableTO.class.cast(anyTO).getMemberships().forEach(membershipTO -> { - conditions.add(searchConditionBuilder.is("key").equalTo(membershipTO.getGroupKey()).wrap()); - }); - - Map assignedGroups = new HashMap<>(); - if (!conditions.isEmpty()) { - groupRestClient.search( - realm, - searchConditionBuilder.isAssignable().and().or(conditions).query(), - -1, - -1, - new SortParam<>("name", true), - null). - forEach(group -> { - assignedGroups.put(group.getKey(), group); - }); + Map assignedGroups = new HashMap<>(); + + int total = GroupableRelatableTO.class.cast(anyTO).getMemberships().size(); + int pages = (total / Constants.MAX_GROUP_LIST_SIZE) + 1; + SortParam sort = new SortParam<>("name", true); + for (int page = 1; page <= pages; page++) { + GroupFiqlSearchConditionBuilder builder = SyncopeClient.getGroupSearchConditionBuilder(); + + List conditions = GroupableRelatableTO.class.cast(anyTO).getMemberships(). + stream(). + skip((page - 1) * Constants.MAX_GROUP_LIST_SIZE). + limit(Constants.MAX_GROUP_LIST_SIZE). + map(m -> builder.is(Constants.KEY_FIELD_NAME).equalTo(m.getGroupKey()).wrap()). + collect(Collectors.toList()); + + if (!conditions.isEmpty()) { + assignedGroups.putAll(groupRestClient.search( + realm, + builder.isAssignable().and().or(conditions).query(), + 1, + Constants.MAX_GROUP_LIST_SIZE, + sort, + null).stream().collect(Collectors.toMap(GroupTO::getKey, GroupTO::getName))); + } } // set group names in membership TOs and remove membership not assignable - List toBeRemoved = new ArrayList<>(); - GroupableRelatableTO.class.cast(anyTO).getMemberships().forEach(membership -> { - if (assignedGroups.containsKey(membership.getGroupKey())) { - membership.setGroupName(assignedGroups.get(membership.getGroupKey()).getName()); - } else { - toBeRemoved.add(membership); - } - }); - GroupableRelatableTO.class.cast(anyTO).getMemberships().removeAll(toBeRemoved); + GroupableRelatableTO.class.cast(anyTO).getMemberships().stream(). + filter(m -> m.getGroupName() == null && assignedGroups.containsKey(m.getGroupKey())). + forEach(m -> m.setGroupName(assignedGroups.get(m.getGroupKey()))); + GroupableRelatableTO.class.cast(anyTO).getMemberships().removeIf(m -> m.getGroupName() == null); memberships = GroupableRelatableTO.class.cast(anyTO).getMemberships(); + memberships.sort(Comparator.comparing(MembershipTO::getGroupName)); } public List getDynMemberships() { @@ -287,43 +271,37 @@ public List getDynMemberships() { /** * Retrieve dyn group memberships. */ - private void reloadDynMemberships() { - GroupFiqlSearchConditionBuilder searchConditionBuilder = SyncopeClient.getGroupSearchConditionBuilder(); - - ArrayList conditions = new ArrayList<>(); - GroupableRelatableTO.class.cast(anyTO).getDynMemberships().forEach(membership -> { - conditions.add(searchConditionBuilder.is("key").equalTo(membership.getGroupKey()).wrap()); - }); - - Map assignedGroups = new HashMap<>(); - if (!conditions.isEmpty()) { - groupRestClient.search( - "/", - searchConditionBuilder.or(conditions).query(), + protected void reloadDynMemberships() { + GroupFiqlSearchConditionBuilder builder = SyncopeClient.getGroupSearchConditionBuilder(); + + List conditions = GroupableRelatableTO.class.cast(anyTO).getDynMemberships(). + stream().map(membership -> builder.is(Constants.KEY_FIELD_NAME). + equalTo(membership.getGroupKey()).wrap()). + collect(Collectors.toList()); + + dynMemberships = new ArrayList<>(); + if (SyncopeConsoleSession.get().owns(StandardEntitlement.GROUP_SEARCH) && !conditions.isEmpty()) { + dynMemberships.addAll(groupRestClient.search( + SyncopeConstants.ROOT_REALM, + builder.or(conditions).query(), -1, -1, new SortParam<>("name", true), - null). - forEach(group -> { - assignedGroups.put(group.getKey(), group); - }); + null).stream().map(GroupTO::getName).collect(Collectors.toList())); } - - dynMemberships = assignedGroups.values().stream().map(GroupTO::getName).collect(Collectors.toList()); } /** * Reload data if the realm changes (see SYNCOPE-1135). */ - private void reload() { + protected void reload() { boolean reload; - if (Groups.this.templateMode) { reload = realm == null; - realm = "/"; + realm = SyncopeConstants.ROOT_REALM; } else { reload = !Groups.this.anyTO.getRealm().equalsIgnoreCase(realm); - realm = Groups.this.anyTO.getRealm(); + realm = RealmsUtils.getFullPath(Groups.this.anyTO.getRealm()); } if (reload) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountCredentialsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountCredentialsPanel.java new file mode 100644 index 00000000000..3dc71758be2 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountCredentialsPanel.java @@ -0,0 +1,155 @@ +/* + * 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.client.console.wizards.any; + +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPasswordFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.client.console.commons.LinkedAccountPlainAttrProperty; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.PasswordTextField; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; + +public class LinkedAccountCredentialsPanel extends WizardStep { + + private static final long serialVersionUID = 5116461957402341603L; + + private String usernameValue; + + private String passwordValue; + + private final LinkedAccountTO linkedAccountTO; + + public LinkedAccountCredentialsPanel( + final EntityWrapper modelObject, final List whichCredentials) { + super(); + setOutputMarkupId(true); + + linkedAccountTO = modelObject.getInnerObject(); + + boolean isUsernameManagementEnabled = whichCredentials.contains("username"); + AjaxTextFieldPanel usernameField = new AjaxTextFieldPanel( + "username", + "username", + new PropertyModel<>(linkedAccountTO, "username")); + FieldPanel.class.cast(usernameField).setReadOnly(StringUtils.isBlank(linkedAccountTO.getUsername())); + LinkedAccountPlainAttrProperty usernameProperty = new LinkedAccountPlainAttrProperty(); + usernameProperty.setOverridable(StringUtils.isNotBlank(linkedAccountTO.getUsername())); + usernameProperty.setSchema("username"); + usernameProperty.getValues().add(linkedAccountTO.getUsername()); + usernameField.showExternAction( + checkboxToggle(usernameProperty, usernameField).setEnabled(isUsernameManagementEnabled)); + add(usernameField.setOutputMarkupId(true)); + usernameField.setEnabled(isUsernameManagementEnabled); + + boolean isPasswordManagementEnabled = whichCredentials.contains("password"); + AjaxPasswordFieldPanel passwordField = new AjaxPasswordFieldPanel( + "password", + "password", + new PropertyModel<>(linkedAccountTO, "password"), + false); + passwordField.setMarkupId("password"); + passwordField.setRequired(true); + FieldPanel.class.cast(passwordField).setReadOnly(StringUtils.isBlank(linkedAccountTO.getPassword())); + LinkedAccountPlainAttrProperty passwordProperty = new LinkedAccountPlainAttrProperty(); + passwordProperty.setOverridable(StringUtils.isNotBlank(linkedAccountTO.getPassword())); + passwordProperty.setSchema("password"); + passwordProperty.getValues().add(linkedAccountTO.getPassword()); + passwordField.showExternAction( + checkboxToggle(passwordProperty, passwordField).setEnabled(isPasswordManagementEnabled)); + ((PasswordTextField) passwordField.getField()).setResetPassword(false); + add(passwordField.setOutputMarkupId(true)); + passwordField.setEnabled(isPasswordManagementEnabled); + } + + private FormComponent checkboxToggle( + final LinkedAccountPlainAttrProperty property, final FieldPanel panel) { + + final BootstrapToggleConfig config = new BootstrapToggleConfig(). + withOnStyle(BootstrapToggleConfig.Style.success). + withOffStyle(BootstrapToggleConfig.Style.danger). + withSize(BootstrapToggleConfig.Size.mini); + + return new BootstrapToggle("externalAction", new PropertyModel(property, "overridable"), config) { + + private static final long serialVersionUID = -875219845189261873L; + + @Override + protected CheckBox newCheckBox(final String id, final IModel model) { + final CheckBox checkBox = super.newCheckBox(id, model); + checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + FieldPanel.class.cast(panel).setReadOnly(!model.getObject()); + if (model.getObject()) { + if (property.getSchema().equals("password")) { + linkedAccountTO.setPassword(passwordValue); + } else if (property.getSchema().equals("username")) { + linkedAccountTO.setUsername(usernameValue); + } + } else { + if (property.getSchema().equals("password")) { + passwordValue = linkedAccountTO.getPassword(); + linkedAccountTO.setPassword(null); + } else if (property.getSchema().equals("username")) { + usernameValue = linkedAccountTO.getUsername(); + linkedAccountTO.setUsername(null); + } + } + target.add(panel); + } + }); + return checkBox; + } + + @Override + protected IModel getOnLabel() { + return Model.of("Override"); + } + + @Override + protected IModel getOffLabel() { + return Model.of("Override?"); + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + tag.append("class", "overridable", " "); + } + }; + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountDetailsPanel.java new file mode 100644 index 00000000000..6970c5e2cfb --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountDetailsPanel.java @@ -0,0 +1,181 @@ +/* + * 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.client.console.wizards.any; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.client.console.commons.ConnIdSpecialName; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxEventBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.attributes.AjaxCallListener; +import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; +import org.apache.wicket.ajax.attributes.ThrottlingSettings; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LinkedAccountDetailsPanel extends WizardStep { + + private static final long serialVersionUID = 1221037007528732347L; + + private static final Logger LOG = LoggerFactory.getLogger(LinkedAccountDetailsPanel.class); + + private static final int SEARCH_SIZE = 20; + + private final ResourceRestClient resourceRestClient = new ResourceRestClient(); + + private List connObjectKeyFieldValues; + + public LinkedAccountDetailsPanel(final LinkedAccountTO linkedAccountTO) { + super(); + setOutputMarkupId(true); + + AjaxDropDownChoicePanel dropdownResourceField = new AjaxDropDownChoicePanel<>( + "resource", + "resource", + new PropertyModel<>(linkedAccountTO, "resource"), + false); + dropdownResourceField.setChoices(resourceRestClient.list().stream(). + filter(resource -> resource.getProvision(AnyTypeKind.USER.name()).isPresent() + && resource.getProvision(AnyTypeKind.USER.name()).get().getMapping() != null + && !resource.getProvision(AnyTypeKind.USER.name()).get().getMapping().getItems().isEmpty()). + map(resource -> resource.getKey()). + collect(Collectors.toList())); + dropdownResourceField.setOutputMarkupId(true); + dropdownResourceField.addRequiredLabel(); + dropdownResourceField.setNullValid(false); + dropdownResourceField.setRequired(true); + add(dropdownResourceField); + + final String connObjectKeyFieldId = "connObjectKeyValue"; + AjaxTextFieldPanel connObjectKeyField = new AjaxTextFieldPanel( + "connObjectKeyValue", + "connObjectKeyValue", + new PropertyModel<>(linkedAccountTO, "connObjectKeyValue"), + false); + connObjectKeyField.setOutputMarkupId(true); + connObjectKeyField.addRequiredLabel(); + connObjectKeyField.setChoices(Collections.emptyList()); + connObjectKeyField.setEnabled(false); + add(connObjectKeyField); + + dropdownResourceField.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + boolean enabled = dropdownResourceField.getModelObject() != null + && !dropdownResourceField.getModelObject().isEmpty(); + connObjectKeyField.setEnabled(enabled); + if (enabled) { + setConnObjectFieldChoices(connObjectKeyField, dropdownResourceField.getModelObject(), null); + } + target.add(connObjectKeyField); + } + }); + + connObjectKeyField.getField().setMarkupId(connObjectKeyFieldId); + connObjectKeyField.getField().add(new AjaxEventBehavior(Constants.ON_KEYDOWN) { + + private static final long serialVersionUID = 3533589614190959822L; + + @Override + protected void onEvent(final AjaxRequestTarget target) { + String searchTerm = connObjectKeyField.getField().getInput(); + if (StringUtils.isNotBlank(searchTerm) && searchTerm.length() > 1) { + setConnObjectFieldChoices(connObjectKeyField, dropdownResourceField.getModelObject(), searchTerm); + + // If elements are found, send an "arrow down" key event to open input autocomplete dropdown + target.appendJavaScript(connObjectKeyFieldValues.isEmpty() + ? "$('#" + connObjectKeyFieldId + "-autocomplete-container').hide();" + : "var simulatedEvent = new KeyboardEvent('keydown', {keyCode: 40, which: 40}); " + + "document.getElementById('" + connObjectKeyFieldId + "').dispatchEvent(simulatedEvent);"); + } + } + + @Override + protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { + super.updateAjaxAttributes(attributes); + + AjaxCallListener listener = new AjaxCallListener() { + + private static final long serialVersionUID = 2208168001920794667L; + + @Override + public CharSequence getPrecondition(final Component component) { + // Eevaluates weather an ajax call will be executed or not. + // If the key code is "arrow down" or "arrow up" do NOT trigger the event callback + return "var keycode = Wicket.Event.keyCode(attrs.event); " + + "if ((keycode == 40) || (keycode == 38)) {return false;} return true;"; + } + }; + attributes.getAjaxCallListeners().add(listener); + attributes.setThrottlingSettings(new ThrottlingSettings("id", Duration.seconds(1.2), true)); + } + }); + } + + private void setConnObjectFieldChoices( + final AjaxTextFieldPanel ajaxTextFieldPanel, + final String resource, + final String searchTerm) { + + AtomicReference resourceRemoteKey = new AtomicReference<>(ConnIdSpecialName.NAME); + try { + resourceRemoteKey.set(resourceRestClient.read(resource).getProvision(AnyTypeKind.USER.name()).get(). + getMapping().getConnObjectKeyItem().getExtAttrName()); + } catch (Exception ex) { + LOG.error("While reading mapping for resource {}", resource, ex); + } + + ConnObjectTOQuery.Builder builder = new ConnObjectTOQuery.Builder().size(SEARCH_SIZE); + if (StringUtils.isNotBlank(searchTerm)) { + builder.fiql(SyncopeClient.getConnObjectTOFiqlSearchConditionBuilder(). + is(resourceRemoteKey.get()).equalTo(searchTerm + "*").query()).build(); + } + Pair> items = resourceRestClient.searchConnObjects(resource, + AnyTypeKind.USER.name(), + builder, + new SortParam<>(resourceRemoteKey.get(), true)); + + connObjectKeyFieldValues = items.getRight().stream(). + map(item -> item.getAttr(resourceRemoteKey.get()).get().getValues().get(0)). + collect(Collectors.toList()); + ajaxTextFieldPanel.setChoices(connObjectKeyFieldValues); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java new file mode 100644 index 00000000000..b5c55f2fa01 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java @@ -0,0 +1,265 @@ +/* + * 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.client.console.wizards.any; + +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; +import org.apache.syncope.client.console.wicket.markup.html.form.AbstractFieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.common.lib.EntityTOUtils; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.to.PlainSchemaTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.client.console.commons.LinkedAccountPlainAttrProperty; +import org.apache.syncope.common.lib.types.SchemaType; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; +import org.apache.wicket.extensions.markup.html.tabs.ITab; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.ResourceModel; + +public class LinkedAccountPlainAttrsPanel extends AbstractAttrsWizardStep { + + private static final long serialVersionUID = -6664931684253730934L; + + private final LinkedAccountTO linkedAccountTO; + + private final UserTO userTO; + + private final Set fixedAttrs = new HashSet<>(); + + private final List accountPlainAttrProperties = new ArrayList<>(); + + public LinkedAccountPlainAttrsPanel( + final EntityWrapper modelObject, + final UserTO userTO, + final List whichPlainAttrs) throws IllegalArgumentException { + + super(userTO, + AjaxWizard.Mode.EDIT, + new AnyTypeRestClient().read(userTO.getType()).getClasses(), + whichPlainAttrs, + modelObject); + + this.linkedAccountTO = modelObject.getInnerObject(); + this.fixedAttrs.addAll(this.linkedAccountTO.getPlainAttrs().stream(). + filter(attrTO -> checkIsReadonlyAttr(attrTO.getSchema())). + collect(Collectors.toList())); + this.userTO = userTO; + + add(new Accordion("plainSchemas", Collections.singletonList(new AbstractTab( + new ResourceModel("attributes.accordion", "Plain Attributes")) { + + private static final long serialVersionUID = -7078941093668723016L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new PlainSchemasOwn(panelId, schemas, attrTOs); + } + }), Model.of(0)).setOutputMarkupId(true)); + } + + @Override + protected FormComponent checkboxToggle( + final AttrTO attrTO, + final AbstractFieldPanel panel, + final boolean isMultivalue) { + + LinkedAccountPlainAttrProperty property = accountPlainAttrProperties.stream().filter( + existingProperty -> { + return existingProperty.getSchema().equals(attrTO.getSchema()); + }).findFirst().orElseGet(() -> { + LinkedAccountPlainAttrProperty newProperty = new LinkedAccountPlainAttrProperty(); + newProperty.setOverridable(linkedAccountTO.getPlainAttr(attrTO.getSchema()).isPresent()); + newProperty.setSchema(attrTO.getSchema()); + newProperty.getValues().addAll(attrTO.getValues()); + accountPlainAttrProperties.add(newProperty); + return newProperty; + }); + + final BootstrapToggleConfig config = new BootstrapToggleConfig(). + withOnStyle(BootstrapToggleConfig.Style.success). + withOffStyle(BootstrapToggleConfig.Style.danger). + withSize(BootstrapToggleConfig.Size.mini); + + return new BootstrapToggle("externalAction", new PropertyModel(property, "overridable"), config) { + + private static final long serialVersionUID = -875219845189261873L; + + @Override + protected CheckBox newCheckBox(final String id, final IModel model) { + final CheckBox checkBox = super.newCheckBox(id, model); + checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -1107858522700306810L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + if (isMultivalue) { + MultiFieldPanel.class.cast(panel).setFormReadOnly(!model.getObject()); + } else { + FieldPanel.class.cast(panel).setReadOnly(!model.getObject()); + } + + updateAccountPlainSchemas(property, model.getObject()); + target.add(panel); + } + }); + return checkBox; + } + + @Override + protected IModel getOnLabel() { + return Model.of("Override"); + } + + @Override + protected IModel getOffLabel() { + return Model.of("Override?"); + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + tag.append("class", "overridable", " "); + } + }; + } + + private void updateAccountPlainSchemas(final LinkedAccountPlainAttrProperty property, final Boolean modelObject) { + Set withoutCurrentSchema = new HashSet<>(linkedAccountTO.getPlainAttrs().stream(). + filter(attrTO -> !attrTO.getSchema().equals(property.getSchema()) + && checkIsReadonlyAttr(attrTO.getSchema())). + collect(Collectors.toSet())); + linkedAccountTO.getPlainAttrs().clear(); + linkedAccountTO.getPlainAttrs().addAll(withoutCurrentSchema); + if (modelObject) { + linkedAccountTO.getPlainAttrs().add( + fixedAttrs.stream().filter(attrTO -> attrTO.getSchema().equals(property.getSchema())).findFirst(). + orElseGet(() -> new AttrTO.Builder(). + schema(property.getSchema()).values(property.getValues()).build())); + } + } + + @Override + protected SchemaType getSchemaType() { + return SchemaType.PLAIN; + } + + @Override + protected void setAttrs() { + List attrs = new ArrayList<>(); + List notReadonlyValues = schemas.values().stream(). + filter(schema -> checkIsReadonlyAttr(schema.getKey())). + collect(Collectors.toList()); + setFixedAttr(notReadonlyValues); + Map attrMap = EntityTOUtils.buildAttrMap(fixedAttrs); + + attrs.addAll(notReadonlyValues.stream(). + map(schema -> { + AttrTO attrTO = new AttrTO(); + attrTO.setSchema(schema.getKey()); + if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { + attrTO.getValues().add(""); + } else { + attrTO = attrMap.get(schema.getKey()); + } + return attrTO; + }). + collect(Collectors.toList())); + + fixedAttrs.clear(); + fixedAttrs.addAll(attrs); + } + + @Override + protected List getAttrsFromTO() { + return fixedAttrs.stream().sorted(attrComparator).collect(Collectors.toList()); + } + + private void setFixedAttr(final Collection values) { + values.forEach(schema -> { + if (linkedAccountTO.getPlainAttr(schema.getKey()).isPresent()) { + fixedAttrs.add(linkedAccountTO.getPlainAttr(schema.getKey()).get()); + } else if (userTO.getPlainAttr(schema.getKey()).isPresent()) { + fixedAttrs.add(userTO.getPlainAttr(schema.getKey()).get()); + } + }); + } + + private boolean checkIsReadonlyAttr(final String schema) { + return schemas.isEmpty() ? true : !schemas.get(schema).isReadonly(); + } + + private class PlainSchemasOwn extends PlainSchemas> { + + private static final long serialVersionUID = -4730563859116024676L; + + PlainSchemasOwn( + final String id, + final Map schemas, + final IModel> attrTOs) { + + super(id, schemas, attrTOs); + + add(new ListView("schemas", attrTOs) { + + private static final long serialVersionUID = 9101744072914090143L; + + @Override + protected void populateItem(final ListItem item) { + AttrTO attrTO = item.getModelObject(); + final boolean isMultivalue = schemas.get(attrTO.getSchema()).isMultivalue(); + + AbstractFieldPanel panel = setPanel( + schemas, + item, + !linkedAccountTO.getPlainAttr(attrTO.getSchema()).isPresent()); + + panel.showExternAction(checkboxToggle(attrTO, panel, isMultivalue)); + } + }); + } + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPrivilegesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPrivilegesPanel.java new file mode 100644 index 00000000000..bc2864c59f9 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPrivilegesPanel.java @@ -0,0 +1,66 @@ +/* + * 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.client.console.wizards.any; + +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.rest.ApplicationRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.model.util.ListModel; + +public class LinkedAccountPrivilegesPanel extends WizardStep { + + private static final long serialVersionUID = 3388483585148725922L; + + private final ApplicationRestClient applicationRestClient = new ApplicationRestClient(); + + public LinkedAccountPrivilegesPanel(final LinkedAccountTO linkedAccountTO) { + super(); + setOutputMarkupId(true); + + final LoadableDetachableModel> availablePrivilges = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 5275935387613157437L; + + @Override + protected List load() { + return applicationRestClient.list().stream(). + flatMap(app -> app.getPrivileges().stream()). + map(privilege -> privilege.getKey()). + distinct(). + sorted(). + collect(Collectors.toList()); + } + }; + AjaxPalettePanel privilegesPanel = new AjaxPalettePanel.Builder(). + setAllowOrder(true). + setAllowMoveAll(true). + build("privileges", + new PropertyModel<>(linkedAccountTO, "privileges"), + new ListModel<>(availablePrivilges.getObject())); + privilegesPanel.hideLabel(); + privilegesPanel.setOutputMarkupId(true); + add(privilegesPanel); + } + +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java new file mode 100644 index 00000000000..2ae2f528a5a --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java @@ -0,0 +1,145 @@ +/* + * 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.client.console.wizards.any; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.layout.LinkedAccountForm; +import org.apache.syncope.client.console.layout.LinkedAccountFormLayoutInfo; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal.ModalEvent; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.AjaxWizardBuilder; +import org.apache.syncope.common.lib.patch.LinkedAccountPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.model.IModel; + +/** + * Accounts wizard builder. + */ +public class LinkedAccountWizardBuilder extends AjaxWizardBuilder implements LinkedAccountForm { + + private static final long serialVersionUID = -9142332740863374891L; + + private final UserRestClient userRestClient = new UserRestClient(); + + private final IModel model; + + protected LinkedAccountFormLayoutInfo formLayoutInfo; + + public LinkedAccountWizardBuilder( + final IModel model, + final LinkedAccountFormLayoutInfo formLayoutInfo, + final PageReference pageRef) { + super(new LinkedAccountTO(), pageRef); + this.model = model; + this.formLayoutInfo = formLayoutInfo; + } + + @Override + public AjaxWizard build(final String id, final AjaxWizard.Mode mode) { + return super.build(id, mode); + } + + @Override + protected WizardModel buildModelSteps(final LinkedAccountTO modelObject, final WizardModel wizardModel) { + wizardModel.add(new LinkedAccountDetailsPanel(modelObject)); + if (formLayoutInfo.isCredentials()) { + wizardModel.add(new LinkedAccountCredentialsPanel( + new EntityWrapper<>(modelObject), formLayoutInfo.getWhichCredentials())); + } + + if (formLayoutInfo.isPlainAttrs()) { + wizardModel.add(new LinkedAccountPlainAttrsPanel( + new EntityWrapper<>(modelObject), model.getObject(), formLayoutInfo.getWhichPlainAttrs())); + } + + if (formLayoutInfo.isPrivileges()) { + wizardModel.add(new LinkedAccountPrivilegesPanel(modelObject)); + } + + return wizardModel; + } + + @Override + protected Serializable onApplyInternal(final LinkedAccountTO modelObject) { + fixPlainAttrs(modelObject); + + LinkedAccountPatch linkedAccountPatch = new LinkedAccountPatch.Builder().linkedAccountTO(modelObject).build(); + linkedAccountPatch.setLinkedAccountTO(modelObject); + UserPatch userPatch = new UserPatch(); + userPatch.setKey(model.getObject().getKey()); + userPatch.getLinkedAccounts().add(linkedAccountPatch); + model.setObject(userRestClient.update(model.getObject().getETagValue(), userPatch).getEntity()); + + return modelObject; + } + + private void fixPlainAttrs(final LinkedAccountTO linkedAccountTO) { + Set validAttrs = new HashSet<>(linkedAccountTO.getPlainAttrs().stream(). + filter(attr -> !attr.getValues().isEmpty()). + collect(Collectors.toSet())); + linkedAccountTO.getPlainAttrs().clear(); + linkedAccountTO.getPlainAttrs().addAll(validAttrs); + } + + @Override + protected Serializable getCreateCustomPayloadEvent(final Serializable afterObject, final AjaxRequestTarget target) { + LinkedAccountTO linkedAccountTO = LinkedAccountTO.class.cast(afterObject); + return new CreateEvent( + linkedAccountTO.getConnObjectKeyValue(), + model.getObject(), + target); + } + + private static class CreateEvent extends ModalEvent { + + private static final long serialVersionUID = 6416834092156281986L; + + private final String key; + + private final UserTO userTO; + + CreateEvent( + final String key, + final UserTO userTO, + final AjaxRequestTarget target) { + + super(target); + this.key = key; + this.userTO = userTO; + } + + public String getKey() { + return key; + } + + public UserTO getUserTO() { + return userTO; + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java new file mode 100644 index 00000000000..d4e6e15815d --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java @@ -0,0 +1,183 @@ +/* + * 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.client.console.wizards.any; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.commons.SortableDataProviderComparator; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.DirectoryPanel; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.wizard.WizardModel.ICondition; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; + +public class MergeLinkedAccountsResourcesPanel extends WizardStep implements ICondition { + + private static final long serialVersionUID = 1221037007528732347L; + + private final MergeLinkedAccountsWizardModel wizardModel; + + public MergeLinkedAccountsResourcesPanel(final MergeLinkedAccountsWizardModel wizardModel, + final PageReference pageReference) { + super(); + setOutputMarkupId(true); + this.wizardModel = wizardModel; + add(new ResourceSelectionDirectoryPanel("resources", pageReference)); + } + + @Override + public boolean evaluate() { + return SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy(). + isActionAuthorized(this, RENDER); + } + + @Override + public String getTitle() { + setSummaryModel(new StringResourceModel("mergeLinkedAccounts.searchResource.summary", + Model.of(wizardModel.getMergingUser()))); + setTitleModel(new StringResourceModel("mergeLinkedAccounts.searchResource.title", + Model.of(wizardModel.getMergingUser()))); + return super.getTitle(); + } + + private class ResourceSelectionDirectoryPanel extends + DirectoryPanel { + + private static final long serialVersionUID = 6005711052393825472L; + + ResourceSelectionDirectoryPanel(final String id, final PageReference pageReference) { + super(id, pageReference, true); + + this.restClient = new ResourceRestClient(); + modal.size(Modal.Size.Large); + setOutputMarkupId(true); + disableCheckBoxes(); + initResultTable(); + } + + @Override + protected ResourcesDataProvider dataProvider() { + return new ResourcesDataProvider(this.rows); + } + + @Override + protected String paginatorRowsKey() { + return Constants.PREF_RESOURCES_PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + List> columns = new ArrayList<>(); + columns.add(new PropertyColumn<>(new ResourceModel("resource"), "key", "key")); + return columns; + } + + @Override + protected ActionsPanel getActions(final IModel model) { + final ActionsPanel panel = super.getActions(model); + panel.add(new ActionLink() { + + private static final long serialVersionUID = -7978723352517770644L; + + @Override + public void onClick(final AjaxRequestTarget target, final ResourceTO resource) { + MergeLinkedAccountsWizardModel model = MergeLinkedAccountsResourcesPanel.this.wizardModel; + String connObjectKeyValue = restClient.getConnObjectKeyValue( + resource.getKey(), + model.getMergingUser().getType(), + model.getMergingUser().getKey()); + if (connObjectKeyValue != null) { + model.setResource(resource); + String tableId = MergeLinkedAccountsResourcesPanel.this. + get("resources:container:content:searchContainer:resultTable" + + ":tablePanel:groupForm:checkgroup:dataTable"). + getMarkupId(); + String js = "$('#" + tableId + "').removeClass('active');"; + js += "$('#" + tableId + " tbody tr td div').filter(function() " + + "{return $(this).text() === \"" + resource.getKey() + "\";})" + + ".parent().parent().addClass('active');"; + target.prependJavaScript(js); + + } else { + error("Unable to determine connector object key"); + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + } + }, ActionLink.ActionType.SELECT, StandardEntitlement.RESOURCE_READ); + return panel; + } + + @Override + protected Collection getBatches() { + return Collections.emptyList(); + } + + protected final class ResourcesDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = -185944053385660794L; + + private final SortableDataProviderComparator comparator; + + private ResourcesDataProvider(final int paginatorRows) { + super(paginatorRows); + setSort("key", SortOrder.ASCENDING); + comparator = new SortableDataProviderComparator<>(this); + } + + @Override + public Iterator iterator(final long first, final long count) { + List list = restClient.list(); + Collections.sort(list, comparator); + return list.subList((int) first, (int) first + (int) count).iterator(); + } + + @Override + public long size() { + return restClient.list().size(); + } + + @Override + public IModel model(final ResourceTO object) { + return new CompoundPropertyModel<>(object); + } + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java new file mode 100644 index 00000000000..1c63126a12e --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java @@ -0,0 +1,177 @@ +/* + * 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.client.console.wizards.any; + +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.commons.SortableDataProviderComparator; +import org.apache.syncope.client.console.panels.DirectoryPanel; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn; +import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.ResourceModel; +import org.apache.wicket.model.StringResourceModel; + +public class MergeLinkedAccountsReviewPanel extends WizardStep { + + private static final long serialVersionUID = 1221037007528732347L; + + public MergeLinkedAccountsReviewPanel(final MergeLinkedAccountsWizardModel wizardModel, + final PageReference pageReference) { + super(); + setOutputMarkupId(true); + setTitleModel(new StringResourceModel("mergeLinkedAccounts.reviewAccounts.title")); + add(new LinkedAccountsReviewDirectoryPanel("linkedAccounts", pageReference, wizardModel)); + } + + private static class LinkedAccountsReviewDirectoryPanel extends + DirectoryPanel { + + private static final String PAGINATOR_ROWS = "linked.account.review.paginator.rows"; + + private static final long serialVersionUID = 6005711052393825472L; + + private final MergeLinkedAccountsWizardModel wizardModel; + + LinkedAccountsReviewDirectoryPanel(final String id, final PageReference pageReference, + final MergeLinkedAccountsWizardModel wizardModel) { + super(id, pageReference, true); + this.restClient = new ResourceRestClient(); + this.wizardModel = wizardModel; + modal.size(Modal.Size.Large); + setOutputMarkupId(true); + disableCheckBoxes(); + initResultTable(); + } + + @Override + protected LinkedAccountsDataProvider dataProvider() { + return new LinkedAccountsDataProvider(this.rows); + } + + @Override + protected String paginatorRowsKey() { + return PAGINATOR_ROWS; + } + + @Override + protected List> getColumns() { + List> columns = new ArrayList<>(); + columns.add(new PropertyColumn<>(new ResourceModel("resource"), "resource", "resource")); + columns.add(new PropertyColumn<>( + new ResourceModel("connObjectKeyValue"), "connObjectKeyValue", "connObjectKeyValue")); + columns.add(new PropertyColumn<>( + new ResourceModel("username"), "username", "username")); + columns.add(new BooleanPropertyColumn<>( + new ResourceModel("suspended"), "suspended", "suspended")); + return columns; + } + + @Override + protected Collection getBatches() { + return Collections.emptyList(); + } + + private List previewAccounts() { + UserTO mergingUser = wizardModel.getMergingUser(); + + // Move linked accounts into the target/base user as linked accounts + List accounts = mergingUser.getLinkedAccounts().stream().map(acct -> { + LinkedAccountTO linkedAccount = + new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue()) + .password(acct.getPassword()) + .suspended(acct.isSuspended()) + .username(acct.getUsername()) + .build(); + linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs()); + linkedAccount.getPrivileges().addAll(acct.getPrivileges()); + return linkedAccount; + }).collect(Collectors.toList()); + + // Move merging user's resources into the target/base user as a linked account + accounts.addAll(mergingUser.getResources().stream().map(resource -> { + String connObjectKeyValue = restClient.getConnObjectKeyValue(resource, + mergingUser.getType(), mergingUser.getKey()); + return new LinkedAccountTO.Builder(resource, connObjectKeyValue).build(); + }).collect(Collectors.toList())); + + // Move merging user into target/base user as a linked account + String connObjectKeyValue = restClient.getConnObjectKeyValue( + wizardModel.getResource().getKey(), + mergingUser.getType(), mergingUser.getKey()); + LinkedAccountTO linkedAccount = + new LinkedAccountTO.Builder(wizardModel.getResource().getKey(), connObjectKeyValue) + .password(mergingUser.getPassword()) + .suspended(mergingUser.isSuspended()) + .username(mergingUser.getUsername()) + .build(); + linkedAccount.getPlainAttrs().addAll(mergingUser.getPlainAttrs()); + linkedAccount.getPrivileges().addAll(mergingUser.getPrivileges()); + accounts.add(linkedAccount); + + return accounts; + } + + protected final class LinkedAccountsDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = -185944053385660794L; + + private final SortableDataProviderComparator comparator; + + private LinkedAccountsDataProvider(final int paginatorRows) { + super(paginatorRows); + setSort("resource", SortOrder.ASCENDING); + comparator = new SortableDataProviderComparator<>(this); + } + + @Override + public Iterator iterator(final long first, final long count) { + List list = previewAccounts(); + Collections.sort(list, comparator); + return list.subList((int) first, (int) first + (int) count).iterator(); + } + + @Override + public long size() { + return previewAccounts().size(); + } + + @Override + public IModel model(final LinkedAccountTO object) { + return new CompoundPropertyModel<>(object); + } + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java new file mode 100644 index 00000000000..64b9d4a66be --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java @@ -0,0 +1,112 @@ +/* + * 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.client.console.wizards.any; + +import java.util.ArrayList; +import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel; +import org.apache.syncope.client.console.panels.search.SearchClausePanel; +import org.apache.syncope.client.console.panels.search.SearchUtils; +import org.apache.syncope.client.console.panels.search.UserSearchPanel; +import org.apache.syncope.client.console.panels.search.UserSelectionDirectoryPanel; +import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; +import org.apache.syncope.client.console.rest.AnyTypeRestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.to.AnyTypeTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.wicket.Component; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.model.util.ListModel; + +public class MergeLinkedAccountsSearchPanel extends WizardStep { + + private static final long serialVersionUID = 1221037007528732347L; + + private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); + + private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + + private final WebMarkupContainer ownerContainer; + + private final UserSearchPanel userSearchPanel; + + private final UserSelectionDirectoryPanel userDirectoryPanel; + + private final Fragment userSearchFragment; + + private final MergeLinkedAccountsWizardModel wizardModel; + + public MergeLinkedAccountsSearchPanel(final MergeLinkedAccountsWizardModel model, final PageReference pageRef) { + super(); + setOutputMarkupId(true); + + this.wizardModel = model; + setTitleModel(new StringResourceModel("mergeLinkedAccounts.searchUser", Model.of(model.getBaseUser()))); + ownerContainer = new WebMarkupContainer("ownerContainer"); + ownerContainer.setOutputMarkupId(true); + add(ownerContainer); + + userSearchFragment = new Fragment("search", "userSearchFragment", this); + userSearchPanel = UserSearchPanel.class.cast(new UserSearchPanel.Builder( + new ListModel<>(new ArrayList<>())).required(false).enableSearch(MergeLinkedAccountsSearchPanel.this). + build("usersearch")); + userSearchFragment.add(userSearchPanel); + + AnyTypeTO anyTypeTO = anyTypeRestClient.read(AnyTypeKind.USER.name()); + userDirectoryPanel = UserSelectionDirectoryPanel.class.cast(new UserSelectionDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef). + build("searchResult")); + + userSearchFragment.add(userDirectoryPanel); + ownerContainer.add(userSearchFragment); + } + + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { + AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); + String fiql = "username!~" + this.wizardModel.getBaseUser().getUsername() + ';' + + SearchUtils.buildFIQL(userSearchPanel.getModel().getObject(), + SyncopeClient.getUserSearchConditionBuilder()); + userDirectoryPanel.search(fiql, target); + } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) { + @SuppressWarnings("unchecked") + AnySelectionDirectoryPanel.ItemSelection payload = + (AnySelectionDirectoryPanel.ItemSelection) event.getPayload(); + + UserTO selected = payload.getSelection(); + this.wizardModel.setMergingUser(new UserRestClient().read(selected.getKey())); + + String tableId = ((Component) event.getSource()). + get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable"). + getMarkupId(); + String js = "$('#" + tableId + " tr').removeClass('active');"; + js += "$('#" + tableId + " td[title=" + selected.getKey() + "]').parent().addClass('active');"; + payload.getTarget().prependJavaScript(js); + } + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java new file mode 100644 index 00000000000..841cd64b2ee --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java @@ -0,0 +1,193 @@ +/* + * 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.client.console.wizards.any; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.UserDirectoryPanel; +import org.apache.syncope.client.console.rest.ResourceRestClient; +import org.apache.syncope.client.console.rest.UserRestClient; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.apache.syncope.client.console.wizards.AjaxWizardBuilder; +import org.apache.syncope.client.lib.batch.BatchRequest; +import org.apache.syncope.common.lib.patch.LinkedAccountPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.LinkedAccountTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.common.rest.api.Preference; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.batch.BatchRequestItem; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.event.IEventSink; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.model.IModel; + +public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder implements IEventSink { + + private static final long serialVersionUID = -9142332740863374891L; + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final UserDirectoryPanel parentPanel; + + private final BaseModal modal; + + private MergeLinkedAccountsWizardModel model; + + public MergeLinkedAccountsWizardBuilder(final IModel model, final PageReference pageRef, + final UserDirectoryPanel parentPanel, final BaseModal modal) { + super(model.getObject(), pageRef); + this.parentPanel = parentPanel; + this.modal = modal; + } + + @Override + protected WizardModel buildModelSteps(final UserTO modelObject, final WizardModel wizardModel) { + model = new MergeLinkedAccountsWizardModel(modelObject); + wizardModel.add(new MergeLinkedAccountsSearchPanel(model, getPageReference())); + wizardModel.add(new MergeLinkedAccountsResourcesPanel(model, getPageReference())); + wizardModel.add(new MergeLinkedAccountsReviewPanel(model, getPageReference())); + return wizardModel; + } + + @Override + public void onEvent(final IEvent event) { + if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) { + ((AjaxWizard.NewItemCancelEvent) event.getPayload()).getTarget().ifPresent(modal::close); + } + if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { + Optional target = + ((AjaxWizard.NewItemFinishEvent) event.getPayload()).getTarget(); + try { + mergeAccounts(); + + parentPanel.info(parentPanel.getString(Constants.OPERATION_SUCCEEDED)); + target.ifPresent(t -> { + ((BasePage) parentPanel.getPage()).getNotificationPanel().refresh(t); + parentPanel.updateResultTable(t); + modal.close(t); + }); + } catch (Exception e) { + parentPanel.error(parentPanel.getString(Constants.ERROR) + ": " + e.getMessage()); + target.ifPresent(t -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(t)); + } + } + } + + private void mergeAccounts() throws Exception { + UserTO mergingUserTO = model.getMergingUser(); + ResourceRestClient resourceRestClient = new ResourceRestClient(); + + UserPatch userPatch = new UserPatch(); + userPatch.setKey(model.getBaseUser().getUsername()); + + // Move linked accounts into the target/base user as linked accounts + mergingUserTO.getLinkedAccounts().forEach(acct -> { + LinkedAccountTO linkedAccount = + new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue()). + password(acct.getPassword()). + suspended(acct.isSuspended()). + username(acct.getUsername()). + build(); + linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs()); + linkedAccount.getPrivileges().addAll(acct.getPrivileges()); + LinkedAccountPatch patch = new LinkedAccountPatch.Builder(). + linkedAccountTO(linkedAccount). + operation(PatchOperation.ADD_REPLACE). + build(); + userPatch.getLinkedAccounts().add(patch); + }); + + // Move merging user's resources into the target/base user as a linked account + mergingUserTO.getResources().forEach(resource -> { + String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(resource, + mergingUserTO.getType(), mergingUserTO.getKey()); + LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(resource, connObjectKeyValue).build(); + linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs()); + linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges()); + LinkedAccountPatch patch = new LinkedAccountPatch.Builder(). + linkedAccountTO(linkedAccount). + operation(PatchOperation.ADD_REPLACE). + build(); + userPatch.getLinkedAccounts().add(patch); + }); + + // Move merging user into target/base user as a linked account + String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue( + model.getResource().getKey(), + mergingUserTO.getType(), mergingUserTO.getKey()); + LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(model.getResource().getKey(), connObjectKeyValue). + password(mergingUserTO.getPassword()). + suspended(mergingUserTO.isSuspended()). + username(mergingUserTO.getUsername()). + build(); + linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs()); + linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges()); + LinkedAccountPatch patch = new LinkedAccountPatch.Builder().linkedAccountTO(linkedAccount). + operation(PatchOperation.ADD_REPLACE). + build(); + userPatch.getLinkedAccounts().add(patch); + + BatchRequest batchRequest = SyncopeConsoleSession.get().batch(); + + // Delete merging user + BatchRequestItem deleteRequest = new BatchRequestItem(); + deleteRequest.setMethod(HttpMethod.DELETE); + deleteRequest.setRequestURI("/users/" + mergingUserTO.getKey()); + deleteRequest.getHeaders().put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON)); + batchRequest.getItems().add(deleteRequest); + + // Update user with linked accounts + String updateUserPayload = MAPPER.writeValueAsString(userPatch); + BatchRequestItem updateUser = new BatchRequestItem(); + updateUser.setMethod(HttpMethod.PATCH); + updateUser.setRequestURI("/users/" + model.getBaseUser().getUsername()); + updateUser.setHeaders(new HashMap<>()); + updateUser.getHeaders().put(RESTHeaders.PREFER, + Collections.singletonList(Preference.RETURN_NO_CONTENT.toString())); + updateUser.getHeaders().put(HttpHeaders.ACCEPT, + Collections.singletonList(MediaType.APPLICATION_JSON)); + updateUser.getHeaders().put(HttpHeaders.CONTENT_TYPE, + Collections.singletonList(MediaType.APPLICATION_JSON)); + updateUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH, + Collections.singletonList(updateUserPayload.length())); + updateUser.setContent(updateUserPayload); + batchRequest.getItems().add(updateUser); + + Map batchResponse = new UserRestClient().batch(batchRequest); + batchResponse.forEach((key, value) -> { + if (!value.equalsIgnoreCase("success")) { + throw new IllegalArgumentException("Unable to report a success operation status for " + key); + } + }); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java new file mode 100644 index 00000000000..46c80ccb912 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java @@ -0,0 +1,58 @@ +/* + * 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.client.console.wizards.any; + +import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.to.UserTO; + +import java.io.Serializable; + +public class MergeLinkedAccountsWizardModel implements Serializable { + private static final long serialVersionUID = -2420343164344634869L; + + private final UserTO baseUser; + + private UserTO mergingUser; + + private ResourceTO resource; + + public MergeLinkedAccountsWizardModel(final UserTO baseUser) { + this.baseUser = baseUser; + } + + public ResourceTO getResource() { + return resource; + } + + public UserTO getBaseUser() { + return baseUser; + } + + public UserTO getMergingUser() { + return mergingUser; + } + + public void setMergingUser(final UserTO mergingUser) { + this.mergingUser = mergingUser; + } + + public void setResource(final ResourceTO resource) { + this.resource = resource; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java index 6e9a8924cb9..7ca98cd7afa 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java @@ -103,7 +103,7 @@ public Ownership(final GroupWrapper groupWrapper, final PageReference pageRef) { // ----------------------------------------------------------------- // Pre-Authorizations // ----------------------------------------------------------------- - final ActionPermissions permissions = new ActionPermissions(); + ActionPermissions permissions = new ActionPermissions(); setMetaData(MetaDataRoleAuthorizationStrategy.ACTION_PERMISSIONS, permissions); permissions.authorize(RENDER, new Roles(StandardEntitlement.USER_SEARCH)); // ----------------------------------------------------------------- @@ -145,7 +145,7 @@ protected CheckBox newCheckBox(final String id, final IModel model) { final CheckBox checkBox = super.newCheckBox(id, model); checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 18235445704320L; @Override protected void onUpdate(final AjaxRequestTarget target) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java index 3fcecf56f56..7fbc916b2bd 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java @@ -18,51 +18,34 @@ */ package org.apache.syncope.client.console.wizards.any; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.commons.SchemaUtils; -import org.apache.syncope.client.console.wicket.ajax.markup.html.LabelInfo; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; -import org.apache.syncope.client.console.wicket.markup.html.form.AbstractFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSpinnerFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.BinaryFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDateTimeFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.EncryptedFieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; -import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.EntityTOUtils; -import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.AttributableTO; import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.types.AttrSchemaType; import org.apache.syncope.common.lib.types.SchemaType; -import org.apache.wicket.PageReference; import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; -import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.model.util.ListModel; @@ -71,12 +54,6 @@ public class PlainAttrs extends AbstractAttrs { private static final long serialVersionUID = 552437609667518888L; - protected final AjaxWizard.Mode mode; - - protected final AnyTO previousObject; - - protected String fileKey = ""; - public PlainAttrs( final AnyWrapper modelObject, final Form form, @@ -84,8 +61,7 @@ public PlainAttrs( final List anyTypeClasses, final List whichPlainAttrs) throws IllegalArgumentException { - super(modelObject, anyTypeClasses, whichPlainAttrs); - this.mode = mode; + super(modelObject, mode, anyTypeClasses, whichPlainAttrs); if (modelObject.getInnerObject() instanceof UserTO) { fileKey = UserTO.class.cast(modelObject.getInnerObject()).getUsername(); @@ -110,13 +86,13 @@ public PlainAttrs( @Override public WebMarkupContainer getPanel(final String panelId) { - return new PlainSchemas(panelId, schemas, attrTOs); + return new PlainSchemasOwn(panelId, schemas, attrTOs); } }), Model.of(0)).setOutputMarkupId(true)); - add(new ListView("membershipsPlainSchemas", membershipTOs) { + add(new ListView("membershipsPlainSchemas", memberships) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 6741044372185745296L; @Override protected void populateItem(final ListItem item) { @@ -131,10 +107,19 @@ protected void populateItem(final ListItem item) { @Override public WebMarkupContainer getPanel(final String panelId) { - return new PlainSchemas( + return new PlainSchemasMemberships( panelId, membershipSchemas.get(membershipTO.getGroupKey()), - new ListModel<>(getAttrsFromTO(membershipTO))); + new LoadableDetachableModel() { // SYNCOPE-1439 + + private static final long serialVersionUID = 526768546610546553L; + + @Override + protected AttributableTO load() { + return membershipTO; + } + + }); } }), Model.of(-1)).setOutputMarkupId(true)); } @@ -163,23 +148,18 @@ protected List getAttrsFromTO(final MembershipTO membershipTO) { @Override protected void setAttrs() { - List attrs = new ArrayList<>(); - Map attrMap = EntityTOUtils.buildAttrMap(anyTO.getPlainAttrs()); - attrs.addAll(schemas.values().stream().map(schema -> { + List attrs = schemas.values().stream().map(schema -> { AttrTO attrTO = new AttrTO(); attrTO.setSchema(schema.getKey()); if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { attrTO.getValues().add(""); - - // is important to set the schema info only after values setting - attrTO.setSchemaInfo(schema); } else { attrTO = attrMap.get(schema.getKey()); } return attrTO; - }).collect(Collectors.toList())); + }).collect(Collectors.toList()); anyTO.getPlainAttrs().clear(); anyTO.getPlainAttrs().addAll(attrs); @@ -187,239 +167,71 @@ protected void setAttrs() { @Override protected void setAttrs(final MembershipTO membershipTO) { - List attrs = new ArrayList<>(); - - Map attrMap = EntityTOUtils.buildAttrMap(membershipTO.getPlainAttrs()); + Map attrMap = GroupableRelatableTO.class.cast(anyTO).getMembership(membershipTO.getGroupKey()). + isPresent() + ? EntityTOUtils.buildAttrMap(GroupableRelatableTO.class.cast(anyTO). + getMembership(membershipTO.getGroupKey()).get().getPlainAttrs()) + : new HashMap<>(); - attrs.addAll(membershipSchemas.get(membershipTO.getGroupKey()).values().stream(). - map(schema -> { - AttrTO attrTO = new AttrTO(); - attrTO.setSchema(schema.getKey()); - if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { - attrTO.getValues().add(""); - - // is important to set the schema info only after values setting - attrTO.setSchemaInfo(schema); - } else { - attrTO.getValues().addAll(attrMap.get(schema.getKey()).getValues()); - } - return attrTO; - }).collect(Collectors.toList())); + List attrs = membershipSchemas.get(membershipTO.getGroupKey()).values().stream().map(schema -> { + AttrTO attrTO = new AttrTO(); + attrTO.setSchema(schema.getKey()); + if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) { + attrTO.getValues().add(StringUtils.EMPTY); + } else { + attrTO.getValues().addAll(attrMap.get(schema.getKey()).getValues()); + } + return attrTO; + }).collect(Collectors.toList()); membershipTO.getPlainAttrs().clear(); membershipTO.getPlainAttrs().addAll(attrs); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected FieldPanel getFieldPanel(final PlainSchemaTO schemaTO) { - final boolean required; - final boolean readOnly; - final AttrSchemaType type; - final boolean jexlHelp; - - if (mode == AjaxWizard.Mode.TEMPLATE) { - required = false; - readOnly = false; - type = AttrSchemaType.String; - jexlHelp = true; - } else { - required = schemaTO.getMandatoryCondition().equalsIgnoreCase("true"); - readOnly = schemaTO.isReadonly(); - type = schemaTO.getType(); - jexlHelp = false; - - } - - FieldPanel panel; - switch (type) { - case Boolean: - panel = new AjaxCheckBoxPanel( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - new Model<>(), - true); - panel.setRequired(required); - break; - - case Date: - String datePattern = schemaTO.getConversionPattern() == null - ? SyncopeConstants.DEFAULT_DATE_PATTERN - : schemaTO.getConversionPattern(); - - if (datePattern.contains("H")) { - panel = new AjaxDateTimeFieldPanel( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - new Model<>(), - datePattern); - } else { - panel = new AjaxDateFieldPanel( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - new Model<>(), - datePattern); - } - - if (required) { - panel.addRequiredLabel(); - } - - break; - - case Enum: - panel = new AjaxDropDownChoicePanel<>("panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); - ((AjaxDropDownChoicePanel) panel).setChoices(SchemaUtils.getEnumeratedValues(schemaTO)); + protected class PlainSchemasOwn extends PlainSchemas> { - if (StringUtils.isNotBlank(schemaTO.getEnumerationKeys())) { - ((AjaxDropDownChoicePanel) panel).setChoiceRenderer(new IChoiceRenderer() { - - private static final long serialVersionUID = -3724971416312135885L; - - private final Map valueMap = SchemaUtils.getEnumeratedKeyValues(schemaTO); - - @Override - public String getDisplayValue(final String value) { - return valueMap.get(value) == null ? value : valueMap.get(value); - } - - @Override - public String getIdValue(final String value, final int i) { - return value; - } - - @Override - public String getObject( - final String id, final IModel> choices) { - return id; - } - }); - } - - if (required) { - panel.addRequiredLabel(); - } - break; - - case Long: - panel = new AjaxSpinnerFieldPanel.Builder().enableOnChange().build( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - Long.class, - new Model<>()); - - if (required) { - panel.addRequiredLabel(); - } - break; - - case Double: - panel = new AjaxSpinnerFieldPanel.Builder().enableOnChange().step(0.1).build( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - Double.class, - new Model<>()); - - if (required) { - panel.addRequiredLabel(); - } - break; - - case Binary: - final PageReference pageRef = getPageReference(); - panel = new BinaryFieldPanel( - "panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), - new Model<>(), - schemaTO.getMimeType(), - fileKey) { - - private static final long serialVersionUID = -3268213909514986831L; - - @Override - protected PageReference getPageReference() { - return pageRef; - } + private static final long serialVersionUID = -4730563859116024676L; - }; - if (required) { - panel.addRequiredLabel(); - } - break; + public PlainSchemasOwn( + final String id, + final Map schemas, + final IModel> attrTOs) { - case Encrypted: - panel = new EncryptedFieldPanel("panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); + super(id, schemas, attrTOs); - if (required) { - panel.addRequiredLabel(); - } - break; - - default: - panel = new AjaxTextFieldPanel("panel", - schemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), true); + add(new ListView("schemas", attrTOs) { - if (jexlHelp) { - AjaxTextFieldPanel.class.cast(panel).enableJexlHelp(); - } + private static final long serialVersionUID = 9101744072914090143L; - if (required) { - panel.addRequiredLabel(); + @Override + protected void populateItem(final ListItem item) { + PlainSchemaTO schema = schemas.get(item.getModelObject().getSchema()); + setPanel(schemas, item, schema == null ? false : schema.isReadonly()); } + }); } - - panel.setReadOnly(readOnly); - - return panel; } - public class PlainSchemas extends Schemas { + protected class PlainSchemasMemberships extends PlainSchemas { - private static final long serialVersionUID = -4730563859116024676L; + private static final long serialVersionUID = 456754923340249215L; - public PlainSchemas( + public PlainSchemasMemberships( final String id, final Map schemas, - final IModel> attrTOs) { - super(id); + final IModel attributableTO) { - add(new ListView("schemas", attrTOs) { + super(id, schemas, attributableTO); - private static final long serialVersionUID = 9101744072914090143L; + add(new ListView("schemas", new ListModel(attributableTO.getObject(). + getPlainAttrs().stream().sorted(attrComparator).collect(Collectors.toList()))) { + + private static final long serialVersionUID = 5306618783986001008L; @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) protected void populateItem(final ListItem item) { - AttrTO attrTO = item.getModelObject(); - - AbstractFieldPanel panel = getFieldPanel(schemas.get(attrTO.getSchema())); - if (mode == AjaxWizard.Mode.TEMPLATE - || !schemas.get(attrTO.getSchema()).isMultivalue()) { - FieldPanel.class.cast(panel).setNewModel(attrTO.getValues()); - } else { - panel = new MultiFieldPanel.Builder<>( - new PropertyModel<>(attrTO, "values")).build( - "panel", - attrTO.getSchema(), - FieldPanel.class.cast(panel)); - // SYNCOPE-1215 the entire multifield panel must be readonly, not only its field - ((MultiFieldPanel) panel).setReadOnly(schemas.get(attrTO.getSchema()).isReadonly()); - } - item.add(panel); - - if (previousObject != null - && (previousObject.getPlainAttr(attrTO.getSchema()) == null - || !ListUtils.isEqualList( - ListUtils.select(previousObject.getPlainAttr(attrTO.getSchema()).get().getValues(), - object -> StringUtils.isNotEmpty(object)), - ListUtils.select(attrTO.getValues(), object -> StringUtils.isNotEmpty(object))))) { - - List oldValues = previousObject.getPlainAttr(attrTO.getSchema()) == null - ? Collections.emptyList() - : previousObject.getPlainAttr(attrTO.getSchema()).get().getValues(); - panel.showExternAction(new LabelInfo("externalAction", oldValues)); - } + PlainSchemaTO schema = schemas.get(item.getModelObject().getSchema()); + setPanel(schemas, item, schema == null ? false : schema.isReadonly()); } }); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java new file mode 100644 index 00000000000..c0cc57ff11b --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java @@ -0,0 +1,99 @@ +/* + * 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.client.console.wizards.any; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.panels.ListViewPanel; +import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion; +import org.apache.syncope.common.lib.to.ProvisioningReport; +import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; + +public class ProvisioningReportsPanel extends Panel { + + private static final long serialVersionUID = -1450755344104125918L; + + public ProvisioningReportsPanel( + final String id, final List results, final PageReference pageRef) { + super(id); + + List success = results.stream(). + filter(result -> result.getStatus() == ProvisioningReport.Status.SUCCESS). + collect(Collectors.toList()); + add(new Accordion("success", Collections.singletonList(new AbstractTab( + new Model<>(MessageFormat.format(getString("success"), success.size()))) { + + private static final long serialVersionUID = 1037272333056449378L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef). + setItems(success). + withChecks(ListViewPanel.CheckAvailability.NONE). + setCaptionVisible(false). + includes("name", "message"). + build(panelId); + } + }), Model.of(-1)).setOutputMarkupId(true)); + + List failure = results.stream(). + filter(result -> result.getStatus() == ProvisioningReport.Status.FAILURE). + collect(Collectors.toList()); + add(new Accordion("failure", Collections.singletonList(new AbstractTab( + new Model<>(MessageFormat.format(getString("failure"), failure.size()))) { + + private static final long serialVersionUID = 1037272333056449378L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef). + setItems(failure). + withChecks(ListViewPanel.CheckAvailability.NONE). + setCaptionVisible(false). + includes("name", "message"). + build(panelId); + } + }), Model.of(-1)).setOutputMarkupId(true)); + + List ignore = results.stream(). + filter(result -> result.getStatus() == ProvisioningReport.Status.IGNORE). + collect(Collectors.toList()); + add(new Accordion("ignore", Collections.singletonList(new AbstractTab( + new Model<>(MessageFormat.format(getString("ignore"), ignore.size()))) { + + private static final long serialVersionUID = 1037272333056449378L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef). + setItems(ignore). + withChecks(ListViewPanel.CheckAvailability.NONE). + setCaptionVisible(false). + includes("name", "message"). + build(panelId); + } + }), Model.of(-1)).setOutputMarkupId(true)); + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java index 06e126b06af..46e5b3652cb 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java @@ -81,15 +81,17 @@ public class Relationships extends WizardStep implements ICondition { private static final long serialVersionUID = 855618618337931784L; - private final PageReference pageRef; + protected final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); - private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient(); + protected final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); - private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient(); + protected final RelationshipTypeRestClient relationshipTypeRestClient = new RelationshipTypeRestClient(); - private final AnyTO anyTO; + protected final AnyTO anyTO; - private final RelationshipTypeRestClient relationshipTypeRestClient = new RelationshipTypeRestClient(); + protected final Specification specification; + + protected final PageReference pageRef; public Relationships(final AnyWrapper modelObject, final PageReference pageRef) { super(); @@ -106,6 +108,7 @@ public Relationships(final AnyWrapper modelObject, final PageReference pageRe } this.anyTO = modelObject.getInnerObject(); + this.specification = new Specification(); this.pageRef = pageRef; // ------------------------ @@ -120,11 +123,11 @@ public Component getHeader(final String id, final Component parent, final IWizar return super.getHeader(id, parent, wizard).setVisible(false); } - private Fragment getViewFragment() { - final Map> relationships = new HashMap<>(); + protected Fragment getViewFragment() { + Map> relationships = new HashMap<>(); addRelationship(relationships, getCurrentRelationships().toArray(new RelationshipTO[] {})); - final Fragment viewFragment = new Fragment("relationships", "viewFragment", this); + Fragment viewFragment = new Fragment("relationships", "viewFragment", this); viewFragment.setOutputMarkupId(true); viewFragment.add(new Accordion("relationships", relationships.keySet().stream().map(relationship -> { @@ -136,14 +139,13 @@ private Fragment getViewFragment() { public Panel getPanel(final String panelId) { return new ListViewPanel.Builder<>(RelationshipTO.class, pageRef). setItems(relationships.get(relationship)). - includes("otherEndType", "otherEndKey"). + includes("otherEndType", "otherEndKey", "otherEndName"). addAction(new ActionLink() { private static final long serialVersionUID = -6847033126124401556L; @Override - public void onClick( - final AjaxRequestTarget target, final RelationshipTO modelObject) { + public void onClick(final AjaxRequestTarget target, final RelationshipTO modelObject) { removeRelationships(relationships, modelObject); send(Relationships.this, Broadcast.DEPTH, new ListViewReload<>(target)); } @@ -165,7 +167,7 @@ public void renderHead(final IHeaderResponse response) { } }); - final ActionsPanel panel = new ActionsPanel<>("actions", null); + ActionsPanel panel = new ActionsPanel<>("actions", null); viewFragment.add(panel); panel.add(new ActionLink() { @@ -176,7 +178,7 @@ public void renderHead(final IHeaderResponse response) { public void onClick(final AjaxRequestTarget target, final RelationshipTO ignore) { Fragment addFragment = new Fragment("relationships", "addFragment", Relationships.this); addOrReplace(addFragment); - addFragment.add(new Specification().setRenderBodyOnly(true)); + addFragment.add(specification.setRenderBodyOnly(true)); target.add(Relationships.this); } }, ActionType.CREATE, AnyEntitlement.UPDATE.getFor(anyTO.getType())).hideLabel(); @@ -184,16 +186,18 @@ public void onClick(final AjaxRequestTarget target, final RelationshipTO ignore) return viewFragment; } - private List getCurrentRelationships() { + protected List getCurrentRelationships() { return anyTO instanceof GroupableRelatableTO ? GroupableRelatableTO.class.cast(anyTO).getRelationships() : Collections.emptyList(); } - private void addRelationship(final Map> relationships, final RelationshipTO... rels) { + protected void addRelationship( + final Map> relationships, + final RelationshipTO... rels) { for (RelationshipTO relationship : rels) { - final List listrels; + List listrels; if (relationships.containsKey(relationship.getType())) { listrels = relationships.get(relationship.getType()); } else { @@ -204,17 +208,18 @@ private void addRelationship(final Map> relationshi } } - private void addNewRelationships(final RelationshipTO... rels) { + protected void addNewRelationships(final RelationshipTO... rels) { getCurrentRelationships().addAll(Arrays.asList(rels)); } - private void removeRelationships( + protected void removeRelationships( final Map> relationships, final RelationshipTO... rels) { - final List currentRels = getCurrentRelationships(); + + List currentRels = getCurrentRelationships(); for (RelationshipTO relationship : rels) { currentRels.remove(relationship); if (relationships.containsKey(relationship.getType())) { - final List rellist = relationships.get(relationship.getType()); + List rellist = relationships.get(relationship.getType()); rellist.remove(relationship); if (rellist.isEmpty()) { relationships.remove(relationship.getType()); @@ -233,29 +238,38 @@ public class Specification extends Panel { private static final long serialVersionUID = 6199050589175839467L; - private final RelationshipTO rel; + protected final RelationshipTO rel; + + protected final AjaxDropDownChoicePanel type; + + protected final AjaxDropDownChoicePanel otherType; + + protected final WebMarkupContainer container; + + protected final Fragment emptyFragment; - private AnyObjectSearchPanel anyObjectSearchPanel; + protected final Fragment fragment; - private WizardMgtPanel> anyObjectDirectoryPanel; + protected AnyObjectSearchPanel anyObjectSearchPanel; + + protected WizardMgtPanel> anyObjectDirectoryPanel; public Specification() { super("specification"); rel = new RelationshipTO(); - final List availableRels = relationshipTypeRestClient.list().stream(). + List availableRels = relationshipTypeRestClient.list().stream(). map(EntityTO::getKey).collect(Collectors.toList()); - final AjaxDropDownChoicePanel type = new AjaxDropDownChoicePanel<>( - "type", "type", new PropertyModel<>(rel, "type")); + type = new AjaxDropDownChoicePanel<>("type", "type", new PropertyModel<>(rel, "type")); type.setChoices(availableRels); - add(type.setRenderBodyOnly(true)); + add(type.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).setRenderBodyOnly(true)); - final List availableTypes = anyTypeRestClient.listAnyTypes().stream(). + List availableTypes = anyTypeRestClient.listAnyTypes().stream(). filter(anyType -> anyType.getKind() != AnyTypeKind.GROUP && anyType.getKind() != AnyTypeKind.USER).collect(Collectors.toList()); - final AjaxDropDownChoicePanel otherType = new AjaxDropDownChoicePanel<>( + otherType = new AjaxDropDownChoicePanel<>( "otherType", "otherType", new PropertyModel(rel, "otherType") { private static final long serialVersionUID = -5861057041758169508L; @@ -298,22 +312,22 @@ public AnyTypeTO getObject(final String id, final IModel(new ArrayList<>())). - enableSearch(Specification.this). - build("searchPanel"); - fragment.add(anyObjectSearchPanel.setRenderBodyOnly(true)); - - anyObjectDirectoryPanel = new AnyObjectSelectionDirectoryPanel.Builder( - anyTypeClassRestClient.list(anyType.getClasses()), - anyType.getKey(), - pageRef). - setFiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()). - is("key").notNullValue().query()). - setWizardInModal(true).build("searchResultPanel"); - fragment.add(anyObjectDirectoryPanel.setRenderBodyOnly(true)); } target.add(container); } }); } + protected void setupFragment(final AnyTypeTO anyType) { + anyObjectSearchPanel = new AnyObjectSearchPanel.Builder( + anyType.getKey(), + new ListModel<>(new ArrayList<>())). + enableSearch(Specification.this). + build("searchPanel"); + fragment.addOrReplace(anyObjectSearchPanel.setRenderBodyOnly(true)); + + anyObjectDirectoryPanel = new AnyObjectSelectionDirectoryPanel.Builder( + anyTypeClassRestClient.list(anyType.getClasses()), + anyType.getKey(), + pageRef). + setFiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()). + is(Constants.KEY_FIELD_NAME).notNullValue().query()). + setWizardInModal(true).build("searchResultPanel"); + fragment.addOrReplace(anyObjectDirectoryPanel.setRenderBodyOnly(true)); + } + @Override public void onEvent(final IEvent event) { if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { - final AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()). - getTarget(); - final String fiql = SearchUtils.buildFIQL(anyObjectSearchPanel.getModel().getObject(), + AjaxRequestTarget target = + SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget(); + String fiql = SearchUtils.buildFIQL(anyObjectSearchPanel.getModel().getObject(), SyncopeClient.getAnyObjectSearchConditionBuilder(anyObjectSearchPanel.getBackObjectType())); AnyDirectoryPanel.class.cast(Specification.this.anyObjectDirectoryPanel).search(fiql, target); } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) { - final AjaxRequestTarget target = AnySelectionDirectoryPanel.ItemSelection.class.cast(event. - getPayload()).getTarget(); + AjaxRequestTarget target = + AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getTarget(); AnyTO right = AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getSelection(); rel.setOtherEndKey(right.getKey()); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ResultPage.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ResultPage.java index a195b8a2d10..1c96eb144f1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ResultPage.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/ResultPage.java @@ -20,6 +20,7 @@ import java.io.Serializable; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -43,9 +44,7 @@ public ResultPage(final T item, final Serializable result) { add(customResultBody("customResultBody", item, result)); - final ActionsPanel panel = new ActionsPanel<>("action", null); - add(panel); - + ActionsPanel panel = new ActionsPanel<>(Constants.ACTION, null); panel.add(new ActionLink() { private static final long serialVersionUID = 3257738274365467945L; @@ -55,13 +54,12 @@ public void onClick(final AjaxRequestTarget target, final T ignore) { closeAction(target); } }, ActionLink.ActionType.CLOSE, StringUtils.EMPTY).hideLabel(); - - panel.setRenderBodyOnly(true); + add(panel.setRenderBodyOnly(true)); } - protected abstract void closeAction(final AjaxRequestTarget target); + protected abstract void closeAction(AjaxRequestTarget target); - protected abstract Panel customResultBody(final String panleId, final T item, final Serializable result); + protected abstract Panel customResultBody(String panleId, T item, Serializable result); @Override public T getItem() { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Roles.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Roles.java index 974d7e9ed77..66890a017c6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Roles.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Roles.java @@ -22,20 +22,22 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.RoleRestClient; import org.apache.syncope.client.console.wicket.ajax.markup.html.LabelInfo; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; import org.apache.syncope.common.lib.to.AnyTO; -import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.RoleTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.StandardEntitlement; +import org.apache.wicket.Component; import org.apache.wicket.authroles.authorization.strategies.role.metadata.ActionPermissions; import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.extensions.wizard.WizardModel.ICondition; import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.util.ListModel; @@ -44,28 +46,27 @@ public class Roles extends WizardStep implements ICondition { private static final long serialVersionUID = 552437609667518888L; - private final List allRoles; + protected final List allRoles; - public Roles(final AnyWrapper modelObject) { - if (!(modelObject.getInnerObject() instanceof UserTO)) { - throw new IllegalStateException("Invalid instance " + modelObject.getInnerObject()); - } + protected final UserTO userTO; + + protected WebMarkupContainer dynrolesContainer; + + public Roles(final UserWrapper modelObject) { + if (modelObject.getPreviousUserTO() != null + && !modelObject.getInnerObject().getRoles().equals(modelObject.getPreviousUserTO().getRoles())) { - if (UserWrapper.class.cast(modelObject).getPreviousUserTO() != null - && !ListUtils.isEqualList( - UserWrapper.class.cast(modelObject).getInnerObject().getRoles(), - UserWrapper.class.cast(modelObject).getPreviousUserTO().getRoles())) { add(new LabelInfo("changed", StringUtils.EMPTY)); } else { add(new Label("changed", StringUtils.EMPTY)); } - final UserTO entityTO = UserTO.class.cast(modelObject.getInnerObject()); + userTO = modelObject.getInnerObject(); // ----------------------------------------------------------------- // Pre-Authorizations // ----------------------------------------------------------------- - final ActionPermissions permissions = new ActionPermissions(); + ActionPermissions permissions = new ActionPermissions(); setMetaData(MetaDataRoleAuthorizationStrategy.ACTION_PERMISSIONS, permissions); permissions.authorize(RENDER, new org.apache.wicket.authroles.authorization.strategies.role.Roles(StandardEntitlement.ROLE_LIST)); @@ -73,21 +74,54 @@ public Roles(final AnyWrapper modelObject) { this.setOutputMarkupId(true); - allRoles = SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy(). - isActionAuthorized(this, RENDER) - ? new RoleRestClient().list().stream().map(EntityTO::getKey).collect(Collectors.toList()) - : Collections.emptyList(); - Collections.sort(allRoles); + allRoles = getManagedRoles(); - add(new AjaxPalettePanel.Builder().build("roles", - new PropertyModel>(entityTO, "roles"), - new ListModel<>(allRoles)).hideLabel().setOutputMarkupId(true)); + add(buildRolesSelector(modelObject)); - add(new AjaxPalettePanel.Builder().build("dynroles", - new PropertyModel>(entityTO, "dynRoles"), + dynrolesContainer = new WebMarkupContainer("dynrolesContainer"); + dynrolesContainer.setOutputMarkupId(true); + dynrolesContainer.setOutputMarkupPlaceholderTag(true); + add(dynrolesContainer); + + dynrolesContainer.add(new AjaxPalettePanel.Builder().build("dynroles", + new PropertyModel<>(userTO, "dynRoles"), new ListModel<>(allRoles)).hideLabel().setEnabled(false).setOutputMarkupId(true)); } + protected List getManagedRoles() { + return SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy(). + isActionAuthorized(this, RENDER) + ? new RoleRestClient().list().stream().map(RoleTO::getKey).sorted().collect(Collectors.toList()) + : Collections.emptyList(); + } + + protected Component buildRolesSelector(final UserWrapper modelObject) { + return new AjaxPalettePanel.Builder(). + withFilter(). + setAllowOrder(true). + build("roles", + new PropertyModel<>(modelObject.getInnerObject(), "roles"), + new AjaxPalettePanel.Builder.Query() { + + private static final long serialVersionUID = 3900199363626636719L; + + @Override + public List execute(final String filter) { + if (StringUtils.isEmpty(filter) || "*".equals(filter)) { + return allRoles.size() > Constants.MAX_ROLE_LIST_SIZE + ? allRoles.subList(0, Constants.MAX_ROLE_LIST_SIZE) + : allRoles; + + } + return allRoles.stream(). + filter(role -> StringUtils.containsIgnoreCase(role, filter)). + collect(Collectors.toList()); + } + }). + hideLabel(). + setOutputMarkupId(true); + } + @Override public final boolean evaluate() { return CollectionUtils.isNotEmpty(allRoles) diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/StatusPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/StatusPanel.java index 41ba1e30e8c..33c1b9ecbb8 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/StatusPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/StatusPanel.java @@ -41,7 +41,6 @@ import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.Component; import org.apache.wicket.PageReference; @@ -70,7 +69,7 @@ public StatusPanel( super(id); init(any, model, StatusUtils.getReconStatuses( - AnyTypeKind.fromTOClass(any.getClass()), any.getKey(), any.getResources()).stream(). + any.getType(), any.getKey(), any.getResources()).stream(). map(status -> Triple.of( status.getRight().getOnSyncope(), new ConnObjectWrapper(any, status.getLeft(), status.getRight().getOnResource()), diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java index 2e5f7256899..139feb57daf 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/TypeExtensionWizardBuilder.java @@ -112,7 +112,7 @@ public Details(final TypeExtensionTO typeExtensionTO) { map(EntityTO::getKey).collect(Collectors.toList()); AjaxPalettePanel auxClassesPalette = new AjaxPalettePanel.Builder().build( "auxClasses.palette", - new PropertyModel>(typeExtensionTO, "auxClasses"), + new PropertyModel<>(typeExtensionTO, "auxClasses"), new ListModel<>(anyTypeClasses)); add(auxClassesPalette.hideLabel().setOutputMarkupId(true)); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java index e2c549523cb..bcf3d10d4f1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java @@ -61,8 +61,9 @@ public UserDetails( // ------------------------ username = new AjaxTextFieldPanel("username", "username", new PropertyModel<>(userTO, "username"), false); - if (wrapper.getPreviousUserTO() != null && StringUtils. - compare(wrapper.getPreviousUserTO().getUsername(), wrapper.getInnerObject().getUsername()) != 0) { + if (wrapper.getPreviousUserTO() != null && StringUtils.compare( + wrapper.getPreviousUserTO().getUsername(), wrapper.getInnerObject().getUsername()) != 0) { + username.showExternAction(new LabelInfo("externalAction", wrapper.getPreviousUserTO().getUsername())); } @@ -103,7 +104,7 @@ protected Component newTitle(final String markupId, final ITab tab, final Accord @Override protected void onComponentTag(final ComponentTag tag) { super.onComponentTag(tag); - tag.put("style", "color: #337ab7 !important"); + tag.put("style", "color: #337ab7"); } @Override @@ -141,6 +142,5 @@ public EditUserPasswordPanel( add(new Label("warning", new ResourceModel("password.change.warning"))); add(new PasswordPanel("passwordPanel", wrapper, templateMode)); } - } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizer.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizer.java new file mode 100644 index 00000000000..6cc334e3c34 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizer.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.client.console.wizards.any; + +public interface UserFormFinalizer { + + default void beforeUpdate(String userKey) { + // nothing to do; + } + + default void afterUpdate(String userKey) { + // nothing to do; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizerUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizerUtils.java new file mode 100644 index 00000000000..246f070203b --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserFormFinalizerUtils.java @@ -0,0 +1,68 @@ +/* + * 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.client.console.wizards.any; + +import java.util.ArrayList; +import java.util.List; +import org.apache.syncope.client.console.SyncopeConsoleApplication; +import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup; +import org.apache.syncope.client.console.init.ConsoleInitializer; +import org.apache.syncope.client.console.wizards.AjaxWizard; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ClassUtils; + +public final class UserFormFinalizerUtils { + + private static final Logger LOG = LoggerFactory.getLogger(UserFormFinalizerUtils.class); + + private static UserFormFinalizerUtils INSTANCE; + + public static UserFormFinalizerUtils getInstance() { + synchronized (LOG) { + if (INSTANCE == null) { + INSTANCE = new UserFormFinalizerUtils(); + } + } + return INSTANCE; + } + + private final ClassPathScanImplementationLookup classPathScanImplementationLookup; + + private UserFormFinalizerUtils() { + classPathScanImplementationLookup = (ClassPathScanImplementationLookup) SyncopeConsoleApplication.get(). + getServletContext().getAttribute(ConsoleInitializer.CLASSPATH_LOOKUP); + } + + public List getFormFinalizers(final AjaxWizard.Mode mode) { + List finalizers = new ArrayList<>(); + + classPathScanImplementationLookup.getUserFormFinalizerClasses(mode).forEach(applier -> { + if (applier != null) { + try { + finalizers.add(ClassUtils.getConstructorIfAvailable(applier).newInstance()); + } catch (Exception e) { + LOG.error("Could not instantiate {}", applier, e); + } + } + }); + + return finalizers; + } +} diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java index 3c8111cd27a..edc5db5e9b7 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java @@ -19,6 +19,8 @@ package org.apache.syncope.client.console.wizards.any; import java.util.List; +import java.util.Optional; +import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.layout.UserFormLayoutInfo; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.to.RealmTO; @@ -47,7 +49,6 @@ public UserTemplateWizardBuilder( } else { setItem(new UserWrapper(template)); } - } public UserTemplateWizardBuilder( @@ -55,6 +56,7 @@ public UserTemplateWizardBuilder( final List anyTypeClasses, final UserFormLayoutInfo formLayoutInfo, final PageReference pageRef) { + super(anyTypeClasses, formLayoutInfo, pageRef); this.templatable = templatable; @@ -63,17 +65,18 @@ public UserTemplateWizardBuilder( } else { UserTO userTO = new UserTO(); if (templatable instanceof RealmTO) { - userTO.setRealm(String.format("'%s'", RealmTO.class.cast(templatable).getFullPath())); + userTO.setRealm( + String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath()))); } setItem(new UserWrapper(userTO)); } } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - final Details details = super.addOptionalDetailsPanel(modelObject); - if (templatable instanceof RealmTO) { - details.disableRealmSpecification(); + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + Optional> details = super.addOptionalDetailsPanel(modelObject); + if (templatable instanceof RealmTO && details.isPresent()) { + details.get().disableRealmSpecification(); } return details; } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java index 12fd4d9072b..951bf6f1b3b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.layout.UserForm; import org.apache.syncope.client.console.layout.UserFormLayoutInfo; @@ -38,13 +39,6 @@ public class UserWizardBuilder extends AnyWizardBuilder implements UserF protected final UserRestClient userRestClient = new UserRestClient(); - /** - * Constructor to be used for templating only. - * - * @param anyTypeClasses any type classes. - * @param formLayoutInfo form layout. - * @param pageRef reference page. - */ public UserWizardBuilder( final List anyTypeClasses, final UserFormLayoutInfo formLayoutInfo, @@ -53,15 +47,6 @@ public UserWizardBuilder( super(new UserWrapper(null), anyTypeClasses, formLayoutInfo, pageRef); } - /** - * Constructor to be used for Approval and Remediation details only. - * - * @param previousUserTO previous user status. - * @param userTO new user status to be approved. - * @param anyTypeClasses any type classes. - * @param formLayoutInfo from layout. - * @param pageRef reference page. - */ public UserWizardBuilder( final UserTO previousUserTO, final UserTO userTO, @@ -82,7 +67,7 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { ? UserWrapper.class.cast(modelObject).isStorePasswordInSyncope() : StringUtils.isNotBlank(inner.getPassword())); } else { - inner.getPlainAttrs().addAll(cleanEmptyPlainAttrs(inner.getPlainAttrs())); + fixPlainAndVirAttrs(inner, getOriginalItem().getInnerObject()); UserPatch patch = AnyOperations.diff(inner, getOriginalItem().getInnerObject(), false); if (StringUtils.isNotBlank(inner.getPassword())) { @@ -96,7 +81,13 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { result = new ProvisioningResult<>(); result.setEntity(inner); } else { + List finalizers = UserFormFinalizerUtils.getInstance().getFormFinalizers(this.mode); + + finalizers.forEach(finalizer -> finalizer.beforeUpdate(patch.getKey())); + result = userRestClient.update(getOriginalItem().getInnerObject().getETagValue(), patch); + + finalizers.forEach(finalizer -> finalizer.afterUpdate(patch.getKey())); } } @@ -104,13 +95,13 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { } @Override - protected Details addOptionalDetailsPanel(final AnyWrapper modelObject) { - return new UserDetails( + protected Optional> addOptionalDetailsPanel(final AnyWrapper modelObject) { + return Optional.of(new UserDetails( UserWrapper.class.cast(modelObject), mode == AjaxWizard.Mode.TEMPLATE, modelObject.getInnerObject().getKey() != null, UserFormLayoutInfo.class.cast(formLayoutInfo).isPasswordManagement(), - pageRef); + pageRef)); } /** diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/VirAttrs.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/VirAttrs.java index 7850fe9ebb8..c1f6266e010 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/VirAttrs.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/VirAttrs.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -34,6 +35,7 @@ import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.GroupableRelatableTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.VirSchemaTO; import org.apache.syncope.common.lib.types.SchemaType; @@ -63,7 +65,7 @@ public VirAttrs( final List anyTypeClasses, final List whichVirAttrs) { - super(modelObject, anyTypeClasses, whichVirAttrs); + super(modelObject, mode, anyTypeClasses, whichVirAttrs); this.mode = mode; this.modelObject = modelObject; @@ -80,7 +82,7 @@ public WebMarkupContainer getPanel(final String panelId) { } }), Model.of(0)).setOutputMarkupId(true)); - add(new ListView("membershipsVirSchemas", membershipTOs) { + add(new ListView("membershipsVirSchemas", memberships) { private static final long serialVersionUID = 9101744072914090143L; @@ -145,7 +147,13 @@ protected void setAttrs() { protected void setAttrs(final MembershipTO membershipTO) { List attrs = new ArrayList<>(); - Map attrMap = EntityTOUtils.buildAttrMap(anyTO.getVirAttrs()); + final Map attrMap; + if (GroupableRelatableTO.class.cast(anyTO).getMembership(membershipTO.getGroupKey()).isPresent()) { + attrMap = EntityTOUtils.buildAttrMap(GroupableRelatableTO.class.cast(anyTO) + .getMembership(membershipTO.getGroupKey()).get().getVirAttrs()); + } else { + attrMap = new HashMap<>(); + } attrs.addAll(membershipSchemas.get(membershipTO.getGroupKey()).values().stream().map(schema -> { AttrTO attrTO = new AttrTO(); @@ -180,26 +188,23 @@ public VirSchemas( @SuppressWarnings("unchecked") protected void populateItem(final ListItem item) { AttrTO attrTO = item.getModelObject(); + VirSchemaTO virSchemaTO = schemas.get(attrTO.getSchema()); AbstractFieldPanel panel = new AjaxTextFieldPanel( "panel", - schemas.get(attrTO.getSchema()).getLabel(SyncopeConsoleSession.get().getLocale()), + virSchemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), new Model<>(), false); - boolean readonly = attrTO.getSchemaInfo() == null - ? false - : VirSchemaTO.class.cast(attrTO.getSchemaInfo()).isReadonly(); - if (mode == AjaxWizard.Mode.TEMPLATE) { - AjaxTextFieldPanel.class.cast(panel).enableJexlHelp().setEnabled(!readonly); + AjaxTextFieldPanel.class.cast(panel).enableJexlHelp().setEnabled(!virSchemaTO.isReadonly()); } else { panel = new MultiFieldPanel.Builder<>( new PropertyModel>(attrTO, "values")).build( "panel", - schemas.get(attrTO.getSchema()).getLabel(SyncopeConsoleSession.get().getLocale()), + virSchemaTO.getLabel(SyncopeConsoleSession.get().getLocale()), AjaxTextFieldPanel.class.cast(panel)); - panel.setEnabled(!readonly); + panel.setEnabled(!virSchemaTO.isReadonly()); } item.add(panel); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/AbstractConnConfPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/AbstractConnConfPanel.java index 662272f4eda..635d96aa62b 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/AbstractConnConfPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/AbstractConnConfPanel.java @@ -24,7 +24,7 @@ import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.wicket.markup.html.list.ConnConfPropertyListView; -import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.types.ConnConfProperty; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxButton; @@ -36,7 +36,7 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.ResourceModel; -public abstract class AbstractConnConfPanel extends WizardStep implements ICondition { +public abstract class AbstractConnConfPanel extends WizardStep implements ICondition { private static final long serialVersionUID = -2025535531121434050L; @@ -65,7 +65,7 @@ public AbstractConnConfPanel(final T model) { public void onSubmit(final AjaxRequestTarget target) { final Pair result = check(target); if (result.getLeft()) { - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } else { SyncopeConsoleSession.get().error(getString("error_connection") + ": " + result.getRight()); } @@ -76,13 +76,13 @@ public void onSubmit(final AjaxRequestTarget target) { } protected void setConfPropertyListView(final IModel> model, final boolean withOverridable) { - propertiesContainer.add(new ConnConfPropertyListView( + propertiesContainer.addOrReplace(new ConnConfPropertyListView( "connectorProperties", model, withOverridable).setOutputMarkupId(true)); } - protected abstract Pair check(final AjaxRequestTarget taget); + protected abstract Pair check(AjaxRequestTarget taget); - protected abstract List getConnProperties(final T instance); + protected abstract List getConnProperties(T instance); @Override public boolean evaluate() { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java index d69b7d38f9a..de5932887d6 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java @@ -19,54 +19,61 @@ package org.apache.syncope.client.console.wizards.resources; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.commons.RealmsUtils; +import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSpinnerFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.ConnBundleTO; import org.apache.syncope.common.lib.to.ConnInstanceTO; import org.apache.syncope.common.lib.to.ConnPoolConfTO; -import org.apache.syncope.common.lib.to.RealmTO; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.html.form.DropDownChoice; -import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.PropertyModel; public class ConnectorDetailsPanel extends WizardStep { private static final long serialVersionUID = -2435937897614232137L; - - private final LoadableDetachableModel> realms; + private final RealmRestClient realmRestClient = new RealmRestClient(); public ConnectorDetailsPanel(final ConnInstanceTO connInstanceTO, final List bundles) { super(); setOutputMarkupId(true); - final List authRealms = SyncopeConsoleSession.get().getAuthRealms(); - realms = new LoadableDetachableModel>() { + boolean isSearchEnabled = RealmsUtils.isSearchEnabled(); + + final AutoCompleteSettings settings = new AutoCompleteSettings(); + settings.setShowCompleteListOnFocusGain(!isSearchEnabled); + settings.setShowListOnEmptyInput(!isSearchEnabled); + + AjaxSearchFieldPanel realm = new AjaxSearchFieldPanel( + "adminRealm", "adminRealm", new PropertyModel<>(connInstanceTO, "adminRealm"), settings) { - private static final long serialVersionUID = 5275935387613157437L; + private static final long serialVersionUID = -6390474600233486704L; @Override - protected List load() { - return new RealmRestClient().list().stream(). - filter(realm -> authRealms.stream(). - anyMatch(authRealm -> realm.getFullPath().startsWith(authRealm))). - map(RealmTO::getFullPath). - sorted(). - collect(Collectors.toList()); + protected Iterator getChoices(final String input) { + return (isSearchEnabled + ? realmRestClient.search(RealmsUtils.buildQuery(input)).getResult() + : realmRestClient.list(SyncopeConstants.ROOT_REALM)). + stream().filter(realm -> SyncopeConsoleSession.get().getAuthRealms().stream().anyMatch( + authRealm -> realm.getFullPath().startsWith(authRealm))). + map(item -> item.getFullPath()).collect(Collectors.toList()).iterator(); } }; - AjaxDropDownChoicePanel realm = new AjaxDropDownChoicePanel<>( - "adminRealm", "adminRealm", new PropertyModel<>(connInstanceTO, "adminRealm"), false); - realm.setChoices(realms); realm.setOutputMarkupId(true); realm.addRequiredLabel(); add(realm); @@ -77,25 +84,59 @@ protected List load() { displayName.addRequiredLabel(); add(displayName); - AjaxTextFieldPanel location = new AjaxTextFieldPanel( - "location", "location", new PropertyModel<>(connInstanceTO, "location"), false); - location.addRequiredLabel(); - location.setOutputMarkupId(true); - location.setEnabled(false); - add(location); - final AjaxDropDownChoicePanel bundleName = new AjaxDropDownChoicePanel<>( "bundleName", "bundleName", new PropertyModel<>(connInstanceTO, "bundleName"), false); + + if (StringUtils.isNotBlank(connInstanceTO.getLocation())) { + AjaxTextFieldPanel location = new AjaxTextFieldPanel( + "location", "location", new PropertyModel<>(connInstanceTO, "location"), false); + location.addRequiredLabel(); + location.setOutputMarkupId(true); + location.setEnabled(false); + add(location); + } else { + final AjaxDropDownChoicePanel location = new AjaxDropDownChoicePanel<>( + "location", "location", new PropertyModel<>(connInstanceTO, "location"), false); + location.setChoices(new ArrayList<>(SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations())); + location.addRequiredLabel(); + location.setOutputMarkupId(true); + location.getField().setOutputMarkupId(true); + add(location); + + location.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -5609231641453245929L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + ((DropDownChoice) location.getField()).setNullValid(false); + bundleName.setEnabled(true); + + ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + List bundles = connectorRestClient.getAllBundles().stream(). + filter(object -> object.getLocation().equals(connInstanceTO.getLocation())). + collect(Collectors.toList()); + + List listBundles = getBundles(connInstanceTO, bundles); + if (listBundles.size() == 1) { + connInstanceTO.setBundleName(listBundles.get(0)); + bundleName.getField().setModelObject(listBundles.get(0)); + } + bundleName.setChoices(listBundles); + + target.add(bundleName); + } + }); + } + ((DropDownChoice) bundleName.getField()).setNullValid(true); List bundleNames = new ArrayList<>(); bundles.stream(). filter(bundle -> (!bundleNames.contains(bundle.getBundleName()))). - forEachOrdered(bundle -> { - bundleNames.add(bundle.getBundleName()); - }); + forEachOrdered(bundle -> bundleNames.add(bundle.getBundleName())); bundleName.setChoices(bundleNames); bundleName.addRequiredLabel(); @@ -122,7 +163,16 @@ protected void onUpdate(final AjaxRequestTarget target) { ((DropDownChoice) bundleName.getField()).setNullValid(false); version.setEnabled(true); - List versions = getVersions(connInstanceTO, bundles); + List versions; + if (bundles.isEmpty()) { + ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + List bundles = connectorRestClient.getAllBundles().stream(). + filter(object -> object.getLocation().equals(connInstanceTO.getLocation())). + collect(Collectors.toList()); + versions = getVersions(connInstanceTO, bundles); + } else { + versions = getVersions(connInstanceTO, bundles); + } if (versions.size() == 1) { connInstanceTO.setVersion(versions.get(0)); version.getField().setModelObject(versions.get(0)); @@ -139,33 +189,37 @@ protected void onUpdate(final AjaxRequestTarget target) { add(new AjaxSpinnerFieldPanel.Builder().min(0).max(Integer.MAX_VALUE).build( "connRequestTimeout", "connRequestTimeout", Integer.class, - new PropertyModel(connInstanceTO, "connRequestTimeout"))); + new PropertyModel<>(connInstanceTO, "connRequestTimeout"))); add(new AjaxSpinnerFieldPanel.Builder().min(0).max(Integer.MAX_VALUE).build( "poolMaxObjects", "poolMaxObjects", Integer.class, - new PropertyModel(connInstanceTO.getPoolConf(), "maxObjects"))); + new PropertyModel<>(connInstanceTO.getPoolConf(), "maxObjects"))); add(new AjaxSpinnerFieldPanel.Builder().min(0).max(Integer.MAX_VALUE).build( "poolMinIdle", "poolMinIdle", Integer.class, - new PropertyModel(connInstanceTO.getPoolConf(), "minIdle"))); + new PropertyModel<>(connInstanceTO.getPoolConf(), "minIdle"))); add(new AjaxSpinnerFieldPanel.Builder().min(0).max(Integer.MAX_VALUE).build( "poolMaxIdle", "poolMaxIdle", Integer.class, - new PropertyModel(connInstanceTO.getPoolConf(), "maxIdle"))); + new PropertyModel<>(connInstanceTO.getPoolConf(), "maxIdle"))); add(new AjaxSpinnerFieldPanel.Builder().min(0L).max(Long.MAX_VALUE).build( "poolMaxWait", "poolMaxWait", Long.class, - new PropertyModel(connInstanceTO.getPoolConf(), "maxWait"))); + new PropertyModel<>(connInstanceTO.getPoolConf(), "maxWait"))); add(new AjaxSpinnerFieldPanel.Builder().min(0L).max(Long.MAX_VALUE).build( "poolMinEvictableIdleTime", "poolMinEvictableIdleTime", Long.class, - new PropertyModel(connInstanceTO.getPoolConf(), "minEvictableIdleTimeMillis"))); + new PropertyModel<>(connInstanceTO.getPoolConf(), "minEvictableIdleTimeMillis"))); } private List getVersions(final ConnInstanceTO connInstanceTO, final List bundles) { - return bundles.stream().filter(object - -> object.getLocation().equals(connInstanceTO.getLocation()) + return bundles.stream().filter(object -> object.getLocation().equals(connInstanceTO.getLocation()) && object.getBundleName().equals(connInstanceTO.getBundleName())). map(ConnBundleTO::getVersion).collect(Collectors.toList()); } + + private List getBundles(final ConnInstanceTO connInstanceTO, final List bundles) { + return bundles.stream().filter(object -> object.getLocation().equals(connInstanceTO.getLocation())). + map(ConnBundleTO::getBundleName).collect(Collectors.toList()); + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java index 7fcd82c45a1..1b076262f52 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java @@ -82,7 +82,7 @@ protected void onComponentTag(final ComponentTag tag) { } @Override - protected Serializable onApplyInternal(final Serializable modelObject) { + protected ConnInstanceTO onApplyInternal(final Serializable modelObject) { ConnInstanceTO connInstanceTO = ConnInstanceTO.class.cast(modelObject); ConnBundleTO bundleTO = ConnectorWizardBuilder.getBundle(connInstanceTO, bundles); @@ -124,7 +124,16 @@ protected Serializable getCreateCustomPayloadEvent(final Serializable afterObjec } protected static ConnBundleTO getBundle(final ConnInstanceTO connInstanceTO, final List bundles) { - return bundles.stream().filter(bundle + List bundlesList; + if (bundles.isEmpty()) { + ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + bundlesList = connectorRestClient.getAllBundles().stream(). + filter(object -> object.getLocation().equals(connInstanceTO.getLocation())). + collect(Collectors.toList()); + } else { + bundlesList = bundles; + } + return bundlesList.stream().filter(bundle -> bundle.getBundleName().equals(connInstanceTO.getBundleName()) && bundle.getVersion().equals(connInstanceTO.getVersion())). findFirst().orElse(null); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java index ca5b038938b..cfc8ee705da 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.TogglePanel; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; @@ -48,7 +49,7 @@ public class ItemTransformersTogglePanel extends TogglePanel { private ItemTO item; public ItemTransformersTogglePanel(final WebMarkupContainer container, final PageReference pageRef) { - super("outer", "itemTransformersTogglePanel", pageRef); + super(Constants.OUTER, "itemTransformersTogglePanel", pageRef); final LoadableDetachableModel> model = new LoadableDetachableModel>() { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/JEXLTransformersTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/JEXLTransformersTogglePanel.java index fdbab7157b6..e99caf8ccaa 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/JEXLTransformersTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/JEXLTransformersTogglePanel.java @@ -20,6 +20,7 @@ import java.io.Serializable; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.TogglePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.common.lib.to.ItemTO; @@ -40,7 +41,7 @@ public class JEXLTransformersTogglePanel extends TogglePanel { private final AjaxTextFieldPanel pullJEXLTransformer; public JEXLTransformersTogglePanel(final WebMarkupContainer container, final PageReference pageRef) { - super("outer", "jexlTransformersTogglePanel", pageRef); + super(Constants.OUTER, "jexlTransformersTogglePanel", pageRef); Form form = new Form<>("form"); addInnerObject(form); @@ -49,13 +50,13 @@ public JEXLTransformersTogglePanel(final WebMarkupContainer container, final Pag "propagationJEXLTransformer", "Propagation", Model.of("")); - form.add(propagationJEXLTransformer.enableJexlHelp()); + form.add(propagationJEXLTransformer.enableJexlHelp("value.toLowecase()", "'PREFIX' + value")); pullJEXLTransformer = new AjaxTextFieldPanel( "pullJEXLTransformer", "Pull", Model.of("")); - form.add(pullJEXLTransformer.enableJexlHelp()); + form.add(pullJEXLTransformer.enableJexlHelp("value.toLowecase()", "'PREFIX' + value")); form.add(new AjaxSubmitLink("submit", form) { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ObjectTypeTogglePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ObjectTypeTogglePanel.java index 46b99b3baa4..8873dd0559c 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ObjectTypeTogglePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ObjectTypeTogglePanel.java @@ -74,7 +74,7 @@ protected void onError(final AjaxRequestTarget target) { }); } - protected abstract void onSubmit(final String type, final AjaxRequestTarget target); + protected abstract void onSubmit(String type, AjaxRequestTarget target); public void setHeaderLabel(final AjaxRequestTarget target) { setHeader(target, getString("type")); diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java index 8a3aa5ff40d..0506d2df825 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java @@ -26,6 +26,7 @@ import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.ConnConfProperty; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.PropertyModel; @@ -97,7 +98,7 @@ protected final List getConnProperties(final ResourceTO resour props.add(property); }); } - if (createFlag || resourceTO.getConfOverride().isEmpty()) { + if (resourceTO.getConfOverride().isEmpty()) { resourceTO.getConfOverride().clear(); } else { Map valuedProps = new HashMap<>(); @@ -114,4 +115,12 @@ protected final List getConnProperties(final ResourceTO resour return props; } + + public LoadableDetachableModel> getModel() { + return model; + } + + public AjaxButton getCheck() { + return check; + } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java index 52f3b0da9b1..eb6e5dfa213 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java @@ -18,9 +18,14 @@ */ package org.apache.syncope.client.console.wizards.resources; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import org.apache.syncope.client.console.commons.Constants; +import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; @@ -33,6 +38,7 @@ import org.apache.syncope.common.lib.types.TraceLevel; import org.apache.wicket.extensions.wizard.WizardStep; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; @@ -44,6 +50,8 @@ public class ResourceDetailsPanel extends WizardStep { private static final long serialVersionUID = -7982691107029848579L; + private AjaxDropDownChoicePanel connector; + private final ImplementationRestClient implRestClient = new ImplementationRestClient(); private final IModel> propagationActions = new LoadableDetachableModel>() { @@ -57,6 +65,17 @@ protected List load() { } }; + private final IModel> provisionSorters = new LoadableDetachableModel>() { + + private static final long serialVersionUID = 4659376149825914247L; + + @Override + protected List load() { + return implRestClient.list(ImplementationType.PROVISION_SORTER).stream(). + map(EntityTO::getKey).sorted().collect(Collectors.toList()); + } + }; + public ResourceDetailsPanel(final ResourceTO resourceTO, final boolean createFlag) { super(); setOutputMarkupId(true); @@ -67,10 +86,9 @@ public ResourceDetailsPanel(final ResourceTO resourceTO, final boolean createFla add(container); container.add(new AjaxTextFieldPanel( - "key", - new ResourceModel("key", "key"). - getObject(), - new PropertyModel<>(resourceTO, "key"), + Constants.KEY_FIELD_NAME, + new ResourceModel(Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME).getObject(), + new PropertyModel<>(resourceTO, Constants.KEY_FIELD_NAME), false).addRequiredLabel().setEnabled(createFlag)); container.add(new AjaxCheckBoxPanel( @@ -104,6 +122,11 @@ public ResourceDetailsPanel(final ResourceTO resourceTO, final boolean createFla false). setChoices(Arrays.stream(TraceLevel.values()).collect(Collectors.toList())).setNullValid(false)); + container.add(new AjaxDropDownChoicePanel<>( + "provisionSorter", "provisionSorter", + new PropertyModel<>(resourceTO, "provisionSorter"), false). + setChoices(provisionSorters)); + container.add(new AjaxDropDownChoicePanel<>( "updateTraceLevel", new ResourceModel("updateTraceLevel", "updateTraceLevel").getObject(), @@ -125,10 +148,51 @@ public ResourceDetailsPanel(final ResourceTO resourceTO, final boolean createFla false). setChoices(Arrays.stream(TraceLevel.values()).collect(Collectors.toList())).setNullValid(false)); - container.add(new AjaxTextFieldPanel( - "connector", - new ResourceModel("connector", "connector").getObject(), - new Model<>(resourceTO.getConnectorDisplayName()), - false).addRequiredLabel().setEnabled(false)); + if (resourceTO.getConnector() != null) { + container.add(new AjaxTextFieldPanel( + "connector", + new ResourceModel("connector", "connector").getObject(), + new Model<>(resourceTO.getConnectorDisplayName()), + false).addRequiredLabel().setEnabled(false)); + } else { + ConnectorRestClient connectorRestClient = new ConnectorRestClient(); + connector = new AjaxDropDownChoicePanel<>( + "connector", + new ResourceModel("connector", "connector").getObject(), + new PropertyModel<>(resourceTO, "connector"), false); + Map connectorsMap = new HashMap<>(); + connectorRestClient.getAllConnectors().forEach(conn -> connectorsMap.put(conn.getKey(), + conn.getDisplayName())); + connector.setChoices(new ArrayList<>(connectorsMap.keySet())); + connector.setChoiceRenderer(new IChoiceRenderer() { + + private static final long serialVersionUID = 91313845533448846L; + + private final Map valueMap = connectorsMap; + + @Override + public String getDisplayValue(final String value) { + return valueMap.get(value) == null ? value : valueMap.get(value); + } + + @Override + public String getIdValue(final String value, final int i) { + return value; + } + + @Override + public String getObject(final String id, final IModel> choices) { + return id; + } + }); + connector.addRequiredLabel(); + connector.setOutputMarkupId(true); + connector.getField().setOutputMarkupId(true); + container.add(connector); + } + } + + public AjaxDropDownChoicePanel getConnector() { + return connector; } } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java index 655f46200bd..81927204bdf 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup; import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; @@ -124,7 +125,7 @@ protected void setAttrNames(final AjaxTextFieldPanel toBeUpdated) { Set choices = new HashSet<>(); if (SyncopeConstants.REALM_ANYTYPE.equals(provision.getAnyType())) { - choices.add("key"); + choices.add(Constants.KEY_FIELD_NAME); choices.add("name"); choices.add("fullpath"); } else { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceProvisionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceProvisionPanel.java index b3c8b33da03..47ec9ba9401 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceProvisionPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceProvisionPanel.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; @@ -161,8 +160,7 @@ public void onClick(final AjaxRequestTarget target, final ResourceProvision prov Model.of(provision)))); } catch (SyncopeClientException e) { LOG.error("While contacting resource", e); - SyncopeConsoleSession.get().error( - StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } } @@ -175,12 +173,11 @@ public void onClick(final AjaxRequestTarget target, final ResourceProvision prov public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) { try { resourceRestClient.setLatestSyncToken(resourceTO.getKey(), provision.getAnyType()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While setting latest sync token for {}/{}", resourceTO.getKey(), provision.getAnyType(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -193,12 +190,11 @@ public void onClick(final AjaxRequestTarget target, final ResourceProvision prov public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) { try { resourceRestClient.removeSyncToken(resourceTO.getKey(), provision.getAnyType()); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); } catch (Exception e) { LOG.error("While removing sync token for {}/{}", resourceTO.getKey(), provision.getAnyType(), e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } @@ -292,12 +288,11 @@ public void onSubmit(final AjaxRequestTarget target) { }); resourceRestClient.update(resourceTO); - SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED)); + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.close(target); } catch (Exception e) { LOG.error("While creating or updating {}", resourceTO, e); - SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage()) - ? e.getClass().getName() : e.getMessage()); + SyncopeConsoleSession.get().onException(e); } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java index 755c11e6d4f..952fdf09e82 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java @@ -63,6 +63,17 @@ protected Map load() { } }; + private final IModel> propagationPolicies = new LoadableDetachableModel>() { + + private static final long serialVersionUID = -2012833443695917883L; + + @Override + protected Map load() { + return policyRestClient.getPolicies(PolicyType.PROPAGATION).stream(). + collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getDescription)); + } + }; + private final IModel> pullPolicies = new LoadableDetachableModel>() { private static final long serialVersionUID = -2012833443695917883L; @@ -122,6 +133,20 @@ public ResourceSecurityPanel(final ResourceTO resourceTO) { container.add(accountPolicy); // ------------------------------- + // ------------------------------- + // Propagation policy selection + // ------------------------------- + AjaxDropDownChoicePanel propagationPolicy = new AjaxDropDownChoicePanel<>( + "propagationPolicy", + new ResourceModel("propagationPolicy", "propagationPolicy").getObject(), + new PropertyModel<>(resourceTO, "propagationPolicy"), + false); + propagationPolicy.setChoiceRenderer(new PolicyRenderer(propagationPolicies)); + propagationPolicy.setChoices(new ArrayList<>(propagationPolicies.getObject().keySet())); + ((DropDownChoice) propagationPolicy.getField()).setNullValid(true); + container.add(propagationPolicy); + // ------------------------------- + // ------------------------------- // Pull policy selection // ------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java index b08e29b2f8c..1ba7c2088c1 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java @@ -19,16 +19,24 @@ package org.apache.syncope.client.console.wizards.resources; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; import org.apache.syncope.client.console.topology.TopologyNode; +import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.common.lib.to.ResourceTO; +import org.apache.syncope.common.lib.types.ConnConfProperty; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.wizard.WizardModel; import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.PropertyModel; /** * Resource wizard builder. @@ -56,8 +64,9 @@ public AjaxWizard build(final String id, final AjaxWizard.Mode mod @Override protected WizardModel buildModelSteps(final Serializable modelObject, final WizardModel wizardModel) { ResourceTO resourceTO = ResourceTO.class.cast(modelObject); - wizardModel.add(new ResourceDetailsPanel(resourceTO, createFlag)); - wizardModel.add(new ResourceConnConfPanel(resourceTO, createFlag) { + ResourceDetailsPanel resourceDetailsPanel = new ResourceDetailsPanel(resourceTO, createFlag); + + ResourceConnConfPanel resourceConnConfPanel = new ResourceConnConfPanel(resourceTO, createFlag) { private static final long serialVersionUID = -1128269449868933504L; @@ -71,9 +80,65 @@ protected void onComponentTag(final ComponentTag tag) { tag.append("class", "scrollable-tab-content", " "); } - }); - wizardModel.add(new ResourceConnCapabilitiesPanel( - resourceTO, connectorRestClient.read(resourceTO.getConnector()).getCapabilities())); + }; + + if (createFlag && resourceDetailsPanel.getConnector() != null) { + resourceDetailsPanel.getConnector().getField().add( + new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = 4600298808455564695L; + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + resourceTO.setConnector(resourceDetailsPanel.getConnector().getModelObject()); + + LoadableDetachableModel> model = + new LoadableDetachableModel>() { + + private static final long serialVersionUID = -2965284931860212687L; + + @Override + protected List load() { + List confOverride = + resourceConnConfPanel.getConnProperties(resourceTO); + resourceTO.getConfOverride().clear(); + resourceTO.getConfOverride().addAll(confOverride); + + return new PropertyModel>(modelObject, "confOverride") { + + private static final long serialVersionUID = -7809699384012595307L; + + @Override + public List getObject() { + List res = new ArrayList<>(super.getObject()); + + // re-order properties + Collections.sort(res, (left, right) -> { + if (left == null) { + return -1; + } else { + return left.compareTo(right); + } + }); + + return res; + } + }.getObject(); + } + }; + resourceConnConfPanel.setConfPropertyListView(model, true); + target.add(resourceConnConfPanel.getCheck().setVisible(true).setEnabled(true)); + } + }); + } + wizardModel.add(resourceDetailsPanel); + wizardModel.add(resourceConnConfPanel); + if (resourceTO.getConnector() != null) { + wizardModel.add(new ResourceConnCapabilitiesPanel( + resourceTO, connectorRestClient.read(resourceTO.getConnector()).getCapabilities())); + } else { + wizardModel.add(new ResourceConnCapabilitiesPanel(resourceTO, Collections.emptySet())); + } wizardModel.add(new ResourceSecurityPanel(resourceTO)); return wizardModel; @@ -81,7 +146,7 @@ protected void onComponentTag(final ComponentTag tag) { @Override protected ResourceTO onApplyInternal(final Serializable modelObject) { - ResourceTO resourceTO = ResourceTO.class.cast(modelObject); + ResourceTO resourceTO = (ResourceTO) modelObject; if (createFlag) { resourceTO = resourceRestClient.create(resourceTO); } else { diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java index 1f3ecd3c09b..1d274da1c25 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.panels.search.UserSearchPanel; import org.apache.syncope.client.console.rest.ApplicationRestClient; import org.apache.syncope.client.console.rest.DynRealmRestClient; @@ -33,6 +34,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wizards.AjaxWizardBuilder; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.EntityTO; import org.apache.syncope.common.lib.to.RealmTO; import org.apache.syncope.common.lib.to.RoleTO; @@ -51,7 +53,7 @@ public class RoleWizardBuilder extends AjaxWizardBuilder { private static final long serialVersionUID = 5945391813567245081L; - private final RoleRestClient groupRestClient = new RoleRestClient(); + private final RoleRestClient restClient = new RoleRestClient(); /** * Construct. @@ -72,9 +74,9 @@ public RoleWizardBuilder(final RoleTO roleTO, final PageReference pageRef) { */ @Override public AjaxWizardBuilder setItem(final RoleWrapper item) { - return (AjaxWizardBuilder) (item != null - ? super.setItem(new RoleWrapper(item.getInnerObject())) - : super.setItem(null)); + return (AjaxWizardBuilder) (item == null + ? super.setItem(null) + : super.setItem(new RoleWrapper(item.getInnerObject()))); } @Override @@ -82,9 +84,10 @@ protected Serializable onApplyInternal(final RoleWrapper modelObject) { modelObject.fillDynamicConditions(); if (getOriginalItem() == null || getOriginalItem().getInnerObject() == null || StringUtils.isBlank(getOriginalItem().getInnerObject().getKey())) { - groupRestClient.create(modelObject.getInnerObject()); + + restClient.create(modelObject.getInnerObject()); } else { - groupRestClient.update(modelObject.getInnerObject()); + restClient.update(modelObject.getInnerObject()); } return null; } @@ -105,7 +108,9 @@ public static class Details extends WizardStep { public Details(final RoleWrapper modelObject) { add(new AjaxTextFieldPanel( - "key", "key", new PropertyModel<>(modelObject.getInnerObject(), "key"), false). + Constants.KEY_FIELD_NAME, + Constants.KEY_FIELD_NAME, + new PropertyModel<>(modelObject.getInnerObject(), Constants.KEY_FIELD_NAME), false). setEnabled(StringUtils.isEmpty(modelObject.getInnerObject().getKey()))); // ------------------------ @@ -161,7 +166,7 @@ public Realms(final RoleTO modelObject) { setTitleModel(new ResourceModel("realms")); add(new AjaxPalettePanel.Builder<>().build("realms", new PropertyModel<>(modelObject, "realms"), - new ListModel<>(new RealmRestClient().list().stream(). + new ListModel<>(new RealmRestClient().list(SyncopeConstants.ROOT_REALM).stream(). map(RealmTO::getFullPath).collect(Collectors.toList()))). hideLabel().setOutputMarkupId(true)); } @@ -195,5 +200,4 @@ public Privileges(final RoleTO modelObject) { hideLabel().setOutputMarkupId(true)); } } - } diff --git a/client/console/src/main/resources/META-INF/resources/css/accessibility.css b/client/console/src/main/resources/META-INF/resources/css/accessibility.css new file mode 100644 index 00000000000..21b9201ecb9 --- /dev/null +++ b/client/console/src/main/resources/META-INF/resources/css/accessibility.css @@ -0,0 +1,44 @@ +/* + * 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. + */ + +.btn-accessibility { + position: absolute; + right: 0; + font-size: 12px; + padding: 15px 15px 0 0; +} + +.btn-accessibility i { + font-size: 30px; +} + +.btn-accessibility:hover { + cursor:pointer; +} + +.control-sidebar-menu a:focus { + outline: 1px #949494 solid; +} + +#change_contrast { + top: 0; +} +#change_fontSize { + top: 40px; +} diff --git a/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css b/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css new file mode 100644 index 00000000000..da2645696e2 --- /dev/null +++ b/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css @@ -0,0 +1,101 @@ +/* + * 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. + */ + +body, +p { + font-size: 200%; +} + +.menu a, +.treeview-menu a, +.control-sidebar-subheading { + font-size: 80% !important; +} + +.box-header > .fa, +.box-header > .glyphicon, +.box-header > .ion, .box-header .box-title, +.dropdown-toggle .filter-option, +.dropdown-menu a, +.menu-info span, +.user-header, +.header, +.modal-title, +.small-box p, +.alert-widget, +.alert-widget > a > .label, +.alert h4, +#mappings .fa, +.popover-content, +.box .dropdown-toggle .glyphicon, +.box-header button, +.toggle .toggle-group .btn, +.input-group .form-control label { + font-size: 120%; +} + +input, +select, +.footer a, +.modal-footer button, +.dropdown-menu:not([role='menu']), +.dropdown-menu > li.header, +div#tablehandling ul.menu i, +.content-header > .breadcrumb, +.btn-primary:not(.btn-circle), +.modal-content .box .dialog pre { + font-size: 100% !important; +} + +#topology .window { + height: 90px; +} + +.dataTables_length select { + font-size: 85% !important; +} + +button.close { + font-size: 2em; +} + +.details-footer .information { + font-size: 12px; +} + +.btn-file i, +.btn-file span, +.input-group-btn button.btn-primary { + font-size: 20px !important; +} + +.checkbox input[type=checkbox], +.checkbox-inline input[type=checkbox], +.radio input[type=radio], +.radio-inline input[type=radio], +input[type=checkbox], +input[type=radio] { + width: 20px; + height: 20px; +} + +.k-timepicker, +.k-datepicker { + width: 200px !important; +} diff --git a/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css b/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css new file mode 100644 index 00000000000..aa4d5b953f9 --- /dev/null +++ b/client/console/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css @@ -0,0 +1,296 @@ +/* + * 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. + */ + +/* General +============================================================================= */ +.content-wrapper, +.content-wrapper .box, +.modal-content, +.modal-header, +.background-footer, +input:not(:disabled):not([type="file"]), +select, +select option, +button:not(.close), +.table .sorting, +.dataTables_paginate a, +.modal-footer, +.box-header, +.dropdown-menu, +.main-footer, +.circular-actions a, +.k-select, +.file-caption.kv-fileinput-caption { + color: #f7f7f7 !important; + background-color: #0f1417 !important; +} + + +.dataTable thead th:after, +.close { + color: #f7f7f7 !important; + opacity: 0.8 !important; +} + + +.input-group-addon a, +.input-group-addon i, +.dropdown-menu > li:not(.disabled) > a:hover, +.dropdown-menu > li:not(.disabled) > a:hover span, +.dropdown-menu > li:not(.disabled) > a:focus, +.dropdown-menu > li:not(.disabled) > a:focus span, +#startAtContainer .input-group-addon span, +#startAtContainer .k-widget span, +#startAtContainer .k-widget i, +#startAtContainer .k-widget input, +.modal-footer i, +.navbar-nav > .user-menu > .dropdown-menu > .user-footer .btn-default, +.content-wrapper .nav-tabs a, +.wrapper .content-wrapper .nav.nav-tabs li.active a span, +.wrapper .content-wrapper .nav.nav-tabs li.active a, +.wrapper .content-wrapper .nav.nav-tabs li a:hover span, +.wrapper .content-wrapper .nav.nav-tabs li a:hover, +.wrapper .content-wrapper .nav.nav-tabs li a:active span, +.wrapper .content-wrapper .nav.nav-tabs li a:active, +.wrapper .content-wrapper .nav.nav-tabs li a:focus span, +.wrapper .content-wrapper .nav.nav-tabs li a:focus, +.wrapper .content-wrapper .tab-content .btn-primary .fa-download { + color: #000000 !important; +} + + +.content-wrapper a:not(.btn-primary), +.content-wrapper span:not(.label-info):not([role="presentation"]):not([class^='cm-']), +.content-wrapper p, +.content-wrapper .box-title a, +.realm-choice .dropdown-menu > li:not(.disabled) > a:hover, +.realm-choice .dropdown-menu > li:not(.disabled) > a:hover span, +.realm-choice .dropdown-menu > li:not(.disabled) > a:focus, +.realm-choice .dropdown-menu > li:not(.disabled) > a:focus span, +.box-header, +.breadcrumb .active, +table tbody tr:hover button span, +.dropdown-menu > li > a, +.modal-header button, +.circular-actions a i, +.wizard-form .input-group-addon i, +#startAtContainer .input-group-addon i, +.attribute .input-group-addon i { + color: #f7f7f7 !important; +} + + +.box.box-primary { + border-top-color: #f7f7f7; +} + + +img, +.content-wrapper div.btn.btn-file span.hidden-xs { + background-color: #f7f7f7; + color: #000000 !important; +} +.btn-primary, +.callout.callout-info, +.alert-info, +.label-info, +.modal-info .modal-body { + background-color: #f7f7f7 !important; + color: #000000 !important; +} + + +.logo img, +.modal-content .input-group .input-group-addon { + background-color: transparent; +} + + +.modal-dialog { + border: 2px solid white; +} + + +#veil:not(:required):after { + -webkit-box-shadow: #f7f7f7 1.5em 0 0 0, + #f7f7f7 1.1em 1.1em 0 0, + #f7f7f7 0 1.5em 0 0, + #f7f7f7 -1.1em 1.1em 0 0, + rgba(0, 0, 0, 0.5) -1.5em 0 0 0, + rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, + #f7f7f7 0 -1.5em 0 0, + #f7f7f7 1.1em -1.1em 0 0; + box-shadow: #f7f7f7 1.5em 0 0 0, + #f7f7f7 1.1em 1.1em 0 0, + #f7f7f7 0 1.5em 0 0, + #f7f7f7 -1.1em 1.1em 0 0, + #f7f7f7 -1.5em 0 0 0, + #f7f7f7 -1.1em -1.1em 0 0, + #f7f7f7 0 -1.5em 0 0, + #f7f7f7 1.1em -1.1em 0 0; +} + + +.input-group input:disabled, +.input-group input[disabled], +.control-sidebar-dark .control-sidebar-menu > li > a:hover, +.table-hover > tbody > tr:hover, +.skin-blue .main-header .navbar .sidebar-toggle:hover, +.main-header .navbar .sidebar-toggle:hover, +.skin-blue .main-header .logo:hover, +.main-header .logo:hover, +div.toggle-menu ul li:hover, +.skin-blue .main-header .navbar .nav > li > a:hover, +.skin-blue .main-header .navbar .nav > li > a:active, +.skin-blue .main-header .navbar .nav > li > a:focus, +.skin-blue .main-header .navbar .nav .open > a:hover, +.skin-blue .main-header .navbar .nav .open > a:focus, +.skin-blue .sidebar-menu > li:hover > a, +.skin-blue .sidebar-menu > li.active > a, +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-menu > li > a:hover, +.k-block, +.k-draghandle, +.k-grid-header, +.k-grouping-header, +.k-header, +.k-pager-wrap, +.k-toolbar, +.k-treemap-tile, +.k-picker-wrap, +.k-state-disabled, +.km-pane-wrapper .k-header { + background-color: rgba(108, 115, 117, 0.55); +} + + +.sidebar-mini .main-header .navbar { + background-color: #222d32; /* color from 'skin-blue' */ + border-bottom: 1px white solid; + box-sizing: border-box; +} +.sidebar-mini .main-header .logo, +div.toggle-menu, +.skin-blue .main-header li.user-header { + background-color: #222d32; +} + + +div.toggle-menu { + border: 1px solid #f7f7f7; +} + + +a, +.pagination > .active > a, +.pagination > .active > a:focus, +.pagination > .active > a:hover, +.pagination > .active > span, +.pagination > .active > span:focus, +.pagination > .active > span:hover { + border-color: #76abd9; +} +a { + color: #76abd9; +} + + +.logs button.btn-primary { + border-color: #f7f7f7; +} +.logs button.btn-primary:hover { + border-color: #adadad; +} +div.infolabel, +.input-group input:disabled, +.input-group input[disabled] { + color: #d2d2d2; +} + + +.bg-red, +.callout.callout-danger, +.alert-danger, +.alert-error, +.label-danger, +.modal-danger .modal-body, +.btn-danger.active, +.btn-danger:active, +.open>.dropdown-toggle.btn-danger { + background-color: #942819 !important; +} +.bg-yellow { + background-color: #6F4706 !important; +} +.bg-green, +.copy-clipboard-feedback, +.btn-success, +.callout.callout-success, +.alert-success, +.label-success, +.modal-success .modal-body { + background-color: #005C32 !important; +} +.bg-aqua { + background-color: #004E61 !important; +} +.bg-yellow, +.callout.callout-warning, +.alert-warning, +.label-warning, +.modal-warning .modal-body { + background-color: #6F4706 !important; +} +.callout.callout-warning { + border-color: #6F4706; +} + + +.bootstrap-select .btn.btn-default { + background-color: rgba(101, 101, 101, 0.7) !important; +} + + +/* Login page +============================================================================= */ +.login-body { + background-image: linear-gradient(rgb(31, 109, 142), #004626); +} + +.login-logo { + background: transparent; +} + +.btn-accessibility { + color: #f7f7f7 !important; +} + +.form-signin .btn-primary, +.form-signin .btn-primary { + border: 2px solid white; +} +.form-signin .btn-primary.focus, +.form-signin .btn-primary:focus { + border-color: #8c8c8c; +} + +.login-card { + background-color: #0f1417; +} \ No newline at end of file diff --git a/client/console/src/main/resources/META-INF/resources/css/batch.css b/client/console/src/main/resources/META-INF/resources/css/batch.css new file mode 100644 index 00000000000..ec3d70ede88 --- /dev/null +++ b/client/console/src/main/resources/META-INF/resources/css/batch.css @@ -0,0 +1,52 @@ +/* + * 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. + */ +div.batch{ + display:inline-table; +} + +div.batchCell{ + display: table-cell; + vertical-align: middle; + text-align: center; + width: 40px; + padding-left: 7px; +} + +.pageRowElement{ + display: inline-table; + width: 100%; +} + +div#selectedObjects table { + border-collapse: collapse; +} + +div#selectedObjects table td, div#selectedObjects table th { + border: 1px solid #eee; + padding: .5em 10px; +} + +div#selectedObjects .circular-actions { + padding: 3px; + margin: 10px 0px 5px 0px; +} + +.modal-body { + max-height: 650px !important; +} diff --git a/client/console/src/main/resources/META-INF/resources/css/bulk.css b/client/console/src/main/resources/META-INF/resources/css/bulk.css deleted file mode 100644 index 0cf6b23eb9e..00000000000 --- a/client/console/src/main/resources/META-INF/resources/css/bulk.css +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ -div.bulkAction{ - display:inline-table; -} - -div.bulkActionCell{ - display: table-cell; - vertical-align: middle; - text-align: center; - width: 40px; - padding-left: 7px; -} - -.pageRowElement{ - display: inline-table; - width: 100%; -} - -div#selectedObjects table { - border-collapse: collapse; -} - -div#selectedObjects table td, div#selectedObjects table th { - border: 1px solid #eee; - padding: .5em 10px; -} - -div#selectedObjects .circular-actions { - padding: 3px; - margin: 10px 0px 5px 0px; -} - -.modal-body { - max-height: 650px !important; -} diff --git a/client/console/src/main/resources/META-INF/resources/css/login.css b/client/console/src/main/resources/META-INF/resources/css/login.css index 575df6626cb..cfa50775c7c 100644 --- a/client/console/src/main/resources/META-INF/resources/css/login.css +++ b/client/console/src/main/resources/META-INF/resources/css/login.css @@ -71,7 +71,6 @@ body, html { min-height: 1em; } -.form-signin #inputPassword, .form-signin #inputPassword { direction: ltr; height: 44px; diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css index c2dcf2c5539..2081ab887ba 100644 --- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css +++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css @@ -517,6 +517,10 @@ END - Style for Information panel width: 110px !important; } +.table > tbody > tr > td.list_view_panel_labels { + vertical-align: middle; +} + div.searchResult{ padding-top: 30px; display: block; @@ -798,7 +802,7 @@ START - Search - AjaxDateTimePicker } .clause .field { - line-height: 34px; + line-height: 30px; float: left; padding: 0 3px 0px 0px; display: inline-block !important; @@ -829,7 +833,7 @@ START - Search - AjaxDateTimePicker } .clause .value{ - width: 220px; + width: 285px; } .clause .date{ @@ -880,7 +884,7 @@ li.todoitem a { } .popover{ - max-width: 100%; + max-width: 100%; } #popover:hover { @@ -1162,3 +1166,28 @@ div#inline-actions ul.menu i, div#tablehandling ul.menu i { div#tablehandling ul.menu li a { padding: 0px !important; } + +.realm-choice #realm-choice-field { + width: 200px; + padding-right: 20px; +} + +.realm-choice #realm-choice-folder { + color: #3c8dbc; +} + +.palette-scrollbar { + min-width: 100%; + overflow: scroll; +} + +.search-spinner { + line-height: 20px !important; +} + +@-moz-document url-prefix(''){ + .palette-scrollbar { + width: auto !important; + } +} + diff --git a/client/console/src/main/resources/META-INF/resources/css/topology.css b/client/console/src/main/resources/META-INF/resources/css/topology.css index e153619c6b5..91496a3dd75 100644 --- a/client/console/src/main/resources/META-INF/resources/css/topology.css +++ b/client/console/src/main/resources/META-INF/resources/css/topology.css @@ -65,6 +65,10 @@ background-color:rgba(100,200,200,0.08); } +.topology_conn_errored { + background-color:rgba(217,83,79,0.56); +} + .topology_res { background-color:rgba(250,230,0,0.09); } diff --git a/client/console/src/main/resources/META-INF/resources/js/accessibility.js b/client/console/src/main/resources/META-INF/resources/js/accessibility.js new file mode 100644 index 00000000000..b10a48e53b8 --- /dev/null +++ b/client/console/src/main/resources/META-INF/resources/js/accessibility.js @@ -0,0 +1,228 @@ +/* + * 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. + */ + +$(document).ready(function () { + + var locationDomain = window.location.origin + '/' + window.location.pathname.split('/')[1]; + + var initAssetsManager = function () { + var AssetsManager = {}; + + var createLink = function (id, url) { + if (!$('link#' + id).length && !$('link[href="' + url + '"').length) { + var link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = url; + link.type = "text/css"; + $('head').append(link); + } + }; + + var createScript = function (id, url) { + if (!$('script#' + id).length && !$('script[src="' + url + '"').length) { + var script = document.createElement('script'); + script.src = url; + $('body').append(script); + } + }; + + AssetsManager.checkAlreadyLoaded = function (url, type) { + var elems = (type === 'css') ? document.styleSheets : ((type === 'js') ? document.scripts : ''); + var attr = (type === 'js') ? 'src' : ((type === 'css') ? 'href' : 'none'); + for (var i in elems) { + var attrUrl = elems[i][attr] || ""; + var assetName = attrUrl.split("/").slice(-1).join(); + if (attrUrl !== "" + && (assetName === url.split("/").slice(-1).join() || assetName === url)) { + return true; + } + } + return false; + }; + + var removeLoaded = function (url, type) { + var tag = (type === 'js') ? 'script' : ((type === 'css') ? 'link' : ''); + if (AssetsManager.checkAlreadyLoaded(url, type)) { + $(tag + '[href~="' + url + '"]').remove(); + } + }; + + AssetsManager.inject = function (id, url, type) { + switch (type) { + case 'js': + createScript(id, url); + break; + + case 'css': + createLink(id, url); + break; + + default: + break; + } + }; + + AssetsManager.remove = function (url, type) { + removeLoaded(url, type); + }; + + return AssetsManager; + }; + + var initAccessibilityController = function () { + AccessibilityController = {}; + + var AssetsManager = initAssetsManager(); + + var fontSizeFiles = [ + locationDomain + '/css/accessibility/accessibilityFont.css' + ]; + + var darkThemeFiles = [ + locationDomain + '/css/AdminLTE_skins/skin-blue.css', + locationDomain + '/css/accessibility/accessibilityHC.css' + ]; + + var darkThemeMainClass = 'skin-blue'; + var defaultThemeMainClass = 'skin-green-light'; + + var doSwitch = function (check, files) { + if (!check) { + for (var i = 0; i < files.length; i++) { + AssetsManager.remove(files[i], 'css'); + } + } else { + for (var i = 0; i < files.length; i++) { + AssetsManager.inject('theme_css_' + i, files[i], 'css'); + } + } + }; + + var doSwitchTheme = function (check, files) { + doSwitch(check, files); + + if ($('body').hasClass(defaultThemeMainClass) && check) { + $('body').removeClass(defaultThemeMainClass).addClass(darkThemeMainClass); + } else { + $('body').removeClass(darkThemeMainClass).addClass(defaultThemeMainClass); + } + }; + + var savePreference = function (key, value) { + window.localStorage.setItem(key, value); + }; + + var getPreference = function (key) { + var storageValue = window.localStorage.getItem(key); + if (storageValue === null) { + savePreference(key, 'false'); + } + return storageValue === 'true'; + }; + + AccessibilityController.FONT_SIZE_PREF = 'font_size_pref'; + AccessibilityController.HC_THEME_PREF = 'hc_theme_pref'; + + var isIncreasedFont = getPreference(AccessibilityController.FONT_SIZE_PREF); + var isHighContrast = getPreference(AccessibilityController.HC_THEME_PREF); + + AccessibilityController.checkPref = function (pref) { + switch (pref) { + case AccessibilityController.FONT_SIZE_PREF: + doSwitch(isIncreasedFont, fontSizeFiles); + break; + + case AccessibilityController.HC_THEME_PREF: + doSwitchTheme(isHighContrast, darkThemeFiles); + break; + + default: + break; + } + }; + + AccessibilityController.switchIncreasedFont = function () { + isIncreasedFont = !isIncreasedFont; + doSwitch(isIncreasedFont, fontSizeFiles); + savePreference(AccessibilityController.FONT_SIZE_PREF, isIncreasedFont); + }; + + AccessibilityController.switchTheme = function () { + isHighContrast = !isHighContrast; + doSwitchTheme(isHighContrast, darkThemeFiles); + savePreference(AccessibilityController.HC_THEME_PREF, isHighContrast); + }; + + return AccessibilityController; + }; + + var AccessibilityController = initAccessibilityController(); + + AccessibilityController.checkPref(AccessibilityController.FONT_SIZE_PREF); + AccessibilityController.checkPref(AccessibilityController.HC_THEME_PREF); + + $('#change_contrast').off('click.acc_hc'); + $('#change_contrast').on('click.acc_hc', function () { + AccessibilityController.switchTheme(); + return false; + }); + + $('#change_contrast').off('keydown.key_acc_hc keypress.key_acc_hc'); + $('#change_contrast').on('keydown.key_acc_hc keypress.key_acc_hc', function (event) { + // check "enter" key pressed + if (event.which === 13) { + AccessibilityController.switchTheme(); + + event.preventDefault(); + return false; + } + }); + + $('#change_fontSize').off('click.acc_f'); + $('#change_fontSize').on('click.acc_f', function () { + AccessibilityController.switchIncreasedFont(); + return false; + }); + + $('#change_fontSize').off('keydown.key_acc_f keypress.key_acc_f'); + $('#change_fontSize').on('keydown.key_acc_f keypress.key_acc_f', function (event) { + // check "enter" key pressed + if (event.which === 13) { + AccessibilityController.switchIncreasedFont(); + + event.preventDefault(); + return false; + } + }); + + $('body').off('keydown.acc_binding keypress.acc_binding'); + $('body').on('keydown.acc_binding keypress.acc_binding', function (event) { + // alt - shift - F + // alt - shift - H + if (event.altKey && event.shiftKey) { + if (event.keyCode === 72) { + AccessibilityController.switchTheme(); + } else if (event.keyCode === 70) { + AccessibilityController.switchIncreasedFont(); + } + event.preventDefault(); + } + }); + +}); diff --git a/client/console/src/main/resources/MIMETypes.json b/client/console/src/main/resources/MIMETypes.json index b49ceca2bb2..f49a5a1150a 100644 --- a/client/console/src/main/resources/MIMETypes.json +++ b/client/console/src/main/resources/MIMETypes.json @@ -690,6 +690,9 @@ { "name": "application/x-zip-compressed" }, + { + "name": "application/yaml" + }, { "name": "application/zip" }, diff --git a/client/console/src/main/resources/console.properties b/client/console/src/main/resources/console.properties index 3139c1ce494..5abb76150db 100644 --- a/client/console/src/main/resources/console.properties +++ b/client/console/src/main/resources/console.properties @@ -16,8 +16,6 @@ # under the License. console.directory=${conf.directory} -site=${project.parent.url} - anonymousUser=${anonymousUser} anonymousKey=${anonymousKey} @@ -31,17 +29,12 @@ maxUploadFileSizeMB=5 # Max wait time on apply changes from modals/wizards (given in seconds) maxWaitTimeOnApplyChanges=30 -csrf=true - -flowableModelerDirectory=${flowable-modeler.directory} - reconciliationReportKey=c3520ad9-179f-49e7-b315-d684d216dd97 page.dashboard=org.apache.syncope.client.console.pages.Dashboard page.realms=org.apache.syncope.client.console.pages.Realms page.topology=org.apache.syncope.client.console.topology.Topology page.reports=org.apache.syncope.client.console.pages.Reports -page.workflow=org.apache.syncope.client.console.pages.Workflow page.audit=org.apache.syncope.client.console.pages.Audit page.implementations=org.apache.syncope.client.console.pages.Implementations page.logs=org.apache.syncope.client.console.pages.Logs @@ -50,3 +43,18 @@ page.types=org.apache.syncope.client.console.pages.Types page.policies=org.apache.syncope.client.console.pages.Policies page.notifications=org.apache.syncope.client.console.pages.Notifications page.parameters=org.apache.syncope.client.console.pages.Parameters + +default.any.panel.class=org.apache.syncope.client.console.panels.AnyPanel + +topology.corePoolSize=10 +topology.maxPoolSize=20 +topology.queueCapacity=50 + +x-forward=true +csrf=true + +security.headers.X-XSS-Protection=1; mode=block +security.headers.Strict-Transport-Security=max-age=31536000; includeSubDomains; preload +security.headers.X-Content-Type-Options=nosniff +security.headers.X-Frame-Options=sameorigin +#security.headers.Content-Security-Policy=default-src https: diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication.properties index 41b2c461f5f..634b6f4d810 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication.properties @@ -71,7 +71,10 @@ intAttrNameInfo.help=Besides auto-completed attributes, you can also refer to gr confirmGlobalLogout=Do you really want to perform global logout? implementations=Implementations -timeout=Operation is taking to long: it will be executed in background. Please check later for the result (errors won't be triggered). +timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered). security=Security before=Before after=After +nomatch=No matches found +tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). +confirmDelegation=Do you really want to switch user? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_fr_CA.properties new file mode 100644 index 00000000000..e370dc4bdb2 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_fr_CA.properties @@ -0,0 +1,78 @@ +# 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. +users=Utilisateurs +groups=Groupes +configuration=Configuration +resources=Ressources +connectors=Raccordements +reports=Rapports +tasks=T\u00e2ches +logout=D\u00e9connexion +schema=Sch\u00e9ma +operation_succeeded=Op\u00e9ration compl\u00e9t\u00e9e avec succ\u00e8s +operation_error=Une erreur s'est produite pendant l'op\u00e9ration +alert=Alerte: +confirmDelete=Voulez-vous vraiment supprimer le(s) article(s) s\u00e9lectionn\u00e9(s)? +confirmUnlink=Voulez-vous vraiment supprimer le lien entre le(s) article(s) s\u00e9lectionn\u00e9(s) et la ressource? +confirmUnassign=Souhaitez-vous vraiment supprimer l'affectation entre le(s) article(s) s\u00e9lectionn\u00e9(s) et la ressource? +confirmDeprovision=Souhaitez-vous vraiment d\u00e9provisionner le(s) article(s) s\u00e9lectionn\u00e9(s)? +confirmProvision=Souhaitez-vous vraiment approvisionner le(s) article(s) s\u00e9lectionn\u00e9(s)? +confirmClone=Voulez-vous vraiment cloner le(s) article(s) s\u00e9lectionn\u00e9(s)? +dropDownChoiceField.nullValid=Faire un choix +DateTimeField$HoursValidator=La valeur horaire doit \u00eatre dans la gamme (1, 12) +error=Erreur +generic_error=Une erreur s'est produite pendant l'op\u00e9ration +id=Cl\u00e9 +name=Nom +palette.available=Disponible +palette.selected=S\u00e9lectionn\u00e9 +jexl_info=Ce champ requiert une expression JEXL, par exemple: +jexl_ex1=nom de famille + ',' + pr\u00e9nom +jexl_ex2=new.' + nom de famille +jexl_syntax_url=R\u00e9f\u00e9rence JEXL compl\u00e8te +create=Cr\u00e9er +key=Cl\u00e9 +types=Types +dashboard=Page d'accueil +realms=Domaines +topology=Topologie +roles=R\u00f4les +policies=Politiques +workflow=Flux +logs=Registres +layouts=Dispositions +notifications=Notifications +parameters=Param\u00e8tres +extensions=Extensions +NavigatorLabel=Affichage des rang\u00e9es ${from} \u00e0 ${to} de ${of} +displayRows=Rang\u00e9es \u00e0 afficher +OrderByLink.CSS.ascending=tri_asc +OrderByLink.CSS.descending=tri_desc +OrderByLink.CSS.none=tri +entitlements=Droits et privil\u00e8ges +audit=Audit +connectors.confirm.reload=Cette demande est potentiellement dangereuse pour le d\u00e9roulement des op\u00e9rations, continuer? +intAttrNameInfo.help=En plus des attributs auto-compl\u00e9t\u00e9s, vous pouvez \u00e9galement faire r\u00e9f\u00e9rence \u00e0 des groupes, des objets, des appartenances ou des privil\u00e8ges, par exemple: +confirmGlobalLogout=Voulez-vous vraiment effectuer une d\u00e9connexion compl\u00e8te? +implementations=Impl\u00e9mentations +timeout=L'op\u00e9ration prend trop de temps: elle sera ex\u00e9cut\u00e9e en arri\u00e8re-plan. Veuillez v\u00e9rifier plus tard le r\u00e9sultat (les erreurs ne seront pas d\u00e9tect\u00e9es). +security=S\u00e9curit\u00e9 +before=Avant +after=Apr\u00e8s +nomatch=Aucune correspondance trouv\u00e9e +tooLargeFile=Fichier trop volumineux, la taille maximale autoris\u00e9e est de $ {maxUploadSizeB} octets ($ {maxUploadSizeMB} Mo). +confirmDelegation=Do you really want to switch user? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_it.properties index bb0b3af9f27..3794be7e87b 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_it.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_it.properties @@ -75,3 +75,6 @@ timeout=L'operazione sta durando troppo: sar\u00e0 eseguita in background. Verif security=Sicurezza before=Prima after=Dopo +nomatch=Nessun risultato trovato +tooLargeFile=File troppo grande, la dimensione massima ammessa \u00e8 ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). +confirmDelegation=Vuoi davvero cambiare utenza? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ja.properties index dda9afc866e..afc04587342 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ja.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ja.properties @@ -70,6 +70,9 @@ intAttrNameInfo.help=\u30aa\u30fc\u30c8\u30b3\u30f3\u30d7\u30ea\u30fc\u30c8\u306 confirmGlobalLogout=\u30b0\u30ed\u30fc\u30d0\u30eb\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b? implementations=\u5c0e\u5165 timeout=\u64cd\u4f5c\u306b\u9577\u6642\u9593\u304b\u304b\u3063\u3066\u3044\u307e\u3059\: \u30d0\u30c3\u30af\u30b0\u30e9\u30a6\u30f3\u30c9\u3067\u5b9f\u884c\u3055\u308c\u307e\u3059\u3002 \u7d50\u679c\u3092\u5f8c\u3067\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044 (\u30a8\u30e9\u30fc\u306f\u5f15\u304d\u8d77\u3053\u3057\u307e\u305b\u3093)\u3002 -security=Security -before=Before -after=After +security=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3 +before=\u524d +after=\u5f8c +nomatch=No matches found +tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). +confirmDelegation=Do you really want to switch user? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_pt_BR.properties index ceea6758f42..fa90aa8a2a7 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_pt_BR.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_pt_BR.properties @@ -71,7 +71,10 @@ intAttrNameInfo.help=Besides auto-completed attributes, you can also refer to gr confirmGlobalLogout=Do you really want to perform global logout? implementations=Implementa\u00e7\u00f5es -timeout=Operation is taking to long: it will be executed in background. Please check later for the result (errors won't be triggered). +timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered). security=Security before=Before after=After +nomatch=Nenhuma correspond\u00eancia encontrada +tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). +confirmDelegation=Do you really want to switch user? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ru.properties index c603b392f2f..3123da480b4 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ru.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/SyncopeConsoleApplication_ru.properties @@ -70,7 +70,10 @@ intAttrNameInfo.help=\u041f\u043e\u043c\u0438\u043c\u043e \u0430\u0432\u0442\u04 confirmGlobalLogout=Do you really want to perform global logout? implementations=\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 -timeout=Operation is taking to long: it will be executed in background. Please check later for the result (errors won't be triggered). +timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered). security=Security before=Before after=After +nomatch=No matches found +tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). +confirmDelegation=Do you really want to switch user? diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.html b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.html deleted file mode 100644 index 5d5d1294ef2..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - [APPROVAL] - - diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.properties b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.properties deleted file mode 100644 index 941c89687fc..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -approval.details=Approval details diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_it.properties deleted file mode 100644 index 941c89687fc..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_it.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -approval.details=Approval details diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ja.properties deleted file mode 100644 index 84106b96c45..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ja.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -approval.details=\u627f\u8a8d\u8a73\u7d30 diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_pt_BR.properties deleted file mode 100644 index 941c89687fc..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_pt_BR.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -approval.details=Approval details diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ru.properties deleted file mode 100644 index 941c89687fc..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/approvals/ApprovalModal_ru.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -approval.details=Approval details diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html new file mode 100644 index 00000000000..78e44edfffc --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html @@ -0,0 +1,31 @@ + + + + +
+
+
+
+
+ +
+ + + diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties new file mode 100644 index 00000000000..cec89d0704b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties @@ -0,0 +1,19 @@ +# 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. +current=Current +previous=Previous +restore=Restore diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties new file mode 100644 index 00000000000..6073f49651e --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties @@ -0,0 +1,19 @@ +# 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. +current=Courant +previous=Pr\u00e9c\u00e9dent +restore=Restaurer diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties new file mode 100644 index 00000000000..7af93d885b5 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties @@ -0,0 +1,19 @@ +# 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. +current=Attuale +previous=Precedente +restore=Ripristina diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties new file mode 100644 index 00000000000..cec89d0704b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties @@ -0,0 +1,19 @@ +# 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. +current=Current +previous=Previous +restore=Restore diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties new file mode 100644 index 00000000000..cec89d0704b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties @@ -0,0 +1,19 @@ +# 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. +current=Current +previous=Previous +restore=Restore diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties new file mode 100644 index 00000000000..cec89d0704b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties @@ -0,0 +1,19 @@ +# 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. +current=Current +previous=Previous +restore=Restore diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html new file mode 100644 index 00000000000..48c38eafe25 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.html @@ -0,0 +1,26 @@ + + + +
+ +
+ [History] +
+ diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties new file mode 100644 index 00000000000..3896fbbe61b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal.properties @@ -0,0 +1,19 @@ +# 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. +who=Who +date=Date +audit.diff.view=History view diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_fr_CA.properties new file mode 100644 index 00000000000..5fa2070e197 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_fr_CA.properties @@ -0,0 +1,19 @@ +# 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. +who=Qui +date=Date +audit.diff.view=Vue historique diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties new file mode 100644 index 00000000000..20253a96c26 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_it.properties @@ -0,0 +1,19 @@ +# 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. +who=Chi +date=Data +audit.diff.view=Vista storica diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties new file mode 100644 index 00000000000..3896fbbe61b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ja.properties @@ -0,0 +1,19 @@ +# 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. +who=Who +date=Date +audit.diff.view=History view diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties new file mode 100644 index 00000000000..3896fbbe61b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_pt_BR.properties @@ -0,0 +1,19 @@ +# 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. +who=Who +date=Date +audit.diff.view=History view diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties new file mode 100644 index 00000000000..3896fbbe61b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryModal_ru.properties @@ -0,0 +1,19 @@ +# 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. +who=Who +date=Date +audit.diff.view=History view diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.html b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.html new file mode 100644 index 00000000000..f369ec6ae35 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.html @@ -0,0 +1,34 @@ + + + + + + +
+ [DataTable]
+ +
+
+ [Actions] +
+
+
+
+ diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.properties new file mode 100644 index 00000000000..cc83de79849 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.properties @@ -0,0 +1,22 @@ +# 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. +SUCCESS=SUCCESS +FAILURE=FAILURE +CREATED=PENDING +NOT_ATTEMPTED=NOT ATTEMPTED +batch.result.header=Result +UNKNOWN=UNKNOWN diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_fr_CA.properties new file mode 100644 index 00000000000..95b57abd2ee --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_fr_CA.properties @@ -0,0 +1,22 @@ +# 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. +SUCCESS=SUCCÈS +FAILURE=ÉCHEC +CREATED=EN ATTENTE +NOT_ATTEMPTED=AUCUNE TENTATIVE +batch.result.header=Résultat +UNKNOWN=INCONNU diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_it.properties new file mode 100644 index 00000000000..c10493c59f2 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_it.properties @@ -0,0 +1,22 @@ +# 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. +SUCCESS=SUCCESSO +FAILURE=FALLIMENTO +CREATED=IN CORSO +NOT_ATTEMPTED=NON EFFETTUATO +batch.result.header=Risultato +UNKNOWN=SCONOSCIUTO diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ja.properties new file mode 100644 index 00000000000..29759532a05 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ja.properties @@ -0,0 +1,22 @@ +# 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. +SUCCESS=\u6210\u529f +FAILURE=\u5931\u6557 +CREATED=\u4fdd\u7559 +NOT_ATTEMPTED=\u672a\u8a66\u884c +batch.result.header=\u7d50\u679c +UNKNOWN=\u4e0d\u660e diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_pt_BR.properties new file mode 100644 index 00000000000..cc83de79849 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_pt_BR.properties @@ -0,0 +1,22 @@ +# 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. +SUCCESS=SUCCESS +FAILURE=FAILURE +CREATED=PENDING +NOT_ATTEMPTED=NOT ATTEMPTED +batch.result.header=Result +UNKNOWN=UNKNOWN diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ru.properties new file mode 100644 index 00000000000..20c4b0a020b --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ru.properties @@ -0,0 +1,23 @@ +# 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. +# +SUCCESS=\u0412\u042b\u041f\u041e\u041b\u041d\u0415\u041d\u041e +FAILURE=\u041e\u0428\u0418\u0411\u041a\u0410 +CREATED=\u041e\u0416\u0418\u0414\u0410\u041d\u0418\u0415 +NOT_ATTEMPTED=\u041f\u041e\u041f\u042b\u0422\u041a\u0410 \u041d\u0415 \u0412\u042b\u041f\u041e\u041b\u041d\u042f\u041b\u0410\u0421\u042c +batch.result.header=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 +UNKNOWN=UNKNOWN diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkActionModal.html b/client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchModal.html similarity index 100% rename from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkActionModal.html rename to client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchModal.html diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.html b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.html deleted file mode 100644 index 5cebcb5479e..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - -
- [DataTable]
- -
-
- [Actions] -
-
-
-
- diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.properties b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.properties deleted file mode 100644 index 9caa225e2a3..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.properties +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -SUCCESS=SUCCESS -FAILURE=FAILURE -CREATED=PENDING -NOT_ATTEMPTED=NOT ATTEMPTED -bulk.action.result.header=Result diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_it.properties deleted file mode 100644 index 5943644a515..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_it.properties +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -SUCCESS=SUCCESSO -FAILURE=FALLIMENTO -CREATED=IN CORSO -NOT_ATTEMPTED=NON EFFETTUATO -bulk.action.result.header=Risultato diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ja.properties deleted file mode 100644 index 312222d0440..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ja.properties +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -SUCCESS=\u6210\u529f -FAILURE=\u5931\u6557 -CREATED=\u4fdd\u7559 -NOT_ATTEMPTED=\u672a\u8a66\u884c -bulk.action.result.header=\u7d50\u679c diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_pt_BR.properties deleted file mode 100644 index 9caa225e2a3..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_pt_BR.properties +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -SUCCESS=SUCCESS -FAILURE=FAILURE -CREATED=PENDING -NOT_ATTEMPTED=NOT ATTEMPTED -bulk.action.result.header=Result diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ru.properties deleted file mode 100644 index 0fd0ddb1c43..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ru.properties +++ /dev/null @@ -1,27 +0,0 @@ -# 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. -# -# SUCCESS=ВЫПОЛÐЕÐО -SUCCESS=\u0412\u042b\u041f\u041e\u041b\u041d\u0415\u041d\u041e -# FAILURE=ОШИБКР-FAILURE=\u041e\u0428\u0418\u0411\u041a\u0410 -# CREATED=ОЖИДÐÐИЕ -CREATED=\u041e\u0416\u0418\u0414\u0410\u041d\u0418\u0415 -# NOT_ATTEMPTED=ПОПЫТКРÐЕ ВЫПОЛÐЯЛÐСЬ -NOT_ATTEMPTED=\u041f\u041e\u041f\u042b\u0422\u041a\u0410 \u041d\u0415 \u0412\u042b\u041f\u041e\u041b\u041d\u042f\u041b\u0410\u0421\u042c -# bulk.action.result.header=Результат -bulk.action.result.header=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy index 978e76db1e8..8926d21273f 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy @@ -18,6 +18,7 @@ */ import groovy.transform.CompileStatic import org.apache.syncope.core.persistence.api.dao.AccountRule +import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount import org.apache.syncope.core.persistence.api.entity.user.User @CompileStatic @@ -26,4 +27,6 @@ class MyAccountRule implements AccountRule { void enforce(User user) { } + void enforce(LinkedAccount account) { + } } diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyItemTransformer.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyItemTransformer.groovy index fd4b5d41cc2..971b5584bae 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyItemTransformer.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyItemTransformer.groovy @@ -17,7 +17,10 @@ * under the License. */ import groovy.transform.CompileStatic +import java.util.List; +import org.apache.commons.lang3.tuple.Pair import org.apache.syncope.common.lib.to.EntityTO +import org.apache.syncope.common.lib.types.AttrSchemaType import org.apache.syncope.core.persistence.api.entity.Entity import org.apache.syncope.core.persistence.api.entity.PlainAttrValue import org.apache.syncope.core.persistence.api.entity.resource.Item @@ -27,12 +30,13 @@ import org.apache.syncope.core.provisioning.api.data.ItemTransformer class MyItemTransformer implements ItemTransformer { @Override - List beforePropagation( + Pair> beforePropagation( Item item, Entity entity, + AttrSchemaType schemaType, List values) { - return values; + return Pair.of(schemaType, values); } @Override diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy index df22aa231bf..2c27d57aa7e 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyLogicActions.groovy @@ -35,4 +35,4 @@ class MyLogicActions implements LogicActions { M beforeUpdate(final M input) { return input; } -} +} \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy index dab38baca8a..03a1595a9ab 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy @@ -18,11 +18,15 @@ */ import groovy.transform.CompileStatic import org.apache.syncope.core.persistence.api.dao.PasswordRule +import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount import org.apache.syncope.core.persistence.api.entity.user.User @CompileStatic class MyPasswordRule implements PasswordRule { - void enforce(User user) { + void enforce(User user, String clearPassword) { + } + + void enforce(LinkedAccount account) { } } diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPropagationActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPropagationActions.groovy index 6a24777af2e..ffabe4cfbf9 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPropagationActions.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPropagationActions.groovy @@ -18,6 +18,11 @@ * under the License. */ import groovy.transform.CompileStatic +import java.util.Collections +import java.util.Optional +import java.util.Set +import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit +import org.apache.syncope.core.persistence.api.entity.resource.Provision import org.apache.syncope.core.persistence.api.entity.task.PropagationTask import org.apache.syncope.core.persistence.api.entity.task.TaskExec import org.apache.syncope.core.provisioning.api.propagation.PropagationActions @@ -26,6 +31,16 @@ import org.identityconnectors.framework.common.objects.ConnectorObject @CompileStatic class MyPropagationActions implements PropagationActions { + @Override + Set moreAttrsToGet(Optional task, OrgUnit orgUnit) { + return Collections.emptySet(); + } + + @Override + Set moreAttrsToGet(Optional task, Provision provision) { + return Collections.emptySet(); + } + @Override void before(PropagationTask task, ConnectorObject beforeObj) { // do nothing diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy new file mode 100644 index 00000000000..c59a91ce14f --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy @@ -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. + */ +import groovy.transform.CompileStatic +import org.apache.syncope.core.persistence.api.ProvisionSorter +import org.apache.syncope.core.persistence.api.entity.resource.Provision + + +@CompileStatic +class MyProvisionSorter implements ProvisionSorter { + + @Override + int compare(Provision provision1, Provision provision2) { + return 0; + } + +} \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy index 893e7a97f89..c7aa21e0f46 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullActions.groovy @@ -17,22 +17,36 @@ * under the License. */ import groovy.transform.CompileStatic +import java.util.Collections +import java.util.Set import org.apache.syncope.common.lib.patch.AnyPatch import org.apache.syncope.common.lib.to.EntityTO +import org.apache.syncope.common.lib.to.ProvisioningReport +import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit +import org.apache.syncope.core.persistence.api.entity.resource.Provision import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningActions import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile -import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport import org.apache.syncope.core.provisioning.api.pushpull.PullActions import org.identityconnectors.framework.common.objects.SyncDelta import org.quartz.JobExecutionException @CompileStatic class MyPullActions implements PullActions { + + @Override + Set moreAttrsToGet(ProvisioningProfile profile, OrgUnit orgUnit) { + return Collections.emptySet(); + } + + @Override + Set moreAttrsToGet(ProvisioningProfile profile, Provision provision) { + return Collections.emptySet(); + } @Override - SyncDelta preprocess(SyncDelta delta) { + SyncDelta preprocess(ProvisioningProfile profile, SyncDelta delta) { return delta; } @@ -119,4 +133,4 @@ class MyPullActions implements PullActions { return null; } -} +} \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullCorrelationRule.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullCorrelationRule.groovy index bb56ebe04e7..383afc245ae 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullCorrelationRule.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPullCorrelationRule.groovy @@ -20,13 +20,13 @@ import groovy.transform.CompileStatic import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule import org.apache.syncope.core.persistence.api.dao.search.SearchCond import org.apache.syncope.core.persistence.api.entity.resource.Provision -import org.identityconnectors.framework.common.objects.ConnectorObject +import org.identityconnectors.framework.common.objects.SyncDelta @CompileStatic class MyPullCorrelationRule implements PullCorrelationRule { @Override - SearchCond getSearchCond(ConnectorObject connObj, Provision provision) { + SearchCond getSearchCond(SyncDelta syncDelta, Provision provision) { } } diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPushActions.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPushActions.groovy index dad420636fd..8664c44efd5 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPushActions.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPushActions.groovy @@ -18,15 +18,22 @@ * under the License. */ import groovy.transform.CompileStatic +import java.util.Collections +import java.util.Set +import org.apache.syncope.common.lib.to.ProvisioningReport import org.apache.syncope.core.persistence.api.entity.Entity import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile -import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport import org.apache.syncope.core.provisioning.api.pushpull.PushActions import org.quartz.JobExecutionException @CompileStatic class MyPushActions implements PushActions { + @Override + Set moreAttrsToGet(ProvisioningProfile profile, Entity entity) { + return Collections.emptySet(); + } + @Override Entity beforeAssign( ProvisioningProfile profile, @@ -109,4 +116,4 @@ class MyPushActions implements PushActions { // do nothing } -} +} \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReconFilterBuilder.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReconFilterBuilder.groovy index b19b88e66aa..7c07828dcff 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReconFilterBuilder.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReconFilterBuilder.groovy @@ -18,13 +18,15 @@ */ import groovy.transform.CompileStatic import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder +import org.identityconnectors.framework.common.objects.ObjectClass import org.identityconnectors.framework.common.objects.filter.Filter +import org.identityconnectors.framework.impl.api.local.operations.FilteredResultsHandler @CompileStatic class MyReconFilterBuilder implements ReconFilterBuilder { @Override - Filter build() { - return PASS_THROUGH; + Filter build(ObjectClass objectClass) { + return new FilteredResultsHandler.PassThroughFilter() } } diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReportlet.groovy b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReportlet.groovy index 07447c21cb6..2ffc16c73b6 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReportlet.groovy +++ b/client/console/src/main/resources/org/apache/syncope/client/console/implementations/MyReportlet.groovy @@ -20,11 +20,12 @@ import groovy.transform.CompileStatic import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator import org.apache.syncope.core.persistence.api.dao.Reportlet import org.xml.sax.SAXException +import java.util.concurrent.atomic.AtomicReference @CompileStatic class MyReportlet implements Reportlet { @Override - void extract(ContentHandler handler) throws SAXException { + void extract(ContentHandler handler, AtomicReference status) throws SAXException { } } diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel_fr_CA.properties new file mode 100644 index 00000000000..17131f0621d --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel_fr_CA.properties @@ -0,0 +1,20 @@ +# 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. +any.edit=Modifier ${key} +any.new=Nouveau modèle de courriel +any.finish=Soumettre +any.cancel=Annuler diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel_fr_CA.properties new file mode 100644 index 00000000000..849647c1054 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel_fr_CA.properties @@ -0,0 +1,20 @@ +# 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. +any.edit=Modifier ${key} +any.new=Nouvelle notification +any.finish=Soumettre +any.cancel=Annuler diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationTasks_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationTasks_fr_CA.properties new file mode 100644 index 00000000000..6ffc14e9e0c --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationTasks_fr_CA.properties @@ -0,0 +1,25 @@ +# 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. +sender=Expéditeur +template=Modèle +traceLevel=Niveau de traces +notification=Notification +about=A propos de +events=Événements +selfAsRecipient=Inclure les utilisateurs correspondant à A propos de +checkRecipients=Rechercher des destinataires +isActive=Activé diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Abouts_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Abouts_fr_CA.properties new file mode 100644 index 00000000000..e5c624aab4d --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Abouts_fr_CA.properties @@ -0,0 +1,17 @@ +# 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. +search=Condition pour sélection de About diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Details_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Details_fr_CA.properties new file mode 100644 index 00000000000..8139155972c --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Details_fr_CA.properties @@ -0,0 +1,21 @@ +# 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. +subject=Sujet +sender=Expéditeur +template=Modèle +traceLevel=Niveau de traces +isActive=Actif diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Recipients_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Recipients_fr_CA.properties new file mode 100644 index 00000000000..b3c57ba9696 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/notifications/NotificationWizardBuilder$Recipients_fr_CA.properties @@ -0,0 +1,23 @@ +# 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. +recipients=Destinataires +recipientsProvider=Fournisseur du destinataire de l'avis +staticRecipients=Récipiendaires statiques +selfAsRecipient=Inclure les utilisateurs correspondant à A propos de +userNotifications=Notifications utilisateur +userNotificationsWarning=Ne pas cocher cette case à moins que les événements ne ciblent des utilisateurs +search=Recherche de destinataires diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.html deleted file mode 100644 index 5bf62344e4d..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -
-

 

- -
- -
-
-
-
-
-
- \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties deleted file mode 100644 index 1ea806333af..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties +++ /dev/null @@ -1,34 +0,0 @@ -# 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. -taskId=Task -key=Key -description=Description -createTime=Create Time -dueDate=Due Date -owner=Owner -claim=Claim -manage=Manage -approvals=Approvals -delete=Delete -type=Type -username=Username -new_user=New User -creationDate = Creation Date -claimDate = Claim Dare -approval.edit=Approval Edit -approval.manage=Approval Manage -any.edit=Edit ${anyTO.type} ${anyTO.username} diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties deleted file mode 100644 index 7e12ce5ff7b..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties +++ /dev/null @@ -1,34 +0,0 @@ -# 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. -taskId=Task -key=Chiave -description=Descrizione -createTime=Data di creazione -dueDate=Scadenza -owner=Esecutore -claim=Richiedi -manage=Gestisci -approvals=Approvazioni -delete=Rimuovi -type=Tipo -username=Utente -new_user=Nuovo utente -creationDate = Data creazione -claimDate = Data rivendicazione -approval.manage=Gestisci Approvazione -approval.edit=Modifica Approvazione -any.edit=Edit ${anyTO.type} ${anyTO.username} diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties deleted file mode 100644 index b2ff6261227..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties +++ /dev/null @@ -1,34 +0,0 @@ -# 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. -taskId=\u30bf\u30b9\u30af -key=\u30ad\u30fc -description=\u8aac\u660e -createTime=\u4f5c\u6210\u6642\u523b -dueDate=\u671f\u9650 -owner=\u30aa\u30fc\u30ca\u30fc -claim=\u7533\u8acb -manage=\u7ba1\u7406 -approvals=\u627f\u8a8d -delete=\u524a\u9664 -type=\u30bf\u30a4\u30d7 -username=\u30e6\u30fc\u30b6\u30fc\u540d -new_user=\u65b0\u3057\u3044\u30e6\u30fc\u30b6\u30fc -creationDate = \u4f5c\u6210\u65e5 -claimDate = \u7533\u8acb\u65e5 -approval.edit=\u627f\u8a8d \u7de8\u96c6 -approval.manage=\u627f\u8a8d \u7ba1\u7406 -any.edit=${anyTO.type} ${anyTO.username} \u3092\u7de8\u96c6 diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties deleted file mode 100644 index e5ff74cffb2..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties +++ /dev/null @@ -1,34 +0,0 @@ -# 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. -taskId=Tarefa -key=Chave -description=Descri\u00e7\u00e3o -createTime=Tempo de Cria\u00e7\u00e3o -dueDate=Data acordada -owner=Propriet\u00e1rio -claim=Requerimento -manage=Ger\u00eancia -approvals=Aprova\u00e7\u00f5es -delete=Excluir -type=Tipo -username=Usu\u00e1rio -new_user=Novo Usu\u00e1rio -creationDate = Data de cria\u00e7\u00e3o -claimDate = Data de reivindica\u00e7\u00e3o -approval.manage=Aprova\u00e7\u00e3o -approval.edit=Aprova\u00e7\u00e3o -any.edit=Edit ${anyTO.type} ${anyTO.username} diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties deleted file mode 100644 index 6f4fb58b75d..00000000000 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties +++ /dev/null @@ -1,52 +0,0 @@ -# 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. -# -# taskId=\u00d0\u0097\u00d0\u00b0\u00d1\u008f\u00d0\u00b2\u00d0\u00ba\u00d0\u00b0 -taskId=\u0417\u0430\u044f\u0432\u043a\u0430 -# key=\u00d0\u0098\u00d0\u00b4\u00d0\u00b5\u00d0\u00bd\u00d1\u0082\u00d0\u00b8\u00d1\u0084\u00d0\u00b8\u00d0\u00ba\u00d0\u00b0\u00d1\u0082\u00d0\u00be\u00d1\u0080 -key=\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 -# description=\u00d0\u009e\u00d0\u00bf\u00d0\u00b8\u00d1\u0081\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 -description=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 -# createTime=\u00d0\u0094\u00d0\u00b0\u00d1\u0082\u00d0\u00b0 \u00d1\u0081\u00d0\u00be\u00d0\u00b7\u00d0\u00b4\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d1\u008f -createTime=\u0414\u0430\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f -# dueDate=\u00d0\u00a1\u00d1\u0080\u00d0\u00be\u00d0\u00ba -dueDate=\u0421\u0440\u043e\u043a -# owner=\u00d0\u0092\u00d0\u00bb\u00d0\u00b0\u00d0\u00b4\u00d0\u00b5\u00d0\u00bb\u00d0\u00b5\u00d1\u0086 -owner=\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446 -# claim=\u00d0\u0092\u00d1\u008b\u00d0\u00bf\u00d0\u00be\u00d0\u00bb\u00d0\u00bd\u00d0\u00b8\u00d1\u0082\u00d1\u008c \u00d1\u0081\u00d0\u00be\u00d0\u00b3\u00d0\u00bb\u00d0\u00b0\u00d1\u0081\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 -claim=\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u0435 -# manage=\u00d0\u00a3\u00d0\u00bf\u00d1\u0080\u00d0\u00b0\u00d0\u00b2\u00d0\u00bb\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 -manage=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 -# approvals=\u00d0\u0097\u00d0\u00b0\u00d1\u008f\u00d0\u00b2\u00d0\u00ba\u00d0\u00b8 -approvals=\u0417\u0430\u044f\u0432\u043a\u0438 -# delete=\u00d0\u00a3\u00d0\u00b4\u00d0\u00b0\u00d0\u00bb\u00d0\u00b8\u00d1\u0082\u00d1\u008c -delete=\u0423\u0434\u0430\u043b\u0438\u0442\u044c -# type=\u00d0\u00a2\u00d0\u00b8\u00d0\u00bf -type=\u0422\u0438\u043f -# username=\u00d0\u0098\u00d0\u00bc\u00d1\u008f \u00d0\u00bf\u00d0\u00be\u00d0\u00bb\u00d1\u008c\u00d0\u00b7\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d1\u0082\u00d0\u00b5\u00d0\u00bb\u00d1\u008f -username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f -# new_user=\u00d0\u00a1\u00d0\u00be\u00d0\u00b7\u00d0\u00b4\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 \u00d0\u00bf\u00d0\u00be\u00d0\u00bb\u00d1\u008c\u00d0\u00b7\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d1\u0082\u00d0\u00b5\u00d0\u00bb\u00d1\u008f -new_user=\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f -# creationDate=\u00d0\u0094\u00d0\u00b0\u00d1\u0082\u00d0\u00b0 \u00d1\u0081\u00d0\u00be\u00d0\u00b7\u00d0\u00b4\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d1\u008f -creationDate=\u0414\u0430\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f -# claimDate=\u00d0\u0094\u00d0\u00b0\u00d1\u0082\u00d0\u00b0 \u00d0\u00bd\u00d0\u00b0\u00d1\u0087\u00d0\u00b0\u00d0\u00bb\u00d0\u00b0 \u00d1\u0081\u00d0\u00be\u00d0\u00b3\u00d0\u00bb\u00d0\u00b0\u00d1\u0081\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d1\u008f -claimDate=\u0414\u0430\u0442\u0430 \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u044f -# approval.edit=\u00d0\u00a1\u00d0\u00be\u00d0\u00b3\u00d0\u00bb\u00d0\u00b0\u00d1\u0081\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 -approval.edit=\u0421\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u0435 -# approval.manage=\u00d0\u00a1\u00d0\u00be\u00d0\u00b3\u00d0\u00bb\u00d0\u00b0\u00d1\u0081\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5 -approval.manage=\u0421\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u0435 -any.edit=Edit ${anyTO.type} ${anyTO.username} diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html index 169e2d6bdd7..7f0f1a38dc3 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html @@ -26,7 +26,7 @@ - + @@ -34,14 +34,18 @@ - + + + + + + @@ -65,8 +69,8 @@ - +
  • +
    +
    +
    +
    +

    +

    +
    +
    + + + +
    +
  • -
  • @@ -143,7 +161,7 @@ @@ -222,6 +240,32 @@

    Download

  • + +

    Accessibility

    + +
    diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties index 6bfa175c289..83e32e7e8df 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties @@ -23,3 +23,5 @@ hostname=Host Name processors=Available Processors os=Operating System jvm=JVM +delegations=Delegations +endDelegation=End Delegation diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_fr_CA.properties new file mode 100644 index 00000000000..a7d7e6bc8f3 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_fr_CA.properties @@ -0,0 +1,26 @@ +# 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. +home=Accueil +version=Version +domain=Domaine +systemInfo=Information sur le syst\u00e8me +hostname=Nom d'h\u00f4te +processors=Processeurs disponibles +os=Syst\u00e8me d'exploitation +jvm=JVM +delegations=Delegations +endDelegation=End Delegation diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties index fe10072b690..848a14b03a0 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties @@ -16,10 +16,12 @@ # under the License. home=Home version=Versione -domain=Dom\u00ednio +domain=Dominio systemInfo=Informazioni di Sistema hostname=Nome Host processors=Processori Disponibili os=Sistema Operativo jvm=JVM +delegations=Deleghe +endDelegation=Termina Delega diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties index a1e527e9dd2..aef6d116bef 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties @@ -23,3 +23,5 @@ hostname=\u30db\u30b9\u30c8\u540d processors=\u5229\u7528\u53ef\u80fd\u306a\u30d7\u30ed\u30bb\u30c3\u30b5\u30fc os=\u30aa\u30da\u30ec\u30fc\u30c6\u30a3\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0 jvm=JVM +delegations=Delegations +endDelegation=End Delegation diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties index 0c2da7028ee..88b9897850e 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties @@ -23,3 +23,5 @@ hostname=Host Name processors=Available Processors os=Operating System jvm=JVM +delegations=Delegations +endDelegation=End Delegation diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties index e6c61056e4f..ac5c1f23e08 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties @@ -23,3 +23,5 @@ hostname=\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430 processors=\u041f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b os=\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 jvm=JVM +delegations=Delegations +endDelegation=End Delegation diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html new file mode 100644 index 00000000000..ec8232eccb1 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html @@ -0,0 +1,42 @@ + + + + +
    +
    +
    +
    +
    +
    + [FILTER] + + + +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Dashboard_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Dashboard_fr_CA.properties new file mode 100644 index 00000000000..17fd01b19b0 --- /dev/null +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Dashboard_fr_CA.properties @@ -0,0 +1,19 @@ +# 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. +overview=Résumé +control=Contrôle +accessTokens=Jetons d'accès diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html index bb5cd3aa099..973c1bc88a1 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html +++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html @@ -27,16 +27,33 @@ - + + + + + - + + +
    +
    + + +
    +
    + + +
    +
    -
    +