diff --git a/dbflute_fess/dfprop/esfluteMap.dfprop b/dbflute_fess/dfprop/esfluteMap.dfprop index d1287c8017..f7d3ab560b 100644 --- a/dbflute_fess/dfprop/esfluteMap.dfprop +++ b/dbflute_fess/dfprop/esfluteMap.dfprop @@ -129,6 +129,11 @@ map:{ ; esclientDiFile = esclient.xml ; esfluteDiFile = esflute_config.xml } + ; fess_config.tag_type = map:{ + ; package = config + ; esclientDiFile = esclient.xml + ; esfluteDiFile = esflute_config.xml + } ; fess_config.thumbnail_queue = map:{ ; package = config ; esclientDiFile = esclient.xml diff --git a/src/main/config/es/fess_config_label_type.json b/src/main/config/es/fess_config_label_type.json index b3daff8835..65abd1438c 100644 --- a/src/main/config/es/fess_config_label_type.json +++ b/src/main/config/es/fess_config_label_type.json @@ -16,9 +16,6 @@ "includedPaths" : { "type" : "keyword" }, - "kind" : { - "type" : "keyword" - }, "name" : { "type" : "keyword" }, @@ -65,4 +62,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/main/config/es/fess_config_tag_type.json b/src/main/config/es/fess_config_tag_type.json new file mode 100644 index 0000000000..19c144b0ca --- /dev/null +++ b/src/main/config/es/fess_config_tag_type.json @@ -0,0 +1,65 @@ +{ + "fess_config.tag_type" : { + "aliases" : { }, + "mappings" : { + "tag_type" : { + "properties" : { + "createdBy" : { + "type" : "keyword" + }, + "createdTime" : { + "type" : "long" + }, + "name" : { + "type" : "keyword" + }, + "owner" : { + "type" : "keyword" + }, + "paths" : { + "type" : "keyword" + }, + "permissions" : { + "type" : "keyword" + }, + "sortOrder" : { + "type" : "integer" + }, + "updatedBy" : { + "type" : "keyword" + }, + "updatedTime" : { + "type" : "long" + }, + "virtualHost" : { + "type" : "keyword" + } + } + } + }, + "settings" : { + "index" : { + "dbflute" : { + "tag_type" : { + "properties" : { + "paths" : { + "array" : "true" + }, + "permissions" : { + "array" : "true" + } + } + } + }, + "creation_date" : "1509021055215", + "number_of_shards" : "5", + "number_of_replicas" : "1", + "uuid" : "Zylt7BsVRhiOasQfUWBg0w", + "version" : { + "created" : "6000051" + }, + "provided_name" : "fess_config.tag_type" + } + } + } +} diff --git a/src/main/config/openapi/v2/openapi-user.yaml b/src/main/config/openapi/v2/openapi-user.yaml index 97fa78f4a1..22225e748e 100644 --- a/src/main/config/openapi/v2/openapi-user.yaml +++ b/src/main/config/openapi/v2/openapi-user.yaml @@ -72,7 +72,13 @@ tags: - name: favorite description: Favorite operations - name: tag - description: Tags that users add to documents + description: |- + Tags of logged-in users. Each user creates and manages their own tags + and puts them on documents; a shared tag of another user can be seen + and filtered on but not changed. Every tag endpoint answers + `invalid_request` (400) while `user.tag.enabled` is false, and + `auth_required` (401) to a caller without a login session: an access + token never stands in for a login here. - name: auth description: Authentication and session operations - name: ui @@ -1088,6 +1094,122 @@ paths: '415': { $ref: '#/components/responses/UnsupportedMediaType' } '500': { $ref: '#/components/responses/InternalServerError' } + /tags: + get: + tags: [tag] + summary: List the caller's tags + operationId: tagsListV2 + responses: + '200': + description: The caller's tags, by sort order and name. + content: + application/json: + schema: + $ref: '#/components/schemas/TagsListResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + '500': { $ref: '#/components/responses/InternalServerError' } + post: + tags: [tag] + summary: Create a tag + description: |- + Creates a tag of the caller. The name is NFKC-normalized, whitespace + runs collapse to one space and the ends are trimmed; it then has 1 to + `user.tag.name.max.length` characters and no control or format + characters. A caller has at most `user.tag.max.tags` tags + (`invalid_request`). A tag of the same name of the caller already + existing is `conflict` (409); the same name of another user is a + different tag. + operationId: tagsCreateV2 + parameters: + - { $ref: '#/components/parameters/CsrfHeader' } + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagCreateRequest' + responses: + '200': + description: Tag created. + content: + application/json: + schema: + $ref: '#/components/schemas/TagResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + '409': { $ref: '#/components/responses/Conflict' } + '413': { $ref: '#/components/responses/PayloadTooLarge' } + '415': { $ref: '#/components/responses/UnsupportedMediaType' } + '500': { $ref: '#/components/responses/InternalServerError' } + + /tags/{tagId}: + parameters: + - { $ref: '#/components/parameters/TagId' } + put: + tags: [tag] + summary: Rename a tag or change whether it is shared + description: |- + Only the owner can change a tag: the shared tag of another user is + `forbidden` (403) and a tag the caller cannot see is `not_found` (404). + A new `name` renames the tag, which gives it a new `id` and `value`; + the documents carrying the old value get the new one when the tag + queue is next processed (about a minute). Renaming to a name the + caller already uses is `conflict` (409) and leaves the tag unchanged. + `shared` only changes who sees the tag. A write that keeps losing a + race with another writer is also `conflict`. + operationId: tagsUpdateV2 + parameters: + - { $ref: '#/components/parameters/CsrfHeader' } + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagUpdateRequest' + responses: + '200': + description: Tag updated. + content: + application/json: + schema: + $ref: '#/components/schemas/TagUpdateResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + '409': { $ref: '#/components/responses/Conflict' } + '413': { $ref: '#/components/responses/PayloadTooLarge' } + '415': { $ref: '#/components/responses/UnsupportedMediaType' } + '500': { $ref: '#/components/responses/InternalServerError' } + delete: + tags: [tag] + summary: Delete a tag + description: |- + Deletes a tag of the caller. The documents lose its value when the tag + queue is next processed. The shared tag of another user is `forbidden` + (403) and a tag the caller cannot see is `not_found` (404). + operationId: tagsDeleteV2 + parameters: + - { $ref: '#/components/parameters/CsrfHeader' } + responses: + '200': + description: Tag deleted. + content: + application/json: + schema: + $ref: '#/components/schemas/TagDeleteResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '404': { $ref: '#/components/responses/NotFound' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + '500': { $ref: '#/components/responses/InternalServerError' } + /documents/{docId}/tags: parameters: - { $ref: '#/components/parameters/DocId' } @@ -1095,15 +1217,14 @@ paths: tags: [tag] summary: List the tags of a document description: |- - Lists the tags of the document that the caller can see. A tag is a - label of the kind `tag`: its name is the tag name, its included paths - list the tagged URLs and its permissions decide who can see it. - Answers `400` while `user.tag.enabled` is false, and `404` when the - caller cannot see the document. - operationId: documentTagsGetV2 + Returns the tags on the document's URL that the caller can see and + the caller's own tags that are not on it. The document is resolved + with the caller's roles; a document the caller cannot search is + `not_found` (404). + operationId: documentTagsListV2 responses: '200': - description: Tags returned. + description: Tags of the document. content: application/json: schema: @@ -1115,16 +1236,16 @@ paths: '500': { $ref: '#/components/responses/InternalServerError' } post: tags: [tag] - summary: Tag a document + summary: Put a tag of the caller on a document description: |- - Tags the document's URL for the logged-in user; an access token does - not stand in for a login. The name is NFKC-normalized, whitespace is - collapsed and it is trimmed. When a tag of the name exists, the URL is - added to its included paths and the user to its permissions; otherwise - a tag that only the user can see is created. Tagging again answers - `added: false`. `user.tag.max.document.tags` limits the tags of one - document. - operationId: documentTagsPostV2 + Adds the document's URL to a tag of the caller, given by `id`, or by + `name`, in which case a private tag of the name is created when the + caller has none (counted against `user.tag.max.tags`). A tag holds at + most `user.tag.max.paths` URLs (`invalid_request`). Another user's + tag is `forbidden` (403) or, when not visible, `not_found` (404). + Every indexed document of the URL gets the tag when the tag queue is + next processed (about a minute); the tag endpoints show it at once. + operationId: documentTagsAddV2 parameters: - { $ref: '#/components/parameters/CsrfHeader' } requestBody: @@ -1132,10 +1253,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DocumentTagPostRequest' + $ref: '#/components/schemas/DocumentTagAddRequest' responses: '200': - description: Document tagged, or already tagged by the user. + description: The tag is on the document. content: application/json: schema: @@ -1145,28 +1266,27 @@ paths: '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '405': { $ref: '#/components/responses/MethodNotAllowed' } + '409': { $ref: '#/components/responses/Conflict' } '413': { $ref: '#/components/responses/PayloadTooLarge' } '415': { $ref: '#/components/responses/UnsupportedMediaType' } '500': { $ref: '#/components/responses/InternalServerError' } + + /documents/{docId}/tags/{tagId}: + parameters: + - { $ref: '#/components/parameters/DocId' } + - { $ref: '#/components/parameters/TagId' } delete: tags: [tag] - summary: Remove the caller from a tag + summary: Take a tag of the caller off a document description: |- - Removes the logged-in user from the permissions of the tag. The tag is - deleted, and removed from the documents, when no permission of a user, - a group or a role is left. Answers `403` when the user is not in the - permissions of the tag. - operationId: documentTagsDeleteV2 + Removes the document's URL from a tag of the caller. Another user's + tag is `forbidden` (403) or, when not visible, `not_found` (404). + operationId: documentTagsRemoveV2 parameters: - { $ref: '#/components/parameters/CsrfHeader' } - - name: value - in: query - required: true - description: The tag value. - schema: { type: string } responses: '200': - description: The user was removed from the tag. + description: The tag is off the document. content: application/json: schema: @@ -1176,6 +1296,7 @@ paths: '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '405': { $ref: '#/components/responses/MethodNotAllowed' } + '409': { $ref: '#/components/responses/Conflict' } '500': { $ref: '#/components/responses/InternalServerError' } /cache/{docId}: @@ -1223,6 +1344,12 @@ components: description: Document identifier (matches `[A-Za-z0-9_-]+`). required: true schema: { type: string, minLength: 1, maxLength: 512, pattern: '^[A-Za-z0-9_-]+$' } + TagId: + name: tagId + in: path + description: Tag id, the SHA-256 of the tag value in lowercase hex. + required: true + schema: { type: string, pattern: '^[0-9a-f]{64}$' } CsrfHeader: name: X-Fess-CSRF-Token in: header @@ -1255,6 +1382,13 @@ components: content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } + Conflict: + description: |- + The request conflicts with the current state (e.g. a tag of the name + already exists, or the tag kept changing concurrently). + content: + application/json: + schema: { $ref: '#/components/schemas/ErrorEnvelope' } MethodNotAllowed: description: |- HTTP method is not supported for this endpoint. The `Allow` header @@ -1480,12 +1614,28 @@ components: name: { type: string } result: type: array + description: |- + For the `tag` field, only the tags the caller can see, + each with `label`, `owner`, `mine` and `shared`; a + caller without a login gets no `tag` facet. items: type: object required: [value, count] properties: value: { type: string } count: { type: integer, format: int64 } + label: + type: string + description: '`tag` facet only: the tag name.' + owner: + type: string + description: '`tag` facet only: the user who owns the tag.' + mine: + type: boolean + description: '`tag` facet only: whether the caller owns the tag.' + shared: + type: boolean + description: '`tag` facet only: whether every logged-in user sees the tag.' facet_query: type: array description: Present only when facet queries were requested. @@ -1552,14 +1702,6 @@ components: description: Stored as the string `"true"`/`"false"`, not a JSON boolean. click_count: { type: integer, format: int64 } favorite_count: { type: integer, format: int64 } - tags: - type: array - description: |- - The tags of the document that the caller can see. Present only while - `user.tag.enabled` is true and the document has such tags. The `tag` - index field itself is not returned. - items: - $ref: '#/components/schemas/DocumentTag' similar_docs_count: type: integer format: int64 @@ -1571,8 +1713,31 @@ components: description: |- Present only when result collapsing is enabled. Never present on `/documents/all`. + tags: + type: array + description: |- + `/search` only, while `user.tag.enabled` is true: the tags on the + document that the caller can see. Absent when there is none, and + always absent for a caller without a login. The raw `tag` field is + never returned. + items: + $ref: '#/components/schemas/HitTag' additionalProperties: true + HitTag: + type: object + required: [value, name, owner, mine, shared] + properties: + value: + type: string + description: |- + Tag value, `base64url(name):base64url(owner)` without padding; + filter with `fields.tag=`. + name: { type: string } + owner: { type: string, description: User who owns the tag. } + mine: { type: boolean, description: Whether the caller owns the tag. } + shared: { type: boolean, description: Whether every logged-in user sees the tag. } + # --- Suggest / labels / popular --------------------------------- SuggestWordsResponse: type: object @@ -1748,6 +1913,7 @@ components: - user_favorite - search_history - search_export + - user_tag - popular_word - suggest_search_log - suggest_documents @@ -1780,8 +1946,8 @@ components: user_tag: type: boolean description: |- - `true` when users can see and add tags - (`/documents/{docId}/tags`, `user.tag.enabled`). + `true` when user tags are enabled (`user.tag.enabled`): + the tag endpoints answer and search hits carry `tags`. popular_word: { type: boolean } suggest_search_log: { type: boolean } suggest_documents: { type: boolean } @@ -2324,27 +2490,102 @@ components: Absent on the first POST that newly records the favorite. # --- Tags -------------------------------------------------------- + TagListItem: + type: object + required: [id, value, name, shared, sort_order, path_count] + properties: + id: { type: string, description: 'Tag id (SHA-256 of `value`, lowercase hex).' } + value: { type: string, description: 'Tag value, `base64url(name):base64url(owner)`.' } + name: { type: string } + shared: { type: boolean, description: Whether every logged-in user sees the tag. } + sort_order: { type: integer } + path_count: { type: integer, format: int64, description: Number of URLs the tag is on. } + DocumentTag: type: object - required: [value, name, mine] + required: [id, value, name, owner, mine, shared] properties: - value: - type: string - description: The value of the label of the tag; filter results with `fields.tag=`. + id: { type: string, description: 'Tag id (SHA-256 of `value`, lowercase hex).' } + value: { type: string, description: 'Tag value, `base64url(name):base64url(owner)`.' } name: { type: string } - mine: - type: boolean - description: '`true` when the caller is in the permissions of the tag.' + owner: { type: string, description: User who owns the tag. } + mine: { type: boolean, description: Whether the caller owns the tag. } + shared: { type: boolean, description: Whether every logged-in user sees the tag. } + + TagsListResponse: + type: object + required: [response] + properties: + response: + allOf: + - $ref: '#/components/schemas/EnvelopeMeta' + - type: object + required: [tags] + properties: + tags: + type: array + items: { $ref: '#/components/schemas/TagListItem' } - DocumentTagPostRequest: + TagCreateRequest: type: object required: [name] properties: - name: - type: string - description: |- - 1 to `user.tag.name.max.length` (default 50) characters, without a - control or format character. + name: { type: string, description: 'Tag name, 1 to `user.tag.name.max.length` characters after normalization.' } + shared: { type: boolean, default: false, description: Whether every logged-in user sees the tag. } + + TagUpdateRequest: + type: object + description: At least one of `name` and `shared`. + properties: + name: { type: string, description: New tag name. } + shared: { type: boolean } + + TagResponse: + type: object + required: [response] + properties: + response: + allOf: + - $ref: '#/components/schemas/EnvelopeMeta' + - type: object + required: [tag] + properties: + tag: { $ref: '#/components/schemas/TagListItem' } + + TagUpdateResponse: + type: object + required: [response] + properties: + response: + allOf: + - $ref: '#/components/schemas/EnvelopeMeta' + - type: object + required: [tag, renamed] + properties: + tag: { $ref: '#/components/schemas/TagListItem' } + renamed: + type: boolean + description: Whether the tag was renamed, in which case `tag.id` and `tag.value` are new. + + TagDeleteResponse: + type: object + required: [response] + properties: + response: + allOf: + - $ref: '#/components/schemas/EnvelopeMeta' + - type: object + required: [id, deleted] + properties: + id: { type: string } + deleted: { type: boolean, enum: [true] } + + DocumentTagAddRequest: + type: object + description: Either `id` (a tag of the caller) or `name`; `id` wins when both are given. + properties: + id: { type: string, pattern: '^[0-9a-f]{64}$' } + name: { type: string } DocumentTagsResponse: type: object @@ -2354,22 +2595,27 @@ components: allOf: - $ref: '#/components/schemas/EnvelopeMeta' - type: object - required: [doc_id, addable, tags] + required: [doc_id, tags, addable] properties: doc_id: { type: string } - addable: - type: boolean - description: '`true` when the caller is logged in.' added: type: boolean - description: POST only. `false` when the user had already tagged the document. + description: '`POST` only: `false` when the tag was already on the document.' + tag: + allOf: + - $ref: '#/components/schemas/DocumentTag' + description: '`POST` only: the tag that was put on the document.' removed: type: boolean - description: DELETE only. + description: '`DELETE` only: `false` when the tag was not on the document.' tags: type: array - items: - $ref: '#/components/schemas/DocumentTag' + description: Tags on the document's URL that the caller can see. + items: { $ref: '#/components/schemas/DocumentTag' } + addable: + type: array + description: The caller's tags that are not on the document's URL. + items: { $ref: '#/components/schemas/DocumentTag' } # --- Cache ------------------------------------------------------- CacheResponse: diff --git a/src/main/java/org/codelibs/fess/api/v2/SearchApiV2Manager.java b/src/main/java/org/codelibs/fess/api/v2/SearchApiV2Manager.java index 52e3626f35..87eb980467 100644 --- a/src/main/java/org/codelibs/fess/api/v2/SearchApiV2Manager.java +++ b/src/main/java/org/codelibs/fess/api/v2/SearchApiV2Manager.java @@ -27,8 +27,8 @@ import org.codelibs.fess.api.v2.handlers.ChatSessionClearHandler; import org.codelibs.fess.api.v2.handlers.ChatStreamHandler; import org.codelibs.fess.api.v2.handlers.ClickHandler; -import org.codelibs.fess.api.v2.handlers.DocumentTagsHandler; import org.codelibs.fess.api.v2.handlers.CsrfRequirement; +import org.codelibs.fess.api.v2.handlers.DocumentTagsHandler; import org.codelibs.fess.api.v2.handlers.ExportSearchHandler; import org.codelibs.fess.api.v2.handlers.FavoriteGetHandler; import org.codelibs.fess.api.v2.handlers.FavoritePostHandler; @@ -47,6 +47,7 @@ import org.codelibs.fess.api.v2.handlers.SearchHandler; import org.codelibs.fess.api.v2.handlers.SearchHistoryHandler; import org.codelibs.fess.api.v2.handlers.SuggestWordsHandler; +import org.codelibs.fess.api.v2.handlers.TagsHandler; import org.codelibs.fess.api.v2.handlers.UiConfigHandler; import org.codelibs.fess.app.service.AccessTokenService; import org.codelibs.fess.app.web.base.login.FessLoginAssist; @@ -103,7 +104,11 @@ public class SearchApiV2Manager extends BaseApiManager { @Resource protected FavoritePostHandler favoritePostHandler; - /** Handles {@code GET/POST/DELETE /api/v2/documents/{id}/tags} (list, add and remove user tags). */ + /** Handles {@code GET/POST /api/v2/tags} and {@code PUT/DELETE /api/v2/tags/{id}} (the caller's own tags). */ + @Resource + protected TagsHandler tagsHandler; + + /** Handles {@code GET/POST /api/v2/documents/{id}/tags} and {@code DELETE /api/v2/documents/{id}/tags/{tagId}}. */ @Resource protected DocumentTagsHandler documentTagsHandler; @@ -307,11 +312,16 @@ public void process(final HttpServletRequest request, final HttpServletResponse } return; } - // "/documents/tags" leaves no doc id between the two ends, like "/documents/favorite" above. - if (sub.startsWith("/documents/") && sub.endsWith("/tags") && sub.length() > "/documents/".length() + "/tags".length()) { - final String docId = sub.substring("/documents/".length(), sub.length() - "/tags".length()); - documentTagsHandler.handle(request, response, docId); - return; + // "/documents/{docId}/tags" and "/documents/{docId}/tags/{tagId}". The doc id and the tag + // id are single non-empty segments; anything else under /documents/ falls through to the + // not_found arm below. + if (sub.startsWith("/documents/")) { + final String[] segments = sub.substring("/documents/".length()).split("/", -1); + if ((segments.length == 2 || segments.length == 3) && !segments[0].isEmpty() && "tags".equals(segments[1]) + && (segments.length == 2 || !segments[2].isEmpty())) { + documentTagsHandler.handle(request, response, segments[0], segments.length == 3 ? segments[2] : null); + return; + } } if (sub.startsWith("/cache/")) { final String docId = sub.substring("/cache/".length()); @@ -331,6 +341,15 @@ public void process(final HttpServletRequest request, final HttpServletResponse ComponentUtil.getV2EnvelopeWriter().writeError(response, V2ErrorCode.NOT_FOUND, "unknown action on document: " + sub); return; } + // "/tags" and "/tags/{tagId}": the tag id is one non-empty segment. + if ("/tags".equals(sub)) { + tagsHandler.handle(request, response, null); + return; + } + if (sub.startsWith("/tags/") && sub.indexOf('/', "/tags/".length()) < 0) { + tagsHandler.handle(request, response, sub.substring("/tags/".length())); + return; + } switch (sub) { case "/health" -> healthHandler.handle(request, response); case "/search" -> searchHandler.handle(request, response); diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/AbstractTagHandler.java b/src/main/java/org/codelibs/fess/api/v2/handlers/AbstractTagHandler.java new file mode 100644 index 0000000000..636cac6023 --- /dev/null +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/AbstractTagHandler.java @@ -0,0 +1,315 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.api.v2.handlers; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.api.v2.V2ErrorCode; +import org.codelibs.fess.app.service.TagTypeService; +import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; +import org.codelibs.fess.entity.TagChange; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.helper.TagTypeHelper; +import org.codelibs.fess.mylasta.action.FessUserBean; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.codelibs.fess.util.ComponentUtil; +import org.dbflute.optional.OptionalThing; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * Shared parts of the user tag endpoints ({@link TagsHandler} and {@link DocumentTagsHandler}). + * + *

Every tag endpoint needs {@code user.tag.enabled} and a logged-in user: the user of the login session, which an + * access token never stands in for. A caller changes only the tags it owns ({@code owner} equals + * {@link FessUserBean#getUserId()}); the tag of someone else is reported as {@link V2ErrorCode#FORBIDDEN} when the + * caller can see it and as {@link V2ErrorCode#NOT_FOUND} otherwise, so that a hidden tag is not revealed.

+ */ +public abstract class AbstractTagHandler { + + private static final Logger logger = LogManager.getLogger(AbstractTagHandler.class); + + /** The request bodies hold a short name, an id and a flag. */ + protected static final int MAX_BODY_BYTES = 1024; + + /** How many times an update that lost a race with another writer is read and applied again. */ + protected static final int MAX_UPDATE_ATTEMPTS = 3; + + /** A tag type id: the SHA-256 of the tag value in lowercase hex. */ + private static final Pattern TAG_ID_PATTERN = Pattern.compile("[0-9a-f]{64}"); + + /** + * Default constructor. + */ + protected AbstractTagHandler() { + // no-op + } + + /** A failure that is answered with an error envelope. */ + protected static class TagRequestException extends Exception { + private static final long serialVersionUID = 1L; + + /** The error code of the envelope. */ + protected final transient V2ErrorCode code; + + /** + * Creates the failure. + * + * @param code the error code of the envelope + * @param message the error message of the envelope + */ + protected TagRequestException(final V2ErrorCode code, final String message) { + super(message, null, false, false); + this.code = code; + } + } + + /** A change applied to a freshly read tag type before it is written back. */ + @FunctionalInterface + protected interface TagTypeModifier { + /** + * Changes the tag type. + * + * @param tagType the tag type as stored, with its paths + * @return true if the tag type was changed and has to be written + * @throws TagRequestException if the change is refused + */ + boolean modify(TagType tagType) throws TagRequestException; + } + + /** + * Checks that user tags are enabled and returns the logged-in user. + * + * @return the user id of the caller + * @throws TagRequestException if user tags are disabled or nobody is logged in + */ + protected String checkRequest() throws TagRequestException { + if (!ComponentUtil.getFessConfig().isUserTagEnabled()) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "tag feature is not available"); + } + final String userId = getUserBean().map(FessUserBean::getUserId).filter(StringUtil::isNotBlank).orElse(null); + if (userId == null) { + throw new TagRequestException(V2ErrorCode.AUTH_REQUIRED, "login required"); + } + return userId; + } + + /** + * Reads the JSON body of the request. + * + * @param req the request + * @return the body + * @throws TagRequestException if the body is not a small JSON object + * @throws IOException if the body cannot be read + */ + protected Map readBody(final HttpServletRequest req) throws TagRequestException, IOException { + try { + return ComponentUtil.getV2JsonBody().read(req, MAX_BODY_BYTES); + } catch (final V2JsonBody.PayloadTooLargeException e) { + throw new TagRequestException(V2ErrorCode.PAYLOAD_TOO_LARGE, e.getMessage()); + } catch (final V2JsonBody.UnsupportedMediaTypeException e) { + throw new TagRequestException(V2ErrorCode.UNSUPPORTED_MEDIA_TYPE, e.getMessage()); + } catch (final V2JsonBody.MalformedJsonException e) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, e.getMessage()); + } + } + + /** + * Returns the normalized tag name of a request. + * + * @param name the name in the request body + * @return the normalized name + * @throws TagRequestException if the name is not a valid tag name + */ + protected String toTagName(final Object name) throws TagRequestException { + final String normalized = name instanceof final String value ? getTagTypeHelper().normalizeName(value).orElse(null) : null; + if (normalized == null) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, + "invalid tag name: enter 1 to " + ComponentUtil.getFessConfig().getUserTagNameMaxLengthAsInteger() + " characters"); + } + return normalized; + } + + /** + * Returns the {@code shared} flag of a request body. + * + * @param body the request body + * @return the flag, or null when the body has none + * @throws TagRequestException if the flag is not a boolean + */ + protected Boolean toShared(final Map body) throws TagRequestException { + final Object shared = body.get("shared"); + if (shared == null || shared instanceof Boolean) { + return (Boolean) shared; + } + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "shared must be a boolean"); + } + + /** + * Builds a new tag of the caller, checking {@code user.tag.max.tags}. The tag is not stored. + * + * @param name the normalized tag name + * @param userId the caller, who owns the tag + * @param shared whether the tag is shared + * @param paths the paths of the tag + * @return the tag type with its id set + * @throws TagRequestException if the caller has {@code user.tag.max.tags} tags + */ + protected TagType newTagType(final String name, final String userId, final boolean shared, final String[] paths) + throws TagRequestException { + final int maxTags = ComponentUtil.getFessConfig().getUserTagMaxTagsAsInteger(); + if (getTagTypeService().countByOwner(userId) >= maxTags) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "too many tags: a user can have up to " + maxTags + " tags"); + } + final TagTypeHelper helper = getTagTypeHelper(); + final long now = ComponentUtil.getSystemHelper().getCurrentTimeAsLong(); + final TagType tagType = new TagType(); + tagType.setName(name); + tagType.setOwner(userId); + tagType.setId(helper.toId(tagType.getTagValue())); + tagType.setPaths(paths); + tagType.setPermissions(helper.buildPermissions(userId, shared)); + tagType.setVirtualHost(ComponentUtil.getVirtualHostHelper().getVirtualHostKey()); + tagType.setSortOrder(0); + tagType.setCreatedBy(userId); + tagType.setCreatedTime(now); + tagType.setUpdatedBy(userId); + tagType.setUpdatedTime(now); + return tagType; + } + + /** + * Reads a tag type that the caller owns, with its paths. + * + * @param id the tag type id + * @param userId the caller + * @return the tag type + * @throws TagRequestException NOT_FOUND if there is no such tag or the caller cannot see it, FORBIDDEN if the + * caller can see it but does not own it + */ + protected TagType getOwnTagType(final String id, final String userId) throws TagRequestException { + final TagType tagType = id != null && TAG_ID_PATTERN.matcher(id).matches() ? getTagTypeService().getTagType(id).orElse(null) : null; + if (tagType == null) { + throw new TagRequestException(V2ErrorCode.NOT_FOUND, "tag not found"); + } + if (!userId.equals(tagType.getOwner())) { + final String value = tagType.getTagValue(); + if (getTagTypeHelper().getVisibleTagTypes(List.of(value), SearchRequestType.JSON).containsKey(value)) { + throw new TagRequestException(V2ErrorCode.FORBIDDEN, "the tag is not yours"); + } + throw new TagRequestException(V2ErrorCode.NOT_FOUND, "tag not found"); + } + return tagType; + } + + /** + * Reads a tag type the caller owns, changes it and writes it back. A write that lost a race with another writer + * is retried on a fresh read, so that a concurrent change of the paths is not overwritten. + * + * @param id the tag type id + * @param userId the caller + * @param modifier the change + * @return the tag type as written, or as read when the modifier changed nothing + * @throws TagRequestException if the tag is not the caller's, the change is refused, or every attempt lost a race + */ + protected TagType updateTagType(final String id, final String userId, final TagTypeModifier modifier) throws TagRequestException { + for (int attempt = 1;; attempt++) { + final TagType tagType = getOwnTagType(id, userId); + if (!modifier.modify(tagType)) { + return tagType; + } + tagType.setUpdatedBy(userId); + tagType.setUpdatedTime(ComponentUtil.getSystemHelper().getCurrentTimeAsLong()); + try { + getTagTypeService().update(tagType); + return tagType; + } catch (final TagTypeConflictException e) { + if (attempt >= MAX_UPDATE_ATTEMPTS) { + logger.warn("Failed to update the tag after {} attempts: id={}", attempt, id, e); + throw new TagRequestException(V2ErrorCode.CONFLICT, "the tag was changed concurrently; try again"); + } + if (logger.isDebugEnabled()) { + logger.debug("The tag was changed concurrently; retrying: id={}, attempt={}", id, attempt); + } + } + } + } + + /** + * Queues a change of the tag field of the documents. + * + * @param change the change + */ + protected void enqueue(final TagChange change) { + if (ComponentUtil.getFessConfig().isUserTagEnabled()) { + getTagTypeHelper().enqueue(change); + } + } + + /** + * Builds the JSON of a tag as a document or a search hit shows it. + * + * @param tagType the tag type + * @param userId the caller + * @return {@code {id, value, name, owner, mine, shared}} + */ + protected Map toDocumentTag(final TagType tagType, final String userId) { + final String value = tagType.getTagValue(); + final Map map = new LinkedHashMap<>(); + map.put("id", getTagTypeHelper().toId(value)); + map.put("value", value); + map.put("name", tagType.getName()); + map.put("owner", tagType.getOwner()); + map.put("mine", userId.equals(tagType.getOwner())); + map.put("shared", getTagTypeHelper().isShared(tagType)); + return map; + } + + /** + * Returns the tag type helper. + * + * @return the helper + */ + protected TagTypeHelper getTagTypeHelper() { + return ComponentUtil.getTagTypeHelper(); + } + + /** + * Returns the tag type service. Exposed as a seam for unit tests. + * + * @return the service + */ + protected TagTypeService getTagTypeService() { + return ComponentUtil.getComponent(TagTypeService.class); + } + + /** + * Returns the user of the login session. An access token is not a login session. Exposed as a seam for unit + * tests. + * + * @return the logged-in user + */ + protected OptionalThing getUserBean() { + return ComponentUtil.getFessLoginAssist().getSavedUserBean(); + } +} diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/CsrfRequirement.java b/src/main/java/org/codelibs/fess/api/v2/handlers/CsrfRequirement.java index 446d0e3660..6fee937f56 100644 --- a/src/main/java/org/codelibs/fess/api/v2/handlers/CsrfRequirement.java +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/CsrfRequirement.java @@ -89,9 +89,6 @@ public boolean requiresCsrf(final String subPath, final String method) { if (subPath.startsWith("/documents/") && subPath.endsWith("/favorite")) { return true; } - if (subPath.startsWith("/documents/") && subPath.endsWith("/tags")) { - return true; - } if ("/chat".equals(subPath)) { return true; } diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/DocumentTagsHandler.java b/src/main/java/org/codelibs/fess/api/v2/handlers/DocumentTagsHandler.java index 9129b77dca..59945a423e 100644 --- a/src/main/java/org/codelibs/fess/api/v2/handlers/DocumentTagsHandler.java +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/DocumentTagsHandler.java @@ -17,52 +17,51 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codelibs.core.lang.StringUtil; import org.codelibs.fess.api.v2.V2ErrorCode; import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; -import org.codelibs.fess.helper.LabelTypeHelper.LabelTypeItem; -import org.codelibs.fess.helper.TagHelper; -import org.codelibs.fess.helper.TagHelper.AddResult; -import org.codelibs.fess.mylasta.action.FessUserBean; +import org.codelibs.fess.entity.TagChange; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.helper.TagTypeHelper; import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.config.exentity.TagType; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.DocumentUtil; -import org.dbflute.optional.OptionalThing; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** - * Handles {@code GET}, {@code POST} and {@code DELETE /api/v2/documents/{docId}/tags}. + * Handles the tags of one document: {@code GET/POST /api/v2/documents/{docId}/tags} and + * {@code DELETE /api/v2/documents/{docId}/tags/{id}}. * - *

A tag is a label type of the kind {@code tag}: its name is the tag name, its included paths list the tagged - * URLs and its permissions list who can see it.

+ *

A tag is put on the {@code url} of the document: the URL is added to the paths of the tag and the change of the + * tag field of every document of the URL is queued. The document is resolved with the caller's roles, so a document + * the caller cannot search is not found.

* *
    - *
  • {@code GET} lists the tags of the document that the caller can see, and whether the caller added each.
  • - *
  • {@code POST} with {@code {"name": "..."}} tags the document for the logged-in user: the URL is added to the - * tag of the name and the user to its permissions, and a new tag is visible only to the user.
  • - *
  • {@code DELETE ?value=} removes the logged-in user from the permissions of the tag. The tag is - * deleted when no permission is left.
  • + *
  • {@code GET} lists the tags on the document that the caller can see ({@code tags}) and the caller's own tags + * that are not on it ({@code addable}).
  • + *
  • {@code POST} with {@code {"id": "..."}} puts the caller's tag on the document; with {@code {"name": "..."}} it + * puts the caller's tag of the name on it, creating a private tag when there is none. A tag holds at most + * {@code user.tag.max.paths} URLs.
  • + *
  • {@code DELETE .../tags/{id}} takes the caller's tag off the document.
  • *
- * - *

The document is resolved through the caller's roles, so a document the caller cannot search cannot be - * tagged. Writes need a logged-in user; an access token does not stand in for one.

*/ -public class DocumentTagsHandler { +public class DocumentTagsHandler extends AbstractTagHandler { private static final Logger logger = LogManager.getLogger(DocumentTagsHandler.class); - /** The request body has one short string. */ - private static final int MAX_BODY_BYTES = 1024; - /** * Default constructor used by the DI container. The handler holds no per-request state. */ @@ -70,157 +69,159 @@ public DocumentTagsHandler() { // no-op } - /** A failure that is answered with an error envelope. */ - private static class TagRequestException extends Exception { - private static final long serialVersionUID = 1L; - - private final V2ErrorCode code; - - TagRequestException(final V2ErrorCode code, final String message) { - super(message, null, false, false); - this.code = code; - } - } - /** - * Processes one {@code /api/v2/documents/{docId}/tags} request. + * Processes one {@code /api/v2/documents/{docId}/tags} or {@code /api/v2/documents/{docId}/tags/{id}} request. * * @param req the incoming HTTP request * @param res the HTTP response to write to - * @param docId the document id extracted from the URL path + * @param docId the document id from the URL path + * @param tagId the tag id of {@code .../tags/{id}}, or null for {@code .../tags} * @throws IOException if writing the envelope fails */ - public void handle(final HttpServletRequest req, final HttpServletResponse res, final String docId) throws IOException { + public void handle(final HttpServletRequest req, final HttpServletResponse res, final String docId, final String tagId) + throws IOException { final String method = req.getMethod() == null ? StringUtil.EMPTY : req.getMethod().toUpperCase(Locale.ROOT); - if (!"GET".equals(method) && !"POST".equals(method) && !"DELETE".equals(method)) { - res.setHeader("Allow", "GET, POST, DELETE"); + final boolean collection = tagId == null; + if (collection ? !"GET".equals(method) && !"POST".equals(method) : !"DELETE".equals(method)) { + res.setHeader("Allow", collection ? "GET, POST" : "DELETE"); ComponentUtil.getV2EnvelopeWriter().writeError(res, V2ErrorCode.METHOD_NOT_ALLOWED, "method not allowed"); return; } - final String context = "/api/v2/documents/" + docId + "/tags " + method; + if (!ComponentUtil.getV2DocIdValidator().isValid(docId)) { + ComponentUtil.getV2EnvelopeWriter().writeError(res, V2ErrorCode.INVALID_REQUEST, "invalid doc_id"); + return; + } try { - if (!ComponentUtil.getV2DocIdValidator().isValid(docId)) { - throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "invalid doc_id"); - } - final TagHelper tagHelper = ComponentUtil.getTagHelper(); - if (!tagHelper.isEnabled()) { - throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "tag feature is not available"); - } - final String userId = getUserBean().map(FessUserBean::getUserId).orElse(null); - if (!"GET".equals(method) && StringUtil.isBlank(userId)) { - throw new TagRequestException(V2ErrorCode.AUTH_REQUIRED, "login required"); - } + final String userId = checkRequest(); final String url = getDocumentUrl(docId); - final Map payload = new LinkedHashMap<>(); payload.put("doc_id", docId); switch (method) { - case "POST" -> payload.put("added", addTag(req, tagHelper, userId, url)); - case "DELETE" -> payload.put("removed", removeTag(req, tagHelper, userId)); + case "POST" -> addTag(req, userId, url, payload); + case "DELETE" -> payload.put("removed", removeTag(tagId, userId, url)); default -> { // GET lists the tags only } } - payload.put("addable", StringUtil.isNotBlank(userId)); - payload.put("tags", buildTags(req, tagHelper, userId, url)); + putTags(payload, userId, url); ComponentUtil.getV2EnvelopeWriter().writeSuccess(res, payload); } catch (final TagRequestException e) { ComponentUtil.getV2EnvelopeWriter().writeError(res, e.code, e.getMessage()); } catch (final Exception e) { - ComponentUtil.getV2EnvelopeWriter().writeInternalError(res, e, logger, context); + ComponentUtil.getV2EnvelopeWriter().writeInternalError(res, e, logger, "/api/v2/documents/" + docId + "/tags " + method); } } /** - * Validates the request body of a POST and tags the URL. - * - * @return true if the URL was tagged, false if the user had already tagged it + * Puts the caller's tag of the request body on the URL and adds {@code added} and {@code tag} to the payload. */ - private boolean addTag(final HttpServletRequest req, final TagHelper tagHelper, final String userId, final String url) + private void addTag(final HttpServletRequest req, final String userId, final String url, final Map payload) throws TagRequestException, IOException { - final Map body; - try { - body = ComponentUtil.getV2JsonBody().read(req, MAX_BODY_BYTES); - } catch (final V2JsonBody.PayloadTooLargeException e) { - throw new TagRequestException(V2ErrorCode.PAYLOAD_TOO_LARGE, e.getMessage()); - } catch (final V2JsonBody.UnsupportedMediaTypeException e) { - throw new TagRequestException(V2ErrorCode.UNSUPPORTED_MEDIA_TYPE, e.getMessage()); - } catch (final V2JsonBody.MalformedJsonException e) { - throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, e.getMessage()); - } - final Object name = body.get("name"); - final String normalizedName = name instanceof final String nameValue ? tagHelper.normalizeName(nameValue) : null; - if (normalizedName == null) { - throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, - "invalid tag name: enter 1 to " + tagHelper.getNameMaxLength() + " characters"); - } - final FessConfig fessConfig = ComponentUtil.getFessConfig(); - final AddResult result = tagHelper.addTag(userId, url, normalizedName); - switch (result) { - case TOO_MANY_TAGS -> throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, - "too many tags: a document can have up to " + fessConfig.getUserTagMaxDocumentTagsAsInteger() + " tags"); - case TOO_MANY_LABELS -> throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, - "no more tags can be created: the number of labels reached page.labeltype.max.fetch.size"); - case ALREADY_ADDED -> { - return false; + final Map body = readBody(req); + final String id; + if (body.get("id") instanceof final String value) { + id = value; + } else if (body.get("name") != null) { + final String name = toTagName(body.get("name")); + id = getTagTypeHelper().toId(getTagTypeHelper().toTagValue(name, userId)); + if (!getTagTypeService().getTagType(id).isPresent()) { + final TagType created = createTag(name, userId, url); + if (created != null) { + payload.put("added", true); + payload.put("tag", toDocumentTag(created, userId)); + return; + } + } + } else { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "name or id is required"); } - default -> { - updateDocuments(() -> tagHelper.addTagToDocuments(url, tagHelper.toValue(normalizedName)), url); + final boolean[] added = { false }; + final int maxPaths = ComponentUtil.getFessConfig().getUserTagMaxPathsAsInteger(); + final TagType tagType = updateTagType(id, userId, t -> { + final String[] paths = t.getPaths() == null ? new String[0] : t.getPaths(); + if (Arrays.asList(paths).contains(url)) { + return false; + } + if (paths.length >= maxPaths) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, + "too many documents: a tag can be put on up to " + maxPaths + " documents"); + } + final String[] newPaths = Arrays.copyOf(paths, paths.length + 1); + newPaths[paths.length] = url; + t.setPaths(newPaths); + added[0] = true; return true; + }); + if (added[0]) { + enqueue(TagChange.add(tagType.getTagValue(), url)); } - } + payload.put("added", added[0]); + payload.put("tag", toDocumentTag(tagType, userId)); } /** - * Removes the user from the permissions of the tag of the DELETE request. + * Creates a private tag of the caller on the URL and queues the addition. * - * @return true if the user was removed + * @return the tag, or null when a concurrent request created the tag of the name first */ - private boolean removeTag(final HttpServletRequest req, final TagHelper tagHelper, final String userId) throws TagRequestException { - final String value = req.getParameter("value"); - final LabelTypeItem item = StringUtil.isBlank(value) ? null - : getTagItemList(req).stream().filter(i -> value.equals(i.getValue())).findFirst().orElse(null); - if (item == null) { - throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "invalid tag value"); - } - if (!tagHelper.isMine(item, userId)) { - throw new TagRequestException(V2ErrorCode.FORBIDDEN, "the tag was not added by the user"); - } - if (tagHelper.removeTag(userId, value)) { - updateDocuments(() -> tagHelper.removeTagFromDocuments(value), value); + private TagType createTag(final String name, final String userId, final String url) throws TagRequestException { + final TagType tagType = newTagType(name, userId, false, new String[] { url }); + try { + getTagTypeService().insert(tagType); + } catch (final TagTypeConflictException e) { + if (logger.isDebugEnabled()) { + logger.debug("The tag was created concurrently; adding the URL to it: id={}", tagType.getId()); + } + return null; } - return true; + enqueue(TagChange.add(tagType.getTagValue(), url)); + return tagType; } /** - * Updates the tag field of the indexed documents. The label type is already stored, so a failure here is - * logged and the next crawl or the label updater job brings the documents up to date. + * Takes the caller's tag off the URL. + * + * @return true if the tag was on the URL */ - private void updateDocuments(final Runnable update, final String target) { - try { - update.run(); - } catch (final Exception e) { - logger.warn("Failed to update the tag field of the documents: target={}", target, e); + private boolean removeTag(final String tagId, final String userId, final String url) throws TagRequestException { + final boolean[] removed = { false }; + final TagType tagType = updateTagType(tagId, userId, t -> { + final String[] paths = t.getPaths() == null ? new String[0] : t.getPaths(); + final String[] newPaths = Arrays.stream(paths).filter(p -> !url.equals(p)).toArray(String[]::new); + if (newPaths.length == paths.length) { + return false; + } + t.setPaths(newPaths); + removed[0] = true; + return true; + }); + if (removed[0]) { + enqueue(TagChange.remove(tagType.getTagValue(), url)); } + return removed[0]; } - private List> buildTags(final HttpServletRequest req, final TagHelper tagHelper, final String userId, - final String url) { + /** + * Adds {@code tags}, the tags on the URL that the caller can see, and {@code addable}, the caller's tags that are + * not on the URL, to the payload. + */ + private void putTags(final Map payload, final String userId, final String url) { + final TagTypeHelper helper = getTagTypeHelper(); + final Set values = helper.findTagValuesByUrls(List.of(url)).getOrDefault(url, Collections.emptySet()); final List> tags = new ArrayList<>(); - for (final LabelTypeItem item : getTagItemList(req)) { - if (item.getUrlSet().contains(url)) { - final Map tag = new LinkedHashMap<>(); - tag.put("value", item.getValue()); - tag.put("name", item.getLabel()); - tag.put("mine", tagHelper.isMine(item, userId)); - tags.add(tag); + helper.getVisibleTagTypes(values, SearchRequestType.JSON).values().forEach(tagType -> tags.add(toDocumentTag(tagType, userId))); + final List> addable = new ArrayList<>(); + for (final TagType tagType : getTagTypeService().getTagTypeListByOwner(userId)) { + if (!values.contains(tagType.getTagValue())) { + addable.add(toDocumentTag(tagType, userId)); } } - return tags; + payload.put("tags", tags); + payload.put("addable", addable); } /** - * Returns the URL of the document, resolved through the caller's roles. + * Returns the URL of the document, resolved with the caller's roles. */ private String getDocumentUrl(final String docId) throws TagRequestException { final FessConfig fessConfig = ComponentUtil.getFessConfig(); @@ -242,25 +243,4 @@ private String getDocumentUrl(final String docId) throws TagRequestException { protected Map getDocument(final String docId, final String[] fields) { return ComponentUtil.getSearchHelper().getDocumentByDocId(docId, fields, getUserBean()).orElse(null); } - - /** - * Returns the tags that the caller can see. Exposed as a seam for unit tests. - * - * @param req the request - * @return the visible tags - */ - protected List getTagItemList(final HttpServletRequest req) { - final Locale locale = req.getLocale() == null ? Locale.ROOT : req.getLocale(); - return ComponentUtil.getLabelTypeHelper().getTagItemList(SearchRequestType.JSON, locale); - } - - /** - * Returns the user of the login session. An access token is not a login session. Exposed as a seam for unit - * tests. - * - * @return the logged-in user - */ - protected OptionalThing getUserBean() { - return ComponentUtil.getRequestManager().findUserBean(FessUserBean.class); - } } diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/SearchHandler.java b/src/main/java/org/codelibs/fess/api/v2/handlers/SearchHandler.java index 12c371a882..cc0f1819b7 100644 --- a/src/main/java/org/codelibs/fess/api/v2/handlers/SearchHandler.java +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/SearchHandler.java @@ -17,12 +17,14 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,12 +36,13 @@ import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; import org.codelibs.fess.exception.InvalidQueryException; import org.codelibs.fess.exception.ResultOffsetExceededException; -import org.codelibs.fess.helper.LabelTypeHelper.LabelTypeItem; import org.codelibs.fess.helper.RelatedContentHelper; import org.codelibs.fess.helper.RelatedQueryHelper; import org.codelibs.fess.helper.SearchHelper; +import org.codelibs.fess.helper.TagTypeHelper; import org.codelibs.fess.mylasta.action.FessUserBean; import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.config.exentity.TagType; import org.codelibs.fess.query.QueryFieldConfig; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.FacetResponse; @@ -108,7 +111,7 @@ public void handle(final HttpServletRequest request, final HttpServletResponse r final SearchHelper searchHelper = ComponentUtil.getSearchHelper(); final FessConfig fessConfig = ComponentUtil.getFessConfig(); final SearchRenderData data = new SearchRenderData(); - final V2JsonRequestParams params = new V2JsonRequestParams(request, fessConfig); + final V2JsonRequestParams params = createRequestParams(request, fessConfig); params.enableRedirect(); searchHelper.search(params, data, OptionalThing.empty()); // A search request rewriter can send the search elsewhere (e.g. a bang to another @@ -121,8 +124,8 @@ public void handle(final HttpServletRequest request, final HttpServletResponse r return; } final OptionalThing userBean = getSavedUserBean(); - final Map payload = - buildPayload(params.getQuery(), data, getTagItemMap(request), userBean.map(FessUserBean::getUserId).orElse(null)); + final String userId = userBean.map(FessUserBean::getUserId).filter(StringUtil::isNotBlank).orElse(null); + final Map payload = buildPayload(params.getQuery(), data, getVisibleTagTypes(data, userId), userId); // Evaluated per search: a user whose group and role permissions are still loading or // failed sees fewer results. payload.put("permission_state", @@ -148,6 +151,74 @@ public void handle(final HttpServletRequest request, final HttpServletResponse r } } + /** + * Creates the request parameters of the search. The tag field is fetched while user tags are + * enabled so that the hits can carry the tags the caller can see; it is added here rather than + * to the global response fields, which every other API returns as is. + * + * @param request the incoming HTTP request + * @param fessConfig the Fess configuration + * @return the request parameters + */ + protected V2JsonRequestParams createRequestParams(final HttpServletRequest request, final FessConfig fessConfig) { + return new V2JsonRequestParams(request, fessConfig) { + @Override + public String[] getResponseFields() { + final String[] fields = super.getResponseFields(); + if (!fessConfig.isUserTagEnabled()) { + return fields; + } + final Set fieldSet = new LinkedHashSet<>(Arrays.asList(fields)); + fieldSet.add(fessConfig.getIndexFieldTag()); + return fieldSet.toArray(new String[0]); + } + }; + } + + /** + * Returns the tags of the hits and the tag facet that the caller can see, looked up in one call. + * Nobody who is not logged in sees a tag. A hit can carry the tags of any number of users, so + * only the values among the tags the caller can see, at most {@code user.tag.visible.max.size} + * and computed once per request (the tag facet shares it), are looked up. + * + * @param data the search result + * @param userId the logged-in user, or null + * @return the visible tag types keyed by tag value + */ + Map getVisibleTagTypes(final SearchRenderData data, final String userId) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + if (userId == null || !fessConfig.isUserTagEnabled()) { + return Collections.emptyMap(); + } + final String tagField = fessConfig.getIndexFieldTag(); + final Set values = new LinkedHashSet<>(); + final List> docs = data.getDocumentItems(); + if (docs != null) { + docs.forEach(doc -> values.addAll(toStringList(doc.get(tagField)))); + } + final FacetResponse facetResponse = data.getFacetResponse(); + if (facetResponse != null && facetResponse.getFieldList() != null) { + facetResponse.getFieldList() + .stream() + .filter(field -> tagField.equals(field.getName())) + .forEach(field -> values.addAll(field.getValueCountMap().keySet())); + } + if (values.isEmpty()) { + return Collections.emptyMap(); + } + try { + final TagTypeHelper tagTypeHelper = ComponentUtil.getTagTypeHelper(); + values.retainAll(tagTypeHelper.getVisibleTagValues(SearchRequestType.JSON)); + if (values.isEmpty()) { + return Collections.emptyMap(); + } + return tagTypeHelper.getVisibleTagTypes(values, SearchRequestType.JSON); + } catch (final RuntimeException e) { + logger.warn("Failed to resolve the visible tags; the hits are returned without tags.", e); + return Collections.emptyMap(); + } + } + /** * Resolves the logged-in user. A seam so tests supply a user without the login subsystem; * a failed lookup counts as a guest. @@ -175,11 +246,11 @@ protected OptionalThing getSavedUserBean() { * * @param query the original {@code q} parameter (may be {@code null}) * @param data the populated render data returned by the search helper - * @param tagItemMap the tags that the caller can see, by value + * @param visibleTagTypes the tag types the caller can see, keyed by tag value * @param userId the logged-in user, or null * @return the ordered payload map ready for envelope serialization */ - private Map buildPayload(final String query, final SearchRenderData data, final Map tagItemMap, + private Map buildPayload(final String query, final SearchRenderData data, final Map visibleTagTypes, final String userId) { final RelatedQueryHelper relatedQueryHelper = ComponentUtil.getRelatedQueryHelper(); final RelatedContentHelper relatedContentHelper = ComponentUtil.getRelatedContentHelper(); @@ -207,11 +278,11 @@ private Map buildPayload(final String query, final SearchRenderD payload.put("requested_time", data.getRequestedTime()); payload.put("related_query", relatedQueryHelper.getRelatedQueries(query)); payload.put("related_contents", relatedContentHelper.getRelatedContents(query)); - payload.put("data", filterDocuments(data.getDocumentItems(), tagItemMap, userId)); + payload.put("data", filterDocuments(data.getDocumentItems(), visibleTagTypes, userId)); final FacetResponse facetResponse = data.getFacetResponse(); if (facetResponse != null && facetResponse.hasFacetResponse()) { - payload.put("facet_field", buildFacetField(facetResponse, tagItemMap)); + payload.put("facet_field", buildFacetField(facetResponse, visibleTagTypes, userId)); payload.put("facet_query", buildFacetQuery(facetResponse)); } return payload; @@ -224,21 +295,22 @@ private Map buildPayload(final String query, final SearchRenderD *

Drops blank keys and {@code null} values — matching v1's behavior so the * wire payload size stays stable across versions.

* - *

The {@code tag} field is not an API response field: the tags of the label types that the caller - * can see are returned as {@code tags}, a list of {@code {value, name, mine}}.

+ *

The raw {@code tag} field is never returned, even when + * {@code query.additional.api.response.fields} lists it: its values decode to the name and the + * owner of every tag on the document. The tags the caller can see are returned as {@code tags}, + * a list of {@code {value, name, owner, mine, shared}}, on a hit that has at least one.

* * @param docs the raw document items from {@link SearchRenderData} - * @param tagItemMap the tags that the caller can see, by value + * @param visibleTagTypes the tag types the caller can see, keyed by tag value * @param userId the logged-in user, or null * @return a new list of filtered, order-preserved document maps */ - List> filterDocuments(final List> docs, final Map tagItemMap, + List> filterDocuments(final List> docs, final Map visibleTagTypes, final String userId) { if (docs == null || docs.isEmpty()) { return new ArrayList<>(0); } final QueryFieldConfig cfg = ComponentUtil.getQueryFieldConfig(); - // never returned as is, even when query.additional.api.response.fields lists it final String tagField = ComponentUtil.getFessConfig().getIndexFieldTag(); final List> out = new ArrayList<>(docs.size()); for (final Map doc : docs) { @@ -249,15 +321,15 @@ List> filterDocuments(final List> docs, filtered.put(name, e.getValue()); } } - if (!tagItemMap.isEmpty()) { + if (!visibleTagTypes.isEmpty()) { final List> tags = new ArrayList<>(); for (final String value : toStringList(doc.get(tagField))) { - final LabelTypeItem item = tagItemMap.get(value); - if (item != null) { + final TagType tagType = visibleTagTypes.get(value); + if (tagType != null) { final Map tag = new LinkedHashMap<>(); tag.put("value", value); - tag.put("name", item.getLabel()); - tag.put("mine", ComponentUtil.getTagHelper().isMine(item, userId)); + tag.put("name", tagType.getName()); + putOwnership(tag, tagType, userId); tags.add(tag); } } @@ -270,6 +342,27 @@ List> filterDocuments(final List> docs, return out; } + /** Adds {@code owner}, {@code mine} and {@code shared} of a tag. */ + private static void putOwnership(final Map map, final TagType tagType, final String userId) { + map.put("owner", tagType.getOwner()); + map.put("mine", userId != null && userId.equals(tagType.getOwner())); + map.put("shared", ComponentUtil.getTagTypeHelper().isShared(tagType)); + } + + private static List toStringList(final Object value) { + final List list = new ArrayList<>(); + if (value instanceof final Collection collection) { + collection.forEach(v -> list.add(String.valueOf(v))); + } else if (value instanceof final Object[] array) { + for (final Object v : array) { + list.add(String.valueOf(v)); + } + } else if (value != null) { + list.add(value.toString()); + } + return list; + } + /** * Builds the {@code facet_field} array — one entry per facet field with the * field name and the value/count pairs nested inside a {@code result} array. @@ -281,18 +374,22 @@ List> filterDocuments(final List> docs, * @return a list of {@code {name, result:[{value, count}]}} maps */ List> buildFacetField(final FacetResponse facetResponse) { - return buildFacetField(facetResponse, Collections.emptyMap()); + return buildFacetField(facetResponse, Collections.emptyMap(), null); } /** - * Builds the {@code facet_field} array. The values of the {@code tag} facet are limited to the tags that the - * caller can see, so a tag is not revealed by its count, and each carries the tag name as {@code label}. + * Builds the {@code facet_field} array. A bucket of the {@code tag} facet is kept only for a tag + * the caller can see and carries its name as {@code label} with {@code owner}, {@code mine} and + * {@code shared}. * * @param facetResponse the populated facet response (never {@code null}) - * @param tagItemMap the tags that the caller can see, by value + * @param visibleTagTypes the tag types the caller can see, keyed by tag value + * @param userId the logged-in user, or null * @return a list of {@code {name, result:[{value, count}]}} maps */ - List> buildFacetField(final FacetResponse facetResponse, final Map tagItemMap) { + List> buildFacetField(final FacetResponse facetResponse, final Map visibleTagTypes, + final String userId) { + final String tagField = ComponentUtil.getFessConfig().getIndexFieldTag(); final List fields = facetResponse.getFieldList(); if (fields == null || fields.isEmpty()) { return new ArrayList<>(0); @@ -302,17 +399,18 @@ List> buildFacetField(final FacetResponse facetResponse, fin final Map entry = new LinkedHashMap<>(); entry.put("name", field.getName()); final List> results = new ArrayList<>(); - final boolean tagField = ComponentUtil.getFessConfig().getIndexFieldTag().equals(field.getName()); + final boolean isTagField = tagField.equals(field.getName()); for (final Map.Entry vc : field.getValueCountMap().entrySet()) { - final LabelTypeItem tagItem = tagField ? tagItemMap.get(vc.getKey()) : null; - if (tagField && tagItem == null) { + final TagType tagType = isTagField ? visibleTagTypes.get(vc.getKey()) : null; + if (isTagField && tagType == null) { continue; } final Map result = new LinkedHashMap<>(); result.put("value", vc.getKey()); result.put("count", vc.getValue()); - if (tagItem != null) { - result.put("label", tagItem.getLabel()); + if (tagType != null) { + result.put("label", tagType.getName()); + putOwnership(result, tagType, userId); } results.add(result); } @@ -322,44 +420,6 @@ List> buildFacetField(final FacetResponse facetResponse, fin return out; } - /** - * Returns the tags that the caller can see by value, or an empty map while user tags are disabled. - * Exposed as a seam for unit tests. - * - * @param request the request - * @return the visible tags by value - */ - protected Map getTagItemMap(final HttpServletRequest request) { - try { - if (!ComponentUtil.getFessConfig().isUserTagEnabled()) { - return Collections.emptyMap(); - } - final Locale locale = request.getLocale() == null ? Locale.ROOT : request.getLocale(); - final Map map = new LinkedHashMap<>(); - ComponentUtil.getLabelTypeHelper() - .getTagItemList(SearchRequestType.JSON, locale) - .forEach(item -> map.put(item.getValue(), item)); - return map; - } catch (final Exception e) { - logger.debug("Failed to resolve the tags; no tags are returned.", e); - return Collections.emptyMap(); - } - } - - private static List toStringList(final Object value) { - final List list = new ArrayList<>(); - if (value instanceof final Collection collection) { - collection.forEach(v -> list.add(String.valueOf(v))); - } else if (value instanceof final Object[] array) { - for (final Object v : array) { - list.add(String.valueOf(v)); - } - } else if (value != null) { - list.add(value.toString()); - } - return list; - } - /** * Builds the {@code facet_query} array — a flat list of value/count pairs * for each configured facet query. diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/TagsHandler.java b/src/main/java/org/codelibs/fess/api/v2/handlers/TagsHandler.java new file mode 100644 index 0000000000..fe84547716 --- /dev/null +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/TagsHandler.java @@ -0,0 +1,268 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.api.v2.handlers; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.api.v2.V2ErrorCode; +import org.codelibs.fess.app.service.TagTypeService; +import org.codelibs.fess.entity.TagChange; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.helper.TagTypeHelper; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.codelibs.fess.util.ComponentUtil; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * Handles the tags of the logged-in user: {@code GET/POST /api/v2/tags} and {@code PUT/DELETE /api/v2/tags/{id}}. + * + *
    + *
  • {@code GET /tags} lists the caller's tags as {@code {id, value, name, shared, sort_order, path_count}}.
  • + *
  • {@code POST /tags} with {@code {"name": "...", "shared": false}} creates a tag. A tag of the same name of the + * caller is a {@link V2ErrorCode#CONFLICT}; a caller has at most {@code user.tag.max.tags} tags.
  • + *
  • {@code PUT /tags/{id}} with {@code {"name"?, "shared"?}} renames the tag, which gives it a new id and value + * and queues the rename of the value on the documents, and/or changes whether it is shared, which only changes + * who sees it.
  • + *
  • {@code DELETE /tags/{id}} deletes the tag and queues its removal from the documents.
  • + *
+ */ +public class TagsHandler extends AbstractTagHandler { + + private static final Logger logger = LogManager.getLogger(TagsHandler.class); + + /** + * Default constructor used by the DI container. The handler holds no per-request state. + */ + public TagsHandler() { + // no-op + } + + /** + * Processes one {@code /api/v2/tags} or {@code /api/v2/tags/{id}} request. + * + * @param req the incoming HTTP request + * @param res the HTTP response to write to + * @param tagId the tag id of {@code /tags/{id}}, or null for {@code /tags} + * @throws IOException if writing the envelope fails + */ + public void handle(final HttpServletRequest req, final HttpServletResponse res, final String tagId) throws IOException { + final String method = req.getMethod() == null ? StringUtil.EMPTY : req.getMethod().toUpperCase(Locale.ROOT); + final boolean collection = tagId == null; + if (collection ? !"GET".equals(method) && !"POST".equals(method) : !"PUT".equals(method) && !"DELETE".equals(method)) { + res.setHeader("Allow", collection ? "GET, POST" : "PUT, DELETE"); + ComponentUtil.getV2EnvelopeWriter().writeError(res, V2ErrorCode.METHOD_NOT_ALLOWED, "method not allowed"); + return; + } + try { + final String userId = checkRequest(); + final Map payload = switch (method) { + case "GET" -> listTags(userId); + case "POST" -> createTag(req, userId); + case "PUT" -> updateTag(req, tagId, userId); + default -> deleteTag(tagId, userId); + }; + ComponentUtil.getV2EnvelopeWriter().writeSuccess(res, payload); + } catch (final TagRequestException e) { + ComponentUtil.getV2EnvelopeWriter().writeError(res, e.code, e.getMessage()); + } catch (final Exception e) { + ComponentUtil.getV2EnvelopeWriter().writeInternalError(res, e, logger, "/api/v2/tags " + method); + } + } + + private Map listTags(final String userId) { + final TagTypeService service = getTagTypeService(); + final Map pathCountMap = service.getPathCountMapByOwner(userId); + final List> tags = new ArrayList<>(); + for (final TagType tagType : service.getTagTypeListByOwner(userId)) { + final Long pathCount = pathCountMap.get(tagType.getName()); + tags.add(toTag(tagType, pathCount == null ? 0L : pathCount)); + } + final Map payload = new LinkedHashMap<>(); + payload.put("tags", tags); + return payload; + } + + private Map createTag(final HttpServletRequest req, final String userId) throws TagRequestException, IOException { + final Map body = readBody(req); + final String name = toTagName(body.get("name")); + final Boolean shared = toShared(body); + final TagType tagType = newTagType(name, userId, Boolean.TRUE.equals(shared), new String[0]); + try { + getTagTypeService().insert(tagType); + } catch (final TagTypeConflictException e) { + throw new TagRequestException(V2ErrorCode.CONFLICT, "a tag with the name already exists"); + } + final Map payload = new LinkedHashMap<>(); + payload.put("tag", toTag(tagType, 0L)); + return payload; + } + + private Map updateTag(final HttpServletRequest req, final String tagId, final String userId) + throws TagRequestException, IOException { + final Map body = readBody(req); + final String name = body.get("name") == null ? null : toTagName(body.get("name")); + final Boolean shared = toShared(body); + if (name == null && shared == null) { + throw new TagRequestException(V2ErrorCode.INVALID_REQUEST, "name or shared is required"); + } + final TagType current = getOwnTagType(tagId, userId); + final TagType result; + final boolean renamed = name != null && !name.equals(current.getName()); + if (renamed) { + result = renameTag(current, name, shared, userId); + } else if (shared == null) { + result = current; + } else { + result = updateTagType(tagId, userId, tagType -> { + final String[] permissions = applyShared(tagType.getPermissions(), tagType.getOwner(), shared); + if (Arrays.equals(permissions, tagType.getPermissions())) { + return false; + } + tagType.setPermissions(permissions); + return true; + }); + } + final Map payload = new LinkedHashMap<>(); + payload.put("tag", toTag(result, result.getPaths() == null ? 0L : result.getPaths().length)); + payload.put("renamed", renamed); + return payload; + } + + /** + * Renames a tag: the tag of the new name is created with the paths, permissions, virtual host and sort order of + * the old one, the old one is deleted, and the rename of the value on the documents is queued. When the caller + * has a tag of the new name already, nothing changes. The old tag is deleted only if it did not change since it + * was read; otherwise the new tag is removed again and the rename is retried on a fresh read, so that a URL + * added to the old tag meanwhile is carried over. + */ + private TagType renameTag(final TagType read, final String name, final Boolean shared, final String userId) throws TagRequestException { + final TagTypeHelper helper = getTagTypeHelper(); + TagType current = read; + for (int attempt = 1;; attempt++) { + if (attempt > 1) { + current = getOwnTagType(read.getId(), userId); + } + final TagType renamed = new TagType(); + renamed.setName(name); + renamed.setOwner(current.getOwner()); + renamed.setId(helper.toId(renamed.getTagValue())); + renamed.setPaths(current.getPaths()); + renamed.setPermissions( + shared == null ? current.getPermissions() : applyShared(current.getPermissions(), current.getOwner(), shared)); + renamed.setVirtualHost(current.getVirtualHost()); + renamed.setSortOrder(current.getSortOrder()); + renamed.setCreatedBy(current.getCreatedBy()); + renamed.setCreatedTime(current.getCreatedTime()); + renamed.setUpdatedBy(userId); + renamed.setUpdatedTime(ComponentUtil.getSystemHelper().getCurrentTimeAsLong()); + final boolean replaced; + try { + replaced = getTagTypeService().replace(current, renamed); + } catch (final TagTypeConflictException e) { + throw new TagRequestException(V2ErrorCode.CONFLICT, "a tag with the name already exists"); + } + if (!replaced) { + if (attempt >= MAX_UPDATE_ATTEMPTS) { + logger.warn("Failed to rename the tag after {} attempts: id={}", attempt, read.getId()); + throw new TagRequestException(V2ErrorCode.CONFLICT, "the tag was changed concurrently; try again"); + } + if (logger.isDebugEnabled()) { + logger.debug("The tag was changed concurrently; retrying the rename: id={}, attempt={}", read.getId(), attempt); + } + continue; + } + enqueue(TagChange.rename(current.getTagValue(), renamed.getTagValue())); + return renamed; + } + } + + /** + * Adds the sharing roles to the permissions of a tag, or removes them. The owner's role and any + * permission an administrator added are kept. + * + * @param permissions the current permissions + * @param owner the owner of the tag + * @param shared whether the tag is to be shared + * @return the new permissions + */ + private String[] applyShared(final String[] permissions, final String owner, final boolean shared) { + final TagTypeHelper helper = getTagTypeHelper(); + final List ownList = Arrays.asList(helper.buildPermissions(owner, false)); + final List sharingList = new ArrayList<>(Arrays.asList(helper.buildPermissions(owner, true))); + sharingList.removeAll(ownList); + final List list = new ArrayList<>(); + ownList.forEach(p -> { + if (!list.contains(p)) { + list.add(p); + } + }); + if (permissions != null) { + for (final String p : permissions) { + if (!list.contains(p) && !sharingList.contains(p)) { + list.add(p); + } + } + } + if (shared) { + list.addAll(sharingList); + } + return list.toArray(String[]::new); + } + + private Map deleteTag(final String tagId, final String userId) throws TagRequestException { + for (int attempt = 1;; attempt++) { + final TagType tagType = getOwnTagType(tagId, userId); + try { + getTagTypeService().delete(tagType); + } catch (final TagTypeConflictException e) { + if (attempt >= MAX_UPDATE_ATTEMPTS) { + logger.warn("Failed to delete the tag after {} attempts: id={}", attempt, tagId, e); + throw new TagRequestException(V2ErrorCode.CONFLICT, "the tag was changed concurrently; try again"); + } + continue; + } + enqueue(TagChange.delete(tagType.getTagValue())); + break; + } + final Map payload = new LinkedHashMap<>(); + payload.put("id", tagId); + payload.put("deleted", true); + return payload; + } + + private Map toTag(final TagType tagType, final long pathCount) { + final String value = tagType.getTagValue(); + final Map map = new LinkedHashMap<>(); + map.put("id", getTagTypeHelper().toId(value)); + map.put("value", value); + map.put("name", tagType.getName()); + map.put("shared", getTagTypeHelper().isShared(tagType)); + map.put("sort_order", tagType.getSortOrder() == null ? 0 : tagType.getSortOrder()); + map.put("path_count", pathCount); + return map; + } +} diff --git a/src/main/java/org/codelibs/fess/api/v2/handlers/UiConfigHandler.java b/src/main/java/org/codelibs/fess/api/v2/handlers/UiConfigHandler.java index 9f8c9fddbf..b6bd2e1449 100644 --- a/src/main/java/org/codelibs/fess/api/v2/handlers/UiConfigHandler.java +++ b/src/main/java/org/codelibs/fess/api/v2/handlers/UiConfigHandler.java @@ -423,7 +423,8 @@ public void handle(final HttpServletRequest req, final HttpServletResponse res) features.put("search_history", cfg.isSearchHistoryEnabled() && searchLogEnabled); // search_export: whether GET /api/v2/documents/export is available, so the theme shows the export links. features.put("search_export", cfg.isApiSearchExport()); - // user_tag: whether users can see and add tags (GET/POST/DELETE /api/v2/documents/{doc_id}/tags). + // user_tag: whether logged-in users can manage their tags and put them on documents + // (/api/v2/tags and /api/v2/documents/{doc_id}/tags), and whether hits carry tags. features.put("user_tag", cfg.isUserTagEnabled()); features.put("popular_word", cfg.isWebApiPopularWord()); features.put("suggest_search_log", cfg.isSuggestSearchLog()); diff --git a/src/main/java/org/codelibs/fess/app/pager/TagTypePager.java b/src/main/java/org/codelibs/fess/app/pager/TagTypePager.java new file mode 100644 index 0000000000..51f229c50e --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/pager/TagTypePager.java @@ -0,0 +1,256 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.pager; + +import java.io.Serializable; +import java.util.List; + +import org.codelibs.fess.util.ComponentUtil; + +/** + * Pager for tag types. + */ +public class TagTypePager implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public TagTypePager() { + // do nothing + } + + /** The default page size. */ + public static final int DEFAULT_PAGE_SIZE = 20; + + /** The default current page number. */ + public static final int DEFAULT_CURRENT_PAGE_NUMBER = 1; + + /** The total number of records. */ + private int allRecordCount; + + /** The total number of pages. */ + private int allPageCount; + + /** Indicates if a previous page exists. */ + private boolean existPrePage; + + /** Indicates if a next page exists. */ + private boolean existNextPage; + + /** The list of page numbers. */ + private List pageNumberList; + + /** The size of a page. */ + private int pageSize; + + /** The current page number. */ + private int currentPageNumber; + + /** The ID of the tag type. */ + public String id; + + /** The name of the tag type. */ + public String name; + + /** The owner of the tag type. */ + public String owner; + + /** The sort order of the tag type. */ + public String sortOrder; + + /** The creator of the tag type. */ + public String createdBy; + + /** The created time of the tag type. */ + public String createdTime; + + /** The version number of the tag type. */ + public String versionNo; + + /** + * Clears the pager fields. + */ + public void clear() { + allRecordCount = 0; + allPageCount = 0; + existPrePage = false; + existNextPage = false; + pageSize = getDefaultPageSize(); + currentPageNumber = getDefaultCurrentPageNumber(); + + id = null; + name = null; + owner = null; + sortOrder = null; + createdBy = null; + createdTime = null; + versionNo = null; + + } + + /** + * Returns the default current page number. + * + * @return The default current page number. + */ + protected int getDefaultCurrentPageNumber() { + return DEFAULT_CURRENT_PAGE_NUMBER; + } + + /** + * Returns the total number of records. + * + * @return The total number of records. + */ + public int getAllRecordCount() { + return allRecordCount; + } + + /** + * Sets the total number of records. + * + * @param allRecordCount The total number of records. + */ + public void setAllRecordCount(final int allRecordCount) { + this.allRecordCount = allRecordCount; + } + + /** + * Returns the total number of pages. + * + * @return The total number of pages. + */ + public int getAllPageCount() { + return allPageCount; + } + + /** + * Sets the total number of pages. + * + * @param allPageCount The total number of pages. + */ + public void setAllPageCount(final int allPageCount) { + this.allPageCount = allPageCount; + } + + /** + * Returns true if a previous page exists. + * + * @return True if a previous page exists. + */ + public boolean isExistPrePage() { + return existPrePage; + } + + /** + * Sets whether a previous page exists. + * + * @param existPrePage True if a previous page exists. + */ + public void setExistPrePage(final boolean existPrePage) { + this.existPrePage = existPrePage; + } + + /** + * Returns true if a next page exists. + * + * @return True if a next page exists. + */ + public boolean isExistNextPage() { + return existNextPage; + } + + /** + * Sets whether a next page exists. + * + * @param existNextPage True if a next page exists. + */ + public void setExistNextPage(final boolean existNextPage) { + this.existNextPage = existNextPage; + } + + /** + * Returns the page size. + * + * @return The page size. + */ + public int getPageSize() { + if (pageSize <= 0) { + pageSize = getDefaultPageSize(); + } + return pageSize; + } + + /** + * Sets the page size. + * + * @param pageSize The page size. + */ + public void setPageSize(final int pageSize) { + this.pageSize = pageSize; + } + + /** + * Returns the current page number. + * + * @return The current page number. + */ + public int getCurrentPageNumber() { + if (currentPageNumber <= 0) { + currentPageNumber = getDefaultCurrentPageNumber(); + } + return currentPageNumber; + } + + /** + * Sets the current page number. + * + * @param currentPageNumber The current page number. + */ + public void setCurrentPageNumber(final int currentPageNumber) { + this.currentPageNumber = currentPageNumber; + } + + /** + * Returns the list of page numbers. + * + * @return The list of page numbers. + */ + public List getPageNumberList() { + return pageNumberList; + } + + /** + * Sets the list of page numbers. + * + * @param pageNumberList The list of page numbers. + */ + public void setPageNumberList(final List pageNumberList) { + this.pageNumberList = pageNumberList; + } + + /** + * Returns the default page size. + * + * @return The default page size. + */ + protected int getDefaultPageSize() { + return ComponentUtil.getFessConfig().getPagingPageSizeAsInteger(); + } + +} diff --git a/src/main/java/org/codelibs/fess/app/service/LabelTypeService.java b/src/main/java/org/codelibs/fess/app/service/LabelTypeService.java index 936226b1ab..6c90c00fa8 100644 --- a/src/main/java/org/codelibs/fess/app/service/LabelTypeService.java +++ b/src/main/java/org/codelibs/fess/app/service/LabelTypeService.java @@ -125,20 +125,6 @@ public List getLabelTypeList() { }); } - /** - * Get the label type of a value. - * - * @param value The value of the label type. - * @return The label type, or empty if no label type has the value. - */ - public OptionalEntity getLabelTypeByValue(final String value) { - return labelTypeBhv.selectList(cb -> { - cb.query().setValue_Term(value); - cb.query().addOrderBy_CreatedTime_Asc(); - cb.fetchFirst(1); - }).stream().findFirst().map(OptionalEntity::of).orElseGet(OptionalEntity::empty); - } - /** * Store a label type. * diff --git a/src/main/java/org/codelibs/fess/app/service/TagTypeService.java b/src/main/java/org/codelibs/fess/app/service/TagTypeService.java new file mode 100644 index 0000000000..d554eeb16d --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/service/TagTypeService.java @@ -0,0 +1,345 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.beans.util.BeanUtil; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fesen.opensearch.search.aggregations.Aggregations; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.terms.Terms; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.ValueCount; +import org.codelibs.fess.Constants; +import org.codelibs.fess.app.pager.TagTypePager; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.config.allcommon.EsPagingResultBean; +import org.codelibs.fess.opensearch.config.cbean.TagTypeCB; +import org.codelibs.fess.opensearch.config.exbhv.TagTypeBhv; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.dbflute.cbean.result.PagingResultBean; +import org.dbflute.optional.OptionalEntity; + +import jakarta.annotation.Resource; + +/** + * Service class for tag types, the tags each logged-in user owns. + * + *

The id of a tag type is derived from its name and owner, so a create must not overwrite a + * tag type that already exists, and an update must not overwrite a change made since the tag + * type was read. Both are enforced by the search engine: {@link #insert(TagType)} creates the + * document only when its id is free, and {@link #update(TagType)} writes it only when its + * sequence number and primary term are still the ones read. A lost race on either is reported as + * a {@link TagTypeConflictException}.

+ */ +public class TagTypeService extends FessAppService { + + private static final Logger logger = LogManager.getLogger(TagTypeService.class); + + /** The name of the aggregations that count the paths of each tag. */ + private static final String PATH_COUNT_AGGREGATION = "path_count"; + + /** The TagType behavior. */ + @Resource + protected TagTypeBhv tagTypeBhv; + + /** The Fess config. */ + @Resource + protected FessConfig fessConfig; + + /** + * Default constructor. + */ + public TagTypeService() { + } + + /** + * Get a page of tag types. + * + * @param tagTypePager The pager for tag types. + * @return A list of tag types. + */ + public List getTagTypeList(final TagTypePager tagTypePager) { + + final PagingResultBean tagTypeList = tagTypeBhv.selectPage(cb -> { + cb.paging(tagTypePager.getPageSize(), tagTypePager.getCurrentPageNumber()); + setupListCondition(cb, tagTypePager); + }); + + // update pager + BeanUtil.copyBeanToBean(tagTypeList, tagTypePager, option -> option.include(Constants.PAGER_CONVERSION_RULE)); + tagTypePager.setPageNumberList(tagTypeList.pageRange(op -> { + op.rangeSize(fessConfig.getPagingPageRangeSizeAsInteger()); + }).createPageNumberList()); + + return tagTypeList; + } + + /** + * Set up list conditions. + * + * @param cb The condition bean. + * @param tagTypePager The pager for tag types. + */ + protected void setupListCondition(final TagTypeCB cb, final TagTypePager tagTypePager) { + if (StringUtil.isNotBlank(tagTypePager.name)) { + cb.query().setName_Wildcard(wrapQuery(tagTypePager.name)); + } + if (StringUtil.isNotBlank(tagTypePager.owner)) { + cb.query().setOwner_Wildcard(wrapQuery(tagTypePager.owner)); + } + specifyColumnsWithoutPaths(cb); + + // setup condition + cb.query().addOrderBy_SortOrder_Asc(); + cb.query().addOrderBy_Name_Asc(); + cb.query().addOrderBy_Owner_Asc(); + } + + /** + * Get a tag type with all of its fields. + * + * @param id The ID of the tag type. + * @return An optional entity of the tag type. + */ + public OptionalEntity getTagType(final String id) { + return tagTypeBhv.selectByPK(id); + } + + /** + * Get the tag types of one owner, sorted by sort order and name. + * + *

The returned entities do not carry their {@code paths}, which can hold + * {@code user.tag.max.paths} URLs each. Read a tag type with {@link #getTagType(String)} + * before passing it to {@link #update(TagType)}, or its paths are cleared.

+ * + * @param owner The owner of the tag types. + * @return A list of tag types without their paths. + */ + public List getTagTypeListByOwner(final String owner) { + return tagTypeBhv.selectList(cb -> { + cb.query().setOwner_Term(owner); + specifyColumnsWithoutPaths(cb); + cb.query().addOrderBy_SortOrder_Asc(); + cb.query().addOrderBy_Name_Asc(); + cb.paging(fessConfig.getUserTagMaxTagsAsInteger(), 1); + }); + } + + /** + * Count the paths of each tag type of one owner without reading the paths: a terms + * aggregation on the name, unique per owner, with a value count of the paths. + * + * @param owner The owner of the tag types. + * @return The number of paths keyed by tag name; a tag without paths may be missing. + */ + public Map getPathCountMapByOwner(final String owner) { + final int maxTags = fessConfig.getUserTagMaxTagsAsInteger(); + final EsPagingResultBean result = (EsPagingResultBean) tagTypeBhv.selectPage(cb -> { + cb.fetchFirst(0); + cb.query().setOwner_Term(owner); + cb.aggregation() + .setName_Terms(PATH_COUNT_AGGREGATION, op -> op.size(maxTags), ca -> ca.setPaths_Count(PATH_COUNT_AGGREGATION, null)); + }); + final Map pathCountMap = new HashMap<>(); + final Aggregations aggregations = result.getAggregations(); + if (aggregations != null && aggregations.get(PATH_COUNT_AGGREGATION) instanceof final Terms terms) { + for (final Terms.Bucket bucket : terms.getBuckets()) { + final ValueCount valueCount = bucket.getAggregations().get(PATH_COUNT_AGGREGATION); + pathCountMap.put(bucket.getKeyAsString(), valueCount == null ? 0L : valueCount.getValue()); + } + } + return pathCountMap; + } + + /** + * Count the tag types of one owner. + * + * @param owner The owner of the tag types. + * @return The number of tag types the owner has. + */ + public long countByOwner(final String owner) { + return tagTypeBhv.selectCount(cb -> cb.query().setOwner_Term(owner)); + } + + /** + * Create a tag type. Its id must be set; the write fails when a tag type with that id exists. + * + * @param tagType The tag type to create. + * @throws TagTypeConflictException When a tag type with the same id already exists. + */ + public void insert(final TagType tagType) { + try { + tagTypeBhv.insert(tagType, op -> { + op.setCreate(true); + op.setRefreshPolicy(Constants.TRUE); + }); + } catch (final RuntimeException e) { + if (isVersionConflict(e)) { + throw new TagTypeConflictException("The tag type already exists: id=" + tagType.getId(), e); + } + throw e; + } + } + + /** + * Update a tag type that was read with {@link #getTagType(String)}. The write fails when the + * stored tag type changed since it was read. + * + * @param tagType The tag type to update, carrying the sequence number and primary term it was read with. + * @throws IllegalArgumentException When the tag type carries no sequence number or primary term. + * @throws TagTypeConflictException When the stored tag type changed since it was read. + */ + public void update(final TagType tagType) { + final Long seqNo = tagType.getSeqNo(); + final Long primaryTerm = tagType.getPrimaryTerm(); + if (seqNo == null || seqNo.longValue() < 0L || primaryTerm == null || primaryTerm.longValue() <= 0L) { + throw new IllegalArgumentException("The tag type has no sequence number or primary term: id=" + tagType.getId()); + } + try { + tagTypeBhv.update(tagType, op -> { + op.setIfSeqNo(seqNo); + op.setIfPrimaryTerm(primaryTerm); + op.setRefreshPolicy(Constants.TRUE); + }); + } catch (final RuntimeException e) { + if (isVersionConflict(e)) { + throw new TagTypeConflictException("The tag type was changed concurrently: id=" + tagType.getId(), e); + } + throw e; + } + } + + /** + * Delete a tag type that was read with {@link #getTagType(String)}. The delete fails when the + * stored tag type changed since it was read, so that a concurrent change, such as a URL added to + * its paths, is not dropped unseen. + * + * @param tagType The tag type to delete, carrying the sequence number and primary term it was read with. + * @throws IllegalArgumentException When the tag type carries no sequence number or primary term. + * @throws TagTypeConflictException When the stored tag type changed since it was read. + */ + public void delete(final TagType tagType) { + final Long seqNo = tagType.getSeqNo(); + final Long primaryTerm = tagType.getPrimaryTerm(); + if (seqNo == null || seqNo.longValue() < 0L || primaryTerm == null || primaryTerm.longValue() <= 0L) { + throw new IllegalArgumentException("The tag type has no sequence number or primary term: id=" + tagType.getId()); + } + try { + tagTypeBhv.delete(tagType, op -> { + op.setIfSeqNo(seqNo); + op.setIfPrimaryTerm(primaryTerm); + op.setRefreshPolicy(Constants.TRUE); + }); + } catch (final RuntimeException e) { + if (isVersionConflict(e)) { + throw new TagTypeConflictException("The tag type was changed concurrently: id=" + tagType.getId(), e); + } + throw e; + } + } + + /** + * Replaces a tag type with one of another id, which a change of the name or the owner needs + * since the id is derived from them. The replacement is created first; then the tag type is + * deleted only if it did not change since it was read, so that a change made meanwhile, such as + * a URL added to its paths, is not dropped unseen. When the delete fails, the replacement is + * removed again so that both are not left behind. + * + * @param current The tag type to replace, carrying the sequence number and primary term it was read with. + * @param replacement The tag type to create, with its id set. + * @return true if the tag type was replaced; false if it changed since it was read, in which case nothing changed + * @throws TagTypeConflictException When a tag type with the id of the replacement already exists. + */ + public boolean replace(final TagType current, final TagType replacement) { + insert(replacement); + try { + delete(current); + return true; + } catch (final TagTypeConflictException e) { + if (logger.isDebugEnabled()) { + logger.debug("The tag type was changed concurrently: id={}", current.getId(), e); + } + removeReplacement(replacement); + return false; + } catch (final RuntimeException e) { + removeReplacement(replacement); + throw e; + } + } + + /** + * Deletes the tag type created by {@link #replace(TagType, TagType)} when the tag type it was to + * replace could not be deleted. A failure is logged; the leftover tag type can be deleted later. + * + * @param replacement The tag type created for the replacement. + */ + protected void removeReplacement(final TagType replacement) { + try { + getTagType(replacement.getId()).ifPresent(this::delete); + } catch (final RuntimeException e) { + logger.warn("Failed to remove the tag type created for a replacement: id={}", replacement.getId(), e); + } + } + + /** + * Fetches every field except {@code paths}. + * + * @param cb The condition bean. + */ + protected void specifyColumnsWithoutPaths(final TagTypeCB cb) { + cb.specify().columnName(); + cb.specify().columnOwner(); + cb.specify().columnPermissions(); + cb.specify().columnVirtualHost(); + cb.specify().columnSortOrder(); + cb.specify().columnCreatedBy(); + cb.specify().columnCreatedTime(); + cb.specify().columnUpdatedBy(); + cb.specify().columnUpdatedTime(); + } + + /** + * Tests whether the throwable, or one of its causes, is a version conflict of the search + * engine. The in-process client throws a {@code VersionConflictEngineException}; the HTTP + * client throws a generic exception whose message names the error type. + * + * @param t The throwable to inspect. + * @return true if it is a version conflict. + */ + protected boolean isVersionConflict(final Throwable t) { + Throwable cur = t; + while (cur != null) { + if (cur.getClass().getName().endsWith("VersionConflictEngineException")) { + return true; + } + final String msg = cur.getMessage(); + if (msg != null && msg.contains("type=version_conflict_engine_exception")) { + return true; + } + final Throwable next = cur.getCause(); + if (next == cur) { + break; + } + cur = next; + } + return false; + } +} diff --git a/src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java b/src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java index 328e116651..181d23dc0e 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java @@ -48,6 +48,7 @@ import org.codelibs.fess.app.web.admin.storage.AdminStorageAction; import org.codelibs.fess.app.web.admin.suggest.AdminSuggestAction; import org.codelibs.fess.app.web.admin.systeminfo.AdminSysteminfoAction; +import org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction; import org.codelibs.fess.app.web.admin.user.AdminUserAction; import org.codelibs.fess.app.web.admin.webauth.AdminWebauthAction; import org.codelibs.fess.app.web.admin.webconfig.AdminWebconfigAction; @@ -106,6 +107,8 @@ protected String getActionRole() { AdminDataconfigAction.ROLE + VIEW, // AdminLabeltypeAction.ROLE, // AdminLabeltypeAction.ROLE + VIEW, // + AdminTagtypeAction.ROLE, // + AdminTagtypeAction.ROLE + VIEW, // AdminKeymatchAction.ROLE, // AdminKeymatchAction.ROLE + VIEW, // AdminBoostdocAction.ROLE, // @@ -178,6 +181,8 @@ protected String getActionRole() { AdminDataconfigAction.ROLE + VIEW, // AdminLabeltypeAction.ROLE, // AdminLabeltypeAction.ROLE + VIEW, // + AdminTagtypeAction.ROLE, // + AdminTagtypeAction.ROLE + VIEW, // AdminKeymatchAction.ROLE, // AdminKeymatchAction.ROLE + VIEW, // AdminBoostdocAction.ROLE, // @@ -250,6 +255,8 @@ protected String getActionRole() { AdminDataconfigAction.ROLE + VIEW, // AdminLabeltypeAction.ROLE, // AdminLabeltypeAction.ROLE + VIEW, // + AdminTagtypeAction.ROLE, // + AdminTagtypeAction.ROLE + VIEW, // AdminKeymatchAction.ROLE, // AdminKeymatchAction.ROLE + VIEW, // AdminBoostdocAction.ROLE, // @@ -352,6 +359,9 @@ public static Class getAdminActionClass(final FessUse if (user.hasRoles(getActionRoles(AdminLabeltypeAction.ROLE))) { return AdminLabeltypeAction.class; } + if (user.hasRoles(getActionRoles(AdminTagtypeAction.ROLE))) { + return AdminTagtypeAction.class; + } if (user.hasRoles(getActionRoles(AdminKeymatchAction.ROLE))) { return AdminKeymatchAction.class; } diff --git a/src/main/java/org/codelibs/fess/app/web/admin/labeltype/CreateForm.java b/src/main/java/org/codelibs/fess/app/web/admin/labeltype/CreateForm.java index b13229ac2c..a101a1291c 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/labeltype/CreateForm.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/labeltype/CreateForm.java @@ -16,7 +16,6 @@ package org.codelibs.fess.app.web.admin.labeltype; import org.codelibs.fess.app.web.CrudMode; -import org.codelibs.fess.opensearch.config.exentity.LabelType; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.validation.CustomSize; import org.lastaflute.web.validation.Required; @@ -59,14 +58,6 @@ public CreateForm() { @Pattern(regexp = "^[a-zA-Z0-9_]+$") public String value; - /** - * The kind of the label type: label (assigned to documents whose URL matches the paths) or tag (a tag that users - * add from the search screen; the included paths are the tagged URLs). A blank kind is a label. - */ - @Size(max = 10) - @Pattern(regexp = "^(label|tag)$") - public String kind; - /** * The paths to include for this label type. */ @@ -117,7 +108,6 @@ public CreateForm() { public void initialize() { crudMode = CrudMode.CREATE; sortOrder = 0; - kind = LabelType.KIND_LABEL; createdBy = ComponentUtil.getSystemHelper().getUsername(); createdTime = ComponentUtil.getSystemHelper().getCurrentTimeAsLong(); permissions = ComponentUtil.getFessConfig().getSearchDefaultDisplayPermission(); diff --git a/src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java b/src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java index edc46ea8dc..23d3768e56 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java @@ -87,18 +87,20 @@ public AdminSearchlistAction() { * client-supplied doc map (server-side, regardless of transport) by {@link #stripSystemManagedFields} * before it is overlaid onto the fetched entity in {@link #create}/{@link #update}. */ - private static final Set SYSTEM_MANAGED_FIELDS = - Set.of(Constants.CONTENT_CHUNK_VECTOR_FIELD, Constants.CONTENT_CHUNK_STATUS_FIELD); + private static Set systemManagedFields() { + return Set.of(Constants.CONTENT_CHUNK_VECTOR_FIELD, Constants.CONTENT_CHUNK_STATUS_FIELD, + ComponentUtil.getFessConfig().getIndexFieldTag()); + } /** - * Strips {@link #SYSTEM_MANAGED_FIELDS} (and, when the FETCHED entity's existing {@code content} + * Strips {@link #systemManagedFields()} (and, when the FETCHED entity's existing {@code content} * value is a {@code List} -- i.e. an already-chunked document -- the {@code content} key too) from * a client-supplied doc map, in place, before it is overlaid onto the freshly-fetched entity via * {@code entity.putAll(...)}. * *

{@link #registerContentReadOnly} only renders the {@code content} textarea as {@code disabled} * for an already-chunked document, and the editable-field walk in {@link #registerExtraFields} only - * hides {@link #SYSTEM_MANAGED_FIELDS} from that same rendering pass -- neither stops a direct POST + * hides {@link #systemManagedFields()} from that same rendering pass -- neither stops a direct POST * or API call that includes these keys in the submitted map, since a disabled/hidden form control is * purely a client-side rendering convention. This method is the server-side enforcement point, * shared by this action's {@link #create}/{@link #update} and {@code ApiAdminSearchlistAction}'s @@ -113,7 +115,7 @@ public static void stripSystemManagedFields(final Map entity, fi if (doc == null) { return; } - SYSTEM_MANAGED_FIELDS.forEach(doc::remove); + systemManagedFields().forEach(doc::remove); if (entity != null && entity.get("content") instanceof List) { doc.remove("content"); } @@ -431,6 +433,7 @@ public HtmlResponse create(final CreateForm form) { entity.put(fessConfig.getIndexFieldId(), newId); final String index = fessConfig.getIndexDocumentUpdateIndex(); + ComponentUtil.getTagTypeHelper().applyTags(List.of(entity)); searchEngineClient.store(index, entity); saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL)); } catch (final Exception e) { @@ -476,6 +479,7 @@ public HtmlResponse update(final EditForm form) { } } + ComponentUtil.getTagTypeHelper().applyTags(List.of(entity)); searchEngineClient.store(index, entity); saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL)); } catch (final Exception e) { @@ -616,10 +620,11 @@ private void registerExtraFields(final RenderData data) { registerContentReadOnly(data, doc); + final Set systemManagedFields = systemManagedFields(); final List extraFieldNames = new ArrayList<>(); final Map extraFieldTypes = new TreeMap<>(); candidateFields.stream() - .filter(key -> !STANDARD_EDIT_FIELDS.contains(key) && !reservedFields.contains(key) && !SYSTEM_MANAGED_FIELDS.contains(key)) + .filter(key -> !STANDARD_EDIT_FIELDS.contains(key) && !reservedFields.contains(key) && !systemManagedFields.contains(key)) .forEach(key -> { final String type; if (arrayFieldSet.contains(key)) { diff --git a/src/main/java/org/codelibs/fess/app/web/admin/tagtype/AdminTagtypeAction.java b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/AdminTagtypeAction.java new file mode 100644 index 0000000000..f90ac70652 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/AdminTagtypeAction.java @@ -0,0 +1,610 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.admin.tagtype; + +import static org.codelibs.core.stream.StreamUtil.stream; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.Constants; +import org.codelibs.fess.annotation.Secured; +import org.codelibs.fess.app.pager.TagTypePager; +import org.codelibs.fess.app.service.RoleTypeService; +import org.codelibs.fess.app.service.TagTypeService; +import org.codelibs.fess.app.web.CrudMode; +import org.codelibs.fess.app.web.base.FessAdminAction; +import org.codelibs.fess.entity.TagChange; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.helper.PermissionHelper; +import org.codelibs.fess.helper.SystemHelper; +import org.codelibs.fess.helper.TagTypeHelper; +import org.codelibs.fess.mylasta.action.FessMessages; +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.util.RenderDataUtil; +import org.dbflute.optional.OptionalEntity; +import org.dbflute.optional.OptionalThing; +import org.lastaflute.web.Execute; +import org.lastaflute.web.response.HtmlResponse; +import org.lastaflute.web.response.render.RenderData; +import org.lastaflute.web.ruts.process.ActionRuntime; + +import jakarta.annotation.Resource; + +/** + * Admin action for the tags users own. An administrator manages the tags of every user. + * + *

The id of a tag is derived from its name and owner, so a change of either replaces the tag + * with a new one and renames the tag value on the documents. A change of the paths adds the tag + * to, or removes it from, the documents of the changed URLs. Those document changes are queued + * only while {@code user.tag.enabled} is true; otherwise the {@code tag_updater} job rebuilds the + * tag field later. Every write checks that the tag did not change since it was read.

+ */ +public class AdminTagtypeAction extends FessAdminAction { + + /** + * Default constructor. + */ + public AdminTagtypeAction() { + } + + /** The role name for tag type administration. */ + public static final String ROLE = "admin-tagtype"; + + /** Logger for this class. */ + private static final Logger logger = LogManager.getLogger(AdminTagtypeAction.class); + + /** Thrown when a tag of the same name and owner exists already. */ + public static class TagTypeExistsException extends TagTypeConflictException { + + private static final long serialVersionUID = 1L; + + /** + * Creates the exception. + * + * @param message the message + * @param cause the cause + */ + public TagTypeExistsException(final String message, final Throwable cause) { + super(message, cause); + } + } + + // =================================================================================== + // Attribute + // ========= + /** Service for tag type operations. */ + @Resource + private TagTypeService tagTypeService; + + /** Pager for tag type list pagination. */ + @Resource + private TagTypePager tagTypePager; + + /** Service for role type operations. */ + @Resource + private RoleTypeService roleTypeService; + + // =================================================================================== + // Hook + // ====== + @Override + protected void setupHtmlData(final ActionRuntime runtime) { + super.setupHtmlData(runtime); + runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameTagtype())); + } + + @Override + protected String getActionRole() { + return ROLE; + } + + // =================================================================================== + // Search Execute + // ============== + /** + * Displays the tag type list page. + * + * @param form the search form + * @return HTML response for the list page + */ + @Execute + @Secured({ ROLE, ROLE + VIEW }) + public HtmlResponse index(final SearchForm form) { + return asListHtml(); + } + + /** + * Displays the tag type list with pagination. + * + * @param pageNumber the page number + * @param form the search form + * @return HTML response for the list page + */ + @Execute + @Secured({ ROLE, ROLE + VIEW }) + public HtmlResponse list(final OptionalThing pageNumber, final SearchForm form) { + pageNumber.ifPresent(num -> { + tagTypePager.setCurrentPageNumber(pageNumber.get()); + }).orElse(() -> { + tagTypePager.setCurrentPageNumber(0); + }); + return asHtml(path_AdminTagtype_AdminTagtypeJsp).renderWith(data -> { + searchPaging(data, form); + }); + } + + /** + * Searches tag types by name and owner. + * + * @param form the search form + * @return HTML response for the search results + */ + @Execute + @Secured({ ROLE, ROLE + VIEW }) + public HtmlResponse search(final SearchForm form) { + copyBeanToBean(form, tagTypePager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE)); + return asHtml(path_AdminTagtype_AdminTagtypeJsp).renderWith(data -> { + searchPaging(data, form); + }); + } + + /** + * Resets the search criteria and displays the default list. + * + * @param form the search form + * @return HTML response for the reset list + */ + @Execute + @Secured({ ROLE, ROLE + VIEW }) + public HtmlResponse reset(final SearchForm form) { + tagTypePager.clear(); + return asHtml(path_AdminTagtype_AdminTagtypeJsp).renderWith(data -> { + searchPaging(data, form); + }); + } + + /** + * Sets up data for search result pagination. + * + * @param data the render data + * @param form the search form + */ + protected void searchPaging(final RenderData data, final SearchForm form) { + RenderDataUtil.register(data, "tagTypeItems", tagTypeService.getTagTypeList(tagTypePager)); // page navi + + // restore from pager + copyBeanToBean(tagTypePager, form, op -> op.include("name", "owner")); + } + + // =================================================================================== + // Edit Execute + // ============ + /** + * Displays the create new tag type page. + * + * @return HTML response for the create page + */ + @Execute + @Secured({ ROLE }) + public HtmlResponse createnew() { + saveToken(); + return asHtml(path_AdminTagtype_AdminTagtypeEditJsp).useForm(CreateForm.class, op -> { + op.setup(form -> { + form.initialize(); + form.crudMode = CrudMode.CREATE; + }); + }).renderWith(data -> { + registerRoleTypeItems(data); + }); + } + + /** + * Displays the edit tag type page. + * + * @param form the edit form + * @return HTML response for the edit page + */ + @Execute + @Secured({ ROLE }) + public HtmlResponse edit(final EditForm form) { + validate(form, messages -> {}, this::asListHtml); + final String id = form.id; + tagTypeService.getTagType(id).ifPresent(entity -> { + copyToForm(entity, form); + }).orElse(() -> { + throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml); + }); + saveToken(); + if (form.crudMode.intValue() == CrudMode.EDIT) { + // back + form.crudMode = CrudMode.DETAILS; + return asDetailsHtml(); + } + form.crudMode = CrudMode.EDIT; + return asEditHtml(); + } + + /** + * Displays the tag type details page. + * + * @param crudMode the CRUD mode + * @param id the tag type ID + * @return HTML response for the details page + */ + @Execute + @Secured({ ROLE, ROLE + VIEW }) + public HtmlResponse details(final int crudMode, final String id) { + verifyCrudMode(crudMode, CrudMode.DETAILS, this::asListHtml); + saveToken(); + return asHtml(path_AdminTagtype_AdminTagtypeDetailsJsp).useForm(EditForm.class, op -> { + op.setup(form -> { + tagTypeService.getTagType(id).ifPresent(entity -> { + copyToForm(entity, form); + form.crudMode = crudMode; + }).orElse(() -> { + throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml); + }); + }); + }).renderWith(data -> { + registerRoleTypeItems(data); + }); + } + + // ----------------------------------------------------- + // Actually Crud + // ------------- + /** + * Creates a new tag type. + * + * @param form the create form + * @return HTML response after creation + */ + @Execute + @Secured({ ROLE }) + public HtmlResponse create(final CreateForm form) { + verifyCrudMode(form.crudMode, CrudMode.CREATE, this::asListHtml); + validate(form, messages -> validateTagType(form, messages), this::asEditHtml); + verifyToken(this::asEditHtml); + try { + createTagType(tagTypeService, form); + saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL)); + } catch (final TagTypeExistsException e) { + throwValidationError(messages -> messages.addErrorsTagtypeAlreadyExists(GLOBAL), this::asEditHtml); + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)), + this::asEditHtml); + } + return redirect(getClass()); + } + + /** + * Updates an existing tag type. + * + * @param form the edit form + * @return HTML response after update + */ + @Execute + @Secured({ ROLE }) + public HtmlResponse update(final EditForm form) { + verifyCrudMode(form.crudMode, CrudMode.EDIT, this::asListHtml); + validate(form, messages -> validateTagType(form, messages), this::asEditHtml); + verifyToken(this::asEditHtml); + final OptionalEntity result; + try { + result = updateTagType(tagTypeService, form); + } catch (final TagTypeExistsException e) { + throwValidationError(messages -> messages.addErrorsTagtypeAlreadyExists(GLOBAL), this::asEditHtml); + return null; + } catch (final TagTypeConflictException e) { + throwValidationError(messages -> messages.addErrorsTagtypeChangedConcurrently(GLOBAL), this::asEditHtml); + return null; + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationError(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)), + this::asEditHtml); + return null; + } + result.ifPresent(entity -> { + saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL)); + }).orElse(() -> { + throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), this::asEditHtml); + }); + return redirect(getClass()); + } + + /** + * Deletes a tag type. + * + * @param form the edit form + * @return HTML response after deletion + */ + @Execute + @Secured({ ROLE }) + public HtmlResponse delete(final EditForm form) { + verifyCrudMode(form.crudMode, CrudMode.DETAILS, this::asListHtml); + validate(form, messages -> {}, this::asDetailsHtml); + verifyToken(this::asDetailsHtml); + final String id = form.id; + tagTypeService.getTagType(id).ifPresent(entity -> { + // delete the tag only as it was shown + entity.setSeqNo(form.seqNo); + entity.setPrimaryTerm(form.primaryTerm); + try { + deleteTagType(tagTypeService, entity); + saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL)); + } catch (final TagTypeConflictException e) { + throwValidationError(messages -> messages.addErrorsTagtypeChangedConcurrently(GLOBAL), this::asDetailsHtml); + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationError(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)), + this::asDetailsHtml); + } + }).orElse(() -> { + throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asDetailsHtml); + }); + return redirect(getClass()); + } + + // =================================================================================== + // Assist Logic + // ============ + /** + * Validates the fields of a tag type that the annotations of the form cannot: the name must be + * a valid tag name, the owner must not be blank and the number of paths must not exceed + * {@code user.tag.max.paths}. + * + * @param form the form + * @param messages the messages to add the errors to + */ + public static void validateTagType(final CreateForm form, final FessMessages messages) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + if (form.name != null && ComponentUtil.getTagTypeHelper().normalizeName(form.name).isEmpty()) { + messages.addErrorsTagtypeInvalidName("name", String.valueOf(fessConfig.getUserTagNameMaxLengthAsInteger())); + } + if (form.owner != null && StringUtil.isBlank(form.owner)) { + messages.addConstraintsRequiredMessage("owner"); + } + final int maxPaths = fessConfig.getUserTagMaxPathsAsInteger(); + if (splitPaths(form.paths).length > maxPaths) { + messages.addErrorsTagtypeTooManyPaths("paths", String.valueOf(maxPaths)); + } + } + + /** + * Splits the paths of a form: one URL per line, trimmed, without blank lines and duplicates. + * + * @param paths the paths as entered + * @return the paths + */ + public static String[] splitPaths(final String paths) { + if (StringUtil.isBlank(paths)) { + return new String[0]; + } + return Arrays.stream(paths.split("\r\n|[\r\n]")).map(String::trim).filter(StringUtil::isNotBlank).distinct().toArray(String[]::new); + } + + /** + * Creates a tag type from a validated form and queues adding the tag to the documents of its paths. + * + * @param tagTypeService the tag type service + * @param form the validated form + * @return the created tag type + * @throws TagTypeExistsException if a tag of the same name and owner exists + */ + public static TagType createTagType(final TagTypeService tagTypeService, final CreateForm form) { + final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); + final String username = systemHelper.getUsername(); + final long currentTime = systemHelper.getCurrentTimeAsLong(); + final TagType tagType = new TagType(); + tagType.setCreatedBy(username); + tagType.setCreatedTime(currentTime); + copyToEntity(form, tagType, username, currentTime); + try { + tagTypeService.insert(tagType); + } catch (final TagTypeConflictException e) { + throw new TagTypeExistsException("The tag type already exists: id=" + tagType.getId(), e); + } + final String value = tagType.getTagValue(); + for (final String path : tagType.getPaths()) { + enqueue(TagChange.add(value, path)); + } + return tagType; + } + + /** + * Updates a tag type from a validated form and queues the changes of the documents: a rename + * when the name or the owner changed, then the tag added to the documents of new paths and + * removed from those of dropped ones. The write fails when the tag type changed since the + * version the form carries was read. + * + * @param tagTypeService the tag type service + * @param form the validated form + * @return the updated tag type, or empty when there is no tag type of the id + * @throws TagTypeExistsException if the name or owner changed to those of another tag + * @throws TagTypeConflictException if the tag type changed since it was read + */ + public static OptionalEntity updateTagType(final TagTypeService tagTypeService, final EditForm form) { + return tagTypeService.getTagType(form.id).map(current -> { + current.setSeqNo(form.seqNo); + current.setPrimaryTerm(form.primaryTerm); + final String oldValue = current.getTagValue(); + final String[] oldPaths = current.getPaths() == null ? new String[0] : current.getPaths(); + final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); + final TagType edited = new TagType(); + edited.setCreatedBy(current.getCreatedBy()); + edited.setCreatedTime(current.getCreatedTime()); + copyToEntity(form, edited, systemHelper.getUsername(), systemHelper.getCurrentTimeAsLong()); + if (!current.getOwner().equals(edited.getOwner())) { + edited.setPermissions(moveOwnerPermission(edited.getPermissions(), current.getOwner(), edited.getOwner())); + } + final String newValue = edited.getTagValue(); + if (oldValue.equals(newValue)) { + edited.setSeqNo(current.getSeqNo()); + edited.setPrimaryTerm(current.getPrimaryTerm()); + tagTypeService.update(edited); + } else { + final boolean replaced; + try { + replaced = tagTypeService.replace(current, edited); + } catch (final TagTypeConflictException e) { + throw new TagTypeExistsException("The tag type already exists: id=" + edited.getId(), e); + } + if (!replaced) { + throw new TagTypeConflictException("The tag type was changed concurrently: id=" + current.getId(), null); + } + enqueue(TagChange.rename(oldValue, newValue)); + } + final Set oldPathSet = new LinkedHashSet<>(Arrays.asList(oldPaths)); + final Set newPathSet = new LinkedHashSet<>(Arrays.asList(edited.getPaths())); + newPathSet.stream().filter(path -> !oldPathSet.contains(path)).forEach(path -> enqueue(TagChange.add(newValue, path))); + oldPathSet.stream().filter(path -> !newPathSet.contains(path)).forEach(path -> enqueue(TagChange.remove(newValue, path))); + return edited; + }); + } + + /** + * Replaces the user role of the old owner with that of the new owner, so that the old owner + * does not keep seeing the tag. The sharing roles and any other role are kept. + * + * @param permissions the permissions as entered + * @param oldOwner the owner the tag had + * @param newOwner the owner the tag gets + * @return the permissions + */ + protected static String[] moveOwnerPermission(final String[] permissions, final String oldOwner, final String newOwner) { + final SystemHelper systemHelper = ComponentUtil.getSystemHelper(); + final Set oldRoles = new HashSet<>(); + oldRoles.add(systemHelper.getSearchRoleByDirectoryUser(oldOwner)); + oldRoles.add(systemHelper.getSearchRoleByUser(oldOwner)); + final String newRole = systemHelper.getSearchRoleByDirectoryUser(newOwner); + final Set moved = new LinkedHashSet<>(); + for (final String permission : permissions) { + moved.add(oldRoles.contains(permission) ? newRole : permission); + } + return moved.toArray(String[]::new); + } + + /** + * Deletes a tag type and queues removing the tag from the documents. + * + * @param tagTypeService the tag type service + * @param tagType the tag type with the sequence number and primary term it was read with + * @throws TagTypeConflictException if the tag type changed since it was read + */ + public static void deleteTagType(final TagTypeService tagTypeService, final TagType tagType) { + tagTypeService.delete(tagType); + enqueue(TagChange.delete(tagType.getTagValue())); + } + + /** + * Copies a tag type to a form: the paths and the decoded permissions one per line, and the + * sequence number and primary term that a later write checks. + * + * @param entity the tag type + * @param form the form + */ + public static void copyToForm(final TagType entity, final EditForm form) { + form.id = entity.getId(); + form.seqNo = entity.getSeqNo(); + form.primaryTerm = entity.getPrimaryTerm(); + form.name = entity.getName(); + form.owner = entity.getOwner(); + form.paths = entity.getPaths() == null ? null : String.join("\n", entity.getPaths()); + final PermissionHelper permissionHelper = ComponentUtil.getPermissionHelper(); + form.permissions = stream(entity.getPermissions()).get( + stream -> stream.map(permissionHelper::decode).filter(StringUtil::isNotBlank).distinct().collect(Collectors.joining("\n"))); + form.virtualHost = entity.getVirtualHost(); + form.sortOrder = entity.getSortOrder(); + } + + /** + * Copies a validated form to a tag type and sets its id. The permissions default to the owner. + * + * @param form the form + * @param tagType the tag type + * @param username the user who makes the change + * @param currentTime the time of the change + */ + protected static void copyToEntity(final CreateForm form, final TagType tagType, final String username, final long currentTime) { + final TagTypeHelper tagTypeHelper = ComponentUtil.getTagTypeHelper(); + final String owner = form.owner.trim(); + tagType.setName(tagTypeHelper.normalizeName(form.name).orElseThrow(() -> new IllegalArgumentException("invalid tag name"))); + tagType.setOwner(owner); + tagType.setId(tagTypeHelper.toId(tagType.getTagValue())); + tagType.setPaths(splitPaths(form.paths)); + final String[] permissions = encodePermissions(form.permissions); + tagType.setPermissions(permissions.length == 0 ? tagTypeHelper.buildPermissions(owner, false) : permissions); + tagType.setVirtualHost(StringUtil.isBlank(form.virtualHost) ? StringUtil.EMPTY : form.virtualHost.trim()); + tagType.setSortOrder(form.sortOrder == null ? 0 : form.sortOrder); + tagType.setUpdatedBy(username); + tagType.setUpdatedTime(currentTime); + } + + /** + * Queues a change of the tag field of the documents while user tags are enabled. + * + * @param change the change + */ + protected static void enqueue(final TagChange change) { + if (ComponentUtil.getFessConfig().isUserTagEnabled()) { + ComponentUtil.getTagTypeHelper().enqueue(change); + } + } + + /** + * Registers role type items for the dropdown list. + * + * @param data the render data + */ + protected void registerRoleTypeItems(final RenderData data) { + RenderDataUtil.register(data, "roleTypeItems", roleTypeService.getRoleTypeList()); + } + + // =================================================================================== + // JSP + // ========= + private HtmlResponse asListHtml() { + return asHtml(path_AdminTagtype_AdminTagtypeJsp).renderWith(data -> { + RenderDataUtil.register(data, "tagTypeItems", tagTypeService.getTagTypeList(tagTypePager)); // page navi + }).useForm(SearchForm.class, setup -> { + setup.setup(form -> { + copyBeanToBean(tagTypePager, form, op -> op.include("name", "owner")); + }); + }); + } + + private HtmlResponse asEditHtml() { + return asHtml(path_AdminTagtype_AdminTagtypeEditJsp).renderWith(data -> { + registerRoleTypeItems(data); + }); + } + + private HtmlResponse asDetailsHtml() { + return asHtml(path_AdminTagtype_AdminTagtypeDetailsJsp).renderWith(data -> { + registerRoleTypeItems(data); + }); + } +} diff --git a/src/main/java/org/codelibs/fess/app/web/admin/tagtype/CreateForm.java b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/CreateForm.java new file mode 100644 index 0000000000..7208650fa9 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/CreateForm.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.admin.tagtype; + +import org.codelibs.fess.app.web.CrudMode; +import org.codelibs.fess.validation.CustomSize; +import org.lastaflute.web.validation.Required; +import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; + +/** + * The create form for Tag Type. + */ +public class CreateForm { + + /** + * Creates a new CreateForm instance. + */ + public CreateForm() { + } + + /** + * The CRUD mode for the form. + */ + @ValidateTypeFailure + public Integer crudMode; + + /** + * The name of the tag, normalized when it is stored. + */ + @Required + @Size(max = 1000) + public String name; + + /** + * The user who owns the tag. + */ + @Required + @Size(max = 1000) + public String owner; + + /** + * The URLs of the tagged documents, one per line. + */ + public String paths; + + /** + * The permissions required to view this tag, one per line. The owner only when empty. + */ + @CustomSize(maxKey = "form.admin.max.input.size") + public String permissions; + + /** + * The virtual host for the tag. + */ + @Size(max = 1000) + public String virtualHost; + + /** + * The sort order for displaying this tag. + */ + @Min(value = 0) + @Max(value = 2147483647) + @ValidateTypeFailure + public Integer sortOrder; + + /** + * Initializes the form with default values. + */ + public void initialize() { + crudMode = CrudMode.CREATE; + sortOrder = 0; + } +} diff --git a/src/main/java/org/codelibs/fess/app/web/admin/tagtype/EditForm.java b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/EditForm.java new file mode 100644 index 0000000000..d0b7625a51 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/EditForm.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.admin.tagtype; + +import org.lastaflute.web.validation.Required; +import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure; + +import jakarta.validation.constraints.Size; + +/** + * Form class for editing tag types in the admin interface. The sequence number and primary term + * of the tag type as it was read are sent back, so that a change made since then by its owner or + * another administrator is reported instead of overwritten. + */ +public class EditForm extends CreateForm { + + /** + * Creates a new EditForm instance. + */ + public EditForm() { + } + + /** + * The unique identifier of the tag type being edited. + */ + @Required + @Size(max = 1000) + public String id; + + /** + * The sequence number of the tag type as it was read. + */ + @Required + @ValidateTypeFailure + public Long seqNo; + + /** + * The primary term of the tag type as it was read. + */ + @Required + @ValidateTypeFailure + public Long primaryTerm; + +} diff --git a/src/main/java/org/codelibs/fess/app/web/admin/tagtype/SearchForm.java b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/SearchForm.java new file mode 100644 index 0000000000..dbcec09c77 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/admin/tagtype/SearchForm.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.admin.tagtype; + +/** + * The search form for Tag Type. + */ +public class SearchForm { + + /** + * Default constructor for SearchForm. + */ + public SearchForm() { + } + + /** + * The name field for searching tag types. + */ + public String name; + + /** + * The owner field for searching tag types. + */ + public String owner; + +} diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/documents/ApiAdminDocumentsAction.java b/src/main/java/org/codelibs/fess/app/web/api/admin/documents/ApiAdminDocumentsAction.java index f5c9bbdf20..ddd378568a 100644 --- a/src/main/java/org/codelibs/fess/app/web/api/admin/documents/ApiAdminDocumentsAction.java +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/documents/ApiAdminDocumentsAction.java @@ -169,6 +169,7 @@ public ApiAdminDocumentsAction() { }); } + ComponentUtil.getTagTypeHelper().applyTags(docList); final CrawlingConfigHelper crawlingConfigHelper = ComponentUtil.getCrawlingConfigHelper(); final BulkResponse response = searchEngineClient.addAll(fessConfig.getIndexDocumentUpdateIndex(), docList, (doc, builder) -> { if (doc.get(fessConfig.getIndexFieldConfigId()) instanceof final String configId) { diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/ApiAdminSearchlistAction.java b/src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/ApiAdminSearchlistAction.java index aa3b5fa95c..5f41b9497e 100644 --- a/src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/ApiAdminSearchlistAction.java +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/searchlist/ApiAdminSearchlistAction.java @@ -171,6 +171,7 @@ public JsonResponse docs(final SearchBody body) { entity.put(fessConfig.getIndexFieldId(), newId); final String index = fessConfig.getIndexDocumentUpdateIndex(); + ComponentUtil.getTagTypeHelper().applyTags(List.of(entity)); searchEngineClient.store(index, entity); saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL)); } catch (final Exception e) { @@ -221,6 +222,7 @@ public JsonResponse docs(final SearchBody body) { } } + ComponentUtil.getTagTypeHelper().applyTags(List.of(entity)); searchEngineClient.store(index, entity); saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL)); } catch (final Exception e) { diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/ApiAdminTagtypeAction.java b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/ApiAdminTagtypeAction.java new file mode 100644 index 0000000000..69875cd167 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/ApiAdminTagtypeAction.java @@ -0,0 +1,187 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.api.admin.tagtype; + +import static org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.copyToForm; +import static org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.createTagType; +import static org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.deleteTagType; +import static org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.updateTagType; +import static org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.validateTagType; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.fess.app.pager.TagTypePager; +import org.codelibs.fess.app.service.TagTypeService; +import org.codelibs.fess.app.web.CrudMode; +import org.codelibs.fess.app.web.admin.tagtype.AdminTagtypeAction.TagTypeExistsException; +import org.codelibs.fess.app.web.api.ApiResult; +import org.codelibs.fess.app.web.api.ApiResult.ApiConfigResponse; +import org.codelibs.fess.app.web.api.ApiResult.ApiResponse; +import org.codelibs.fess.app.web.api.ApiResult.ApiUpdateResponse; +import org.codelibs.fess.app.web.api.ApiResult.Status; +import org.codelibs.fess.app.web.api.admin.FessApiAdminAction; +import org.codelibs.fess.exception.TagTypeConflictException; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.lastaflute.web.Execute; +import org.lastaflute.web.response.JsonResponse; + +import jakarta.annotation.Resource; + +/** + * API action for the admin management of the tags users own. A setting carries the + * {@code seq_no} and {@code primary_term} it was read with; an update sends them back and is + * refused when the tag changed since. + */ +public class ApiAdminTagtypeAction extends FessApiAdminAction { + + private static final Logger logger = LogManager.getLogger(ApiAdminTagtypeAction.class); + + /** + * Default constructor. + */ + public ApiAdminTagtypeAction() { + } + + /** Service for tag type operations. */ + @Resource + private TagTypeService tagTypeService; + + /** + * Lists tag types, without their paths. + * + * @param body the search body + * @return the tag types + */ + // GET /api/admin/tagtype/settings + // PUT /api/admin/tagtype/settings + @Execute + public JsonResponse settings(final SearchBody body) { + validateApi(body, messages -> {}); + final TagTypePager pager = copyBeanToNewBean(body, TagTypePager.class); + final List list = tagTypeService.getTagTypeList(pager); + return asJson( + new ApiResult.ApiConfigsResponse().settings(list.stream().map(this::createEditBody).collect(Collectors.toList())) + .total(pager.getAllRecordCount()) + .status(ApiResult.Status.OK) + .result()); + } + + /** + * Returns a tag type with its paths. + * + * @param id the tag type id + * @return the tag type + */ + // GET /api/admin/tagtype/setting/{id} + @Execute + public JsonResponse get$setting(final String id) { + return asJson(new ApiConfigResponse().setting(tagTypeService.getTagType(id).map(this::createEditBody).orElseGet(() -> { + throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id)); + return null; + })).status(Status.OK).result()); + } + + /** + * Creates a tag type. + * + * @param body the tag type + * @return the id of the created tag type + */ + // POST /api/admin/tagtype/setting + @Execute + public JsonResponse post$setting(final CreateBody body) { + validateApi(body, messages -> validateTagType(body, messages)); + body.crudMode = CrudMode.CREATE; + TagType tagType = null; + try { + tagType = createTagType(tagTypeService, body); + } catch (final TagTypeExistsException e) { + throwValidationErrorApi(messages -> messages.addErrorsTagtypeAlreadyExists(GLOBAL)); + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e))); + } + return asJson(new ApiUpdateResponse().id(tagType.getId()).created(true).status(Status.OK).result()); + } + + /** + * Updates a tag type. A change of the name or the owner gives the tag type a new id. + * + * @param body the tag type with the sequence number and primary term it was read with + * @return the id of the updated tag type + */ + // PUT /api/admin/tagtype/setting + @Execute + public JsonResponse put$setting(final EditBody body) { + validateApi(body, messages -> validateTagType(body, messages)); + body.crudMode = CrudMode.EDIT; + TagType tagType = null; + try { + tagType = updateTagType(tagTypeService, body).orElse(null); + } catch (final TagTypeExistsException e) { + throwValidationErrorApi(messages -> messages.addErrorsTagtypeAlreadyExists(GLOBAL)); + } catch (final TagTypeConflictException e) { + throwValidationErrorApi(messages -> messages.addErrorsTagtypeChangedConcurrently(GLOBAL)); + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e))); + } + if (tagType == null) { + throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, body.id)); + } + return asJson(new ApiUpdateResponse().id(tagType.getId()).created(false).status(Status.OK).result()); + } + + /** + * Deletes a tag type. + * + * @param id the tag type id + * @return the result + */ + // DELETE /api/admin/tagtype/setting/{id} + @Execute + public JsonResponse delete$setting(final String id) { + tagTypeService.getTagType(id).ifPresent(entity -> { + try { + deleteTagType(tagTypeService, entity); + saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL)); + } catch (final TagTypeConflictException e) { + throwValidationErrorApi(messages -> messages.addErrorsTagtypeChangedConcurrently(GLOBAL)); + } catch (final Exception e) { + logger.warn("Failed to process a request.", e); + throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e))); + } + }).orElse(() -> { + throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id)); + }); + return asJson(new ApiResponse().status(Status.OK).result()); + } + + /** + * Creates the body of a tag type. + * + * @param entity the tag type + * @return the body + */ + protected EditBody createEditBody(final TagType entity) { + final EditBody body = new EditBody(); + copyToForm(entity, body); + return body; + } +} diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/CreateBody.java b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/CreateBody.java new file mode 100644 index 0000000000..130dd536c7 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/CreateBody.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.api.admin.tagtype; + +import org.codelibs.fess.app.web.admin.tagtype.CreateForm; + +/** + * The request body for creating a tag type through the admin API. + */ +public class CreateBody extends CreateForm { + + /** + * Default constructor. + */ + public CreateBody() { + } + +} diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/EditBody.java b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/EditBody.java new file mode 100644 index 0000000000..ade71ccd8c --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/EditBody.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.api.admin.tagtype; + +import org.codelibs.fess.app.web.admin.tagtype.EditForm; + +/** + * The request body for updating a tag type through the admin API. It carries the + * {@code seq_no} and {@code primary_term} that a read of the tag type returned. + */ +public class EditBody extends EditForm { + + /** + * Default constructor. + */ + public EditBody() { + } + +} diff --git a/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/SearchBody.java b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/SearchBody.java new file mode 100644 index 0000000000..95b485f9e0 --- /dev/null +++ b/src/main/java/org/codelibs/fess/app/web/api/admin/tagtype/SearchBody.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.app.web.api.admin.tagtype; + +import org.codelibs.fess.app.web.api.admin.BaseSearchBody; + +/** + * The request body for searching tag types through the admin API. + */ +public class SearchBody extends BaseSearchBody { + + /** + * Default constructor. + */ + public SearchBody() { + } + + /** The name to search for. */ + public String name; + + /** The owner to search for. */ + public String owner; + +} diff --git a/src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java b/src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java index 9ae23eb582..9d0dd9b9ca 100644 --- a/src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java +++ b/src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java @@ -362,8 +362,6 @@ protected Map generateData(final ResponseData responseData) { // label: labelType final LabelTypeHelper labelTypeHelper = ComponentUtil.getLabelTypeHelper(); putResultDataBody(dataMap, fessConfig.getIndexFieldLabel(), labelTypeHelper.getMatchedLabelValueSet(url)); - // tag: labelType of the kind tag - putTagField(dataMap, url); // role: roleType final List roleTypeList = getRoleTypes(responseData); stream(crawlingConfig.getPermissions()).of(stream -> stream.forEach(p -> roleTypeList.add(p))); diff --git a/src/main/java/org/codelibs/fess/crawler/transformer/FessTransformer.java b/src/main/java/org/codelibs/fess/crawler/transformer/FessTransformer.java index 1e0ab07828..bc4fed8b26 100644 --- a/src/main/java/org/codelibs/fess/crawler/transformer/FessTransformer.java +++ b/src/main/java/org/codelibs/fess/crawler/transformer/FessTransformer.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -182,23 +181,6 @@ default void putResultDataBody(final Map dataMap, final String k } } - /** - * Puts the values of the tags whose included paths list the URL into the tag field while user tags are enabled. - * A tag is a label type of the kind tag, so the tags come only from the label types. - * - * @param dataMap the data map to modify - * @param url the URL of the document - */ - default void putTagField(final Map dataMap, final String url) { - final FessConfig fessConfig = ComponentUtil.getFessConfig(); - if (fessConfig.isUserTagEnabled()) { - final Set tagSet = ComponentUtil.getLabelTypeHelper().getMatchedTagValueSet(url); - if (!tagSet.isEmpty()) { - dataMap.put(fessConfig.getIndexFieldTag(), tagSet.toArray(new String[tagSet.size()])); - } - } - } - /** * Puts data into the result data map after processing it through a template script. * The template is evaluated using the specified script engine with the value and context. diff --git a/src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java b/src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java index 30b1a57e18..7509d60b9c 100644 --- a/src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java +++ b/src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java @@ -593,8 +593,6 @@ && isValidCanonicalUrl(responseData.getUrl(), canonicalUrl)) { putResultDataBody(dataMap, fessConfig.getIndexFieldBoost(), crawlingConfig.getDocumentBoost()); // label: labelType putResultDataBody(dataMap, fessConfig.getIndexFieldLabel(), labelTypeHelper.getMatchedLabelValueSet(url)); - // tag: labelType of the kind tag - putTagField(dataMap, url); // role: roleType final List roleTypeList = new ArrayList<>(); stream(crawlingConfig.getPermissions()).of(stream -> stream.forEach(p -> roleTypeList.add(p))); diff --git a/src/main/java/org/codelibs/fess/ds/callback/IndexUpdateCallbackImpl.java b/src/main/java/org/codelibs/fess/ds/callback/IndexUpdateCallbackImpl.java index e4da0ddbad..b65da4f5e3 100644 --- a/src/main/java/org/codelibs/fess/ds/callback/IndexUpdateCallbackImpl.java +++ b/src/main/java/org/codelibs/fess/ds/callback/IndexUpdateCallbackImpl.java @@ -132,16 +132,6 @@ public void store(final DataStoreParams paramMap, final Map data addFavoriteCountField(dataMap, url, fessConfig.getIndexFieldFavoriteCount()); } - if (fessConfig.isUserTagEnabled()) { - // the tags are users' and come only from the label types of the kind tag - final Set tagSet = ComponentUtil.getLabelTypeHelper().getMatchedTagValueSet(url); - if (tagSet.isEmpty()) { - dataMap.remove(fessConfig.getIndexFieldTag()); - } else { - dataMap.put(fessConfig.getIndexFieldTag(), tagSet.toArray(new String[tagSet.size()])); - } - } - final Set matchedLabelSet = ComponentUtil.getLabelTypeHelper().getMatchedLabelValueSet(url); if (!matchedLabelSet.isEmpty()) { final Set newLabelSet = new HashSet<>(); diff --git a/src/main/java/org/codelibs/fess/entity/QueryContext.java b/src/main/java/org/codelibs/fess/entity/QueryContext.java index 373c682245..717dc1af0e 100644 --- a/src/main/java/org/codelibs/fess/entity/QueryContext.java +++ b/src/main/java/org/codelibs/fess/entity/QueryContext.java @@ -74,8 +74,8 @@ public class QueryContext { /** The type of the search request, or null when the query is not built for a search request. */ protected SearchRequestType searchRequestType = null; - /** The values of the tags that the caller can see, resolved on first use. */ - protected Set visibleTagValueSet = null; + /** Whether the caller can see each tag value used in a condition, resolved on first use. */ + protected Map tagVisibilityMap = null; /** * Constructs a new QueryContext with the specified query string. @@ -293,18 +293,14 @@ public void setSearchRequestType(final SearchRequestType searchRequestType) { } /** - * Gets the values of the tags that the caller can see, as resolved for this query. - * @return The visible tag values, or null if they are not resolved yet. + * Gets whether the caller can see each tag value that a condition of this query has used so far. + * A value is resolved once per query, however many conditions use it. + * @return The visibility of each resolved tag value, never null. */ - public Set getVisibleTagValueSet() { - return visibleTagValueSet; - } - - /** - * Sets the values of the tags that the caller can see. - * @param visibleTagValueSet The visible tag values. - */ - public void setVisibleTagValueSet(final Set visibleTagValueSet) { - this.visibleTagValueSet = visibleTagValueSet; + public Map getTagVisibilityMap() { + if (tagVisibilityMap == null) { + tagVisibilityMap = new HashMap<>(); + } + return tagVisibilityMap; } } diff --git a/src/main/java/org/codelibs/fess/entity/TagChange.java b/src/main/java/org/codelibs/fess/entity/TagChange.java new file mode 100644 index 0000000000..7b02838f5c --- /dev/null +++ b/src/main/java/org/codelibs/fess/entity/TagChange.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.entity; + +/** + * A change of user tags waiting to be applied to the documents of the search index. + * + * @param type the kind of change + * @param value the tag value; the old value for {@link Type#RENAME} + * @param newValue the new tag value of {@link Type#RENAME}, otherwise null + * @param url the URL of the documents of {@link Type#ADD} and {@link Type#REMOVE}, otherwise null + */ +public record TagChange(Type type, String value, String newValue, String url) { + + /** The kind of a tag change. */ + public enum Type { + /** Puts a tag on the documents of a URL. */ + ADD, + /** Takes a tag off the documents of a URL. */ + REMOVE, + /** Takes a tag off every document. */ + DELETE, + /** Replaces a tag value with another on every document. */ + RENAME + } + + /** + * Creates a change that puts a tag on the documents of a URL. + * + * @param value the tag value + * @param url the URL + * @return the change + */ + public static TagChange add(final String value, final String url) { + return new TagChange(Type.ADD, value, null, url); + } + + /** + * Creates a change that takes a tag off the documents of a URL. + * + * @param value the tag value + * @param url the URL + * @return the change + */ + public static TagChange remove(final String value, final String url) { + return new TagChange(Type.REMOVE, value, null, url); + } + + /** + * Creates a change that takes a tag off every document. + * + * @param value the tag value + * @return the change + */ + public static TagChange delete(final String value) { + return new TagChange(Type.DELETE, value, null, null); + } + + /** + * Creates a change that replaces a tag value with another on every document. + * + * @param oldValue the current tag value + * @param newValue the new tag value + * @return the change + */ + public static TagChange rename(final String oldValue, final String newValue) { + return new TagChange(Type.RENAME, oldValue, newValue, null); + } +} diff --git a/src/main/java/org/codelibs/fess/exception/TagTypeConflictException.java b/src/main/java/org/codelibs/fess/exception/TagTypeConflictException.java new file mode 100644 index 0000000000..5abaf41709 --- /dev/null +++ b/src/main/java/org/codelibs/fess/exception/TagTypeConflictException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.exception; + +/** + * Thrown when a tag type cannot be written because another write got there first: a tag type + * with the same id already exists, or the stored one changed since it was read. + */ +public class TagTypeConflictException extends FessSystemException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with the specified detail message and cause. + * + * @param message The detail message. + * @param cause The version conflict reported by the search engine. + */ + public TagTypeConflictException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/codelibs/fess/helper/IndexingHelper.java b/src/main/java/org/codelibs/fess/helper/IndexingHelper.java index 97970acb3f..71f4673116 100644 --- a/src/main/java/org/codelibs/fess/helper/IndexingHelper.java +++ b/src/main/java/org/codelibs/fess/helper/IndexingHelper.java @@ -102,6 +102,9 @@ public void sendDocuments(final SearchEngineClient searchEngineClient, final Doc } }); } + if (fessConfig.isUserTagEnabled()) { + ComponentUtil.getTagTypeHelper().applyTags(docList); + } final CrawlingConfigHelper crawlingConfigHelper = ComponentUtil.getCrawlingConfigHelper(); synchronized (searchEngineClient) { final long deletedDocCount = deleteOldDocuments(searchEngineClient, docList); diff --git a/src/main/java/org/codelibs/fess/helper/LabelTypeHelper.java b/src/main/java/org/codelibs/fess/helper/LabelTypeHelper.java index 4a64995da9..d85b3e64b3 100644 --- a/src/main/java/org/codelibs/fess/helper/LabelTypeHelper.java +++ b/src/main/java/org/codelibs/fess/helper/LabelTypeHelper.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -101,35 +100,11 @@ protected void buildLabelTypeItems(final List labelTypeList) { item.setPermissions(labelType.getPermissions()); item.setVirtualHost(labelType.getVirtualHost()); item.setLocale(labelType.getLocale()); - item.setTag(labelType.isTagKind()); - if (item.isTag()) { - // the included paths of a tag are the tagged URLs, one per line, matched exactly - item.setUrlSet(toUrlSet(labelType.getIncludedPaths())); - } itemList.add(item); } labelTypeItemList = itemList; } - /** - * Splits the included paths of a tag into its URLs. - * - * @param includedPaths The included paths, one URL per line. - * @return The URLs. - */ - public static Set toUrlSet(final String includedPaths) { - final Set urlSet = new LinkedHashSet<>(); - if (StringUtil.isNotBlank(includedPaths)) { - for (final String line : includedPaths.split("\n")) { - final String url = line.trim(); - if (!url.isEmpty()) { - urlSet.add(url); - } - } - } - return urlSet; - } - /** * Returns a list of label type items. * @@ -141,95 +116,13 @@ public List> getLabelTypeItemList(final SearchRequestType se } /** - * Returns a list of label type items. Label types of the kind {@value LabelType#KIND_TAG} are not included. + * Returns a list of label type items. * * @param searchRequestType The search request type. * @param requestLocale The request locale. * @return A list of label type items. */ public List> getLabelTypeItemList(final SearchRequestType searchRequestType, final Locale requestLocale) { - return getVisibleItemList(searchRequestType, requestLocale, false); - } - - /** - * Returns the tags that the current user can see: the label types of the kind {@value LabelType#KIND_TAG} whose - * permissions match the user. The same virtual host, locale and permission rules as - * {@link #getLabelTypeItemList(SearchRequestType, Locale)} apply. - * - * @param searchRequestType The search request type. - * @param requestLocale The request locale. - * @return The visible tags. - */ - public List getTagItemList(final SearchRequestType searchRequestType, final Locale requestLocale) { - return getVisibleItems(searchRequestType, requestLocale, true); - } - - /** - * Returns the values of the tags that the current user can see. - * - * @param searchRequestType The search request type. - * @param requestLocale The request locale. - * @return A set of tag values. - */ - public Set getTagValueSet(final SearchRequestType searchRequestType, final Locale requestLocale) { - return getTagItemList(searchRequestType, requestLocale).stream() - .map(LabelTypeItem::getValue) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } - - /** - * Returns the values of the tags whose included paths list the given URL. A tag matches the exact URL, not a - * regular expression. - * - * @param url The document URL. - * @return A set of tag values. - */ - public Set getMatchedTagValueSet(final String url) { - if (labelTypeItemList == null) { - init(); - } - if (url == null) { - return Collections.emptySet(); - } - final Set valueSet = new LinkedHashSet<>(); - for (final LabelTypeItem item : labelTypeItemList) { - if (item.isTag() && item.getUrlSet().contains(url)) { - valueSet.add(item.getValue()); - } - } - return valueSet; - } - - /** - * Returns the label type items of one kind that the current user can see, as maps with the label and the value. - * - * @param searchRequestType The search request type. - * @param requestLocale The request locale. - * @param tag true for the label types of the kind {@value LabelType#KIND_TAG}, false for the others - * @return A list of maps with the label and the value of each label type. - */ - protected List> getVisibleItemList(final SearchRequestType searchRequestType, final Locale requestLocale, - final boolean tag) { - final List> itemList = new ArrayList<>(); - for (final LabelTypeItem item : getVisibleItems(searchRequestType, requestLocale, tag)) { - final Map map = new HashMap<>(2); - map.put(Constants.ITEM_LABEL, item.getLabel()); - map.put(Constants.ITEM_VALUE, item.getValue()); - itemList.add(map); - } - return itemList; - } - - /** - * Returns the label type items of one kind that the current user can see. - * - * @param searchRequestType The search request type. - * @param requestLocale The request locale. - * @param tag true for the label types of the kind {@value LabelType#KIND_TAG}, false for the others - * @return The visible label type items. - */ - protected List getVisibleItems(final SearchRequestType searchRequestType, final Locale requestLocale, - final boolean tag) { if (labelTypeItemList == null) { init(); } @@ -237,22 +130,23 @@ protected List getVisibleItems(final SearchRequestType searchRequ final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); final List labelList; if (StringUtil.isBlank(virtualHostKey)) { - labelList = labelTypeItemList.stream() - .filter(item -> item.isTag() == tag && matchLocale(requestLocale, item.getLocale())) - .collect(Collectors.toList()); + labelList = + labelTypeItemList.stream().filter(item -> matchLocale(requestLocale, item.getLocale())).collect(Collectors.toList()); } else { labelList = labelTypeItemList.stream() - .filter(item -> item.isTag() == tag && matchLocale(requestLocale, item.getLocale()) - && virtualHostKey.equals(item.getVirtualHost())) + .filter(item -> matchLocale(requestLocale, item.getLocale()) && virtualHostKey.equals(item.getVirtualHost())) .collect(Collectors.toList()); } - final List itemList = new ArrayList<>(); + final List> itemList = new ArrayList<>(); final Set roleSet = ComponentUtil.getRoleQueryHelper().build(searchRequestType); if (roleSet.isEmpty()) { for (final LabelTypeItem item : labelList) { if (item.getPermissions().length == 0) { - itemList.add(item); + final Map map = new HashMap<>(2); + map.put(Constants.ITEM_LABEL, item.getLabel()); + map.put(Constants.ITEM_VALUE, item.getValue()); + itemList.add(map); } } } else { @@ -260,7 +154,10 @@ protected List getVisibleItems(final SearchRequestType searchRequ final Set permissions = stream(item.getPermissions()).get(stream -> stream.collect(Collectors.toSet())); for (final String roleValue : roleSet) { if (permissions.contains(roleValue)) { - itemList.add(item); + final Map map = new HashMap<>(2); + map.put(Constants.ITEM_LABEL, item.getLabel()); + map.put(Constants.ITEM_VALUE, item.getValue()); + itemList.add(map); break; } } @@ -324,10 +221,6 @@ public Set getMatchedLabelValueSet(final String path) { protected void buildLabelTypePatternList(final List labelTypeList) { final List list = new ArrayList<>(); for (final LabelType labelType : labelTypeList) { - if (labelType.isTagKind()) { - // the included paths of a tag are exact URLs, not patterns: see getMatchedTagValueSet - continue; - } final String includedPaths = labelType.getIncludedPaths(); final String excludedPaths = labelType.getExcludedPaths(); if (StringUtil.isNotBlank(includedPaths) || StringUtil.isNotBlank(excludedPaths)) { @@ -345,7 +238,7 @@ protected void buildLabelTypePatternList(final List labelTypeList) { /** * An item of a label type. */ - public static class LabelTypeItem { + protected static class LabelTypeItem { /** * Default constructor. */ @@ -363,10 +256,6 @@ public LabelTypeItem() { private Locale locale; - private boolean tag; - - private Set urlSet = Collections.emptySet(); - /** * Returns the label. * @@ -456,42 +345,6 @@ public Locale getLocale() { public void setLocale(final Locale locale) { this.locale = locale; } - - /** - * Returns whether this label type defines user tags. - * - * @return True if the kind of the label type is {@value LabelType#KIND_TAG}. - */ - public boolean isTag() { - return tag; - } - - /** - * Sets whether this label type defines user tags. - * - * @param tag True if the kind of the label type is {@value LabelType#KIND_TAG}. - */ - public void setTag(final boolean tag) { - this.tag = tag; - } - - /** - * Returns the tagged URLs of a tag. - * - * @return The URLs; empty for a label. - */ - public Set getUrlSet() { - return urlSet; - } - - /** - * Sets the tagged URLs of a tag. - * - * @param urlSet The URLs. - */ - public void setUrlSet(final Set urlSet) { - this.urlSet = urlSet; - } } /** diff --git a/src/main/java/org/codelibs/fess/helper/TagHelper.java b/src/main/java/org/codelibs/fess/helper/TagHelper.java deleted file mode 100644 index b458c74551..0000000000 --- a/src/main/java/org/codelibs/fess/helper/TagHelper.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright 2012-2025 CodeLibs Project and the Others. - * - * Licensed 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.codelibs.fess.helper; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HexFormat; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.codelibs.core.lang.StringUtil; -import org.codelibs.fess.app.service.LabelTypeService; -import org.codelibs.fess.mylasta.direction.FessConfig; -import org.codelibs.fess.opensearch.config.exentity.LabelType; -import org.codelibs.fess.util.ComponentUtil; -import org.codelibs.fesen.opensearch.index.query.QueryBuilders; -import org.codelibs.fesen.opensearch.script.Script; -import org.codelibs.fesen.opensearch.script.ScriptType; - -/** - * Helper for the tags that users add to documents. - * - *

A tag is a label type of the kind {@code tag}: its name is the tag name, its value is derived from the name, its - * included paths list the tagged URLs (one per line, matched exactly) and its permissions list who can see it. A user - * who adds a tag is added to its permissions, and a user in its permissions can remove it from themselves; the label - * type is deleted when no permission is left. Administrators manage tags on the label admin screen like any label.

- * - *

The values of the tags of a URL are indexed in the {@code tag} field, by the crawler from the label types and - * immediately by an update of the indexed documents when a user adds or removes a tag.

- */ -public class TagHelper { - private static final Logger logger = LogManager.getLogger(TagHelper.class); - - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - - /** The number of attempts to store a tag that another request changed at the same time. */ - private static final int MAX_STORE_ATTEMPTS = 3; - - /** The result of adding a tag. */ - public enum AddResult { - /** The URL was tagged or the user was added to the tag. */ - ADDED, - /** The user had already tagged the URL. */ - ALREADY_ADDED, - /** The document has the maximum number of tags. */ - TOO_MANY_TAGS, - /** No more label types can be loaded. */ - TOO_MANY_LABELS - } - - /** - * Default constructor. - */ - public TagHelper() { - // do nothing - } - - /** - * Returns whether users can tag documents. - * - * @return true if {@code user.tag.enabled} is true - */ - public boolean isEnabled() { - return ComponentUtil.getFessConfig().isUserTagEnabled(); - } - - /** - * Returns the label type value of a tag name: the SHA-256 of the name in hex. It satisfies the label value rule - * ({@code [a-zA-Z0-9_]}, at most 100 characters) for any name, and the same name always gets the same value. - * - * @param name the normalized tag name - * @return the tag value - */ - public String toValue(final String name) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(name.getBytes(StandardCharsets.UTF_8))); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is not available.", e); - } - } - - /** - * Normalizes a tag name that a user entered. The name is NFKC-normalized, whitespace is collapsed into one space - * and the name is trimmed. - * - * @param name the entered name - * @return the normalized name, or null if the name is blank, too long, or has a control character or a format - * character such as a zero-width space or a bidirectional override - */ - public String normalizeName(final String name) { - if (name == null) { - return null; - } - final String normalized = WHITESPACE_PATTERN.matcher(Normalizer.normalize(name, Normalizer.Form.NFKC)).replaceAll(" ").trim(); - if (normalized.isEmpty() || normalized.codePointCount(0, normalized.length()) > getNameMaxLength()) { - return null; - } - for (int i = 0; i < normalized.length(); i++) { - final char c = normalized.charAt(i); - if (Character.isISOControl(c) || Character.getType(c) == Character.FORMAT) { - return null; - } - } - return normalized; - } - - /** - * Returns the maximum length of a tag name. - * - * @return the maximum number of characters - */ - public int getNameMaxLength() { - return ComponentUtil.getFessConfig().getUserTagNameMaxLengthAsInteger(); - } - - /** - * Returns the permission value of a user, such as {@code 1alice}. - * - * @param userId the user name - * @return the encoded permission - */ - public String toUserPermission(final String userId) { - return ComponentUtil.getFessConfig().getRoleSearchUserPrefix() + userId; - } - - /** - * Tags the documents of a URL for a user. When a tag of the name exists, the URL is added to its included paths - * and the user to its permissions; otherwise a tag is created that only the user can see. - * - * @param userId the user name - * @param url the URL of the document - * @param name the normalized tag name - * @return the result - */ - public synchronized AddResult addTag(final String userId, final String url, final String name) { - final String value = toValue(name); - final String permission = toUserPermission(userId); - final LabelTypeService labelTypeService = getLabelTypeService(); - for (int attempt = 1;; attempt++) { - final LabelType labelType = labelTypeService.getLabelTypeByValue(value).orElse(null); - final AddResult result; - final LabelType entity; - if (labelType == null) { - if (labelTypeService.getLabelTypeList().size() >= ComponentUtil.getFessConfig().getPageLabeltypeMaxFetchSizeAsInteger()) { - return AddResult.TOO_MANY_LABELS; - } - if (countTags(url) >= getMaxDocumentTags()) { - return AddResult.TOO_MANY_TAGS; - } - entity = createTag(userId, url, name, value, permission); - result = AddResult.ADDED; - } else { - if (!labelType.isTagKind()) { - throw new IllegalStateException("The value of a tag is used by a label: " + value); - } - final Set urlSet = LabelTypeHelper.toUrlSet(labelType.getIncludedPaths()); - final List permissions = toList(labelType.getPermissions()); - if (urlSet.contains(url) && permissions.contains(permission)) { - return AddResult.ALREADY_ADDED; - } - if (!urlSet.contains(url) && countTags(url) >= getMaxDocumentTags()) { - return AddResult.TOO_MANY_TAGS; - } - urlSet.add(url); - if (!permissions.contains(permission)) { - permissions.add(permission); - } - labelType.setIncludedPaths(String.join("\n", urlSet)); - labelType.setPermissions(permissions.toArray(new String[permissions.size()])); - labelType.setUpdatedBy(userId); - labelType.setUpdatedTime(ComponentUtil.getSystemHelper().getCurrentTimeAsLong()); - entity = labelType; - result = AddResult.ADDED; - } - try { - labelTypeService.store(entity); - return result; - } catch (final RuntimeException e) { - // another request changed the same tag; read it again - if (attempt >= MAX_STORE_ATTEMPTS) { - throw e; - } - logger.debug("Retrying to store the tag: value={}, attempt={}", value, attempt, e); - } - } - } - - /** - * Removes a user from the permissions of a tag. The tag is deleted when no permission, of a user, a group or a - * role, is left. - * - * @param userId the user name - * @param value the tag value - * @return true if the tag was deleted, false if it was kept for its other permissions - */ - public synchronized boolean removeTag(final String userId, final String value) { - final String permission = toUserPermission(userId); - final LabelTypeService labelTypeService = getLabelTypeService(); - for (int attempt = 1;; attempt++) { - final LabelType labelType = labelTypeService.getLabelTypeByValue(value).orElse(null); - if (labelType == null || !labelType.isTagKind()) { - return false; - } - final List permissions = toList(labelType.getPermissions()); - if (!permissions.remove(permission)) { - return false; - } - try { - if (permissions.isEmpty()) { - labelTypeService.delete(labelType); - return true; - } - labelType.setPermissions(permissions.toArray(new String[permissions.size()])); - labelType.setUpdatedBy(userId); - labelType.setUpdatedTime(ComponentUtil.getSystemHelper().getCurrentTimeAsLong()); - labelTypeService.store(labelType); - return false; - } catch (final RuntimeException e) { - if (attempt >= MAX_STORE_ATTEMPTS) { - throw e; - } - logger.debug("Retrying to remove the user from the tag: value={}, attempt={}", value, attempt, e); - } - } - } - - /** - * Returns whether a user is in the permissions of a tag. - * - * @param item the tag - * @param userId the user name, or null for an anonymous user - * @return true if the user added the tag - */ - public boolean isMine(final LabelTypeHelper.LabelTypeItem item, final String userId) { - return StringUtil.isNotBlank(userId) && toList(item.getPermissions()).contains(toUserPermission(userId)); - } - - /** - * Adds a tag value to the {@code tag} field of every indexed document of a URL. - * - * @param url the URL - * @param value the tag value - */ - public void addTagToDocuments(final String url, final String value) { - final FessConfig fessConfig = ComponentUtil.getFessConfig(); - final String field = fessConfig.getIndexFieldTag(); - updateDocuments(QueryBuilders.termQuery(fessConfig.getIndexFieldUrl(), url), - "if (ctx._source." + field + " == null) { ctx._source." + field + " = []; } else if (!(ctx._source." + field - + " instanceof List)) { ctx._source." + field + " = [ctx._source." + field + "]; } if (!ctx._source." + field - + ".contains(params.value)) { ctx._source." + field + ".add(params.value); }", - value); - } - - /** - * Removes a tag value from the {@code tag} field of every indexed document. - * - * @param value the tag value - */ - public void removeTagFromDocuments(final String value) { - final String field = ComponentUtil.getFessConfig().getIndexFieldTag(); - updateDocuments(QueryBuilders.termQuery(field, value), - "if (ctx._source." + field + " instanceof List) { ctx._source." + field - + ".removeIf(v -> v == params.value); } else if (ctx._source." + field + " == params.value) { ctx._source.remove('" - + field + "'); }", - value); - } - - private void updateDocuments(final org.codelibs.fesen.opensearch.index.query.QueryBuilder query, final String code, - final String value) { - final FessConfig fessConfig = ComponentUtil.getFessConfig(); - final Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, code, Map.of("value", value)); - final long updated = ComponentUtil.getSearchEngineClient() - .updateByQuery(fessConfig.getIndexDocumentUpdateIndex(), - option -> option.setQuery(query).setFetchSource(new String[] { fessConfig.getIndexFieldUrl() }, null), - (builder, hit) -> builder.setScript(script)); - if (logger.isDebugEnabled()) { - logger.debug("Updated the tag of documents: value={}, documents={}", value, updated); - } - } - - /** - * Creates a tag that only its creator can see. - */ - protected LabelType createTag(final String userId, final String url, final String name, final String value, final String permission) { - final long now = ComponentUtil.getSystemHelper().getCurrentTimeAsLong(); - final LabelType labelType = new LabelType(); - labelType.setName(name); - labelType.setValue(value); - labelType.setKind(LabelType.KIND_TAG); - labelType.setIncludedPaths(url); - labelType.setPermissions(new String[] { permission }); - // a label with a virtual host is shown only on that host, and one without only when no virtual host matches - final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); - labelType.setVirtualHost(StringUtil.isBlank(virtualHostKey) ? StringUtil.EMPTY : virtualHostKey); - labelType.setSortOrder(0); - labelType.setCreatedBy(userId); - labelType.setCreatedTime(now); - labelType.setUpdatedBy(userId); - labelType.setUpdatedTime(now); - return labelType; - } - - /** - * Returns the number of tags of a URL. - */ - protected int countTags(final String url) { - return ComponentUtil.getLabelTypeHelper().getMatchedTagValueSet(url).size(); - } - - /** - * Returns the maximum number of tags on one document. - */ - protected int getMaxDocumentTags() { - return ComponentUtil.getFessConfig().getUserTagMaxDocumentTagsAsInteger(); - } - - /** - * Returns the label type service. - * - * @return the service - */ - protected LabelTypeService getLabelTypeService() { - return ComponentUtil.getComponent(LabelTypeService.class); - } - - private static List toList(final String[] values) { - final List list = new ArrayList<>(); - if (values != null) { - Collections.addAll(list, values); - } - return new ArrayList<>(new LinkedHashSet<>(list)); - } -} diff --git a/src/main/java/org/codelibs/fess/helper/TagTypeHelper.java b/src/main/java/org/codelibs/fess/helper/TagTypeHelper.java new file mode 100644 index 0000000000..5762f9c58e --- /dev/null +++ b/src/main/java/org/codelibs/fess/helper/TagTypeHelper.java @@ -0,0 +1,816 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.helper; + +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.core.misc.Pair; +import org.codelibs.fesen.opensearch.action.update.UpdateRequest; +import org.codelibs.fesen.opensearch.index.query.QueryBuilders; +import org.codelibs.fesen.opensearch.script.Script; +import org.codelibs.fesen.opensearch.script.ScriptType; +import org.codelibs.fess.Constants; +import org.codelibs.fess.entity.TagChange; +import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; +import org.codelibs.fess.mylasta.action.FessUserBean; +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.opensearch.config.cbean.TagTypeCB; +import org.codelibs.fess.opensearch.config.exbhv.TagTypeBhv; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.util.DocumentUtil; +import org.dbflute.optional.OptionalThing; +import org.lastaflute.web.util.LaRequestUtil; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; + +/** + * Helper for user tags: the value a tag puts on a document, the tags a caller may see, and the + * tags of the documents being indexed. + * + *

A tag is a {@code tag_type} document owned by one user. Its value, {@code b64url(name) + ":" + * + b64url(owner)}, is what the {@code tag} field of a document holds, and its id is the SHA-256 + * of that value. Indexing runs in the crawler and data store child processes, so this helper + * reads tag types through {@link TagTypeBhv} only, which {@code esflute_config.xml} registers in + * every container that boots {@code app.xml}.

+ */ +public class TagTypeHelper { + private static final Logger logger = LogManager.getLogger(TagTypeHelper.class); + + /** The prefix of the request attribute caching the visible tag types, followed by the search request type. */ + public static final String VISIBLE_TAG_TYPES_ATTRIBUTE = "fess.tagTypeHelper.visibleTagTypes."; + + /** The prefix of the request attribute caching the visible tag values, followed by the search request type. */ + public static final String VISIBLE_TAG_VALUES_ATTRIBUTE = "fess.tagTypeHelper.visibleTagValues."; + + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + /** How many times an update retries on a version conflict. */ + protected static final int RETRY_ON_CONFLICT = 3; + + /** The TagType behavior. */ + @Resource + protected TagTypeBhv tagTypeBhv; + + /** The tag changes waiting to be applied to the documents. */ + protected final Queue tagChangeQueue = new ConcurrentLinkedQueue<>(); + + /** The number of changes in {@link #tagChangeQueue}. */ + protected final AtomicInteger tagChangeQueueSize = new AtomicInteger(); + + /** + * Whether {@link #processQueue()} wrote to the update index since its last refresh. A search + * by tag must not run before such writes are visible. + */ + protected boolean updateIndexDirty; + + /** + * Default constructor. + */ + public TagTypeHelper() { + // do nothing + } + + /** + * Encodes a tag name and its owner into a tag value. + * + * @param name the normalized tag name + * @param owner the owner of the tag + * @return the tag value + */ + public String toTagValue(final String name, final String owner) { + return TagType.toTagValue(name, owner); + } + + /** + * Decodes a tag value into its name and owner. + * + * @param value the tag value + * @return the name and the owner, or empty when the value is not one {@link #toTagValue(String, String)} returns + */ + public Optional> parseTagValue(final String value) { + if (StringUtil.isEmpty(value)) { + return Optional.empty(); + } + final int pos = value.indexOf(':'); + if (pos <= 0 || pos == value.length() - 1 || value.indexOf(':', pos + 1) >= 0) { + return Optional.empty(); + } + final String name = decodePart(value.substring(0, pos)); + final String owner = decodePart(value.substring(pos + 1)); + if (name == null || owner == null || !value.equals(toTagValue(name, owner))) { + // not canonical: padding, stray bits or a lossy UTF-8 round trip + return Optional.empty(); + } + return Optional.of(new Pair<>(name, owner)); + } + + private String decodePart(final String part) { + try { + final byte[] bytes = Base64.getUrlDecoder().decode(part); + return StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(bytes)) + .toString(); + } catch (final IllegalArgumentException | CharacterCodingException e) { + return null; + } + } + + /** + * Returns the id of the tag type of a tag value. + * + * @param tagValue the tag value + * @return the SHA-256 of the value in lowercase hex + */ + public String toId(final String tagValue) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return HexFormat.of().formatHex(digest.digest(tagValue.getBytes(StandardCharsets.UTF_8))); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 is not available.", e); + } + } + + /** + * Normalizes a tag name: NFKC, whitespace runs collapsed to one space and trimmed. A name + * that is then empty, longer than {@code user.tag.name.max.length} code points, or holds a + * control, format or unpaired surrogate character is refused. + * + * @param raw the name as entered + * @return the normalized name, or empty when the name is refused + */ + public Optional normalizeName(final String raw) { + if (raw == null) { + return Optional.empty(); + } + final String normalized = WHITESPACE_PATTERN.matcher(Normalizer.normalize(raw, Normalizer.Form.NFKC)).replaceAll(" ").trim(); + if (normalized.isEmpty() + || normalized.codePointCount(0, normalized.length()) > ComponentUtil.getFessConfig().getUserTagNameMaxLengthAsInteger()) { + return Optional.empty(); + } + final boolean refused = normalized.codePoints().anyMatch(cp -> { + final int type = Character.getType(cp); + return type == Character.CONTROL || type == Character.FORMAT || type == Character.SURROGATE; + }); + return refused ? Optional.empty() : Optional.of(normalized); + } + + /** + * Tests whether a tag is shared, that is, its permissions hold one of the encoded + * {@code role.search.guest.permissions}. The guest user role is not one of them, so the + * private tag of a user named {@code guest} is not shared. + * + * @param tagType the tag type + * @return true if every logged-in user may see the tag + */ + public boolean isShared(final TagType tagType) { + final String[] permissions = tagType.getPermissions(); + if (permissions == null || permissions.length == 0) { + return false; + } + final List sharedRoleList = getSharedRoleList(); + for (final String permission : permissions) { + if (sharedRoleList.contains(permission)) { + return true; + } + } + return false; + } + + /** + * Builds the permissions stored on a tag: the user role of the owner and, for a shared tag, + * the encoded {@code role.search.guest.permissions}. The guest user role that + * {@link FessConfig#getSearchGuestRoleList()} adds is not stored. + * + * @param owner the owner of the tag + * @param shared whether the tag is shared + * @return the permissions + */ + public String[] buildPermissions(final String owner, final boolean shared) { + final List permissions = new ArrayList<>(); + permissions.add(ComponentUtil.getSystemHelper().getSearchRoleByDirectoryUser(owner)); + if (shared) { + for (final String role : getSharedRoleList()) { + if (!permissions.contains(role)) { + permissions.add(role); + } + } + } + return permissions.toArray(String[]::new); + } + + /** + * Returns the tag types of the given values that the caller may see, without their paths. + * Nothing is visible to a caller who is not logged in. Otherwise a tag is visible when the virtual host of the request is not set or is the tag's, + * and the caller owns the tag or holds one of its permissions, the sharing roles included. + * The result of each value is cached in the request. The admin search, which sees every tag, + * is not handled here: the callers skip the tag restriction for it. + * + * @param values the tag values + * @param type the search request type, never {@link SearchRequestType#ADMIN_SEARCH} + * @return the visible tag types keyed by their value + */ + public Map getVisibleTagTypes(final Collection values, final SearchRequestType type) { + if (values == null || values.isEmpty()) { + return Collections.emptyMap(); + } + final String userId = getLoggedInUserId(); + if (userId == null) { + return Collections.emptyMap(); + } + + final Map> cache = getVisibleTagTypeCache(type); + final Set missing = new LinkedHashSet<>(); + for (final String value : values) { + if (value != null && !cache.containsKey(value)) { + missing.add(value); + } + } + if (!missing.isEmpty()) { + final Map fetched = fetchTagTypes(missing); + final Set roles = getViewerRoles(type); + final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); + for (final String value : missing) { + final TagType tagType = fetched.get(value); + final boolean visible = tagType != null && isVisible(tagType, userId, roles, virtualHostKey); + cache.put(value, visible ? Optional.of(tagType) : Optional.empty()); + } + } + + final Map result = new LinkedHashMap<>(); + for (final String value : values) { + if (value != null) { + cache.get(value).ifPresent(tagType -> result.put(value, tagType)); + } + } + return result; + } + + /** + * Returns the values of the tags the caller may see: the caller's own tags and then those + * of others whose permissions the caller holds, limited to the virtual host of the request and to + * {@code user.tag.visible.max.size} tags. Empty for a caller who is not logged in. The result is + * cached in the request, so the tag facet and the tags of the hits share one lookup. The admin + * search, which sees every tag, is not handled here: the callers skip the tag restriction for it. + * + * @param type the search request type, never {@link SearchRequestType#ADMIN_SEARCH} + * @return the visible tag values + */ + public Set getVisibleTagValues(final SearchRequestType type) { + final String userId = getLoggedInUserId(); + if (userId == null) { + return Collections.emptySet(); + } + final HttpServletRequest request = LaRequestUtil.getOptionalRequest().orElse(null); + final String attributeName = VISIBLE_TAG_VALUES_ATTRIBUTE + (type == null ? StringUtil.EMPTY : type.name()); + if (request != null && request.getAttribute(attributeName) instanceof final Set cached) { + @SuppressWarnings("unchecked") + final Set values = (Set) cached; + return values; + } + final Set values = Collections.unmodifiableSet(fetchVisibleTagValues(userId, type)); + if (request != null) { + request.setAttribute(attributeName, values); + } + return values; + } + + private Set fetchVisibleTagValues(final String userId, final SearchRequestType type) { + final Set roles = getViewerRoles(type); + final String virtualHostKey = ComponentUtil.getVirtualHostHelper().getVirtualHostKey(); + final int maxSize = ComponentUtil.getFessConfig().getUserTagVisibleMaxSizeAsInteger(); + final List tagTypeList = new ArrayList<>(); + // the caller's own tags first, so that tags of others never push them out + tagTypeList.addAll(tagTypeBhv.selectList(cb -> { + cb.query().bool((must, should, mustNot, filter) -> { + filter.setOwner_Term(userId); + if (StringUtil.isNotBlank(virtualHostKey)) { + filter.setVirtualHost_Term(virtualHostKey); + } + }); + specifyNameAndOwner(cb); + cb.fetchFirst(maxSize); + })); + final int remaining = maxSize - tagTypeList.size(); + if (remaining > 0 && !roles.isEmpty()) { + tagTypeList.addAll(tagTypeBhv.selectList(cb -> { + cb.query().bool((must, should, mustNot, filter) -> { + filter.setPermissions_InScope(roles); + mustNot.setOwner_Term(userId); + if (StringUtil.isNotBlank(virtualHostKey)) { + filter.setVirtualHost_Term(virtualHostKey); + } + }); + specifyNameAndOwner(cb); + cb.fetchFirst(remaining); + })); + } + final Set valueSet = new LinkedHashSet<>(); + for (final TagType tagType : tagTypeList) { + final String value = tagType.getTagValue(); + if (value != null && valueSet.size() < maxSize) { + valueSet.add(value); + } + } + return valueSet; + } + + private void specifyNameAndOwner(final TagTypeCB cb) { + cb.specify().columnName(); + cb.specify().columnOwner(); + cb.query().addOrderBy_SortOrder_Asc(); + cb.query().addOrderBy_Name_Asc(); + } + + /** + * Finds the tags put on each of the given URLs. A URL matches a tag only when it equals one + * of the tag's paths. Every matching tag is returned, however many there are. + * + * @param urls the URLs of documents + * @return the tag values of each URL that has a tag + */ + public Map> findTagValuesByUrls(final Collection urls) { + final Set urlSet = new LinkedHashSet<>(); + if (urls != null) { + for (final String url : urls) { + if (StringUtil.isNotEmpty(url)) { + urlSet.add(url); + } + } + } + if (urlSet.isEmpty()) { + return Collections.emptyMap(); + } + final Map> result = new HashMap<>(); + tagTypeBhv.selectBulk(cb -> { + cb.query().setPaths_InScope(urlSet); + cb.specify().columnName(); + cb.specify().columnOwner(); + cb.specify().columnPaths(); + }, list -> { + for (final TagType tagType : list) { + final String value = tagType.getTagValue(); + final String[] paths = tagType.getPaths(); + if (value == null || paths == null) { + continue; + } + for (final String path : paths) { + if (urlSet.contains(path)) { + result.computeIfAbsent(path, k -> new LinkedHashSet<>()).add(value); + } + } + } + }); + return result; + } + + /** + * Sets the tag field of documents about to be indexed from the tags put on their URLs, and + * removes it from a document whose URL has no tag. Does nothing unless + * {@code user.tag.enabled} is true. When the tags cannot be read, the documents are left as + * they are. + * + * @param docList the documents + */ + public void applyTags(final List> docList) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + if (!fessConfig.isUserTagEnabled() || docList == null || docList.isEmpty()) { + return; + } + final String urlField = fessConfig.getIndexFieldUrl(); + final String tagField = fessConfig.getIndexFieldTag(); + final Set urlSet = new LinkedHashSet<>(); + for (final Map doc : docList) { + if (doc.get(urlField) instanceof final String url) { + urlSet.add(url); + } + } + final Map> tagMap; + try { + tagMap = findTagValuesByUrls(urlSet); + } catch (final RuntimeException e) { + logger.error("Failed to read the user tags of documents. Run tag_updater to restore them: documents={}", docList.size(), e); + return; + } + for (final Map doc : docList) { + final Set values = doc.get(urlField) instanceof final String url ? tagMap.get(url) : null; + if (values == null || values.isEmpty()) { + doc.remove(tagField); + } else { + doc.put(tagField, values.toArray(String[]::new)); + } + } + } + + /** + * Queues a tag change to be applied to the documents by {@link #processQueue()}. A change is + * dropped when {@code user.tag.queue.max.size} changes are already waiting. + * + * @param change the tag change + * @return true if the change was queued + */ + public boolean enqueue(final TagChange change) { + if (change == null || change.type() == null) { + return false; + } + final int maxSize = ComponentUtil.getFessConfig().getUserTagQueueMaxSizeAsInteger(); + if (tagChangeQueueSize.incrementAndGet() > maxSize) { + tagChangeQueueSize.decrementAndGet(); + logger.warn("The tag change queue is full. Run tag_updater to restore the dropped change: type={}, limit={}", change.type(), + maxSize); + return false; + } + tagChangeQueue.add(change); + return true; + } + + /** + * Applies the queued tag changes to the documents, in the order they were queued. Additions + * and removals are gathered per URL as the final state of each value and written in bulk, + * {@code user.tag.process.batch.size} URLs at a time; a deletion or a rename first writes what + * was gathered and then updates every document holding the value. A failed write is logged + * and the remaining changes are still applied. + * + * @return the number of changes taken from the queue + */ + public synchronized int processQueue() { + final List changeList = new ArrayList<>(); + TagChange change; + while ((change = tagChangeQueue.poll()) != null) { + tagChangeQueueSize.decrementAndGet(); + changeList.add(change); + } + if (changeList.isEmpty()) { + return 0; + } + final Map> pendingMap = new LinkedHashMap<>(); + updateIndexDirty = false; + for (final TagChange tagChange : changeList) { + switch (tagChange.type()) { + case ADD, REMOVE -> { + if (StringUtil.isNotEmpty(tagChange.url()) && StringUtil.isNotEmpty(tagChange.value())) { + pendingMap.computeIfAbsent(tagChange.url(), k -> new LinkedHashMap<>()) + .put(tagChange.value(), tagChange.type() == TagChange.Type.ADD); + } + } + case DELETE -> { + flushTagChanges(pendingMap); + updateDocumentsWithTag(tagChange.value(), Collections.emptyList(), List.of(tagChange.value())); + } + case RENAME -> { + flushTagChanges(pendingMap); + if (!StringUtil.equals(tagChange.value(), tagChange.newValue()) && StringUtil.isNotEmpty(tagChange.newValue())) { + updateDocumentsWithTag(tagChange.value(), List.of(tagChange.newValue()), List.of(tagChange.value())); + } + } + default -> logger.warn("Unknown tag change: {}", tagChange.type()); + } + } + flushTagChanges(pendingMap); + return changeList.size(); + } + + /** + * Writes the gathered additions and removals to every document of their URLs, and clears them. + * + * @param pendingMap whether each value is to be added (true) or removed (false), keyed by URL + */ + protected void flushTagChanges(final Map> pendingMap) { + if (pendingMap.isEmpty()) { + return; + } + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + final int batchSize = Math.max(1, fessConfig.getUserTagProcessBatchSizeAsInteger()); + final List urlList = new ArrayList<>(pendingMap.keySet()); + for (int i = 0; i < urlList.size(); i += batchSize) { + final List urls = urlList.subList(i, Math.min(i + batchSize, urlList.size())); + int documents = 0; + try { + final List requestList = new ArrayList<>(); + final String index = fessConfig.getIndexDocumentUpdateIndex(); + ComponentUtil.getSearchEngineClient().scrollSearch(index, builder -> { + builder.setQuery(QueryBuilders.termsQuery(fessConfig.getIndexFieldUrl(), urls)) + .setFetchSource(new String[] { fessConfig.getIndexFieldUrl(), fessConfig.getIndexFieldLang() }, null) + .setSize(batchSize); + return true; + }, doc -> { + final String id = DocumentUtil.getValue(doc, fessConfig.getIndexFieldId(), String.class); + final String url = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class); + final Map valueMap = url == null ? null : pendingMap.get(url); + if (id != null && valueMap != null) { + final List addList = new ArrayList<>(); + final List removeList = new ArrayList<>(); + valueMap.forEach((value, add) -> (add ? addList : removeList).add(value)); + requestList.add(new UpdateRequest(index, id).script(createUpdateTagScript(doc, addList, removeList)) + .retryOnConflict(RETRY_ON_CONFLICT)); + } + return true; + }); + documents = requestList.size(); + if (!requestList.isEmpty()) { + updateIndexDirty = true; + ComponentUtil.getSearchHelper().bulkUpdate(builder -> requestList.forEach(builder::add)); + } + } catch (final Exception e) { + logger.warn("Failed to update the user tags of documents. Run tag_updater to restore them: urls={}, documents={}", + urls.size(), documents, e); + } + } + pendingMap.clear(); + } + + /** + * Updates every document holding a tag value. The update index is refreshed first when this + * cycle wrote to it, so that documents just given the value are found. A failure is logged. + * + * @param value the tag value the documents hold + * @param addList the values to put on the documents + * @param removeList the values to take off the documents + */ + protected void updateDocumentsWithTag(final String value, final List addList, final List removeList) { + if (StringUtil.isEmpty(value)) { + return; + } + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + final String index = fessConfig.getIndexDocumentUpdateIndex(); + if (updateIndexDirty) { + try { + refreshUpdateIndex(index); + updateIndexDirty = false; + } catch (final Exception e) { + logger.warn("Failed to refresh the index before updating the user tag of documents: index={}", index, e); + } + } + final AtomicLong documents = new AtomicLong(); + try { + final long count = ComponentUtil.getSearchEngineClient() + .updateByQuery(index, builder -> builder.setQuery(QueryBuilders.termQuery(fessConfig.getIndexFieldTag(), value)) + .setFetchSource(new String[] { fessConfig.getIndexFieldLang() }, null), (builder, hit) -> { + documents.incrementAndGet(); + updateIndexDirty = true; + return builder.setScript(createUpdateTagScript(hit.getSourceAsMap(), addList, removeList)) + .setRetryOnConflict(RETRY_ON_CONFLICT); + }); + if (logger.isDebugEnabled()) { + logger.debug("Updated the user tag of documents: added={}, removed={}, documents={}", addList.size(), removeList.size(), + count); + } + } catch (final Exception e) { + logger.warn("Failed to update the user tag of documents. Run tag_updater to restore them: added={}, removed={}, documents={}", + addList.size(), removeList.size(), documents.get(), e); + } + } + + /** + * Refreshes an index and waits for it, making the writes before it visible to searches. + * + * @param index the index + */ + protected void refreshUpdateIndex(final String index) { + ComponentUtil.getSearchEngineClient() + .admin() + .indices() + .prepareRefresh(index) + .execute() + .actionGet(ComponentUtil.getFessConfig().getIndexIndicesTimeout()); + } + + /** + * Builds the painless code that puts the values of {@code params.add} on a document and takes + * those of {@code params.remove} off it. The field may be missing, a single value or a list; a + * value is never added twice. The two parameters never share a value, so the order of adding + * and removing does not matter, and the code ends with an expression so that + * {@link LanguageHelper#createScript(Map, String)} can append its statements: painless has no + * empty statement to follow a closing brace. + * + * @param field the tag field + * @return the painless code + */ + protected static String buildUpdateTagScript(final String field) { + final String f = "ctx._source." + field; + return "if(" + f + "==null){" + f + "=new ArrayList();}" // + + "else if(!(" + f + " instanceof List)){" + f + "=new ArrayList([" + f + "]);}" // + + "for(v in params.add){if(!" + f + ".contains(v)){" + f + ".add(v);}}" // + + f + ".removeIf(x -> params.remove.contains(x))"; + } + + /** + * Builds the script that puts values on a document and takes others off it, followed by the + * statements that keep its language fields. + * + * @param doc the document, holding its language + * @param addList the values to put on the document + * @param removeList the values to take off the document + * @return the script + */ + protected Script createUpdateTagScript(final Map doc, final List addList, final List removeList) { + final Script script = + ComponentUtil.getLanguageHelper().createScript(doc, buildUpdateTagScript(ComponentUtil.getFessConfig().getIndexFieldTag())); + final Map params = new HashMap<>(); + params.put("add", addList); + params.put("remove", removeList); + return new Script(ScriptType.INLINE, "painless", script.getIdOrCode(), params); + } + + /** + * Reads the tag types of the given values, without their paths. + * + * @param values the tag values + * @return the tag types found, keyed by their value + */ + protected Map fetchTagTypes(final Collection values) { + final Map idMap = new LinkedHashMap<>(); + for (final String value : values) { + if (parseTagValue(value).isPresent()) { + idMap.put(toId(value), value); + } + } + final Map result = new HashMap<>(); + if (idMap.isEmpty()) { + return result; + } + final int chunkSize = Math.max(1, ComponentUtil.getFessConfig().getPageTagtypeMaxFetchSizeAsInteger()); + final List idList = new ArrayList<>(idMap.keySet()); + for (int i = 0; i < idList.size(); i += chunkSize) { + final List ids = idList.subList(i, Math.min(i + chunkSize, idList.size())); + final List tagTypeList = tagTypeBhv.selectList(cb -> { + cb.query().setId_InScope(ids); + specifyColumnsWithoutPaths(cb); + cb.fetchFirst(ids.size()); + }); + for (final TagType tagType : tagTypeList) { + final String value = tagType.getTagValue(); + if (value != null && values.contains(value)) { + result.put(value, tagType); + } + } + } + return result; + } + + /** + * Fetches every field except {@code paths}. + * + * @param cb the condition bean + */ + protected void specifyColumnsWithoutPaths(final TagTypeCB cb) { + cb.specify().columnName(); + cb.specify().columnOwner(); + cb.specify().columnPermissions(); + cb.specify().columnVirtualHost(); + cb.specify().columnSortOrder(); + cb.specify().columnCreatedBy(); + cb.specify().columnCreatedTime(); + cb.specify().columnUpdatedBy(); + cb.specify().columnUpdatedTime(); + } + + /** + * Tests whether a tag is visible to a logged-in caller outside an admin search. + * + * @param tagType the tag type + * @param userId the user id of the caller + * @param roles the roles of the caller, the sharing roles included + * @param virtualHostKey the virtual host key of the request + * @return true if the caller may see the tag + */ + protected boolean isVisible(final TagType tagType, final String userId, final Set roles, final String virtualHostKey) { + if (StringUtil.isNotBlank(virtualHostKey) && !virtualHostKey.equals(tagType.getVirtualHost())) { + return false; + } + if (userId.equals(tagType.getOwner())) { + return true; + } + final String[] permissions = tagType.getPermissions(); + if (permissions != null) { + for (final String permission : permissions) { + if (roles.contains(permission)) { + return true; + } + } + } + return false; + } + + /** + * Returns the roles of the caller together with the sharing roles, without the guest user role. + * + * @param type the search request type + * @return the roles + */ + protected Set getViewerRoles(final SearchRequestType type) { + final Set roles = new HashSet<>(ComponentUtil.getRoleQueryHelper().build(type)); + roles.addAll(getSharedRoleList()); + // the guest user role is the own permission of a user named guest, not a sharing role + roles.remove(getGuestUserRole()); + return roles; + } + + /** + * Returns the roles that mark a tag as shared: the encoded + * {@code role.search.guest.permissions}, without the guest user role that + * {@link FessConfig#getSearchGuestRoleList()} adds. + * + * @return the sharing roles + */ + protected List getSharedRoleList() { + final String guestUserRole = getGuestUserRole(); + final List list = new ArrayList<>(); + for (final String role : ComponentUtil.getFessConfig().getSearchGuestRoleList()) { + if (!guestUserRole.equals(role)) { + list.add(role); + } + } + return list; + } + + private String getGuestUserRole() { + return ComponentUtil.getFessConfig().getRoleSearchUserPrefix() + Constants.GUEST_USER; + } + + /** + * Returns the cache of visible tag types of the current request, or a map that is dropped + * after the call when there is no request, as in a job or a child process. + * + * @param type the search request type + * @return the cache, keyed by tag value; an empty optional marks a value that is not visible + */ + protected Map> getVisibleTagTypeCache(final SearchRequestType type) { + return LaRequestUtil.getOptionalRequest().map(request -> { + final String name = VISIBLE_TAG_TYPES_ATTRIBUTE + (type == null ? StringUtil.EMPTY : type.name()); + @SuppressWarnings("unchecked") + Map> cache = (Map>) request.getAttribute(name); + if (cache == null) { + cache = new HashMap<>(); + request.setAttribute(name, cache); + } + return cache; + }).orElseGet(HashMap::new); + } + + /** + * Returns the user id of the logged-in caller. + * + * @return the user id, or null when the caller is not logged in + */ + protected String getLoggedInUserId() { + return getSavedUserBean().map(FessUserBean::getUserId).filter(StringUtil::isNotBlank).orElse(null); + } + + /** + * Resolves the logged-in user. A failed lookup counts as a caller who is not logged in. + * + * @return the saved user bean, or empty + */ + protected OptionalThing getSavedUserBean() { + try { + return ComponentUtil.getFessLoginAssist().getSavedUserBean(); + } catch (final Exception e) { + logger.debug("Failed to resolve the logged-in user; treating the caller as not logged in.", e); + return OptionalThing.empty(); + } + } +} diff --git a/src/main/java/org/codelibs/fess/job/AggregateLogJob.java b/src/main/java/org/codelibs/fess/job/AggregateLogJob.java index cfba746fbc..431a6b6d5f 100644 --- a/src/main/java/org/codelibs/fess/job/AggregateLogJob.java +++ b/src/main/java/org/codelibs/fess/job/AggregateLogJob.java @@ -22,7 +22,8 @@ /** * Job for aggregating and storing search logs. - * This job processes search logs and stores them in the search log repository. + * This job processes search logs and stores them in the search log repository, and applies the + * queued user tag changes to the documents. */ public class AggregateLogJob { @@ -53,6 +54,15 @@ public String execute() { resultBuf.append(e.getMessage()).append("\n"); } + try { + if (ComponentUtil.getFessConfig().isUserTagEnabled()) { + ComponentUtil.getTagTypeHelper().processQueue(); + } + } catch (final Exception e) { + logger.warn("Failed to apply user tag changes.", e); + resultBuf.append(e.getMessage()).append("\n"); + } + return resultBuf.toString(); } diff --git a/src/main/java/org/codelibs/fess/job/TagUpdaterJob.java b/src/main/java/org/codelibs/fess/job/TagUpdaterJob.java new file mode 100644 index 0000000000..8d5896380b --- /dev/null +++ b/src/main/java/org/codelibs/fess/job/TagUpdaterJob.java @@ -0,0 +1,193 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.job; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.fesen.opensearch.action.update.UpdateRequest; +import org.codelibs.fesen.opensearch.index.query.QueryBuilders; +import org.codelibs.fesen.opensearch.script.Script; +import org.codelibs.fesen.opensearch.script.ScriptType; +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.util.ComponentUtil; +import org.codelibs.fess.util.DocumentUtil; + +/** + * Job that rebuilds the user tags of every document from the tag types, the way + * {@link UpdateLabelJob} rebuilds labels. It recovers tag changes lost from the in-memory queue, + * for example on a restart, and tags lost while {@code user.tag.enabled} was false. + * + *

Every document is walked, because a document that holds a stale tag and one whose URL gained + * a tag must both be found; the tags of each page of {@code user.tag.process.batch.size} + * documents are read with one query, and only the documents whose tags differ are written.

+ */ +public class TagUpdaterJob { + + private static final Logger logger = LogManager.getLogger(TagUpdaterJob.class); + + /** How many times an update retries on a version conflict. */ + protected static final int RETRY_ON_CONFLICT = 3; + + /** The number of documents walked. */ + protected long checked; + + /** The number of documents whose tags were written. */ + protected long updated; + + /** The number of documents whose tags could not be written. */ + protected long failed; + + /** + * Default constructor. + */ + public TagUpdaterJob() { + // do nothing + } + + /** + * Rebuilds the user tags of every document. + * + * @return the result message + */ + public String execute() { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + if (!fessConfig.isUserTagEnabled()) { + return "User tags are disabled.\n"; + } + final int batchSize = Math.max(1, fessConfig.getUserTagProcessBatchSizeAsInteger()); + final String index = fessConfig.getIndexDocumentUpdateIndex(); + final StringBuilder resultBuf = new StringBuilder(); + try { + final List> docList = new ArrayList<>(); + ComponentUtil.getSearchEngineClient().scrollSearch(index, builder -> { + builder.setQuery(QueryBuilders.matchAllQuery()) + .setFetchSource(new String[] { fessConfig.getIndexFieldUrl(), fessConfig.getIndexFieldLang(), + fessConfig.getIndexFieldTag() }, null) + .setSize(batchSize); + return true; + }, doc -> { + docList.add(doc); + if (docList.size() >= batchSize) { + updateTags(index, docList); + docList.clear(); + } + return true; + }); + updateTags(index, docList); + } catch (final Exception e) { + logger.warn("Could not update user tags.", e); + resultBuf.append(e.getMessage()).append("\n"); + } + resultBuf.append(updated) + .append(" documents updated (") + .append(checked) + .append(" documents checked, ") + .append(failed) + .append(" failed)\n"); + return resultBuf.toString(); + } + + /** + * Writes the tags of the documents of one page whose tags differ from those of their URLs. + * + * @param index the index to write + * @param docList the documents, holding their id, URL, language and tags + */ + protected void updateTags(final String index, final List> docList) { + if (docList.isEmpty()) { + return; + } + checked += docList.size(); + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + final String urlField = fessConfig.getIndexFieldUrl(); + final String tagField = fessConfig.getIndexFieldTag(); + final Set urlSet = new LinkedHashSet<>(); + for (final Map doc : docList) { + final String url = DocumentUtil.getValue(doc, urlField, String.class); + if (url != null) { + urlSet.add(url); + } + } + final List requestList = new ArrayList<>(); + try { + final Map> tagMap = ComponentUtil.getTagTypeHelper().findTagValuesByUrls(urlSet); + for (final Map doc : docList) { + final String id = DocumentUtil.getValue(doc, fessConfig.getIndexFieldId(), String.class); + if (id == null) { + continue; + } + final String url = DocumentUtil.getValue(doc, urlField, String.class); + final Set values = url == null ? null : tagMap.get(url); + final Set expected = values == null ? Collections.emptySet() : values; + if (expected.equals(toValueSet(doc.get(tagField)))) { + continue; + } + final Script script; + if (expected.isEmpty()) { + script = ComponentUtil.getLanguageHelper().createScript(doc, "ctx._source.remove('" + tagField + "')"); + } else { + final Script base = ComponentUtil.getLanguageHelper().createScript(doc, "ctx._source." + tagField + "=params.tag"); + final Map params = new HashMap<>(); + params.put("tag", new ArrayList<>(expected)); + script = new Script(ScriptType.INLINE, "painless", base.getIdOrCode(), params); + } + requestList.add(new UpdateRequest(index, id).script(script).retryOnConflict(RETRY_ON_CONFLICT)); + } + if (!requestList.isEmpty()) { + ComponentUtil.getSearchHelper().bulkUpdate(builder -> requestList.forEach(builder::add)); + updated += requestList.size(); + } + } catch (final Exception e) { + failed += requestList.isEmpty() ? docList.size() : requestList.size(); + logger.warn("Failed to update the user tags of documents: documents={}, updates={}", docList.size(), requestList.size(), e); + } + } + + /** + * Reads the tag field of a document, which may be missing, a single value or a list. + * + * @param value the field value + * @return the tag values + */ + protected Set toValueSet(final Object value) { + final Set valueSet = new LinkedHashSet<>(); + if (value instanceof final Collection collection) { + for (final Object v : collection) { + if (v != null) { + valueSet.add(v.toString()); + } + } + } else if (value instanceof final Object[] array) { + for (final Object v : array) { + if (v != null) { + valueSet.add(v.toString()); + } + } + } else if (value != null) { + valueSet.add(value.toString()); + } + return valueSet; + } +} diff --git a/src/main/java/org/codelibs/fess/job/UpdateLabelJob.java b/src/main/java/org/codelibs/fess/job/UpdateLabelJob.java index 72d1eb173c..85409b26ea 100644 --- a/src/main/java/org/codelibs/fess/job/UpdateLabelJob.java +++ b/src/main/java/org/codelibs/fess/job/UpdateLabelJob.java @@ -52,8 +52,7 @@ public UpdateLabelJob() { /** * Executes the label update job. - * Processes documents in the search index and updates their label fields, and their tag fields while user tags - * are enabled. + * Processes documents in the search index and updates their label fields. * * @return execution result message */ @@ -77,22 +76,8 @@ public String execute() { final String url = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class); if (StringUtil.isNotBlank(url)) { final Set labelSet = labelTypeHelper.getMatchedLabelValueSet(url); - final StringBuilder code = new StringBuilder(); - code.append("ctx._source.") - .append(fessConfig.getIndexFieldLabel()) - .append("=new String[]{") - .append(labelSet.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(","))) - .append('}'); - if (fessConfig.isUserTagEnabled()) { - // tags are label types of the kind tag, whose values are [a-zA-Z0-9_] like any label value - final Set tagSet = labelTypeHelper.getMatchedTagValueSet(url); - code.append(";ctx._source.") - .append(fessConfig.getIndexFieldTag()) - .append("=new String[]{") - .append(tagSet.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(","))) - .append('}'); - } - final Script script = languageHelper.createScript(doc, code.toString()); + final Script script = languageHelper.createScript(doc, "ctx._source." + fessConfig.getIndexFieldLabel() + + "=new String[]{" + labelSet.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(",")) + "}"); return builder.setScript(script); } } catch (final Exception e) { diff --git a/src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java b/src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java index b81095c638..00aef5d15f 100644 --- a/src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java +++ b/src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java @@ -370,6 +370,15 @@ public interface FessHtmlPath { /** The path of the HTML: /admin/systeminfo/admin_systeminfo.jsp */ HtmlNext path_AdminSysteminfo_AdminSysteminfoJsp = new HtmlNext("/admin/systeminfo/admin_systeminfo.jsp"); + /** The path of the HTML: /admin/tagtype/admin_tagtype.jsp */ + HtmlNext path_AdminTagtype_AdminTagtypeJsp = new HtmlNext("/admin/tagtype/admin_tagtype.jsp"); + + /** The path of the HTML: /admin/tagtype/admin_tagtype_details.jsp */ + HtmlNext path_AdminTagtype_AdminTagtypeDetailsJsp = new HtmlNext("/admin/tagtype/admin_tagtype_details.jsp"); + + /** The path of the HTML: /admin/tagtype/admin_tagtype_edit.jsp */ + HtmlNext path_AdminTagtype_AdminTagtypeEditJsp = new HtmlNext("/admin/tagtype/admin_tagtype_edit.jsp"); + /** The path of the HTML: /admin/theme/admin_theme.jsp */ HtmlNext path_AdminTheme_AdminThemeJsp = new HtmlNext("/admin/theme/admin_theme.jsp"); diff --git a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java index b6ead77a3f..7c568dff74 100644 --- a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java +++ b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java @@ -1253,6 +1253,9 @@ public class FessLabels extends UserMessages { /** The key of the message: Label Type Details */ public static final String LABELS_labeltype_details = "{labels.labeltype_details}"; + /** The key of the message: Tag Details */ + public static final String LABELS_tagtype_details = "{labels.tagtype_details}"; + /** The key of the message: Path Mapping Details */ public static final String LABELS_pathmap_details = "{labels.pathmap_details}"; @@ -1325,6 +1328,9 @@ public class FessLabels extends UserMessages { /** The key of the message: Label Type List */ public static final String LABELS_labeltype_list = "{labels.labeltype_list}"; + /** The key of the message: Tag List */ + public static final String LABELS_tagtype_list = "{labels.tagtype_list}"; + /** The key of the message: Key Match List */ public static final String LABELS_key_match_list = "{labels.key_match_list}"; @@ -1568,23 +1574,26 @@ public class FessLabels extends UserMessages { /** The key of the message: Value */ public static final String LABELS_labeltype_value = "{labels.labeltype_value}"; - /** The key of the message: Kind */ - public static final String LABELS_labeltype_kind = "{labels.labeltype_kind}"; + /** The key of the message: Included Paths */ + public static final String LABELS_labeltype_included_paths = "{labels.labeltype_included_paths}"; + + /** The key of the message: Excluded Paths */ + public static final String LABELS_labeltype_excluded_paths = "{labels.labeltype_excluded_paths}"; - /** The key of the message: Label */ - public static final String LABELS_labeltype_kind_label = "{labels.labeltype_kind_label}"; + /** The key of the message: Tag */ + public static final String LABELS_tagtype_configuration = "{labels.tagtype_configuration}"; /** The key of the message: Tag */ - public static final String LABELS_labeltype_kind_tag = "{labels.labeltype_kind_tag}"; + public static final String LABELS_tagtype_title_details = "{labels.tagtype_title_details}"; - /** The key of the message: A Tag is a tag that users add from the search screen: Name is the tag name, Included Paths lists the tagged URLs (one URL per line, exact match), and Permissions decides who can see it. A user who adds a tag is added to its Permissions. */ - public static final String LABELS_labeltype_kind_help = "{labels.labeltype_kind_help}"; + /** The key of the message: Name */ + public static final String LABELS_tagtype_name = "{labels.tagtype_name}"; - /** The key of the message: Included Paths */ - public static final String LABELS_labeltype_included_paths = "{labels.labeltype_included_paths}"; + /** The key of the message: Owner */ + public static final String LABELS_tagtype_owner = "{labels.tagtype_owner}"; - /** The key of the message: Excluded Paths */ - public static final String LABELS_labeltype_excluded_paths = "{labels.labeltype_excluded_paths}"; + /** The key of the message: Paths */ + public static final String LABELS_tagtype_paths = "{labels.tagtype_paths}"; /** The key of the message: Role */ public static final String LABELS_roletype_configuration = "{labels.roletype_configuration}"; diff --git a/src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java b/src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java index 1bdcafd874..fbaa586b5a 100644 --- a/src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java +++ b/src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java @@ -491,6 +491,18 @@ public class FessMessages extends FessLabels { /** The key of the message: Related queries are already being generated. */ public static final String ERRORS_related_query_generation_in_progress = "{errors.related_query_generation_in_progress}"; + /** The key of the message: Enter a tag name of 1 to {0} characters without control characters. */ + public static final String ERRORS_tagtype_invalid_name = "{errors.tagtype_invalid_name}"; + + /** The key of the message: A tag can have up to {0} paths. */ + public static final String ERRORS_tagtype_too_many_paths = "{errors.tagtype_too_many_paths}"; + + /** The key of the message: A tag with the same name and owner already exists. */ + public static final String ERRORS_tagtype_already_exists = "{errors.tagtype_already_exists}"; + + /** The key of the message: The tag was changed by someone else. Reload it and try again. */ + public static final String ERRORS_tagtype_changed_concurrently = "{errors.tagtype_changed_concurrently}"; + /** The key of the message: {0} is required. */ public static final String ERRORS_property_required = "{errors.property_required}"; @@ -2893,6 +2905,64 @@ public FessMessages addErrorsRelatedQueryGenerationInProgress(String property) { return this; } + /** + * Add the created action message for the key 'errors.tagtype_invalid_name' with parameters. + *
+     * message: Enter a tag name of 1 to {0} characters without control characters.
+     * 
+ * @param property The property name for the message. (NotNull) + * @param arg0 The parameter arg0 for message. (NotNull) + * @return this. (NotNull) + */ + public FessMessages addErrorsTagtypeInvalidName(String property, String arg0) { + assertPropertyNotNull(property); + add(property, new UserMessage(ERRORS_tagtype_invalid_name, arg0)); + return this; + } + + /** + * Add the created action message for the key 'errors.tagtype_too_many_paths' with parameters. + *
+     * message: A tag can have up to {0} paths.
+     * 
+ * @param property The property name for the message. (NotNull) + * @param arg0 The parameter arg0 for message. (NotNull) + * @return this. (NotNull) + */ + public FessMessages addErrorsTagtypeTooManyPaths(String property, String arg0) { + assertPropertyNotNull(property); + add(property, new UserMessage(ERRORS_tagtype_too_many_paths, arg0)); + return this; + } + + /** + * Add the created action message for the key 'errors.tagtype_already_exists' with parameters. + *
+     * message: A tag with the same name and owner already exists.
+     * 
+ * @param property The property name for the message. (NotNull) + * @return this. (NotNull) + */ + public FessMessages addErrorsTagtypeAlreadyExists(String property) { + assertPropertyNotNull(property); + add(property, new UserMessage(ERRORS_tagtype_already_exists)); + return this; + } + + /** + * Add the created action message for the key 'errors.tagtype_changed_concurrently' with parameters. + *
+     * message: The tag was changed by someone else. Reload it and try again.
+     * 
+ * @param property The property name for the message. (NotNull) + * @return this. (NotNull) + */ + public FessMessages addErrorsTagtypeChangedConcurrently(String property) { + assertPropertyNotNull(property); + add(property, new UserMessage(ERRORS_tagtype_changed_concurrently)); + return this; + } + /** * Add the created action message for the key 'errors.property_required' with parameters. *
diff --git a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
index 53d5742466..40140a75bd 100644
--- a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
+++ b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
@@ -837,7 +837,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g.  */
     String INDEX_DICTIONARY_PREFIX = "index.dictionary.prefix";
 
-    /** The key of the configuration. e.g. lang,role,label,anchor,virtual_host,tag */
+    /** The key of the configuration. e.g. lang,role,label,anchor,virtual_host */
     String INDEX_ADMIN_ARRAY_FIELDS = "index.admin.array.fields";
 
     /** The key of the configuration. e.g. expires,created,timestamp,last_modified */
@@ -1367,8 +1367,20 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. 50 */
     String USER_TAG_NAME_MAX_LENGTH = "user.tag.name.max.length";
 
+    /** The key of the configuration. e.g. 1000 */
+    String USER_TAG_MAX_TAGS = "user.tag.max.tags";
+
+    /** The key of the configuration. e.g. 10000 */
+    String USER_TAG_MAX_PATHS = "user.tag.max.paths";
+
+    /** The key of the configuration. e.g. 10000 */
+    String USER_TAG_QUEUE_MAX_SIZE = "user.tag.queue.max.size";
+
     /** The key of the configuration. e.g. 100 */
-    String USER_TAG_MAX_DOCUMENT_TAGS = "user.tag.max.document.tags";
+    String USER_TAG_PROCESS_BATCH_SIZE = "user.tag.process.batch.size";
+
+    /** The key of the configuration. e.g. 1000 */
+    String USER_TAG_VISIBLE_MAX_SIZE = "user.tag.visible.max.size";
 
     /** The key of the configuration. e.g. 10000 */
     String FORM_ADMIN_MAX_INPUT_SIZE = "form.admin.max.input.size";
@@ -1481,6 +1493,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. 1000 */
     String PAGE_LABELTYPE_MAX_FETCH_SIZE = "page.labeltype.max.fetch.size";
 
+    /** The key of the configuration. e.g. 1000 */
+    String PAGE_TAGTYPE_MAX_FETCH_SIZE = "page.tagtype.max.fetch.size";
+
     /** The key of the configuration. e.g. 1000 */
     String PAGE_ROLETYPE_MAX_FETCH_SIZE = "page.roletype.max.fetch.size";
 
@@ -1823,6 +1838,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. labeltype */
     String ONLINE_HELP_NAME_LABELTYPE = "online.help.name.labeltype";
 
+    /** The key of the configuration. e.g. tagtype */
+    String ONLINE_HELP_NAME_TAGTYPE = "online.help.name.tagtype";
+
     /** The key of the configuration. e.g. duplicatehost */
     String ONLINE_HELP_NAME_DUPLICATEHOST = "online.help.name.duplicatehost";
 
@@ -2198,7 +2216,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. /var/lib/fess/export */
     String INDEX_EXPORT_PATH = "index.export.path";
 
-    /** The key of the configuration. e.g. cache */
+    /** The key of the configuration. e.g. cache,tag */
     String INDEX_EXPORT_EXCLUDE_FIELDS = "index.export.exclude.fields";
 
     /** The key of the configuration. e.g. 100 */
@@ -4838,7 +4856,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /**
      * Get the value for the key 'index.field.tag'. 
* The value is, e.g. tag
- * comment: Field name for the tags of the document in the index: the values of the labels of the kind "tag" whose included paths list the document URL. + * comment: Field name for the user tags of the document in the index. * @return The value of found property. (NotNull: if not found, exception but basically no way) */ String getIndexFieldTag(); @@ -5241,7 +5259,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Get the value for the key 'index.admin.array.fields'.
- * The value is, e.g. lang,role,label,anchor,virtual_host,tag
+ * The value is, e.g. lang,role,label,anchor,virtual_host
* comment: Array-type fields for admin in the index. * @return The value of found property. (NotNull: if not found, exception but basically no way) */ @@ -7201,7 +7219,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Get the value for the key 'user.tag.enabled'.
* The value is, e.g. false
- * comment: Whether logged-in users can tag documents from the search screen. A tag is a label of the kind "tag": its name is the tag name, its included paths list the tagged URLs and its permissions list the users who added it. + * comment: Whether logged-in users can tag documents. Each tag belongs to the user who created it. * @return The value of found property. (NotNull: if not found, exception but basically no way) */ String getUserTagEnabled(); @@ -7209,7 +7227,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Is the property for the key 'user.tag.enabled' true?
* The value is, e.g. false
- * comment: Whether logged-in users can tag documents from the search screen. A tag is a label of the kind "tag": its name is the tag name, its included paths list the tagged URLs and its permissions list the users who added it. + * comment: Whether logged-in users can tag documents. Each tag belongs to the user who created it. * @return The determination, true or false. (if not found, exception but basically no way) */ boolean isUserTagEnabled(); @@ -7217,7 +7235,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Get the value for the key 'user.tag.name.max.length'.
* The value is, e.g. 50
- * comment: Maximum length of a tag name. + * comment: Maximum length of a tag name, in code points. * @return The value of found property. (NotNull: if not found, exception but basically no way) */ String getUserTagNameMaxLength(); @@ -7225,28 +7243,96 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Get the value for the key 'user.tag.name.max.length' as {@link Integer}.
* The value is, e.g. 50
- * comment: Maximum length of a tag name. + * comment: Maximum length of a tag name, in code points. * @return The value of found property. (NotNull: if not found, exception but basically no way) * @throws NumberFormatException When the property is not integer. */ Integer getUserTagNameMaxLengthAsInteger(); /** - * Get the value for the key 'user.tag.max.document.tags'.
+ * Get the value for the key 'user.tag.max.tags'.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tags one user can own. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getUserTagMaxTags(); + + /** + * Get the value for the key 'user.tag.max.tags' as {@link Integer}.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tags one user can own. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getUserTagMaxTagsAsInteger(); + + /** + * Get the value for the key 'user.tag.max.paths'.
+ * The value is, e.g. 10000
+ * comment: Maximum number of URLs one tag can be put on. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getUserTagMaxPaths(); + + /** + * Get the value for the key 'user.tag.max.paths' as {@link Integer}.
+ * The value is, e.g. 10000
+ * comment: Maximum number of URLs one tag can be put on. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getUserTagMaxPathsAsInteger(); + + /** + * Get the value for the key 'user.tag.queue.max.size'.
+ * The value is, e.g. 10000
+ * comment: Maximum number of pending tag changes held in memory until they are applied to the documents. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getUserTagQueueMaxSize(); + + /** + * Get the value for the key 'user.tag.queue.max.size' as {@link Integer}.
+ * The value is, e.g. 10000
+ * comment: Maximum number of pending tag changes held in memory until they are applied to the documents. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getUserTagQueueMaxSizeAsInteger(); + + /** + * Get the value for the key 'user.tag.process.batch.size'.
* The value is, e.g. 100
- * comment: Maximum number of tags on one document. + * comment: Number of URLs updated per bulk request when tag changes are applied to the documents. * @return The value of found property. (NotNull: if not found, exception but basically no way) */ - String getUserTagMaxDocumentTags(); + String getUserTagProcessBatchSize(); /** - * Get the value for the key 'user.tag.max.document.tags' as {@link Integer}.
+ * Get the value for the key 'user.tag.process.batch.size' as {@link Integer}.
* The value is, e.g. 100
- * comment: Maximum number of tags on one document. + * comment: Number of URLs updated per bulk request when tag changes are applied to the documents. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getUserTagProcessBatchSizeAsInteger(); + + /** + * Get the value for the key 'user.tag.visible.max.size'.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tags visible to one user in a search. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getUserTagVisibleMaxSize(); + + /** + * Get the value for the key 'user.tag.visible.max.size' as {@link Integer}.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tags visible to one user in a search. * @return The value of found property. (NotNull: if not found, exception but basically no way) * @throws NumberFormatException When the property is not integer. */ - Integer getUserTagMaxDocumentTagsAsInteger(); + Integer getUserTagVisibleMaxSizeAsInteger(); /** * Get the value for the key 'form.admin.max.input.size'.
@@ -7746,6 +7832,23 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction */ Integer getPageLabeltypeMaxFetchSizeAsInteger(); + /** + * Get the value for the key 'page.tagtype.max.fetch.size'.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tagtype records to fetch per page. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getPageTagtypeMaxFetchSize(); + + /** + * Get the value for the key 'page.tagtype.max.fetch.size' as {@link Integer}.
+ * The value is, e.g. 1000
+ * comment: Maximum number of tagtype records to fetch per page. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + * @throws NumberFormatException When the property is not integer. + */ + Integer getPageTagtypeMaxFetchSizeAsInteger(); + /** * Get the value for the key 'page.roletype.max.fetch.size'.
* The value is, e.g. 1000
@@ -9277,6 +9380,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction */ String getOnlineHelpNameLabeltype(); + /** + * Get the value for the key 'online.help.name.tagtype'.
+ * The value is, e.g. tagtype
+ * comment: Online help key for tag type. + * @return The value of found property. (NotNull: if not found, exception but basically no way) + */ + String getOnlineHelpNameTagtype(); + /** * Get the value for the key 'online.help.name.duplicatehost'.
* The value is, e.g. duplicatehost
@@ -10766,7 +10877,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction /** * Get the value for the key 'index.export.exclude.fields'.
- * The value is, e.g. cache
+ * The value is, e.g. cache,tag
* @return The value of found property. (NotNull: if not found, exception but basically no way) */ String getIndexExportExcludeFields(); @@ -13429,12 +13540,44 @@ public Integer getUserTagNameMaxLengthAsInteger() { return getAsInteger(FessConfig.USER_TAG_NAME_MAX_LENGTH); } - public String getUserTagMaxDocumentTags() { - return get(FessConfig.USER_TAG_MAX_DOCUMENT_TAGS); + public String getUserTagMaxTags() { + return get(FessConfig.USER_TAG_MAX_TAGS); + } + + public Integer getUserTagMaxTagsAsInteger() { + return getAsInteger(FessConfig.USER_TAG_MAX_TAGS); + } + + public String getUserTagMaxPaths() { + return get(FessConfig.USER_TAG_MAX_PATHS); + } + + public Integer getUserTagMaxPathsAsInteger() { + return getAsInteger(FessConfig.USER_TAG_MAX_PATHS); + } + + public String getUserTagQueueMaxSize() { + return get(FessConfig.USER_TAG_QUEUE_MAX_SIZE); } - public Integer getUserTagMaxDocumentTagsAsInteger() { - return getAsInteger(FessConfig.USER_TAG_MAX_DOCUMENT_TAGS); + public Integer getUserTagQueueMaxSizeAsInteger() { + return getAsInteger(FessConfig.USER_TAG_QUEUE_MAX_SIZE); + } + + public String getUserTagProcessBatchSize() { + return get(FessConfig.USER_TAG_PROCESS_BATCH_SIZE); + } + + public Integer getUserTagProcessBatchSizeAsInteger() { + return getAsInteger(FessConfig.USER_TAG_PROCESS_BATCH_SIZE); + } + + public String getUserTagVisibleMaxSize() { + return get(FessConfig.USER_TAG_VISIBLE_MAX_SIZE); + } + + public Integer getUserTagVisibleMaxSizeAsInteger() { + return getAsInteger(FessConfig.USER_TAG_VISIBLE_MAX_SIZE); } public String getFormAdminMaxInputSize() { @@ -13669,6 +13812,14 @@ public Integer getPageLabeltypeMaxFetchSizeAsInteger() { return getAsInteger(FessConfig.PAGE_LABELTYPE_MAX_FETCH_SIZE); } + public String getPageTagtypeMaxFetchSize() { + return get(FessConfig.PAGE_TAGTYPE_MAX_FETCH_SIZE); + } + + public Integer getPageTagtypeMaxFetchSizeAsInteger() { + return getAsInteger(FessConfig.PAGE_TAGTYPE_MAX_FETCH_SIZE); + } + public String getPageRoletypeMaxFetchSize() { return get(FessConfig.PAGE_ROLETYPE_MAX_FETCH_SIZE); } @@ -14401,6 +14552,10 @@ public String getOnlineHelpNameLabeltype() { return get(FessConfig.ONLINE_HELP_NAME_LABELTYPE); } + public String getOnlineHelpNameTagtype() { + return get(FessConfig.ONLINE_HELP_NAME_TAGTYPE); + } + public String getOnlineHelpNameDuplicatehost() { return get(FessConfig.ONLINE_HELP_NAME_DUPLICATEHOST); } @@ -15544,7 +15699,7 @@ protected java.util.Map prepareGeneratedDefaultMap() { defaultMap.put(FessConfig.INDEX_USER_INDEX, "fess_user"); defaultMap.put(FessConfig.INDEX_LOG_INDEX, "fess_log"); defaultMap.put(FessConfig.INDEX_DICTIONARY_PREFIX, ""); - defaultMap.put(FessConfig.INDEX_ADMIN_ARRAY_FIELDS, "lang,role,label,anchor,virtual_host,tag"); + defaultMap.put(FessConfig.INDEX_ADMIN_ARRAY_FIELDS, "lang,role,label,anchor,virtual_host"); defaultMap.put(FessConfig.INDEX_ADMIN_DATE_FIELDS, "expires,created,timestamp,last_modified"); defaultMap.put(FessConfig.INDEX_ADMIN_INTEGER_FIELDS, ""); defaultMap.put(FessConfig.INDEX_ADMIN_LONG_FIELDS, "content_length,favorite_count,click_count"); @@ -15686,7 +15841,11 @@ protected java.util.Map prepareGeneratedDefaultMap() { defaultMap.put(FessConfig.SEARCH_HISTORY_SIZE, "10"); defaultMap.put(FessConfig.USER_TAG_ENABLED, "false"); defaultMap.put(FessConfig.USER_TAG_NAME_MAX_LENGTH, "50"); - defaultMap.put(FessConfig.USER_TAG_MAX_DOCUMENT_TAGS, "100"); + defaultMap.put(FessConfig.USER_TAG_MAX_TAGS, "1000"); + defaultMap.put(FessConfig.USER_TAG_MAX_PATHS, "10000"); + defaultMap.put(FessConfig.USER_TAG_QUEUE_MAX_SIZE, "10000"); + defaultMap.put(FessConfig.USER_TAG_PROCESS_BATCH_SIZE, "100"); + defaultMap.put(FessConfig.USER_TAG_VISIBLE_MAX_SIZE, "1000"); defaultMap.put(FessConfig.FORM_ADMIN_MAX_INPUT_SIZE, "10000"); defaultMap.put(FessConfig.FORM_ADMIN_LABEL_IN_CONFIG_ENABLED, "false"); defaultMap.put(FessConfig.FORM_ADMIN_DEFAULT_TEMPLATE_NAME, "__TEMPLATE__"); @@ -15723,6 +15882,7 @@ protected java.util.Map prepareGeneratedDefaultMap() { defaultMap.put(FessConfig.PAGE_DOCBOOST_MAX_FETCH_SIZE, "1000"); defaultMap.put(FessConfig.PAGE_KEYMATCH_MAX_FETCH_SIZE, "1000"); defaultMap.put(FessConfig.PAGE_LABELTYPE_MAX_FETCH_SIZE, "1000"); + defaultMap.put(FessConfig.PAGE_TAGTYPE_MAX_FETCH_SIZE, "1000"); defaultMap.put(FessConfig.PAGE_ROLETYPE_MAX_FETCH_SIZE, "1000"); defaultMap.put(FessConfig.PAGE_USER_MAX_FETCH_SIZE, "1000"); defaultMap.put(FessConfig.PAGE_ROLE_MAX_FETCH_SIZE, "1000"); @@ -15837,6 +15997,7 @@ protected java.util.Map prepareGeneratedDefaultMap() { defaultMap.put(FessConfig.ONLINE_HELP_NAME_FILECONFIG, "fileconfig"); defaultMap.put(FessConfig.ONLINE_HELP_NAME_FILEAUTH, "fileauth"); defaultMap.put(FessConfig.ONLINE_HELP_NAME_LABELTYPE, "labeltype"); + defaultMap.put(FessConfig.ONLINE_HELP_NAME_TAGTYPE, "tagtype"); defaultMap.put(FessConfig.ONLINE_HELP_NAME_DUPLICATEHOST, "duplicatehost"); defaultMap.put(FessConfig.ONLINE_HELP_NAME_SCHEDULER, "scheduler"); defaultMap.put(FessConfig.ONLINE_HELP_NAME_CRAWLINGINFO, "crawlinginfo"); @@ -15963,7 +16124,7 @@ protected java.util.Map prepareGeneratedDefaultMap() { defaultMap.put(FessConfig.RAG_CHAT_DOCUMENT_MAX_PARTS, "10"); defaultMap.put(FessConfig.RAG_CHAT_RESPONSE_LANGUAGE, "browser"); defaultMap.put(FessConfig.INDEX_EXPORT_PATH, "/var/lib/fess/export"); - defaultMap.put(FessConfig.INDEX_EXPORT_EXCLUDE_FIELDS, "cache"); + defaultMap.put(FessConfig.INDEX_EXPORT_EXCLUDE_FIELDS, "cache,tag"); defaultMap.put(FessConfig.INDEX_EXPORT_SCROLL_SIZE, "100"); defaultMap.put(FessConfig.INDEX_EXPORT_FORMAT, "html"); defaultMap.put(FessConfig.LOG_NOTIFICATION_FLUSH_INTERVAL, "30"); diff --git a/src/main/java/org/codelibs/fess/opensearch/client/SearchEngineClient.java b/src/main/java/org/codelibs/fess/opensearch/client/SearchEngineClient.java index 7081b9b138..98a8a59785 100644 --- a/src/main/java/org/codelibs/fess/opensearch/client/SearchEngineClient.java +++ b/src/main/java/org/codelibs/fess/opensearch/client/SearchEngineClient.java @@ -35,6 +35,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -44,6 +46,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.lucene.util.BytesRef; import org.codelibs.core.beans.util.BeanUtil; import org.codelibs.core.exception.ResourceNotFoundRuntimeException; import org.codelibs.core.io.FileUtil; @@ -166,6 +169,7 @@ import org.codelibs.fesen.opensearch.search.SearchHits; import org.codelibs.fesen.opensearch.search.aggregations.AggregationBuilders; import org.codelibs.fesen.opensearch.search.aggregations.bucket.filter.FilterAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.terms.IncludeExclude; import org.codelibs.fesen.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.codelibs.fesen.opensearch.search.builder.PointInTimeBuilder; import org.codelibs.fesen.opensearch.search.collapse.CollapseBuilder; @@ -1231,7 +1235,8 @@ public void addMapping(final String index, final String docType, final String in * never receives fields that a new release adds to it. The first write of such a field would * map it dynamically, and the intended type could then only be applied by a reindex. Fields * the index already has are never changed. The document index is skipped: its mapping is - * rewritten by plugins and is upgraded by a reindex.

+ * rewritten by plugins and is upgraded by a reindex. Only its tag field is added, by + * {@link #addMissingTagProperty(String, MappingMetadata)}.

* *

Only the webapp process ({@link #isWebappProcess()}) adds the fields. The webapp opens the * client before it starts any job process, so the fields are already there when a crawler, @@ -1245,7 +1250,11 @@ public void addMapping(final String index, final String docType, final String in */ protected void addMissingProperties(final String index, final String docType, final String indexName, final MappingMetadata existingProperties) { - if (DOC_INDEX.equals(index) || !isWebappProcess()) { + if (!isWebappProcess()) { + return; + } + if (DOC_INDEX.equals(index)) { + addMissingTagProperty(indexName, existingProperties); return; } final FessConfig fessConfig = ComponentUtil.getFessConfig(); @@ -1281,6 +1290,40 @@ protected void addMissingProperties(final String index, final String docType, fi } } + /** + * Adds the user tag field to an existing document index that does not have it yet. + * + *

The field was added to the bundled document mapping after release, and an existing index + * would map it dynamically as text on the first tag write, which breaks the terms facet and + * the term filters on it. Only this one field is put, as {@code keyword}. A tag field the index + * already has is never changed; one of another type is reported, since only a reindex can + * change it.

+ * + * @param indexName the actual document index name + * @param existingProperties the index's current {@code properties} mapping + */ + protected void addMissingTagProperty(final String indexName, final MappingMetadata existingProperties) { + final String tagField = ComponentUtil.getFessConfig().getIndexFieldTag(); + final Object existing = existingProperties.sourceAsMap().get(tagField); + if (existing != null) { + final Object type = existing instanceof final Map definition ? definition.get("type") : null; + if (!"keyword".equals(type)) { + logger.warn("The {} field of {} is not mapped as keyword; reindex to use user tags: type={}", tagField, indexName, type); + } + return; + } + final Map properties = Collections.singletonMap(tagField, Collections.singletonMap("type", "keyword")); + try { + if (putMapping(indexName, new ObjectMapper().writeValueAsString(Collections.singletonMap("properties", properties)))) { + logger.info("Added fields to {} mapping: fields={}", indexName, properties.keySet()); + } else { + logger.warn("Failed to add fields to {} mapping: fields={}", indexName, properties.keySet()); + } + } catch (final Exception e) { + logger.warn("Failed to add fields to {} mapping: fields={}", indexName, properties.keySet(), e); + } + } + /** * Returns the bundled properties whose names the existing mapping does not have. * @@ -2693,9 +2736,22 @@ protected void buildFacet(final QueryHelper queryHelper, final QueryFieldConfig messages -> messages.addErrorsInvalidQueryUnsupportedFacetField(UserMessages.GLOBAL_PROPERTY_KEY, f), "Unsupported facet field: " + f); } + final IncludeExclude tagInclude; + if (f.equals(fessConfig.getIndexFieldTag()) && !SearchRequestType.ADMIN_SEARCH.equals(searchRequestType)) { + // a tag value decodes to the name and the owner of a tag: only the tags the caller can see become buckets + tagInclude = buildVisibleTagInclude(fessConfig); + if (tagInclude == null) { + return; + } + } else { + tagInclude = null; + } final String encodedField = BaseEncoding.base64().encode(f.getBytes(StandardCharsets.UTF_8)); final TermsAggregationBuilder termsBuilder = AggregationBuilders.terms(Constants.FACET_FIELD_PREFIX + encodedField).field(f); + if (tagInclude != null) { + termsBuilder.includeExclude(tagInclude); + } termsBuilder.order(facetInfo.getBucketOrder()); if (facetInfo.size != null) { final int maxFacetSize = fessConfig.getQueryFacetFieldsSizeMaxOrDefault(); @@ -2721,6 +2777,33 @@ protected void buildFacet(final QueryHelper queryHelper, final QueryFieldConfig })); } + /** + * Builds the include of the tag facet: the exact values of the tags that the caller can see. + * Not called for the admin search, whose tag facet is not restricted. + * + * @param fessConfig the Fess configuration + * @return the include, or null when the caller can see no tag and the facet is not aggregated + */ + protected IncludeExclude buildVisibleTagInclude(final FessConfig fessConfig) { + if (!fessConfig.isUserTagEnabled()) { + return null; + } + final Set values; + try { + values = ComponentUtil.getTagTypeHelper().getVisibleTagValues(searchRequestType); + } catch (final RuntimeException e) { + logger.warn("Failed to resolve the visible tags; the tag facet is not aggregated. searchRequestType={}", searchRequestType, + e); + return null; + } + if (values.isEmpty()) { + return null; + } + final SortedSet includeValues = new TreeSet<>(); + values.forEach(value -> includeValues.add(new BytesRef(value))); + return new IncludeExclude(includeValues, null); + } + /** * Builds the highlighting configuration. * diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsLabelTypeBhv.java b/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsLabelTypeBhv.java index 1df57a3270..93ee796019 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsLabelTypeBhv.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsLabelTypeBhv.java @@ -77,7 +77,6 @@ protected RESULT createEntity(Map sou result.setCreatedTime(DfTypeUtil.toLong(source.get("createdTime"))); result.setExcludedPaths(DfTypeUtil.toString(source.get("excludedPaths"))); result.setIncludedPaths(DfTypeUtil.toString(source.get("includedPaths"))); - result.setKind(DfTypeUtil.toString(source.get("kind"))); result.setName(DfTypeUtil.toString(source.get("name"))); result.setPermissions(toStringArray(source.get("permissions"))); result.setSortOrder(DfTypeUtil.toInteger(source.get("sortOrder"))); diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsTagTypeBhv.java b/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsTagTypeBhv.java new file mode 100644 index 0000000000..1fe43eb0d8 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsbhv/BsTagTypeBhv.java @@ -0,0 +1,266 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.bsbhv; + +import java.util.List; +import java.util.Map; + +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractBehavior; +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractEntity.RequestOptionCall; +import org.codelibs.fess.opensearch.config.bsentity.dbmeta.TagTypeDbm; +import org.codelibs.fess.opensearch.config.cbean.TagTypeCB; +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.dbflute.Entity; +import org.dbflute.bhv.readable.CBCall; +import org.dbflute.bhv.readable.EntityRowHandler; +import org.dbflute.cbean.ConditionBean; +import org.dbflute.cbean.result.ListResultBean; +import org.dbflute.cbean.result.PagingResultBean; +import org.dbflute.exception.IllegalBehaviorStateException; +import org.dbflute.optional.OptionalEntity; +import org.dbflute.util.DfTypeUtil; +import org.codelibs.fesen.opensearch.action.bulk.BulkRequestBuilder; +import org.codelibs.fesen.opensearch.action.delete.DeleteRequestBuilder; +import org.codelibs.fesen.opensearch.action.index.IndexRequestBuilder; + +/** + * @author ESFlute (using FreeGen) + */ +public abstract class BsTagTypeBhv extends EsAbstractBehavior { + + // =================================================================================== + // Control Override + // ================ + @Override + public String asTableDbName() { + return asEsIndexType(); + } + + @Override + protected String asEsIndex() { + return "fess_config.tag_type"; + } + + @Override + public String asEsIndexType() { + return "tag_type"; + } + + @Override + public String asEsSearchType() { + return "tag_type"; + } + + @Override + public TagTypeDbm asDBMeta() { + return TagTypeDbm.getInstance(); + } + + @Override + protected RESULT createEntity(Map source, Class entityType) { + try { + final RESULT result = entityType.newInstance(); + result.setCreatedBy(DfTypeUtil.toString(source.get("createdBy"))); + result.setCreatedTime(DfTypeUtil.toLong(source.get("createdTime"))); + result.setName(DfTypeUtil.toString(source.get("name"))); + result.setOwner(DfTypeUtil.toString(source.get("owner"))); + result.setPaths(toStringArray(source.get("paths"))); + result.setPermissions(toStringArray(source.get("permissions"))); + result.setSortOrder(DfTypeUtil.toInteger(source.get("sortOrder"))); + result.setUpdatedBy(DfTypeUtil.toString(source.get("updatedBy"))); + result.setUpdatedTime(DfTypeUtil.toLong(source.get("updatedTime"))); + result.setVirtualHost(DfTypeUtil.toString(source.get("virtualHost"))); + return updateEntity(source, result); + } catch (InstantiationException | IllegalAccessException e) { + final String msg = "Cannot create a new instance: " + entityType.getName(); + throw new IllegalBehaviorStateException(msg, e); + } + } + + protected RESULT updateEntity(Map source, RESULT result) { + return result; + } + + // =================================================================================== + // Select + // ====== + public int selectCount(CBCall cbLambda) { + return facadeSelectCount(createCB(cbLambda)); + } + + public OptionalEntity selectEntity(CBCall cbLambda) { + return facadeSelectEntity(createCB(cbLambda)); + } + + protected OptionalEntity facadeSelectEntity(TagTypeCB cb) { + return doSelectOptionalEntity(cb, typeOfSelectedEntity()); + } + + protected OptionalEntity doSelectOptionalEntity(TagTypeCB cb, Class tp) { + return createOptionalEntity(doSelectEntity(cb, tp), cb); + } + + @Override + public TagTypeCB newConditionBean() { + return new TagTypeCB(); + } + + @Override + protected Entity doReadEntity(ConditionBean cb) { + return facadeSelectEntity(downcast(cb)).orElse(null); + } + + public TagType selectEntityWithDeletedCheck(CBCall cbLambda) { + return facadeSelectEntityWithDeletedCheck(createCB(cbLambda)); + } + + public OptionalEntity selectByPK(String id) { + return facadeSelectByPK(id); + } + + protected OptionalEntity facadeSelectByPK(String id) { + return doSelectOptionalByPK(id, typeOfSelectedEntity()); + } + + protected ENTITY doSelectByPK(String id, Class tp) { + return doSelectEntity(xprepareCBAsPK(id), tp); + } + + protected TagTypeCB xprepareCBAsPK(String id) { + assertObjectNotNull("id", id); + return newConditionBean().acceptPK(id); + } + + protected OptionalEntity doSelectOptionalByPK(String id, Class tp) { + return createOptionalEntity(doSelectByPK(id, tp), id); + } + + @Override + protected Class typeOfSelectedEntity() { + return TagType.class; + } + + @Override + protected Class typeOfHandlingEntity() { + return TagType.class; + } + + @Override + protected Class typeOfHandlingConditionBean() { + return TagTypeCB.class; + } + + public ListResultBean selectList(CBCall cbLambda) { + return facadeSelectList(createCB(cbLambda)); + } + + public PagingResultBean selectPage(CBCall cbLambda) { + // #pending same? + return (PagingResultBean) facadeSelectList(createCB(cbLambda)); + } + + public void selectCursor(CBCall cbLambda, EntityRowHandler entityLambda) { + facadeSelectCursor(createCB(cbLambda), entityLambda); + } + + public void selectBulk(CBCall cbLambda, EntityRowHandler> entityLambda) { + delegateSelectBulk(createCB(cbLambda), entityLambda, typeOfSelectedEntity()); + } + + // =================================================================================== + // Update + // ====== + public void insert(TagType entity) { + doInsert(entity, null); + } + + public void insert(TagType entity, RequestOptionCall opLambda) { + entity.asDocMeta().indexOption(opLambda); + doInsert(entity, null); + } + + public void update(TagType entity) { + doUpdate(entity, null); + } + + public void update(TagType entity, RequestOptionCall opLambda) { + entity.asDocMeta().indexOption(opLambda); + doUpdate(entity, null); + } + + public void insertOrUpdate(TagType entity) { + doInsertOrUpdate(entity, null, null); + } + + public void insertOrUpdate(TagType entity, RequestOptionCall opLambda) { + entity.asDocMeta().indexOption(opLambda); + doInsertOrUpdate(entity, null, null); + } + + public void delete(TagType entity) { + doDelete(entity, null); + } + + public void delete(TagType entity, RequestOptionCall opLambda) { + entity.asDocMeta().deleteOption(opLambda); + doDelete(entity, null); + } + + public int queryDelete(CBCall cbLambda) { + return doQueryDelete(createCB(cbLambda), null); + } + + public int[] batchInsert(List list) { + return batchInsert(list, null, null); + } + + public int[] batchInsert(List list, RequestOptionCall call) { + return batchInsert(list, call, null); + } + + public int[] batchInsert(List list, RequestOptionCall call, + RequestOptionCall entityCall) { + return doBatchInsert(new BulkList<>(list, call, entityCall), null); + } + + public int[] batchUpdate(List list) { + return batchUpdate(list, null, null); + } + + public int[] batchUpdate(List list, RequestOptionCall call) { + return batchUpdate(list, call, null); + } + + public int[] batchUpdate(List list, RequestOptionCall call, + RequestOptionCall entityCall) { + return doBatchUpdate(new BulkList<>(list, call, entityCall), null); + } + + public int[] batchDelete(List list) { + return batchDelete(list, null, null); + } + + public int[] batchDelete(List list, RequestOptionCall call) { + return batchDelete(list, call, null); + } + + public int[] batchDelete(List list, RequestOptionCall call, + RequestOptionCall entityCall) { + return doBatchDelete(new BulkList<>(list, call, entityCall), null); + } + + // #pending create, modify, remove +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsLabelType.java b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsLabelType.java index 733e779159..0dd6edf709 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsLabelType.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsLabelType.java @@ -49,9 +49,6 @@ public class BsLabelType extends EsAbstractEntity { /** includedPaths */ protected String includedPaths; - /** kind */ - protected String kind; - /** name */ protected String name; @@ -106,9 +103,6 @@ public Map toSource() { if (includedPaths != null) { addFieldToSource(sourceMap, "includedPaths", includedPaths); } - if (kind != null) { - addFieldToSource(sourceMap, "kind", kind); - } if (name != null) { addFieldToSource(sourceMap, "name", name); } @@ -147,7 +141,6 @@ protected String doBuildColumnString(String dm) { sb.append(dm).append(createdTime); sb.append(dm).append(excludedPaths); sb.append(dm).append(includedPaths); - sb.append(dm).append(kind); sb.append(dm).append(name); sb.append(dm).append(permissions); sb.append(dm).append(sortOrder); @@ -205,16 +198,6 @@ public void setIncludedPaths(String value) { this.includedPaths = value; } - public String getKind() { - checkSpecifiedProperty("kind"); - return convertEmptyToNull(kind); - } - - public void setKind(String value) { - registerModifiedProperty("kind"); - this.kind = value; - } - public String getName() { checkSpecifiedProperty("name"); return convertEmptyToNull(name); diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsTagType.java b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsTagType.java new file mode 100644 index 0000000000..4efa8b0e12 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/BsTagType.java @@ -0,0 +1,253 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.bsentity; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractEntity; +import org.codelibs.fess.opensearch.config.bsentity.dbmeta.TagTypeDbm; + +/** + * ${table.comment} + * @author ESFlute (using FreeGen) + */ +public class BsTagType extends EsAbstractEntity { + + // =================================================================================== + // Definition + // ========== + private static final long serialVersionUID = 1L; + protected static final Class suppressUnusedImportLocalDateTime = LocalDateTime.class; + + // =================================================================================== + // Attribute + // ========= + /** createdBy */ + protected String createdBy; + + /** createdTime */ + protected Long createdTime; + + /** name */ + protected String name; + + /** owner */ + protected String owner; + + /** paths */ + protected String[] paths; + + /** permissions */ + protected String[] permissions; + + /** sortOrder */ + protected Integer sortOrder; + + /** updatedBy */ + protected String updatedBy; + + /** updatedTime */ + protected Long updatedTime; + + /** virtualHost */ + protected String virtualHost; + + // [Referrers] *comment only + + // =================================================================================== + // DB Meta + // ======= + @Override + public TagTypeDbm asDBMeta() { + return TagTypeDbm.getInstance(); + } + + @Override + public String asTableDbName() { + return "tag_type"; + } + + // =================================================================================== + // Source + // ====== + @Override + public Map toSource() { + Map sourceMap = new HashMap<>(); + if (createdBy != null) { + addFieldToSource(sourceMap, "createdBy", createdBy); + } + if (createdTime != null) { + addFieldToSource(sourceMap, "createdTime", createdTime); + } + if (name != null) { + addFieldToSource(sourceMap, "name", name); + } + if (owner != null) { + addFieldToSource(sourceMap, "owner", owner); + } + if (paths != null) { + addFieldToSource(sourceMap, "paths", paths); + } + if (permissions != null) { + addFieldToSource(sourceMap, "permissions", permissions); + } + if (sortOrder != null) { + addFieldToSource(sourceMap, "sortOrder", sortOrder); + } + if (updatedBy != null) { + addFieldToSource(sourceMap, "updatedBy", updatedBy); + } + if (updatedTime != null) { + addFieldToSource(sourceMap, "updatedTime", updatedTime); + } + if (virtualHost != null) { + addFieldToSource(sourceMap, "virtualHost", virtualHost); + } + return sourceMap; + } + + protected void addFieldToSource(Map sourceMap, String field, Object value) { + sourceMap.put(field, value); + } + + // =================================================================================== + // Basic Override + // ============== + @Override + protected String doBuildColumnString(String dm) { + StringBuilder sb = new StringBuilder(); + sb.append(dm).append(createdBy); + sb.append(dm).append(createdTime); + sb.append(dm).append(name); + sb.append(dm).append(owner); + sb.append(dm).append(paths); + sb.append(dm).append(permissions); + sb.append(dm).append(sortOrder); + sb.append(dm).append(updatedBy); + sb.append(dm).append(updatedTime); + sb.append(dm).append(virtualHost); + if (sb.length() > dm.length()) { + sb.delete(0, dm.length()); + } + sb.insert(0, "{").append("}"); + return sb.toString(); + } + + // =================================================================================== + // Accessor + // ======== + public String getCreatedBy() { + checkSpecifiedProperty("createdBy"); + return convertEmptyToNull(createdBy); + } + + public void setCreatedBy(String value) { + registerModifiedProperty("createdBy"); + this.createdBy = value; + } + + public Long getCreatedTime() { + checkSpecifiedProperty("createdTime"); + return createdTime; + } + + public void setCreatedTime(Long value) { + registerModifiedProperty("createdTime"); + this.createdTime = value; + } + + public String getName() { + checkSpecifiedProperty("name"); + return convertEmptyToNull(name); + } + + public void setName(String value) { + registerModifiedProperty("name"); + this.name = value; + } + + public String getOwner() { + checkSpecifiedProperty("owner"); + return convertEmptyToNull(owner); + } + + public void setOwner(String value) { + registerModifiedProperty("owner"); + this.owner = value; + } + + public String[] getPaths() { + checkSpecifiedProperty("paths"); + return paths; + } + + public void setPaths(String[] value) { + registerModifiedProperty("paths"); + this.paths = value; + } + + public String[] getPermissions() { + checkSpecifiedProperty("permissions"); + return permissions; + } + + public void setPermissions(String[] value) { + registerModifiedProperty("permissions"); + this.permissions = value; + } + + public Integer getSortOrder() { + checkSpecifiedProperty("sortOrder"); + return sortOrder; + } + + public void setSortOrder(Integer value) { + registerModifiedProperty("sortOrder"); + this.sortOrder = value; + } + + public String getUpdatedBy() { + checkSpecifiedProperty("updatedBy"); + return convertEmptyToNull(updatedBy); + } + + public void setUpdatedBy(String value) { + registerModifiedProperty("updatedBy"); + this.updatedBy = value; + } + + public Long getUpdatedTime() { + checkSpecifiedProperty("updatedTime"); + return updatedTime; + } + + public void setUpdatedTime(Long value) { + registerModifiedProperty("updatedTime"); + this.updatedTime = value; + } + + public String getVirtualHost() { + checkSpecifiedProperty("virtualHost"); + return convertEmptyToNull(virtualHost); + } + + public void setVirtualHost(String value) { + registerModifiedProperty("virtualHost"); + this.virtualHost = value; + } +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/LabelTypeDbm.java b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/LabelTypeDbm.java index f7b1b40264..0d00937478 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/LabelTypeDbm.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/LabelTypeDbm.java @@ -87,7 +87,6 @@ public DBDef getCurrentDBDef() { "excludedPaths"); setupEpg(_epgMap, et -> ((LabelType) et).getIncludedPaths(), (et, vl) -> ((LabelType) et).setIncludedPaths(DfTypeUtil.toString(vl)), "includedPaths"); - setupEpg(_epgMap, et -> ((LabelType) et).getKind(), (et, vl) -> ((LabelType) et).setKind(DfTypeUtil.toString(vl)), "kind"); setupEpg(_epgMap, et -> ((LabelType) et).getName(), (et, vl) -> ((LabelType) et).setName(DfTypeUtil.toString(vl)), "name"); setupEpg(_epgMap, et -> ((LabelType) et).getPermissions(), (et, vl) -> ((LabelType) et).setPermissions((String[]) vl), "permissions"); @@ -144,8 +143,6 @@ public TableSqlName getTableSqlName() { false, false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); protected final ColumnInfo _columnIncludedPaths = cci("includedPaths", "includedPaths", null, null, String.class, "includedPaths", null, false, false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); - protected final ColumnInfo _columnKind = cci("kind", "kind", null, null, String.class, "kind", null, false, false, false, "keyword", 0, - 0, null, null, false, null, null, null, null, null, false); protected final ColumnInfo _columnName = cci("name", "name", null, null, String.class, "name", null, false, false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); protected final ColumnInfo _columnPermissions = cci("permissions", "permissions", null, null, String[].class, "permissions", null, @@ -177,10 +174,6 @@ public ColumnInfo columnIncludedPaths() { return _columnIncludedPaths; } - public ColumnInfo columnKind() { - return _columnKind; - } - public ColumnInfo columnName() { return _columnName; } @@ -215,7 +208,6 @@ protected List ccil() { ls.add(columnCreatedTime()); ls.add(columnExcludedPaths()); ls.add(columnIncludedPaths()); - ls.add(columnKind()); ls.add(columnName()); ls.add(columnPermissions()); ls.add(columnSortOrder()); diff --git a/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/TagTypeDbm.java b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/TagTypeDbm.java new file mode 100644 index 0000000000..230aabc577 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/bsentity/dbmeta/TagTypeDbm.java @@ -0,0 +1,282 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.bsentity.dbmeta; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import org.codelibs.fess.opensearch.config.exentity.TagType; +import org.dbflute.Entity; +import org.dbflute.dbmeta.AbstractDBMeta; +import org.dbflute.dbmeta.info.ColumnInfo; +import org.dbflute.dbmeta.info.UniqueInfo; +import org.dbflute.dbmeta.name.TableSqlName; +import org.dbflute.dbmeta.property.PropertyGateway; +import org.dbflute.dbway.DBDef; +import org.dbflute.util.DfTypeUtil; + +/** + * @author ESFlute (using FreeGen) + */ +public class TagTypeDbm extends AbstractDBMeta { + + protected static final Class suppressUnusedImportLocalDateTime = LocalDateTime.class; + + // =================================================================================== + // Singleton + // ========= + private static final TagTypeDbm _instance = new TagTypeDbm(); + + private TagTypeDbm() { + } + + public static TagTypeDbm getInstance() { + return _instance; + } + + // =================================================================================== + // Current DBDef + // ============= + @Override + public String getProjectName() { + return null; + } + + @Override + public String getProjectPrefix() { + return null; + } + + @Override + public String getGenerationGapBasePrefix() { + return null; + } + + @Override + public DBDef getCurrentDBDef() { + return null; + } + + // =================================================================================== + // Property Gateway + // ================ + // ----------------------------------------------------- + // Column Property + // --------------- + protected final Map _epgMap = newHashMap(); + { + setupEpg(_epgMap, et -> ((TagType) et).getCreatedBy(), (et, vl) -> ((TagType) et).setCreatedBy(DfTypeUtil.toString(vl)), + "createdBy"); + setupEpg(_epgMap, et -> ((TagType) et).getCreatedTime(), (et, vl) -> ((TagType) et).setCreatedTime(DfTypeUtil.toLong(vl)), + "createdTime"); + setupEpg(_epgMap, et -> ((TagType) et).getName(), (et, vl) -> ((TagType) et).setName(DfTypeUtil.toString(vl)), "name"); + setupEpg(_epgMap, et -> ((TagType) et).getOwner(), (et, vl) -> ((TagType) et).setOwner(DfTypeUtil.toString(vl)), "owner"); + setupEpg(_epgMap, et -> ((TagType) et).getPaths(), (et, vl) -> ((TagType) et).setPaths((String[]) vl), "paths"); + setupEpg(_epgMap, et -> ((TagType) et).getPermissions(), (et, vl) -> ((TagType) et).setPermissions((String[]) vl), "permissions"); + setupEpg(_epgMap, et -> ((TagType) et).getSortOrder(), (et, vl) -> ((TagType) et).setSortOrder(DfTypeUtil.toInteger(vl)), + "sortOrder"); + setupEpg(_epgMap, et -> ((TagType) et).getUpdatedBy(), (et, vl) -> ((TagType) et).setUpdatedBy(DfTypeUtil.toString(vl)), + "updatedBy"); + setupEpg(_epgMap, et -> ((TagType) et).getUpdatedTime(), (et, vl) -> ((TagType) et).setUpdatedTime(DfTypeUtil.toLong(vl)), + "updatedTime"); + setupEpg(_epgMap, et -> ((TagType) et).getVirtualHost(), (et, vl) -> ((TagType) et).setVirtualHost(DfTypeUtil.toString(vl)), + "virtualHost"); + } + + @Override + public PropertyGateway findPropertyGateway(final String prop) { + return doFindEpg(_epgMap, prop); + } + + // =================================================================================== + // Table Info + // ========== + protected final String _tableDbName = "tag_type"; + protected final String _tableDispName = "tag_type"; + protected final String _tablePropertyName = "TagType"; + + public String getTableDbName() { + return _tableDbName; + } + + @Override + public String getTableDispName() { + return _tableDispName; + } + + @Override + public String getTablePropertyName() { + return _tablePropertyName; + } + + @Override + public TableSqlName getTableSqlName() { + return null; + } + + // =================================================================================== + // Column Info + // =========== + protected final ColumnInfo _columnCreatedBy = cci("createdBy", "createdBy", null, null, String.class, "createdBy", null, false, false, + false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnCreatedTime = cci("createdTime", "createdTime", null, null, Long.class, "createdTime", null, false, + false, false, "Long", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnName = cci("name", "name", null, null, String.class, "name", null, false, false, false, "keyword", 0, + 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnOwner = cci("owner", "owner", null, null, String.class, "owner", null, false, false, false, "keyword", + 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnPaths = cci("paths", "paths", null, null, String[].class, "paths", null, false, false, false, + "keyword", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnPermissions = cci("permissions", "permissions", null, null, String[].class, "permissions", null, + false, false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnSortOrder = cci("sortOrder", "sortOrder", null, null, Integer.class, "sortOrder", null, false, false, + false, "Integer", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnUpdatedBy = cci("updatedBy", "updatedBy", null, null, String.class, "updatedBy", null, false, false, + false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnUpdatedTime = cci("updatedTime", "updatedTime", null, null, Long.class, "updatedTime", null, false, + false, false, "Long", 0, 0, null, null, false, null, null, null, null, null, false); + protected final ColumnInfo _columnVirtualHost = cci("virtualHost", "virtualHost", null, null, String.class, "virtualHost", null, false, + false, false, "keyword", 0, 0, null, null, false, null, null, null, null, null, false); + + public ColumnInfo columnCreatedBy() { + return _columnCreatedBy; + } + + public ColumnInfo columnCreatedTime() { + return _columnCreatedTime; + } + + public ColumnInfo columnName() { + return _columnName; + } + + public ColumnInfo columnOwner() { + return _columnOwner; + } + + public ColumnInfo columnPaths() { + return _columnPaths; + } + + public ColumnInfo columnPermissions() { + return _columnPermissions; + } + + public ColumnInfo columnSortOrder() { + return _columnSortOrder; + } + + public ColumnInfo columnUpdatedBy() { + return _columnUpdatedBy; + } + + public ColumnInfo columnUpdatedTime() { + return _columnUpdatedTime; + } + + public ColumnInfo columnVirtualHost() { + return _columnVirtualHost; + } + + protected List ccil() { + List ls = newArrayList(); + ls.add(columnCreatedBy()); + ls.add(columnCreatedTime()); + ls.add(columnName()); + ls.add(columnOwner()); + ls.add(columnPaths()); + ls.add(columnPermissions()); + ls.add(columnSortOrder()); + ls.add(columnUpdatedBy()); + ls.add(columnUpdatedTime()); + ls.add(columnVirtualHost()); + return ls; + } + + // =================================================================================== + // Unique Info + // =========== + @Override + public boolean hasPrimaryKey() { + return false; + } + + @Override + public boolean hasCompoundPrimaryKey() { + return false; + } + + @Override + protected UniqueInfo cpui() { + return null; + } + + // =================================================================================== + // Type Name + // ========= + @Override + public String getEntityTypeName() { + return "org.codelibs.fess.opensearch.config.exentity.TagType"; + } + + @Override + public String getConditionBeanTypeName() { + return "org.codelibs.fess.opensearch.config.cbean.TagTypeCB"; + } + + @Override + public String getBehaviorTypeName() { + return "org.codelibs.fess.opensearch.config.exbhv.TagTypeBhv"; + } + + // =================================================================================== + // Object Type + // =========== + @Override + public Class getEntityType() { + return TagType.class; + } + + // =================================================================================== + // Object Instance + // =============== + @Override + public Entity newEntity() { + return new TagType(); + } + + // =================================================================================== + // Map Communication + // ================= + @Override + public void acceptPrimaryKeyMap(Entity entity, Map primaryKeyMap) { + } + + @Override + public void acceptAllColumnMap(Entity entity, Map allColumnMap) { + } + + @Override + public Map extractPrimaryKeyMap(Entity entity) { + return null; + } + + @Override + public Map extractAllColumnMap(Entity entity) { + return null; + } +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/TagTypeCB.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/TagTypeCB.java new file mode 100644 index 0000000000..8e53593cae --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/TagTypeCB.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean; + +import org.codelibs.fess.opensearch.config.cbean.bs.BsTagTypeCB; + +/** + * @author ESFlute (using FreeGen) + */ +public class TagTypeCB extends BsTagTypeCB { +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsLabelTypeCB.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsLabelTypeCB.java index c5252c8b7b..f5587af2c2 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsLabelTypeCB.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsLabelTypeCB.java @@ -192,10 +192,6 @@ public void columnIncludedPaths() { doColumn("includedPaths"); } - public void columnKind() { - doColumn("kind"); - } - public void columnName() { doColumn("name"); } diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsTagTypeCB.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsTagTypeCB.java new file mode 100644 index 0000000000..21b4e39852 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/bs/BsTagTypeCB.java @@ -0,0 +1,219 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean.bs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractConditionBean; +import org.codelibs.fess.opensearch.config.bsentity.dbmeta.TagTypeDbm; +import org.codelibs.fess.opensearch.config.cbean.TagTypeCB; +import org.codelibs.fess.opensearch.config.cbean.ca.TagTypeCA; +import org.codelibs.fess.opensearch.config.cbean.ca.bs.BsTagTypeCA; +import org.codelibs.fess.opensearch.config.cbean.cq.TagTypeCQ; +import org.codelibs.fess.opensearch.config.cbean.cq.bs.BsTagTypeCQ; +import org.dbflute.cbean.ConditionQuery; +import org.codelibs.fesen.opensearch.action.search.SearchRequestBuilder; +import org.codelibs.fesen.opensearch.index.query.QueryBuilder; + +/** + * @author ESFlute (using FreeGen) + */ +public class BsTagTypeCB extends EsAbstractConditionBean { + + // =================================================================================== + // Attribute + // ========= + protected BsTagTypeCQ _conditionQuery; + protected BsTagTypeCA _conditionAggregation; + protected HpSpecification _specification; + + // =================================================================================== + // Control + // ======= + @Override + public TagTypeDbm asDBMeta() { + return TagTypeDbm.getInstance(); + } + + @Override + public String asTableDbName() { + return "tag_type"; + } + + @Override + public boolean hasSpecifiedColumn() { + return _specification != null; + } + + @Override + public ConditionQuery localCQ() { + return doGetConditionQuery(); + } + + // =================================================================================== + // Primary Key + // =========== + public TagTypeCB acceptPK(String id) { + assertObjectNotNull("id", id); + BsTagTypeCB cb = this; + cb.query().docMeta().setId_Equal(id); + return (TagTypeCB) this; + } + + @Override + public void acceptPrimaryKeyMap(Map primaryKeyMap) { + acceptPK((String) primaryKeyMap.get("_id")); + } + + // =================================================================================== + // Build + // ===== + + @Override + public SearchRequestBuilder build(SearchRequestBuilder builder) { + if (_conditionQuery != null) { + QueryBuilder queryBuilder = _conditionQuery.getQuery(); + if (queryBuilder != null) { + builder.setQuery(queryBuilder); + } + _conditionQuery.getFieldSortBuilderList().forEach(sort -> { + builder.addSort(sort); + }); + } + + if (_conditionAggregation != null) { + _conditionAggregation.getAggregationBuilderList().forEach(builder::addAggregation); + } + + if (_specification != null) { + builder.setFetchSource(_specification.columnList.toArray(new String[_specification.columnList.size()]), null); + } + + return builder; + } + + // =================================================================================== + // Query + // ===== + public BsTagTypeCQ query() { + assertQueryPurpose(); + return doGetConditionQuery(); + } + + protected BsTagTypeCQ doGetConditionQuery() { + if (_conditionQuery == null) { + _conditionQuery = createLocalCQ(); + } + return _conditionQuery; + } + + protected BsTagTypeCQ createLocalCQ() { + return new TagTypeCQ(); + } + + // =================================================================================== + // Aggregation + // =========== + public BsTagTypeCA aggregation() { + assertAggregationPurpose(); + return doGetConditionAggregation(); + } + + protected BsTagTypeCA doGetConditionAggregation() { + if (_conditionAggregation == null) { + _conditionAggregation = createLocalCA(); + } + return _conditionAggregation; + } + + protected BsTagTypeCA createLocalCA() { + return new TagTypeCA(); + } + + // =================================================================================== + // Specify + // ======= + public HpSpecification specify() { + assertSpecifyPurpose(); + if (_specification == null) { + _specification = new HpSpecification(); + } + return _specification; + } + + protected void assertQueryPurpose() { + } + + protected void assertAggregationPurpose() { + } + + protected void assertSpecifyPurpose() { + } + + public static class HpSpecification { + protected List columnList = new ArrayList<>(); + + public void doColumn(String name) { + columnList.add(name); + } + + public void columnId() { + doColumn("_id"); + } + + public void columnCreatedBy() { + doColumn("createdBy"); + } + + public void columnCreatedTime() { + doColumn("createdTime"); + } + + public void columnName() { + doColumn("name"); + } + + public void columnOwner() { + doColumn("owner"); + } + + public void columnPaths() { + doColumn("paths"); + } + + public void columnPermissions() { + doColumn("permissions"); + } + + public void columnSortOrder() { + doColumn("sortOrder"); + } + + public void columnUpdatedBy() { + doColumn("updatedBy"); + } + + public void columnUpdatedTime() { + doColumn("updatedTime"); + } + + public void columnVirtualHost() { + doColumn("virtualHost"); + } + } +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/TagTypeCA.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/TagTypeCA.java new file mode 100644 index 0000000000..59fe7435dc --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/TagTypeCA.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean.ca; + +import org.codelibs.fess.opensearch.config.cbean.ca.bs.BsTagTypeCA; + +/** + * @author ESFlute (using FreeGen) + */ +public class TagTypeCA extends BsTagTypeCA { +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsLabelTypeCA.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsLabelTypeCA.java index 9b4bee2956..33c7a3b5d4 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsLabelTypeCA.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsLabelTypeCA.java @@ -726,136 +726,6 @@ public void setIncludedPaths_Missing(String name, ConditionOptionCall opLambda) { - setKind_Terms("kind", opLambda, null); - } - - public void setKind_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { - setKind_Terms("kind", opLambda, aggsLambda); - } - - public void setKind_Terms(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { - TermsAggregationBuilder builder = regTermsA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - if (aggsLambda != null) { - LabelTypeCA ca = new LabelTypeCA(); - aggsLambda.callback(ca); - ca.getAggregationBuilderList().forEach(builder::subAggregation); - } - } - - public void setKind_SignificantTerms() { - setKind_SignificantTerms(null); - } - - public void setKind_SignificantTerms(ConditionOptionCall opLambda) { - setKind_SignificantTerms("kind", opLambda, null); - } - - public void setKind_SignificantTerms(ConditionOptionCall opLambda, - OperatorCall aggsLambda) { - setKind_SignificantTerms("kind", opLambda, aggsLambda); - } - - public void setKind_SignificantTerms(String name, ConditionOptionCall opLambda, - OperatorCall aggsLambda) { - SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - if (aggsLambda != null) { - LabelTypeCA ca = new LabelTypeCA(); - aggsLambda.callback(ca); - ca.getAggregationBuilderList().forEach(builder::subAggregation); - } - } - - public void setKind_IpRange() { - setKind_IpRange(null); - } - - public void setKind_IpRange(ConditionOptionCall opLambda) { - setKind_IpRange("kind", opLambda, null); - } - - public void setKind_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { - setKind_IpRange("kind", opLambda, aggsLambda); - } - - public void setKind_IpRange(String name, ConditionOptionCall opLambda, - OperatorCall aggsLambda) { - IpRangeAggregationBuilder builder = regIpRangeA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - if (aggsLambda != null) { - LabelTypeCA ca = new LabelTypeCA(); - aggsLambda.callback(ca); - ca.getAggregationBuilderList().forEach(builder::subAggregation); - } - } - - public void setKind_Count() { - setKind_Count(null); - } - - public void setKind_Count(ConditionOptionCall opLambda) { - setKind_Count("kind", opLambda); - } - - public void setKind_Count(String name, ConditionOptionCall opLambda) { - ValueCountAggregationBuilder builder = regCountA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Cardinality() { - setKind_Cardinality(null); - } - - public void setKind_Cardinality(ConditionOptionCall opLambda) { - setKind_Cardinality("kind", opLambda); - } - - public void setKind_Cardinality(String name, ConditionOptionCall opLambda) { - CardinalityAggregationBuilder builder = regCardinalityA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Missing() { - setKind_Missing(null); - } - - public void setKind_Missing(ConditionOptionCall opLambda) { - setKind_Missing("kind", opLambda, null); - } - - public void setKind_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { - setKind_Missing("kind", opLambda, aggsLambda); - } - - public void setKind_Missing(String name, ConditionOptionCall opLambda, - OperatorCall aggsLambda) { - MissingAggregationBuilder builder = regMissingA(name, "kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - if (aggsLambda != null) { - LabelTypeCA ca = new LabelTypeCA(); - aggsLambda.callback(ca); - ca.getAggregationBuilderList().forEach(builder::subAggregation); - } - } - public void setName_Terms() { setName_Terms(null); } diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsTagTypeCA.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsTagTypeCA.java new file mode 100644 index 0000000000..9895091002 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/ca/bs/BsTagTypeCA.java @@ -0,0 +1,1701 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean.ca.bs; + +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractConditionAggregation; +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractConditionQuery; +import org.codelibs.fess.opensearch.config.cbean.ca.TagTypeCA; +import org.codelibs.fess.opensearch.config.cbean.cq.TagTypeCQ; +import org.codelibs.fess.opensearch.config.cbean.cq.bs.BsTagTypeCQ; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.filter.FilterAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.global.GlobalAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.missing.MissingAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.range.IpRangeAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.range.RangeAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.sampler.SamplerAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.terms.SignificantTermsAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.AvgAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.CardinalityAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.ExtendedStatsAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.MaxAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.MinAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.PercentileRanksAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.PercentilesAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.ScriptedMetricAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.StatsAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.SumAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.TopHitsAggregationBuilder; +import org.codelibs.fesen.opensearch.search.aggregations.metrics.ValueCountAggregationBuilder; + +/** + * @author ESFlute (using FreeGen) + */ +public abstract class BsTagTypeCA extends EsAbstractConditionAggregation { + + // =================================================================================== + // Aggregation Set + // ========= + + public void filter(String name, EsAbstractConditionQuery.OperatorCall queryLambda, + ConditionOptionCall opLambda, OperatorCall aggsLambda) { + TagTypeCQ cq = new TagTypeCQ(); + if (queryLambda != null) { + queryLambda.callback(cq); + } + FilterAggregationBuilder builder = regFilterA(name, cq.getQuery()); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void global(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { + GlobalAggregationBuilder builder = regGlobalA(name); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void sampler(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { + SamplerAggregationBuilder builder = regSamplerA(name); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void scriptedMetric(String name, ConditionOptionCall opLambda) { + ScriptedMetricAggregationBuilder builder = regScriptedMetricA(name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void topHits(String name, ConditionOptionCall opLambda) { + TopHitsAggregationBuilder builder = regTopHitsA(name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Terms() { + setCreatedBy_Terms(null); + } + + public void setCreatedBy_Terms(ConditionOptionCall opLambda) { + setCreatedBy_Terms("createdBy", opLambda, null); + } + + public void setCreatedBy_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedBy_Terms("createdBy", opLambda, aggsLambda); + } + + public void setCreatedBy_Terms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedBy_SignificantTerms() { + setCreatedBy_SignificantTerms(null); + } + + public void setCreatedBy_SignificantTerms(ConditionOptionCall opLambda) { + setCreatedBy_SignificantTerms("createdBy", opLambda, null); + } + + public void setCreatedBy_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setCreatedBy_SignificantTerms("createdBy", opLambda, aggsLambda); + } + + public void setCreatedBy_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedBy_IpRange() { + setCreatedBy_IpRange(null); + } + + public void setCreatedBy_IpRange(ConditionOptionCall opLambda) { + setCreatedBy_IpRange("createdBy", opLambda, null); + } + + public void setCreatedBy_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedBy_IpRange("createdBy", opLambda, aggsLambda); + } + + public void setCreatedBy_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedBy_Count() { + setCreatedBy_Count(null); + } + + public void setCreatedBy_Count(ConditionOptionCall opLambda) { + setCreatedBy_Count("createdBy", opLambda); + } + + public void setCreatedBy_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Cardinality() { + setCreatedBy_Cardinality(null); + } + + public void setCreatedBy_Cardinality(ConditionOptionCall opLambda) { + setCreatedBy_Cardinality("createdBy", opLambda); + } + + public void setCreatedBy_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Missing() { + setCreatedBy_Missing(null); + } + + public void setCreatedBy_Missing(ConditionOptionCall opLambda) { + setCreatedBy_Missing("createdBy", opLambda, null); + } + + public void setCreatedBy_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedBy_Missing("createdBy", opLambda, aggsLambda); + } + + public void setCreatedBy_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedTime_Avg() { + setCreatedTime_Avg(null); + } + + public void setCreatedTime_Avg(ConditionOptionCall opLambda) { + setCreatedTime_Avg("createdTime", opLambda); + } + + public void setCreatedTime_Avg(String name, ConditionOptionCall opLambda) { + AvgAggregationBuilder builder = regAvgA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Max() { + setCreatedTime_Max(null); + } + + public void setCreatedTime_Max(ConditionOptionCall opLambda) { + setCreatedTime_Max("createdTime", opLambda); + } + + public void setCreatedTime_Max(String name, ConditionOptionCall opLambda) { + MaxAggregationBuilder builder = regMaxA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Min() { + setCreatedTime_Min(null); + } + + public void setCreatedTime_Min(ConditionOptionCall opLambda) { + setCreatedTime_Min("createdTime", opLambda); + } + + public void setCreatedTime_Min(String name, ConditionOptionCall opLambda) { + MinAggregationBuilder builder = regMinA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Sum() { + setCreatedTime_Sum(null); + } + + public void setCreatedTime_Sum(ConditionOptionCall opLambda) { + setCreatedTime_Sum("createdTime", opLambda); + } + + public void setCreatedTime_Sum(String name, ConditionOptionCall opLambda) { + SumAggregationBuilder builder = regSumA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_ExtendedStats() { + setCreatedTime_ExtendedStats(null); + } + + public void setCreatedTime_ExtendedStats(ConditionOptionCall opLambda) { + setCreatedTime_ExtendedStats("createdTime", opLambda); + } + + public void setCreatedTime_ExtendedStats(String name, ConditionOptionCall opLambda) { + ExtendedStatsAggregationBuilder builder = regExtendedStatsA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Stats() { + setCreatedTime_Stats(null); + } + + public void setCreatedTime_Stats(ConditionOptionCall opLambda) { + setCreatedTime_Stats("createdTime", opLambda); + } + + public void setCreatedTime_Stats(String name, ConditionOptionCall opLambda) { + StatsAggregationBuilder builder = regStatsA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Percentiles() { + setCreatedTime_Percentiles(null); + } + + public void setCreatedTime_Percentiles(ConditionOptionCall opLambda) { + setCreatedTime_Percentiles("createdTime", opLambda); + } + + public void setCreatedTime_Percentiles(String name, ConditionOptionCall opLambda) { + PercentilesAggregationBuilder builder = regPercentilesA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_PercentileRanks(double[] values) { + setCreatedTime_PercentileRanks(values, null); + } + + public void setCreatedTime_PercentileRanks(double[] values, ConditionOptionCall opLambda) { + setCreatedTime_PercentileRanks("createdTime", values, opLambda); + } + + public void setCreatedTime_PercentileRanks(String name, double[] values, + ConditionOptionCall opLambda) { + PercentileRanksAggregationBuilder builder = regPercentileRanksA(name, "createdTime", values); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Histogram() { + setCreatedTime_Histogram(null); + } + + public void setCreatedTime_Histogram(ConditionOptionCall opLambda) { + setCreatedTime_Histogram("createdTime", opLambda, null); + } + + public void setCreatedTime_Histogram(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedTime_Histogram("createdTime", opLambda, aggsLambda); + } + + public void setCreatedTime_Histogram(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + HistogramAggregationBuilder builder = regHistogramA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedTime_Range() { + setCreatedTime_Range(null); + } + + public void setCreatedTime_Range(ConditionOptionCall opLambda) { + setCreatedTime_Range("createdTime", opLambda, null); + } + + public void setCreatedTime_Range(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedTime_Range("createdTime", opLambda, aggsLambda); + } + + public void setCreatedTime_Range(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + RangeAggregationBuilder builder = regRangeA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setCreatedTime_Count() { + setCreatedTime_Count(null); + } + + public void setCreatedTime_Count(ConditionOptionCall opLambda) { + setCreatedTime_Count("createdTime", opLambda); + } + + public void setCreatedTime_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Cardinality() { + setCreatedTime_Cardinality(null); + } + + public void setCreatedTime_Cardinality(ConditionOptionCall opLambda) { + setCreatedTime_Cardinality("createdTime", opLambda); + } + + public void setCreatedTime_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Missing() { + setCreatedTime_Missing(null); + } + + public void setCreatedTime_Missing(ConditionOptionCall opLambda) { + setCreatedTime_Missing("createdTime", opLambda, null); + } + + public void setCreatedTime_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setCreatedTime_Missing("createdTime", opLambda, aggsLambda); + } + + public void setCreatedTime_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setName_Terms() { + setName_Terms(null); + } + + public void setName_Terms(ConditionOptionCall opLambda) { + setName_Terms("name", opLambda, null); + } + + public void setName_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setName_Terms("name", opLambda, aggsLambda); + } + + public void setName_Terms(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setName_SignificantTerms() { + setName_SignificantTerms(null); + } + + public void setName_SignificantTerms(ConditionOptionCall opLambda) { + setName_SignificantTerms("name", opLambda, null); + } + + public void setName_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setName_SignificantTerms("name", opLambda, aggsLambda); + } + + public void setName_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setName_IpRange() { + setName_IpRange(null); + } + + public void setName_IpRange(ConditionOptionCall opLambda) { + setName_IpRange("name", opLambda, null); + } + + public void setName_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setName_IpRange("name", opLambda, aggsLambda); + } + + public void setName_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setName_Count() { + setName_Count(null); + } + + public void setName_Count(ConditionOptionCall opLambda) { + setName_Count("name", opLambda); + } + + public void setName_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Cardinality() { + setName_Cardinality(null); + } + + public void setName_Cardinality(ConditionOptionCall opLambda) { + setName_Cardinality("name", opLambda); + } + + public void setName_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Missing() { + setName_Missing(null); + } + + public void setName_Missing(ConditionOptionCall opLambda) { + setName_Missing("name", opLambda, null); + } + + public void setName_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setName_Missing("name", opLambda, aggsLambda); + } + + public void setName_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "name"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setOwner_Terms() { + setOwner_Terms(null); + } + + public void setOwner_Terms(ConditionOptionCall opLambda) { + setOwner_Terms("owner", opLambda, null); + } + + public void setOwner_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setOwner_Terms("owner", opLambda, aggsLambda); + } + + public void setOwner_Terms(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setOwner_SignificantTerms() { + setOwner_SignificantTerms(null); + } + + public void setOwner_SignificantTerms(ConditionOptionCall opLambda) { + setOwner_SignificantTerms("owner", opLambda, null); + } + + public void setOwner_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setOwner_SignificantTerms("owner", opLambda, aggsLambda); + } + + public void setOwner_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setOwner_IpRange() { + setOwner_IpRange(null); + } + + public void setOwner_IpRange(ConditionOptionCall opLambda) { + setOwner_IpRange("owner", opLambda, null); + } + + public void setOwner_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setOwner_IpRange("owner", opLambda, aggsLambda); + } + + public void setOwner_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setOwner_Count() { + setOwner_Count(null); + } + + public void setOwner_Count(ConditionOptionCall opLambda) { + setOwner_Count("owner", opLambda); + } + + public void setOwner_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Cardinality() { + setOwner_Cardinality(null); + } + + public void setOwner_Cardinality(ConditionOptionCall opLambda) { + setOwner_Cardinality("owner", opLambda); + } + + public void setOwner_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Missing() { + setOwner_Missing(null); + } + + public void setOwner_Missing(ConditionOptionCall opLambda) { + setOwner_Missing("owner", opLambda, null); + } + + public void setOwner_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setOwner_Missing("owner", opLambda, aggsLambda); + } + + public void setOwner_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPaths_Terms() { + setPaths_Terms(null); + } + + public void setPaths_Terms(ConditionOptionCall opLambda) { + setPaths_Terms("paths", opLambda, null); + } + + public void setPaths_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPaths_Terms("paths", opLambda, aggsLambda); + } + + public void setPaths_Terms(String name, ConditionOptionCall opLambda, OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPaths_SignificantTerms() { + setPaths_SignificantTerms(null); + } + + public void setPaths_SignificantTerms(ConditionOptionCall opLambda) { + setPaths_SignificantTerms("paths", opLambda, null); + } + + public void setPaths_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setPaths_SignificantTerms("paths", opLambda, aggsLambda); + } + + public void setPaths_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPaths_IpRange() { + setPaths_IpRange(null); + } + + public void setPaths_IpRange(ConditionOptionCall opLambda) { + setPaths_IpRange("paths", opLambda, null); + } + + public void setPaths_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPaths_IpRange("paths", opLambda, aggsLambda); + } + + public void setPaths_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPaths_Count() { + setPaths_Count(null); + } + + public void setPaths_Count(ConditionOptionCall opLambda) { + setPaths_Count("paths", opLambda); + } + + public void setPaths_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Cardinality() { + setPaths_Cardinality(null); + } + + public void setPaths_Cardinality(ConditionOptionCall opLambda) { + setPaths_Cardinality("paths", opLambda); + } + + public void setPaths_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Missing() { + setPaths_Missing(null); + } + + public void setPaths_Missing(ConditionOptionCall opLambda) { + setPaths_Missing("paths", opLambda, null); + } + + public void setPaths_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPaths_Missing("paths", opLambda, aggsLambda); + } + + public void setPaths_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPermissions_Terms() { + setPermissions_Terms(null); + } + + public void setPermissions_Terms(ConditionOptionCall opLambda) { + setPermissions_Terms("permissions", opLambda, null); + } + + public void setPermissions_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPermissions_Terms("permissions", opLambda, aggsLambda); + } + + public void setPermissions_Terms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPermissions_SignificantTerms() { + setPermissions_SignificantTerms(null); + } + + public void setPermissions_SignificantTerms(ConditionOptionCall opLambda) { + setPermissions_SignificantTerms("permissions", opLambda, null); + } + + public void setPermissions_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setPermissions_SignificantTerms("permissions", opLambda, aggsLambda); + } + + public void setPermissions_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPermissions_IpRange() { + setPermissions_IpRange(null); + } + + public void setPermissions_IpRange(ConditionOptionCall opLambda) { + setPermissions_IpRange("permissions", opLambda, null); + } + + public void setPermissions_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPermissions_IpRange("permissions", opLambda, aggsLambda); + } + + public void setPermissions_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setPermissions_Count() { + setPermissions_Count(null); + } + + public void setPermissions_Count(ConditionOptionCall opLambda) { + setPermissions_Count("permissions", opLambda); + } + + public void setPermissions_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Cardinality() { + setPermissions_Cardinality(null); + } + + public void setPermissions_Cardinality(ConditionOptionCall opLambda) { + setPermissions_Cardinality("permissions", opLambda); + } + + public void setPermissions_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Missing() { + setPermissions_Missing(null); + } + + public void setPermissions_Missing(ConditionOptionCall opLambda) { + setPermissions_Missing("permissions", opLambda, null); + } + + public void setPermissions_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setPermissions_Missing("permissions", opLambda, aggsLambda); + } + + public void setPermissions_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setSortOrder_Avg() { + setSortOrder_Avg(null); + } + + public void setSortOrder_Avg(ConditionOptionCall opLambda) { + setSortOrder_Avg("sortOrder", opLambda); + } + + public void setSortOrder_Avg(String name, ConditionOptionCall opLambda) { + AvgAggregationBuilder builder = regAvgA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Max() { + setSortOrder_Max(null); + } + + public void setSortOrder_Max(ConditionOptionCall opLambda) { + setSortOrder_Max("sortOrder", opLambda); + } + + public void setSortOrder_Max(String name, ConditionOptionCall opLambda) { + MaxAggregationBuilder builder = regMaxA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Min() { + setSortOrder_Min(null); + } + + public void setSortOrder_Min(ConditionOptionCall opLambda) { + setSortOrder_Min("sortOrder", opLambda); + } + + public void setSortOrder_Min(String name, ConditionOptionCall opLambda) { + MinAggregationBuilder builder = regMinA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Sum() { + setSortOrder_Sum(null); + } + + public void setSortOrder_Sum(ConditionOptionCall opLambda) { + setSortOrder_Sum("sortOrder", opLambda); + } + + public void setSortOrder_Sum(String name, ConditionOptionCall opLambda) { + SumAggregationBuilder builder = regSumA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_ExtendedStats() { + setSortOrder_ExtendedStats(null); + } + + public void setSortOrder_ExtendedStats(ConditionOptionCall opLambda) { + setSortOrder_ExtendedStats("sortOrder", opLambda); + } + + public void setSortOrder_ExtendedStats(String name, ConditionOptionCall opLambda) { + ExtendedStatsAggregationBuilder builder = regExtendedStatsA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Stats() { + setSortOrder_Stats(null); + } + + public void setSortOrder_Stats(ConditionOptionCall opLambda) { + setSortOrder_Stats("sortOrder", opLambda); + } + + public void setSortOrder_Stats(String name, ConditionOptionCall opLambda) { + StatsAggregationBuilder builder = regStatsA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Percentiles() { + setSortOrder_Percentiles(null); + } + + public void setSortOrder_Percentiles(ConditionOptionCall opLambda) { + setSortOrder_Percentiles("sortOrder", opLambda); + } + + public void setSortOrder_Percentiles(String name, ConditionOptionCall opLambda) { + PercentilesAggregationBuilder builder = regPercentilesA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_PercentileRanks(double[] values) { + setSortOrder_PercentileRanks(values, null); + } + + public void setSortOrder_PercentileRanks(double[] values, ConditionOptionCall opLambda) { + setSortOrder_PercentileRanks("sortOrder", values, opLambda); + } + + public void setSortOrder_PercentileRanks(String name, double[] values, + ConditionOptionCall opLambda) { + PercentileRanksAggregationBuilder builder = regPercentileRanksA(name, "sortOrder", values); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Histogram() { + setSortOrder_Histogram(null); + } + + public void setSortOrder_Histogram(ConditionOptionCall opLambda) { + setSortOrder_Histogram("sortOrder", opLambda, null); + } + + public void setSortOrder_Histogram(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setSortOrder_Histogram("sortOrder", opLambda, aggsLambda); + } + + public void setSortOrder_Histogram(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + HistogramAggregationBuilder builder = regHistogramA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setSortOrder_Range() { + setSortOrder_Range(null); + } + + public void setSortOrder_Range(ConditionOptionCall opLambda) { + setSortOrder_Range("sortOrder", opLambda, null); + } + + public void setSortOrder_Range(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setSortOrder_Range("sortOrder", opLambda, aggsLambda); + } + + public void setSortOrder_Range(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + RangeAggregationBuilder builder = regRangeA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setSortOrder_Count() { + setSortOrder_Count(null); + } + + public void setSortOrder_Count(ConditionOptionCall opLambda) { + setSortOrder_Count("sortOrder", opLambda); + } + + public void setSortOrder_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Cardinality() { + setSortOrder_Cardinality(null); + } + + public void setSortOrder_Cardinality(ConditionOptionCall opLambda) { + setSortOrder_Cardinality("sortOrder", opLambda); + } + + public void setSortOrder_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Missing() { + setSortOrder_Missing(null); + } + + public void setSortOrder_Missing(ConditionOptionCall opLambda) { + setSortOrder_Missing("sortOrder", opLambda, null); + } + + public void setSortOrder_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setSortOrder_Missing("sortOrder", opLambda, aggsLambda); + } + + public void setSortOrder_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedBy_Terms() { + setUpdatedBy_Terms(null); + } + + public void setUpdatedBy_Terms(ConditionOptionCall opLambda) { + setUpdatedBy_Terms("updatedBy", opLambda, null); + } + + public void setUpdatedBy_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedBy_Terms("updatedBy", opLambda, aggsLambda); + } + + public void setUpdatedBy_Terms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedBy_SignificantTerms() { + setUpdatedBy_SignificantTerms(null); + } + + public void setUpdatedBy_SignificantTerms(ConditionOptionCall opLambda) { + setUpdatedBy_SignificantTerms("updatedBy", opLambda, null); + } + + public void setUpdatedBy_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setUpdatedBy_SignificantTerms("updatedBy", opLambda, aggsLambda); + } + + public void setUpdatedBy_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedBy_IpRange() { + setUpdatedBy_IpRange(null); + } + + public void setUpdatedBy_IpRange(ConditionOptionCall opLambda) { + setUpdatedBy_IpRange("updatedBy", opLambda, null); + } + + public void setUpdatedBy_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedBy_IpRange("updatedBy", opLambda, aggsLambda); + } + + public void setUpdatedBy_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedBy_Count() { + setUpdatedBy_Count(null); + } + + public void setUpdatedBy_Count(ConditionOptionCall opLambda) { + setUpdatedBy_Count("updatedBy", opLambda); + } + + public void setUpdatedBy_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Cardinality() { + setUpdatedBy_Cardinality(null); + } + + public void setUpdatedBy_Cardinality(ConditionOptionCall opLambda) { + setUpdatedBy_Cardinality("updatedBy", opLambda); + } + + public void setUpdatedBy_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Missing() { + setUpdatedBy_Missing(null); + } + + public void setUpdatedBy_Missing(ConditionOptionCall opLambda) { + setUpdatedBy_Missing("updatedBy", opLambda, null); + } + + public void setUpdatedBy_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedBy_Missing("updatedBy", opLambda, aggsLambda); + } + + public void setUpdatedBy_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedTime_Avg() { + setUpdatedTime_Avg(null); + } + + public void setUpdatedTime_Avg(ConditionOptionCall opLambda) { + setUpdatedTime_Avg("updatedTime", opLambda); + } + + public void setUpdatedTime_Avg(String name, ConditionOptionCall opLambda) { + AvgAggregationBuilder builder = regAvgA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Max() { + setUpdatedTime_Max(null); + } + + public void setUpdatedTime_Max(ConditionOptionCall opLambda) { + setUpdatedTime_Max("updatedTime", opLambda); + } + + public void setUpdatedTime_Max(String name, ConditionOptionCall opLambda) { + MaxAggregationBuilder builder = regMaxA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Min() { + setUpdatedTime_Min(null); + } + + public void setUpdatedTime_Min(ConditionOptionCall opLambda) { + setUpdatedTime_Min("updatedTime", opLambda); + } + + public void setUpdatedTime_Min(String name, ConditionOptionCall opLambda) { + MinAggregationBuilder builder = regMinA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Sum() { + setUpdatedTime_Sum(null); + } + + public void setUpdatedTime_Sum(ConditionOptionCall opLambda) { + setUpdatedTime_Sum("updatedTime", opLambda); + } + + public void setUpdatedTime_Sum(String name, ConditionOptionCall opLambda) { + SumAggregationBuilder builder = regSumA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_ExtendedStats() { + setUpdatedTime_ExtendedStats(null); + } + + public void setUpdatedTime_ExtendedStats(ConditionOptionCall opLambda) { + setUpdatedTime_ExtendedStats("updatedTime", opLambda); + } + + public void setUpdatedTime_ExtendedStats(String name, ConditionOptionCall opLambda) { + ExtendedStatsAggregationBuilder builder = regExtendedStatsA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Stats() { + setUpdatedTime_Stats(null); + } + + public void setUpdatedTime_Stats(ConditionOptionCall opLambda) { + setUpdatedTime_Stats("updatedTime", opLambda); + } + + public void setUpdatedTime_Stats(String name, ConditionOptionCall opLambda) { + StatsAggregationBuilder builder = regStatsA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Percentiles() { + setUpdatedTime_Percentiles(null); + } + + public void setUpdatedTime_Percentiles(ConditionOptionCall opLambda) { + setUpdatedTime_Percentiles("updatedTime", opLambda); + } + + public void setUpdatedTime_Percentiles(String name, ConditionOptionCall opLambda) { + PercentilesAggregationBuilder builder = regPercentilesA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_PercentileRanks(double[] values) { + setUpdatedTime_PercentileRanks(values, null); + } + + public void setUpdatedTime_PercentileRanks(double[] values, ConditionOptionCall opLambda) { + setUpdatedTime_PercentileRanks("updatedTime", values, opLambda); + } + + public void setUpdatedTime_PercentileRanks(String name, double[] values, + ConditionOptionCall opLambda) { + PercentileRanksAggregationBuilder builder = regPercentileRanksA(name, "updatedTime", values); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Histogram() { + setUpdatedTime_Histogram(null); + } + + public void setUpdatedTime_Histogram(ConditionOptionCall opLambda) { + setUpdatedTime_Histogram("updatedTime", opLambda, null); + } + + public void setUpdatedTime_Histogram(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedTime_Histogram("updatedTime", opLambda, aggsLambda); + } + + public void setUpdatedTime_Histogram(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + HistogramAggregationBuilder builder = regHistogramA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedTime_Range() { + setUpdatedTime_Range(null); + } + + public void setUpdatedTime_Range(ConditionOptionCall opLambda) { + setUpdatedTime_Range("updatedTime", opLambda, null); + } + + public void setUpdatedTime_Range(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedTime_Range("updatedTime", opLambda, aggsLambda); + } + + public void setUpdatedTime_Range(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + RangeAggregationBuilder builder = regRangeA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setUpdatedTime_Count() { + setUpdatedTime_Count(null); + } + + public void setUpdatedTime_Count(ConditionOptionCall opLambda) { + setUpdatedTime_Count("updatedTime", opLambda); + } + + public void setUpdatedTime_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Cardinality() { + setUpdatedTime_Cardinality(null); + } + + public void setUpdatedTime_Cardinality(ConditionOptionCall opLambda) { + setUpdatedTime_Cardinality("updatedTime", opLambda); + } + + public void setUpdatedTime_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Missing() { + setUpdatedTime_Missing(null); + } + + public void setUpdatedTime_Missing(ConditionOptionCall opLambda) { + setUpdatedTime_Missing("updatedTime", opLambda, null); + } + + public void setUpdatedTime_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setUpdatedTime_Missing("updatedTime", opLambda, aggsLambda); + } + + public void setUpdatedTime_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setVirtualHost_Terms() { + setVirtualHost_Terms(null); + } + + public void setVirtualHost_Terms(ConditionOptionCall opLambda) { + setVirtualHost_Terms("virtualHost", opLambda, null); + } + + public void setVirtualHost_Terms(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setVirtualHost_Terms("virtualHost", opLambda, aggsLambda); + } + + public void setVirtualHost_Terms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + TermsAggregationBuilder builder = regTermsA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setVirtualHost_SignificantTerms() { + setVirtualHost_SignificantTerms(null); + } + + public void setVirtualHost_SignificantTerms(ConditionOptionCall opLambda) { + setVirtualHost_SignificantTerms("virtualHost", opLambda, null); + } + + public void setVirtualHost_SignificantTerms(ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + setVirtualHost_SignificantTerms("virtualHost", opLambda, aggsLambda); + } + + public void setVirtualHost_SignificantTerms(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + SignificantTermsAggregationBuilder builder = regSignificantTermsA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setVirtualHost_IpRange() { + setVirtualHost_IpRange(null); + } + + public void setVirtualHost_IpRange(ConditionOptionCall opLambda) { + setVirtualHost_IpRange("virtualHost", opLambda, null); + } + + public void setVirtualHost_IpRange(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setVirtualHost_IpRange("virtualHost", opLambda, aggsLambda); + } + + public void setVirtualHost_IpRange(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + IpRangeAggregationBuilder builder = regIpRangeA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + + public void setVirtualHost_Count() { + setVirtualHost_Count(null); + } + + public void setVirtualHost_Count(ConditionOptionCall opLambda) { + setVirtualHost_Count("virtualHost", opLambda); + } + + public void setVirtualHost_Count(String name, ConditionOptionCall opLambda) { + ValueCountAggregationBuilder builder = regCountA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Cardinality() { + setVirtualHost_Cardinality(null); + } + + public void setVirtualHost_Cardinality(ConditionOptionCall opLambda) { + setVirtualHost_Cardinality("virtualHost", opLambda); + } + + public void setVirtualHost_Cardinality(String name, ConditionOptionCall opLambda) { + CardinalityAggregationBuilder builder = regCardinalityA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Missing() { + setVirtualHost_Missing(null); + } + + public void setVirtualHost_Missing(ConditionOptionCall opLambda) { + setVirtualHost_Missing("virtualHost", opLambda, null); + } + + public void setVirtualHost_Missing(ConditionOptionCall opLambda, OperatorCall aggsLambda) { + setVirtualHost_Missing("virtualHost", opLambda, aggsLambda); + } + + public void setVirtualHost_Missing(String name, ConditionOptionCall opLambda, + OperatorCall aggsLambda) { + MissingAggregationBuilder builder = regMissingA(name, "virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + if (aggsLambda != null) { + TagTypeCA ca = new TagTypeCA(); + aggsLambda.callback(ca); + ca.getAggregationBuilderList().forEach(builder::subAggregation); + } + } + +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/TagTypeCQ.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/TagTypeCQ.java new file mode 100644 index 0000000000..677c3df0e9 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/TagTypeCQ.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean.cq; + +import org.codelibs.fess.opensearch.config.cbean.cq.bs.BsTagTypeCQ; + +/** + * @author ESFlute (using FreeGen) + */ +public class TagTypeCQ extends BsTagTypeCQ { +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsLabelTypeCQ.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsLabelTypeCQ.java index 8c938fecb1..c9a13143d0 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsLabelTypeCQ.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsLabelTypeCQ.java @@ -1038,230 +1038,6 @@ public BsLabelTypeCQ addOrderBy_IncludedPaths_Desc() { return this; } - public void setKind_Equal(String kind) { - setKind_Term(kind, null); - } - - public void setKind_Equal(String kind, ConditionOptionCall opLambda) { - setKind_Term(kind, opLambda); - } - - public void setKind_Term(String kind) { - setKind_Term(kind, null); - } - - public void setKind_Term(String kind, ConditionOptionCall opLambda) { - TermQueryBuilder builder = regTermQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_NotEqual(String kind) { - setKind_NotTerm(kind, null); - } - - public void setKind_NotTerm(String kind) { - setKind_NotTerm(kind, null); - } - - public void setKind_NotEqual(String kind, ConditionOptionCall opLambda) { - setKind_NotTerm(kind, opLambda); - } - - public void setKind_NotTerm(String kind, ConditionOptionCall opLambda) { - not(not -> not.setKind_Term(kind), opLambda); - } - - public void setKind_Terms(Collection kindList) { - setKind_Terms(kindList, null); - } - - public void setKind_Terms(Collection kindList, ConditionOptionCall opLambda) { - TermsQueryBuilder builder = regTermsQ("kind", kindList); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_InScope(Collection kindList) { - setKind_Terms(kindList, null); - } - - public void setKind_InScope(Collection kindList, ConditionOptionCall opLambda) { - setKind_Terms(kindList, opLambda); - } - - public void setKind_Match(String kind) { - setKind_Match(kind, null); - } - - public void setKind_Match(String kind, ConditionOptionCall opLambda) { - MatchQueryBuilder builder = regMatchQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_MatchPhrase(String kind) { - setKind_MatchPhrase(kind, null); - } - - public void setKind_MatchPhrase(String kind, ConditionOptionCall opLambda) { - MatchPhraseQueryBuilder builder = regMatchPhraseQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_MatchPhrasePrefix(String kind) { - setKind_MatchPhrasePrefix(kind, null); - } - - public void setKind_MatchPhrasePrefix(String kind, ConditionOptionCall opLambda) { - MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Fuzzy(String kind) { - setKind_Fuzzy(kind, null); - } - - public void setKind_Fuzzy(String kind, ConditionOptionCall opLambda) { - MatchQueryBuilder builder = regFuzzyQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Prefix(String kind) { - setKind_Prefix(kind, null); - } - - public void setKind_Prefix(String kind, ConditionOptionCall opLambda) { - PrefixQueryBuilder builder = regPrefixQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Wildcard(String kind) { - setKind_Wildcard(kind, null); - } - - public void setKind_Wildcard(String kind, ConditionOptionCall opLambda) { - WildcardQueryBuilder builder = regWildcardQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Regexp(String kind) { - setKind_Regexp(kind, null); - } - - public void setKind_Regexp(String kind, ConditionOptionCall opLambda) { - RegexpQueryBuilder builder = regRegexpQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_SpanTerm(String kind) { - setKind_SpanTerm("kind", null); - } - - public void setKind_SpanTerm(String kind, ConditionOptionCall opLambda) { - SpanTermQueryBuilder builder = regSpanTermQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_GreaterThan(String kind) { - setKind_GreaterThan(kind, null); - } - - public void setKind_GreaterThan(String kind, ConditionOptionCall opLambda) { - final Object _value = kind; - RangeQueryBuilder builder = regRangeQ("kind", ConditionKey.CK_GREATER_THAN, _value); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_LessThan(String kind) { - setKind_LessThan(kind, null); - } - - public void setKind_LessThan(String kind, ConditionOptionCall opLambda) { - final Object _value = kind; - RangeQueryBuilder builder = regRangeQ("kind", ConditionKey.CK_LESS_THAN, _value); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_GreaterEqual(String kind) { - setKind_GreaterEqual(kind, null); - } - - public void setKind_GreaterEqual(String kind, ConditionOptionCall opLambda) { - final Object _value = kind; - RangeQueryBuilder builder = regRangeQ("kind", ConditionKey.CK_GREATER_EQUAL, _value); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_LessEqual(String kind) { - setKind_LessEqual(kind, null); - } - - public void setKind_LessEqual(String kind, ConditionOptionCall opLambda) { - final Object _value = kind; - RangeQueryBuilder builder = regRangeQ("kind", ConditionKey.CK_LESS_EQUAL, _value); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public void setKind_Exists() { - setKind_Exists(null); - } - - public void setKind_Exists(ConditionOptionCall opLambda) { - ExistsQueryBuilder builder = regExistsQ("kind"); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - @Deprecated - public void setKind_CommonTerms(String kind) { - setKind_CommonTerms(kind, null); - } - - @Deprecated - public void setKind_CommonTerms(String kind, ConditionOptionCall opLambda) { - CommonTermsQueryBuilder builder = regCommonTermsQ("kind", kind); - if (opLambda != null) { - opLambda.callback(builder); - } - } - - public BsLabelTypeCQ addOrderBy_Kind_Asc() { - regOBA("kind"); - return this; - } - - public BsLabelTypeCQ addOrderBy_Kind_Desc() { - regOBD("kind"); - return this; - } - public void setName_Equal(String name) { setName_Term(name, null); } diff --git a/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsTagTypeCQ.java b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsTagTypeCQ.java new file mode 100644 index 0000000000..2b1ffbcf78 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/cbean/cq/bs/BsTagTypeCQ.java @@ -0,0 +1,2297 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.cbean.cq.bs; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; + +import org.codelibs.fess.opensearch.config.allcommon.EsAbstractConditionQuery; +import org.codelibs.fess.opensearch.config.cbean.cq.TagTypeCQ; +import org.dbflute.cbean.ckey.ConditionKey; +import org.codelibs.fesen.opensearch.index.query.BoolQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.CommonTermsQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.ExistsQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.IdsQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.MatchPhrasePrefixQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.MatchPhraseQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.MatchQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.PrefixQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.RangeQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.RegexpQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.SpanTermQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.TermQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.TermsQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.WildcardQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.functionscore.FunctionScoreQueryBuilder; +import org.codelibs.fesen.opensearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; + +/** + * @author ESFlute (using FreeGen) + */ +public abstract class BsTagTypeCQ extends EsAbstractConditionQuery { + + protected static final Class suppressUnusedImportLocalDateTime = LocalDateTime.class; + + // =================================================================================== + // Name Override + // ============= + @Override + public String asTableDbName() { + return "tag_type"; + } + + @Override + public String xgetAliasName() { + return "tag_type"; + } + + // =================================================================================== + // Query Control + // ============= + public void functionScore(OperatorCall queryLambda, ScoreFunctionCall> functionsLambda, + final ConditionOptionCall opLambda) { + TagTypeCQ cq = new TagTypeCQ(); + queryLambda.callback(cq); + final Collection list = new ArrayList<>(); + if (functionsLambda != null) { + functionsLambda.callback((cqLambda, scoreFunctionBuilder) -> { + TagTypeCQ cf = new TagTypeCQ(); + cqLambda.callback(cf); + list.add(new FilterFunctionBuilder(cf.getQuery(), scoreFunctionBuilder)); + }); + } + final FunctionScoreQueryBuilder builder = regFunctionScoreQ(cq.getQuery(), list); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void filtered(FilteredCall filteredLambda) { + filtered(filteredLambda, null); + } + + public void filtered(FilteredCall filteredLambda, ConditionOptionCall opLambda) { + bool((must, should, mustNot, filter) -> { + filteredLambda.callback(must, filter); + }, opLambda); + } + + public void not(OperatorCall notLambda) { + not(notLambda, null); + } + + public void not(final OperatorCall notLambda, final ConditionOptionCall opLambda) { + bool((must, should, mustNot, filter) -> notLambda.callback(mustNot), opLambda); + } + + public void bool(BoolCall boolLambda) { + bool(boolLambda, null); + } + + public void bool(BoolCall boolLambda, ConditionOptionCall opLambda) { + TagTypeCQ mustQuery = new TagTypeCQ(); + TagTypeCQ shouldQuery = new TagTypeCQ(); + TagTypeCQ mustNotQuery = new TagTypeCQ(); + TagTypeCQ filterQuery = new TagTypeCQ(); + boolLambda.callback(mustQuery, shouldQuery, mustNotQuery, filterQuery); + if (mustQuery.hasQueries() || shouldQuery.hasQueries() || mustNotQuery.hasQueries() || filterQuery.hasQueries()) { + BoolQueryBuilder builder = regBoolCQ(mustQuery.getQueryBuilderList(), shouldQuery.getQueryBuilderList(), + mustNotQuery.getQueryBuilderList(), filterQuery.getQueryBuilderList()); + if (opLambda != null) { + opLambda.callback(builder); + } + } + } + + // =================================================================================== + // Query Set + // ========= + public void setId_Equal(String id) { + setId_Term(id, null); + } + + public void setId_Equal(String id, ConditionOptionCall opLambda) { + setId_Term(id, opLambda); + } + + public void setId_Term(String id) { + setId_Term(id, null); + } + + public void setId_Term(String id, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("_id", id); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setId_NotEqual(String id) { + setId_NotTerm(id, null); + } + + public void setId_NotTerm(String id) { + setId_NotTerm(id, null); + } + + public void setId_NotEqual(String id, ConditionOptionCall opLambda) { + setId_NotTerm(id, opLambda); + } + + public void setId_NotTerm(String id, ConditionOptionCall opLambda) { + not(not -> not.setId_Term(id), opLambda); + } + + public void setId_Terms(Collection idList) { + setId_Terms(idList, null); + } + + public void setId_Terms(Collection idList, ConditionOptionCall opLambda) { + IdsQueryBuilder builder = regIdsQ(idList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setId_InScope(Collection idList) { + setId_Terms(idList, null); + } + + public void setId_InScope(Collection idList, ConditionOptionCall opLambda) { + setId_Terms(idList, opLambda); + } + + @Deprecated + public BsTagTypeCQ addOrderBy_Id_Asc() { + regOBA("_id"); + return this; + } + + @Deprecated + public BsTagTypeCQ addOrderBy_Id_Desc() { + regOBD("_id"); + return this; + } + + public void setCreatedBy_Equal(String createdBy) { + setCreatedBy_Term(createdBy, null); + } + + public void setCreatedBy_Equal(String createdBy, ConditionOptionCall opLambda) { + setCreatedBy_Term(createdBy, opLambda); + } + + public void setCreatedBy_Term(String createdBy) { + setCreatedBy_Term(createdBy, null); + } + + public void setCreatedBy_Term(String createdBy, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_NotEqual(String createdBy) { + setCreatedBy_NotTerm(createdBy, null); + } + + public void setCreatedBy_NotTerm(String createdBy) { + setCreatedBy_NotTerm(createdBy, null); + } + + public void setCreatedBy_NotEqual(String createdBy, ConditionOptionCall opLambda) { + setCreatedBy_NotTerm(createdBy, opLambda); + } + + public void setCreatedBy_NotTerm(String createdBy, ConditionOptionCall opLambda) { + not(not -> not.setCreatedBy_Term(createdBy), opLambda); + } + + public void setCreatedBy_Terms(Collection createdByList) { + setCreatedBy_Terms(createdByList, null); + } + + public void setCreatedBy_Terms(Collection createdByList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("createdBy", createdByList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_InScope(Collection createdByList) { + setCreatedBy_Terms(createdByList, null); + } + + public void setCreatedBy_InScope(Collection createdByList, ConditionOptionCall opLambda) { + setCreatedBy_Terms(createdByList, opLambda); + } + + public void setCreatedBy_Match(String createdBy) { + setCreatedBy_Match(createdBy, null); + } + + public void setCreatedBy_Match(String createdBy, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_MatchPhrase(String createdBy) { + setCreatedBy_MatchPhrase(createdBy, null); + } + + public void setCreatedBy_MatchPhrase(String createdBy, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_MatchPhrasePrefix(String createdBy) { + setCreatedBy_MatchPhrasePrefix(createdBy, null); + } + + public void setCreatedBy_MatchPhrasePrefix(String createdBy, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Fuzzy(String createdBy) { + setCreatedBy_Fuzzy(createdBy, null); + } + + public void setCreatedBy_Fuzzy(String createdBy, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Prefix(String createdBy) { + setCreatedBy_Prefix(createdBy, null); + } + + public void setCreatedBy_Prefix(String createdBy, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Wildcard(String createdBy) { + setCreatedBy_Wildcard(createdBy, null); + } + + public void setCreatedBy_Wildcard(String createdBy, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Regexp(String createdBy) { + setCreatedBy_Regexp(createdBy, null); + } + + public void setCreatedBy_Regexp(String createdBy, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_SpanTerm(String createdBy) { + setCreatedBy_SpanTerm("createdBy", null); + } + + public void setCreatedBy_SpanTerm(String createdBy, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_GreaterThan(String createdBy) { + setCreatedBy_GreaterThan(createdBy, null); + } + + public void setCreatedBy_GreaterThan(String createdBy, ConditionOptionCall opLambda) { + final Object _value = createdBy; + RangeQueryBuilder builder = regRangeQ("createdBy", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_LessThan(String createdBy) { + setCreatedBy_LessThan(createdBy, null); + } + + public void setCreatedBy_LessThan(String createdBy, ConditionOptionCall opLambda) { + final Object _value = createdBy; + RangeQueryBuilder builder = regRangeQ("createdBy", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_GreaterEqual(String createdBy) { + setCreatedBy_GreaterEqual(createdBy, null); + } + + public void setCreatedBy_GreaterEqual(String createdBy, ConditionOptionCall opLambda) { + final Object _value = createdBy; + RangeQueryBuilder builder = regRangeQ("createdBy", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_LessEqual(String createdBy) { + setCreatedBy_LessEqual(createdBy, null); + } + + public void setCreatedBy_LessEqual(String createdBy, ConditionOptionCall opLambda) { + final Object _value = createdBy; + RangeQueryBuilder builder = regRangeQ("createdBy", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedBy_Exists() { + setCreatedBy_Exists(null); + } + + public void setCreatedBy_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("createdBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setCreatedBy_CommonTerms(String createdBy) { + setCreatedBy_CommonTerms(createdBy, null); + } + + @Deprecated + public void setCreatedBy_CommonTerms(String createdBy, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("createdBy", createdBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_CreatedBy_Asc() { + regOBA("createdBy"); + return this; + } + + public BsTagTypeCQ addOrderBy_CreatedBy_Desc() { + regOBD("createdBy"); + return this; + } + + public void setCreatedTime_Equal(Long createdTime) { + setCreatedTime_Term(createdTime, null); + } + + public void setCreatedTime_Equal(Long createdTime, ConditionOptionCall opLambda) { + setCreatedTime_Term(createdTime, opLambda); + } + + public void setCreatedTime_Term(Long createdTime) { + setCreatedTime_Term(createdTime, null); + } + + public void setCreatedTime_Term(Long createdTime, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_NotEqual(Long createdTime) { + setCreatedTime_NotTerm(createdTime, null); + } + + public void setCreatedTime_NotTerm(Long createdTime) { + setCreatedTime_NotTerm(createdTime, null); + } + + public void setCreatedTime_NotEqual(Long createdTime, ConditionOptionCall opLambda) { + setCreatedTime_NotTerm(createdTime, opLambda); + } + + public void setCreatedTime_NotTerm(Long createdTime, ConditionOptionCall opLambda) { + not(not -> not.setCreatedTime_Term(createdTime), opLambda); + } + + public void setCreatedTime_Terms(Collection createdTimeList) { + setCreatedTime_Terms(createdTimeList, null); + } + + public void setCreatedTime_Terms(Collection createdTimeList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("createdTime", createdTimeList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_InScope(Collection createdTimeList) { + setCreatedTime_Terms(createdTimeList, null); + } + + public void setCreatedTime_InScope(Collection createdTimeList, ConditionOptionCall opLambda) { + setCreatedTime_Terms(createdTimeList, opLambda); + } + + public void setCreatedTime_Match(Long createdTime) { + setCreatedTime_Match(createdTime, null); + } + + public void setCreatedTime_Match(Long createdTime, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_MatchPhrase(Long createdTime) { + setCreatedTime_MatchPhrase(createdTime, null); + } + + public void setCreatedTime_MatchPhrase(Long createdTime, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_MatchPhrasePrefix(Long createdTime) { + setCreatedTime_MatchPhrasePrefix(createdTime, null); + } + + public void setCreatedTime_MatchPhrasePrefix(Long createdTime, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Fuzzy(Long createdTime) { + setCreatedTime_Fuzzy(createdTime, null); + } + + public void setCreatedTime_Fuzzy(Long createdTime, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_GreaterThan(Long createdTime) { + setCreatedTime_GreaterThan(createdTime, null); + } + + public void setCreatedTime_GreaterThan(Long createdTime, ConditionOptionCall opLambda) { + final Object _value = createdTime; + RangeQueryBuilder builder = regRangeQ("createdTime", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_LessThan(Long createdTime) { + setCreatedTime_LessThan(createdTime, null); + } + + public void setCreatedTime_LessThan(Long createdTime, ConditionOptionCall opLambda) { + final Object _value = createdTime; + RangeQueryBuilder builder = regRangeQ("createdTime", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_GreaterEqual(Long createdTime) { + setCreatedTime_GreaterEqual(createdTime, null); + } + + public void setCreatedTime_GreaterEqual(Long createdTime, ConditionOptionCall opLambda) { + final Object _value = createdTime; + RangeQueryBuilder builder = regRangeQ("createdTime", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_LessEqual(Long createdTime) { + setCreatedTime_LessEqual(createdTime, null); + } + + public void setCreatedTime_LessEqual(Long createdTime, ConditionOptionCall opLambda) { + final Object _value = createdTime; + RangeQueryBuilder builder = regRangeQ("createdTime", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setCreatedTime_Exists() { + setCreatedTime_Exists(null); + } + + public void setCreatedTime_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("createdTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setCreatedTime_CommonTerms(Long createdTime) { + setCreatedTime_CommonTerms(createdTime, null); + } + + @Deprecated + public void setCreatedTime_CommonTerms(Long createdTime, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("createdTime", createdTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_CreatedTime_Asc() { + regOBA("createdTime"); + return this; + } + + public BsTagTypeCQ addOrderBy_CreatedTime_Desc() { + regOBD("createdTime"); + return this; + } + + public void setName_Equal(String name) { + setName_Term(name, null); + } + + public void setName_Equal(String name, ConditionOptionCall opLambda) { + setName_Term(name, opLambda); + } + + public void setName_Term(String name) { + setName_Term(name, null); + } + + public void setName_Term(String name, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_NotEqual(String name) { + setName_NotTerm(name, null); + } + + public void setName_NotTerm(String name) { + setName_NotTerm(name, null); + } + + public void setName_NotEqual(String name, ConditionOptionCall opLambda) { + setName_NotTerm(name, opLambda); + } + + public void setName_NotTerm(String name, ConditionOptionCall opLambda) { + not(not -> not.setName_Term(name), opLambda); + } + + public void setName_Terms(Collection nameList) { + setName_Terms(nameList, null); + } + + public void setName_Terms(Collection nameList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("name", nameList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_InScope(Collection nameList) { + setName_Terms(nameList, null); + } + + public void setName_InScope(Collection nameList, ConditionOptionCall opLambda) { + setName_Terms(nameList, opLambda); + } + + public void setName_Match(String name) { + setName_Match(name, null); + } + + public void setName_Match(String name, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_MatchPhrase(String name) { + setName_MatchPhrase(name, null); + } + + public void setName_MatchPhrase(String name, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_MatchPhrasePrefix(String name) { + setName_MatchPhrasePrefix(name, null); + } + + public void setName_MatchPhrasePrefix(String name, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Fuzzy(String name) { + setName_Fuzzy(name, null); + } + + public void setName_Fuzzy(String name, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Prefix(String name) { + setName_Prefix(name, null); + } + + public void setName_Prefix(String name, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Wildcard(String name) { + setName_Wildcard(name, null); + } + + public void setName_Wildcard(String name, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Regexp(String name) { + setName_Regexp(name, null); + } + + public void setName_Regexp(String name, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_SpanTerm(String name) { + setName_SpanTerm("name", null); + } + + public void setName_SpanTerm(String name, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_GreaterThan(String name) { + setName_GreaterThan(name, null); + } + + public void setName_GreaterThan(String name, ConditionOptionCall opLambda) { + final Object _value = name; + RangeQueryBuilder builder = regRangeQ("name", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_LessThan(String name) { + setName_LessThan(name, null); + } + + public void setName_LessThan(String name, ConditionOptionCall opLambda) { + final Object _value = name; + RangeQueryBuilder builder = regRangeQ("name", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_GreaterEqual(String name) { + setName_GreaterEqual(name, null); + } + + public void setName_GreaterEqual(String name, ConditionOptionCall opLambda) { + final Object _value = name; + RangeQueryBuilder builder = regRangeQ("name", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_LessEqual(String name) { + setName_LessEqual(name, null); + } + + public void setName_LessEqual(String name, ConditionOptionCall opLambda) { + final Object _value = name; + RangeQueryBuilder builder = regRangeQ("name", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setName_Exists() { + setName_Exists(null); + } + + public void setName_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("name"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setName_CommonTerms(String name) { + setName_CommonTerms(name, null); + } + + @Deprecated + public void setName_CommonTerms(String name, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("name", name); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_Name_Asc() { + regOBA("name"); + return this; + } + + public BsTagTypeCQ addOrderBy_Name_Desc() { + regOBD("name"); + return this; + } + + public void setOwner_Equal(String owner) { + setOwner_Term(owner, null); + } + + public void setOwner_Equal(String owner, ConditionOptionCall opLambda) { + setOwner_Term(owner, opLambda); + } + + public void setOwner_Term(String owner) { + setOwner_Term(owner, null); + } + + public void setOwner_Term(String owner, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_NotEqual(String owner) { + setOwner_NotTerm(owner, null); + } + + public void setOwner_NotTerm(String owner) { + setOwner_NotTerm(owner, null); + } + + public void setOwner_NotEqual(String owner, ConditionOptionCall opLambda) { + setOwner_NotTerm(owner, opLambda); + } + + public void setOwner_NotTerm(String owner, ConditionOptionCall opLambda) { + not(not -> not.setOwner_Term(owner), opLambda); + } + + public void setOwner_Terms(Collection ownerList) { + setOwner_Terms(ownerList, null); + } + + public void setOwner_Terms(Collection ownerList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("owner", ownerList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_InScope(Collection ownerList) { + setOwner_Terms(ownerList, null); + } + + public void setOwner_InScope(Collection ownerList, ConditionOptionCall opLambda) { + setOwner_Terms(ownerList, opLambda); + } + + public void setOwner_Match(String owner) { + setOwner_Match(owner, null); + } + + public void setOwner_Match(String owner, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_MatchPhrase(String owner) { + setOwner_MatchPhrase(owner, null); + } + + public void setOwner_MatchPhrase(String owner, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_MatchPhrasePrefix(String owner) { + setOwner_MatchPhrasePrefix(owner, null); + } + + public void setOwner_MatchPhrasePrefix(String owner, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Fuzzy(String owner) { + setOwner_Fuzzy(owner, null); + } + + public void setOwner_Fuzzy(String owner, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Prefix(String owner) { + setOwner_Prefix(owner, null); + } + + public void setOwner_Prefix(String owner, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Wildcard(String owner) { + setOwner_Wildcard(owner, null); + } + + public void setOwner_Wildcard(String owner, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Regexp(String owner) { + setOwner_Regexp(owner, null); + } + + public void setOwner_Regexp(String owner, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_SpanTerm(String owner) { + setOwner_SpanTerm("owner", null); + } + + public void setOwner_SpanTerm(String owner, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_GreaterThan(String owner) { + setOwner_GreaterThan(owner, null); + } + + public void setOwner_GreaterThan(String owner, ConditionOptionCall opLambda) { + final Object _value = owner; + RangeQueryBuilder builder = regRangeQ("owner", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_LessThan(String owner) { + setOwner_LessThan(owner, null); + } + + public void setOwner_LessThan(String owner, ConditionOptionCall opLambda) { + final Object _value = owner; + RangeQueryBuilder builder = regRangeQ("owner", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_GreaterEqual(String owner) { + setOwner_GreaterEqual(owner, null); + } + + public void setOwner_GreaterEqual(String owner, ConditionOptionCall opLambda) { + final Object _value = owner; + RangeQueryBuilder builder = regRangeQ("owner", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_LessEqual(String owner) { + setOwner_LessEqual(owner, null); + } + + public void setOwner_LessEqual(String owner, ConditionOptionCall opLambda) { + final Object _value = owner; + RangeQueryBuilder builder = regRangeQ("owner", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setOwner_Exists() { + setOwner_Exists(null); + } + + public void setOwner_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("owner"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setOwner_CommonTerms(String owner) { + setOwner_CommonTerms(owner, null); + } + + @Deprecated + public void setOwner_CommonTerms(String owner, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("owner", owner); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_Owner_Asc() { + regOBA("owner"); + return this; + } + + public BsTagTypeCQ addOrderBy_Owner_Desc() { + regOBD("owner"); + return this; + } + + public void setPaths_Equal(String paths) { + setPaths_Term(paths, null); + } + + public void setPaths_Equal(String paths, ConditionOptionCall opLambda) { + setPaths_Term(paths, opLambda); + } + + public void setPaths_Term(String paths) { + setPaths_Term(paths, null); + } + + public void setPaths_Term(String paths, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_NotEqual(String paths) { + setPaths_NotTerm(paths, null); + } + + public void setPaths_NotTerm(String paths) { + setPaths_NotTerm(paths, null); + } + + public void setPaths_NotEqual(String paths, ConditionOptionCall opLambda) { + setPaths_NotTerm(paths, opLambda); + } + + public void setPaths_NotTerm(String paths, ConditionOptionCall opLambda) { + not(not -> not.setPaths_Term(paths), opLambda); + } + + public void setPaths_Terms(Collection pathsList) { + setPaths_Terms(pathsList, null); + } + + public void setPaths_Terms(Collection pathsList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("paths", pathsList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_InScope(Collection pathsList) { + setPaths_Terms(pathsList, null); + } + + public void setPaths_InScope(Collection pathsList, ConditionOptionCall opLambda) { + setPaths_Terms(pathsList, opLambda); + } + + public void setPaths_Match(String paths) { + setPaths_Match(paths, null); + } + + public void setPaths_Match(String paths, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_MatchPhrase(String paths) { + setPaths_MatchPhrase(paths, null); + } + + public void setPaths_MatchPhrase(String paths, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_MatchPhrasePrefix(String paths) { + setPaths_MatchPhrasePrefix(paths, null); + } + + public void setPaths_MatchPhrasePrefix(String paths, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Fuzzy(String paths) { + setPaths_Fuzzy(paths, null); + } + + public void setPaths_Fuzzy(String paths, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Prefix(String paths) { + setPaths_Prefix(paths, null); + } + + public void setPaths_Prefix(String paths, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Wildcard(String paths) { + setPaths_Wildcard(paths, null); + } + + public void setPaths_Wildcard(String paths, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Regexp(String paths) { + setPaths_Regexp(paths, null); + } + + public void setPaths_Regexp(String paths, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_SpanTerm(String paths) { + setPaths_SpanTerm("paths", null); + } + + public void setPaths_SpanTerm(String paths, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_GreaterThan(String paths) { + setPaths_GreaterThan(paths, null); + } + + public void setPaths_GreaterThan(String paths, ConditionOptionCall opLambda) { + final Object _value = paths; + RangeQueryBuilder builder = regRangeQ("paths", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_LessThan(String paths) { + setPaths_LessThan(paths, null); + } + + public void setPaths_LessThan(String paths, ConditionOptionCall opLambda) { + final Object _value = paths; + RangeQueryBuilder builder = regRangeQ("paths", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_GreaterEqual(String paths) { + setPaths_GreaterEqual(paths, null); + } + + public void setPaths_GreaterEqual(String paths, ConditionOptionCall opLambda) { + final Object _value = paths; + RangeQueryBuilder builder = regRangeQ("paths", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_LessEqual(String paths) { + setPaths_LessEqual(paths, null); + } + + public void setPaths_LessEqual(String paths, ConditionOptionCall opLambda) { + final Object _value = paths; + RangeQueryBuilder builder = regRangeQ("paths", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPaths_Exists() { + setPaths_Exists(null); + } + + public void setPaths_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("paths"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setPaths_CommonTerms(String paths) { + setPaths_CommonTerms(paths, null); + } + + @Deprecated + public void setPaths_CommonTerms(String paths, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("paths", paths); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_Paths_Asc() { + regOBA("paths"); + return this; + } + + public BsTagTypeCQ addOrderBy_Paths_Desc() { + regOBD("paths"); + return this; + } + + public void setPermissions_Equal(String permissions) { + setPermissions_Term(permissions, null); + } + + public void setPermissions_Equal(String permissions, ConditionOptionCall opLambda) { + setPermissions_Term(permissions, opLambda); + } + + public void setPermissions_Term(String permissions) { + setPermissions_Term(permissions, null); + } + + public void setPermissions_Term(String permissions, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_NotEqual(String permissions) { + setPermissions_NotTerm(permissions, null); + } + + public void setPermissions_NotTerm(String permissions) { + setPermissions_NotTerm(permissions, null); + } + + public void setPermissions_NotEqual(String permissions, ConditionOptionCall opLambda) { + setPermissions_NotTerm(permissions, opLambda); + } + + public void setPermissions_NotTerm(String permissions, ConditionOptionCall opLambda) { + not(not -> not.setPermissions_Term(permissions), opLambda); + } + + public void setPermissions_Terms(Collection permissionsList) { + setPermissions_Terms(permissionsList, null); + } + + public void setPermissions_Terms(Collection permissionsList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("permissions", permissionsList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_InScope(Collection permissionsList) { + setPermissions_Terms(permissionsList, null); + } + + public void setPermissions_InScope(Collection permissionsList, ConditionOptionCall opLambda) { + setPermissions_Terms(permissionsList, opLambda); + } + + public void setPermissions_Match(String permissions) { + setPermissions_Match(permissions, null); + } + + public void setPermissions_Match(String permissions, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_MatchPhrase(String permissions) { + setPermissions_MatchPhrase(permissions, null); + } + + public void setPermissions_MatchPhrase(String permissions, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_MatchPhrasePrefix(String permissions) { + setPermissions_MatchPhrasePrefix(permissions, null); + } + + public void setPermissions_MatchPhrasePrefix(String permissions, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Fuzzy(String permissions) { + setPermissions_Fuzzy(permissions, null); + } + + public void setPermissions_Fuzzy(String permissions, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Prefix(String permissions) { + setPermissions_Prefix(permissions, null); + } + + public void setPermissions_Prefix(String permissions, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Wildcard(String permissions) { + setPermissions_Wildcard(permissions, null); + } + + public void setPermissions_Wildcard(String permissions, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Regexp(String permissions) { + setPermissions_Regexp(permissions, null); + } + + public void setPermissions_Regexp(String permissions, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_SpanTerm(String permissions) { + setPermissions_SpanTerm("permissions", null); + } + + public void setPermissions_SpanTerm(String permissions, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_GreaterThan(String permissions) { + setPermissions_GreaterThan(permissions, null); + } + + public void setPermissions_GreaterThan(String permissions, ConditionOptionCall opLambda) { + final Object _value = permissions; + RangeQueryBuilder builder = regRangeQ("permissions", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_LessThan(String permissions) { + setPermissions_LessThan(permissions, null); + } + + public void setPermissions_LessThan(String permissions, ConditionOptionCall opLambda) { + final Object _value = permissions; + RangeQueryBuilder builder = regRangeQ("permissions", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_GreaterEqual(String permissions) { + setPermissions_GreaterEqual(permissions, null); + } + + public void setPermissions_GreaterEqual(String permissions, ConditionOptionCall opLambda) { + final Object _value = permissions; + RangeQueryBuilder builder = regRangeQ("permissions", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_LessEqual(String permissions) { + setPermissions_LessEqual(permissions, null); + } + + public void setPermissions_LessEqual(String permissions, ConditionOptionCall opLambda) { + final Object _value = permissions; + RangeQueryBuilder builder = regRangeQ("permissions", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setPermissions_Exists() { + setPermissions_Exists(null); + } + + public void setPermissions_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("permissions"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setPermissions_CommonTerms(String permissions) { + setPermissions_CommonTerms(permissions, null); + } + + @Deprecated + public void setPermissions_CommonTerms(String permissions, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("permissions", permissions); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_Permissions_Asc() { + regOBA("permissions"); + return this; + } + + public BsTagTypeCQ addOrderBy_Permissions_Desc() { + regOBD("permissions"); + return this; + } + + public void setSortOrder_Equal(Integer sortOrder) { + setSortOrder_Term(sortOrder, null); + } + + public void setSortOrder_Equal(Integer sortOrder, ConditionOptionCall opLambda) { + setSortOrder_Term(sortOrder, opLambda); + } + + public void setSortOrder_Term(Integer sortOrder) { + setSortOrder_Term(sortOrder, null); + } + + public void setSortOrder_Term(Integer sortOrder, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_NotEqual(Integer sortOrder) { + setSortOrder_NotTerm(sortOrder, null); + } + + public void setSortOrder_NotTerm(Integer sortOrder) { + setSortOrder_NotTerm(sortOrder, null); + } + + public void setSortOrder_NotEqual(Integer sortOrder, ConditionOptionCall opLambda) { + setSortOrder_NotTerm(sortOrder, opLambda); + } + + public void setSortOrder_NotTerm(Integer sortOrder, ConditionOptionCall opLambda) { + not(not -> not.setSortOrder_Term(sortOrder), opLambda); + } + + public void setSortOrder_Terms(Collection sortOrderList) { + setSortOrder_Terms(sortOrderList, null); + } + + public void setSortOrder_Terms(Collection sortOrderList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("sortOrder", sortOrderList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_InScope(Collection sortOrderList) { + setSortOrder_Terms(sortOrderList, null); + } + + public void setSortOrder_InScope(Collection sortOrderList, ConditionOptionCall opLambda) { + setSortOrder_Terms(sortOrderList, opLambda); + } + + public void setSortOrder_Match(Integer sortOrder) { + setSortOrder_Match(sortOrder, null); + } + + public void setSortOrder_Match(Integer sortOrder, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_MatchPhrase(Integer sortOrder) { + setSortOrder_MatchPhrase(sortOrder, null); + } + + public void setSortOrder_MatchPhrase(Integer sortOrder, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_MatchPhrasePrefix(Integer sortOrder) { + setSortOrder_MatchPhrasePrefix(sortOrder, null); + } + + public void setSortOrder_MatchPhrasePrefix(Integer sortOrder, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Fuzzy(Integer sortOrder) { + setSortOrder_Fuzzy(sortOrder, null); + } + + public void setSortOrder_Fuzzy(Integer sortOrder, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_GreaterThan(Integer sortOrder) { + setSortOrder_GreaterThan(sortOrder, null); + } + + public void setSortOrder_GreaterThan(Integer sortOrder, ConditionOptionCall opLambda) { + final Object _value = sortOrder; + RangeQueryBuilder builder = regRangeQ("sortOrder", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_LessThan(Integer sortOrder) { + setSortOrder_LessThan(sortOrder, null); + } + + public void setSortOrder_LessThan(Integer sortOrder, ConditionOptionCall opLambda) { + final Object _value = sortOrder; + RangeQueryBuilder builder = regRangeQ("sortOrder", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_GreaterEqual(Integer sortOrder) { + setSortOrder_GreaterEqual(sortOrder, null); + } + + public void setSortOrder_GreaterEqual(Integer sortOrder, ConditionOptionCall opLambda) { + final Object _value = sortOrder; + RangeQueryBuilder builder = regRangeQ("sortOrder", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_LessEqual(Integer sortOrder) { + setSortOrder_LessEqual(sortOrder, null); + } + + public void setSortOrder_LessEqual(Integer sortOrder, ConditionOptionCall opLambda) { + final Object _value = sortOrder; + RangeQueryBuilder builder = regRangeQ("sortOrder", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setSortOrder_Exists() { + setSortOrder_Exists(null); + } + + public void setSortOrder_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("sortOrder"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setSortOrder_CommonTerms(Integer sortOrder) { + setSortOrder_CommonTerms(sortOrder, null); + } + + @Deprecated + public void setSortOrder_CommonTerms(Integer sortOrder, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("sortOrder", sortOrder); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_SortOrder_Asc() { + regOBA("sortOrder"); + return this; + } + + public BsTagTypeCQ addOrderBy_SortOrder_Desc() { + regOBD("sortOrder"); + return this; + } + + public void setUpdatedBy_Equal(String updatedBy) { + setUpdatedBy_Term(updatedBy, null); + } + + public void setUpdatedBy_Equal(String updatedBy, ConditionOptionCall opLambda) { + setUpdatedBy_Term(updatedBy, opLambda); + } + + public void setUpdatedBy_Term(String updatedBy) { + setUpdatedBy_Term(updatedBy, null); + } + + public void setUpdatedBy_Term(String updatedBy, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_NotEqual(String updatedBy) { + setUpdatedBy_NotTerm(updatedBy, null); + } + + public void setUpdatedBy_NotTerm(String updatedBy) { + setUpdatedBy_NotTerm(updatedBy, null); + } + + public void setUpdatedBy_NotEqual(String updatedBy, ConditionOptionCall opLambda) { + setUpdatedBy_NotTerm(updatedBy, opLambda); + } + + public void setUpdatedBy_NotTerm(String updatedBy, ConditionOptionCall opLambda) { + not(not -> not.setUpdatedBy_Term(updatedBy), opLambda); + } + + public void setUpdatedBy_Terms(Collection updatedByList) { + setUpdatedBy_Terms(updatedByList, null); + } + + public void setUpdatedBy_Terms(Collection updatedByList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("updatedBy", updatedByList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_InScope(Collection updatedByList) { + setUpdatedBy_Terms(updatedByList, null); + } + + public void setUpdatedBy_InScope(Collection updatedByList, ConditionOptionCall opLambda) { + setUpdatedBy_Terms(updatedByList, opLambda); + } + + public void setUpdatedBy_Match(String updatedBy) { + setUpdatedBy_Match(updatedBy, null); + } + + public void setUpdatedBy_Match(String updatedBy, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_MatchPhrase(String updatedBy) { + setUpdatedBy_MatchPhrase(updatedBy, null); + } + + public void setUpdatedBy_MatchPhrase(String updatedBy, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_MatchPhrasePrefix(String updatedBy) { + setUpdatedBy_MatchPhrasePrefix(updatedBy, null); + } + + public void setUpdatedBy_MatchPhrasePrefix(String updatedBy, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Fuzzy(String updatedBy) { + setUpdatedBy_Fuzzy(updatedBy, null); + } + + public void setUpdatedBy_Fuzzy(String updatedBy, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Prefix(String updatedBy) { + setUpdatedBy_Prefix(updatedBy, null); + } + + public void setUpdatedBy_Prefix(String updatedBy, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Wildcard(String updatedBy) { + setUpdatedBy_Wildcard(updatedBy, null); + } + + public void setUpdatedBy_Wildcard(String updatedBy, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Regexp(String updatedBy) { + setUpdatedBy_Regexp(updatedBy, null); + } + + public void setUpdatedBy_Regexp(String updatedBy, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_SpanTerm(String updatedBy) { + setUpdatedBy_SpanTerm("updatedBy", null); + } + + public void setUpdatedBy_SpanTerm(String updatedBy, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_GreaterThan(String updatedBy) { + setUpdatedBy_GreaterThan(updatedBy, null); + } + + public void setUpdatedBy_GreaterThan(String updatedBy, ConditionOptionCall opLambda) { + final Object _value = updatedBy; + RangeQueryBuilder builder = regRangeQ("updatedBy", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_LessThan(String updatedBy) { + setUpdatedBy_LessThan(updatedBy, null); + } + + public void setUpdatedBy_LessThan(String updatedBy, ConditionOptionCall opLambda) { + final Object _value = updatedBy; + RangeQueryBuilder builder = regRangeQ("updatedBy", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_GreaterEqual(String updatedBy) { + setUpdatedBy_GreaterEqual(updatedBy, null); + } + + public void setUpdatedBy_GreaterEqual(String updatedBy, ConditionOptionCall opLambda) { + final Object _value = updatedBy; + RangeQueryBuilder builder = regRangeQ("updatedBy", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_LessEqual(String updatedBy) { + setUpdatedBy_LessEqual(updatedBy, null); + } + + public void setUpdatedBy_LessEqual(String updatedBy, ConditionOptionCall opLambda) { + final Object _value = updatedBy; + RangeQueryBuilder builder = regRangeQ("updatedBy", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedBy_Exists() { + setUpdatedBy_Exists(null); + } + + public void setUpdatedBy_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("updatedBy"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setUpdatedBy_CommonTerms(String updatedBy) { + setUpdatedBy_CommonTerms(updatedBy, null); + } + + @Deprecated + public void setUpdatedBy_CommonTerms(String updatedBy, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("updatedBy", updatedBy); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_UpdatedBy_Asc() { + regOBA("updatedBy"); + return this; + } + + public BsTagTypeCQ addOrderBy_UpdatedBy_Desc() { + regOBD("updatedBy"); + return this; + } + + public void setUpdatedTime_Equal(Long updatedTime) { + setUpdatedTime_Term(updatedTime, null); + } + + public void setUpdatedTime_Equal(Long updatedTime, ConditionOptionCall opLambda) { + setUpdatedTime_Term(updatedTime, opLambda); + } + + public void setUpdatedTime_Term(Long updatedTime) { + setUpdatedTime_Term(updatedTime, null); + } + + public void setUpdatedTime_Term(Long updatedTime, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_NotEqual(Long updatedTime) { + setUpdatedTime_NotTerm(updatedTime, null); + } + + public void setUpdatedTime_NotTerm(Long updatedTime) { + setUpdatedTime_NotTerm(updatedTime, null); + } + + public void setUpdatedTime_NotEqual(Long updatedTime, ConditionOptionCall opLambda) { + setUpdatedTime_NotTerm(updatedTime, opLambda); + } + + public void setUpdatedTime_NotTerm(Long updatedTime, ConditionOptionCall opLambda) { + not(not -> not.setUpdatedTime_Term(updatedTime), opLambda); + } + + public void setUpdatedTime_Terms(Collection updatedTimeList) { + setUpdatedTime_Terms(updatedTimeList, null); + } + + public void setUpdatedTime_Terms(Collection updatedTimeList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("updatedTime", updatedTimeList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_InScope(Collection updatedTimeList) { + setUpdatedTime_Terms(updatedTimeList, null); + } + + public void setUpdatedTime_InScope(Collection updatedTimeList, ConditionOptionCall opLambda) { + setUpdatedTime_Terms(updatedTimeList, opLambda); + } + + public void setUpdatedTime_Match(Long updatedTime) { + setUpdatedTime_Match(updatedTime, null); + } + + public void setUpdatedTime_Match(Long updatedTime, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_MatchPhrase(Long updatedTime) { + setUpdatedTime_MatchPhrase(updatedTime, null); + } + + public void setUpdatedTime_MatchPhrase(Long updatedTime, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_MatchPhrasePrefix(Long updatedTime) { + setUpdatedTime_MatchPhrasePrefix(updatedTime, null); + } + + public void setUpdatedTime_MatchPhrasePrefix(Long updatedTime, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Fuzzy(Long updatedTime) { + setUpdatedTime_Fuzzy(updatedTime, null); + } + + public void setUpdatedTime_Fuzzy(Long updatedTime, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_GreaterThan(Long updatedTime) { + setUpdatedTime_GreaterThan(updatedTime, null); + } + + public void setUpdatedTime_GreaterThan(Long updatedTime, ConditionOptionCall opLambda) { + final Object _value = updatedTime; + RangeQueryBuilder builder = regRangeQ("updatedTime", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_LessThan(Long updatedTime) { + setUpdatedTime_LessThan(updatedTime, null); + } + + public void setUpdatedTime_LessThan(Long updatedTime, ConditionOptionCall opLambda) { + final Object _value = updatedTime; + RangeQueryBuilder builder = regRangeQ("updatedTime", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_GreaterEqual(Long updatedTime) { + setUpdatedTime_GreaterEqual(updatedTime, null); + } + + public void setUpdatedTime_GreaterEqual(Long updatedTime, ConditionOptionCall opLambda) { + final Object _value = updatedTime; + RangeQueryBuilder builder = regRangeQ("updatedTime", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_LessEqual(Long updatedTime) { + setUpdatedTime_LessEqual(updatedTime, null); + } + + public void setUpdatedTime_LessEqual(Long updatedTime, ConditionOptionCall opLambda) { + final Object _value = updatedTime; + RangeQueryBuilder builder = regRangeQ("updatedTime", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setUpdatedTime_Exists() { + setUpdatedTime_Exists(null); + } + + public void setUpdatedTime_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("updatedTime"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setUpdatedTime_CommonTerms(Long updatedTime) { + setUpdatedTime_CommonTerms(updatedTime, null); + } + + @Deprecated + public void setUpdatedTime_CommonTerms(Long updatedTime, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("updatedTime", updatedTime); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_UpdatedTime_Asc() { + regOBA("updatedTime"); + return this; + } + + public BsTagTypeCQ addOrderBy_UpdatedTime_Desc() { + regOBD("updatedTime"); + return this; + } + + public void setVirtualHost_Equal(String virtualHost) { + setVirtualHost_Term(virtualHost, null); + } + + public void setVirtualHost_Equal(String virtualHost, ConditionOptionCall opLambda) { + setVirtualHost_Term(virtualHost, opLambda); + } + + public void setVirtualHost_Term(String virtualHost) { + setVirtualHost_Term(virtualHost, null); + } + + public void setVirtualHost_Term(String virtualHost, ConditionOptionCall opLambda) { + TermQueryBuilder builder = regTermQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_NotEqual(String virtualHost) { + setVirtualHost_NotTerm(virtualHost, null); + } + + public void setVirtualHost_NotTerm(String virtualHost) { + setVirtualHost_NotTerm(virtualHost, null); + } + + public void setVirtualHost_NotEqual(String virtualHost, ConditionOptionCall opLambda) { + setVirtualHost_NotTerm(virtualHost, opLambda); + } + + public void setVirtualHost_NotTerm(String virtualHost, ConditionOptionCall opLambda) { + not(not -> not.setVirtualHost_Term(virtualHost), opLambda); + } + + public void setVirtualHost_Terms(Collection virtualHostList) { + setVirtualHost_Terms(virtualHostList, null); + } + + public void setVirtualHost_Terms(Collection virtualHostList, ConditionOptionCall opLambda) { + TermsQueryBuilder builder = regTermsQ("virtualHost", virtualHostList); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_InScope(Collection virtualHostList) { + setVirtualHost_Terms(virtualHostList, null); + } + + public void setVirtualHost_InScope(Collection virtualHostList, ConditionOptionCall opLambda) { + setVirtualHost_Terms(virtualHostList, opLambda); + } + + public void setVirtualHost_Match(String virtualHost) { + setVirtualHost_Match(virtualHost, null); + } + + public void setVirtualHost_Match(String virtualHost, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regMatchQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_MatchPhrase(String virtualHost) { + setVirtualHost_MatchPhrase(virtualHost, null); + } + + public void setVirtualHost_MatchPhrase(String virtualHost, ConditionOptionCall opLambda) { + MatchPhraseQueryBuilder builder = regMatchPhraseQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_MatchPhrasePrefix(String virtualHost) { + setVirtualHost_MatchPhrasePrefix(virtualHost, null); + } + + public void setVirtualHost_MatchPhrasePrefix(String virtualHost, ConditionOptionCall opLambda) { + MatchPhrasePrefixQueryBuilder builder = regMatchPhrasePrefixQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Fuzzy(String virtualHost) { + setVirtualHost_Fuzzy(virtualHost, null); + } + + public void setVirtualHost_Fuzzy(String virtualHost, ConditionOptionCall opLambda) { + MatchQueryBuilder builder = regFuzzyQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Prefix(String virtualHost) { + setVirtualHost_Prefix(virtualHost, null); + } + + public void setVirtualHost_Prefix(String virtualHost, ConditionOptionCall opLambda) { + PrefixQueryBuilder builder = regPrefixQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Wildcard(String virtualHost) { + setVirtualHost_Wildcard(virtualHost, null); + } + + public void setVirtualHost_Wildcard(String virtualHost, ConditionOptionCall opLambda) { + WildcardQueryBuilder builder = regWildcardQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Regexp(String virtualHost) { + setVirtualHost_Regexp(virtualHost, null); + } + + public void setVirtualHost_Regexp(String virtualHost, ConditionOptionCall opLambda) { + RegexpQueryBuilder builder = regRegexpQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_SpanTerm(String virtualHost) { + setVirtualHost_SpanTerm("virtualHost", null); + } + + public void setVirtualHost_SpanTerm(String virtualHost, ConditionOptionCall opLambda) { + SpanTermQueryBuilder builder = regSpanTermQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_GreaterThan(String virtualHost) { + setVirtualHost_GreaterThan(virtualHost, null); + } + + public void setVirtualHost_GreaterThan(String virtualHost, ConditionOptionCall opLambda) { + final Object _value = virtualHost; + RangeQueryBuilder builder = regRangeQ("virtualHost", ConditionKey.CK_GREATER_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_LessThan(String virtualHost) { + setVirtualHost_LessThan(virtualHost, null); + } + + public void setVirtualHost_LessThan(String virtualHost, ConditionOptionCall opLambda) { + final Object _value = virtualHost; + RangeQueryBuilder builder = regRangeQ("virtualHost", ConditionKey.CK_LESS_THAN, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_GreaterEqual(String virtualHost) { + setVirtualHost_GreaterEqual(virtualHost, null); + } + + public void setVirtualHost_GreaterEqual(String virtualHost, ConditionOptionCall opLambda) { + final Object _value = virtualHost; + RangeQueryBuilder builder = regRangeQ("virtualHost", ConditionKey.CK_GREATER_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_LessEqual(String virtualHost) { + setVirtualHost_LessEqual(virtualHost, null); + } + + public void setVirtualHost_LessEqual(String virtualHost, ConditionOptionCall opLambda) { + final Object _value = virtualHost; + RangeQueryBuilder builder = regRangeQ("virtualHost", ConditionKey.CK_LESS_EQUAL, _value); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public void setVirtualHost_Exists() { + setVirtualHost_Exists(null); + } + + public void setVirtualHost_Exists(ConditionOptionCall opLambda) { + ExistsQueryBuilder builder = regExistsQ("virtualHost"); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + @Deprecated + public void setVirtualHost_CommonTerms(String virtualHost) { + setVirtualHost_CommonTerms(virtualHost, null); + } + + @Deprecated + public void setVirtualHost_CommonTerms(String virtualHost, ConditionOptionCall opLambda) { + CommonTermsQueryBuilder builder = regCommonTermsQ("virtualHost", virtualHost); + if (opLambda != null) { + opLambda.callback(builder); + } + } + + public BsTagTypeCQ addOrderBy_VirtualHost_Asc() { + regOBA("virtualHost"); + return this; + } + + public BsTagTypeCQ addOrderBy_VirtualHost_Desc() { + regOBD("virtualHost"); + return this; + } + +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/exbhv/TagTypeBhv.java b/src/main/java/org/codelibs/fess/opensearch/config/exbhv/TagTypeBhv.java new file mode 100644 index 0000000000..aa03505c12 --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/exbhv/TagTypeBhv.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.exbhv; + +import java.util.regex.Pattern; + +import org.codelibs.fess.opensearch.config.bsbhv.BsTagTypeBhv; +import org.codelibs.fess.util.ComponentUtil; + +/** + * @author FreeGen + */ +public class TagTypeBhv extends BsTagTypeBhv { + private String indexName = null; + + @Override + protected String asEsIndex() { + if (indexName == null) { + final String name = ComponentUtil.getFessConfig().getIndexConfigIndex(); + indexName = super.asEsIndex().replaceFirst(Pattern.quote("fess_config"), name); + } + return indexName; + } +} diff --git a/src/main/java/org/codelibs/fess/opensearch/config/exentity/LabelType.java b/src/main/java/org/codelibs/fess/opensearch/config/exentity/LabelType.java index 525f3b976c..a66faec571 100644 --- a/src/main/java/org/codelibs/fess/opensearch/config/exentity/LabelType.java +++ b/src/main/java/org/codelibs/fess/opensearch/config/exentity/LabelType.java @@ -26,16 +26,6 @@ public class LabelType extends BsLabelType { private static final long serialVersionUID = 1L; - - /** The kind of a label that the crawler assigns to documents by the included and excluded paths. */ - public static final String KIND_LABEL = "label"; - - /** - * The kind of a label that is a tag users add from the search screen: its name is the tag name, its included paths - * list the tagged URLs (matched exactly) and its permissions list who can see it, including the users who added it. - */ - public static final String KIND_TAG = "tag"; - private Locale locale; public String getId() { @@ -54,15 +44,6 @@ public void setVersionNo(final Long version) { asDocMeta().version(version); } - /** - * Returns whether this label type defines user tags. A label type without a kind is a plain label. - * - * @return true if the kind is {@value #KIND_TAG} - */ - public boolean isTagKind() { - return KIND_TAG.equals(getKind()); - } - public Locale getLocale() { if (locale == null) { if (getValue() == null) { @@ -76,7 +57,7 @@ public Locale getLocale() { @Override public String toString() { return "LabelType [createdBy=" + createdBy + ", createdTime=" + createdTime + ", excludedPaths=" + excludedPaths - + ", includedPaths=" + includedPaths + ", kind=" + kind + ", name=" + name + ", sortOrder=" + sortOrder + ", updatedBy=" - + updatedBy + ", updatedTime=" + updatedTime + ", value=" + value + ", docMeta=" + docMeta + "]"; + + ", includedPaths=" + includedPaths + ", name=" + name + ", sortOrder=" + sortOrder + ", updatedBy=" + updatedBy + + ", updatedTime=" + updatedTime + ", value=" + value + ", docMeta=" + docMeta + "]"; } } diff --git a/src/main/java/org/codelibs/fess/opensearch/config/exentity/TagType.java b/src/main/java/org/codelibs/fess/opensearch/config/exentity/TagType.java new file mode 100644 index 0000000000..1746c69ded --- /dev/null +++ b/src/main/java/org/codelibs/fess/opensearch/config/exentity/TagType.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed 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.codelibs.fess.opensearch.config.exentity; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; + +import org.codelibs.fess.opensearch.config.bsentity.BsTagType; + +/** + * A tag of one user: its name, its owner and the URLs of the documents it is put on. + * + * @author FreeGen + */ +public class TagType extends BsTagType { + + private static final long serialVersionUID = 1L; + + public String getId() { + return asDocMeta().id(); + } + + public void setId(final String id) { + asDocMeta().id(id); + } + + public Long getVersionNo() { + return asDocMeta().version(); + } + + public void setVersionNo(final Long version) { + asDocMeta().version(version); + } + + public Long getSeqNo() { + return asDocMeta().seqNo(); + } + + public void setSeqNo(final Long seqNo) { + asDocMeta().seqNo(seqNo); + } + + public Long getPrimaryTerm() { + return asDocMeta().primaryTerm(); + } + + public void setPrimaryTerm(final Long primaryTerm) { + asDocMeta().primaryTerm(primaryTerm); + } + + /** + * Returns the value this tag puts on the tag field of a document: the name and the owner, each + * encoded as Base64URL without padding from UTF-8, joined by {@code ':'}. + * + * @return the tag value, or null when the name or the owner is not set + */ + public String getTagValue() { + if (name == null || owner == null) { + return null; + } + return toTagValue(name, owner); + } + + /** + * Encodes a tag name and its owner into the value a tag puts on the tag field of a document: + * each encoded as Base64URL without padding from UTF-8, joined by {@code ':'}. Neither part + * can contain {@code ':'}, so the value splits back into the two unambiguously. + * + * @param name the tag name + * @param owner the owner of the tag + * @return the tag value + */ + public static String toTagValue(final String name, final String owner) { + final Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); + return encoder.encodeToString(name.getBytes(StandardCharsets.UTF_8)) + ":" + + encoder.encodeToString(owner.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String toString() { + return "TagType [createdBy=" + createdBy + ", createdTime=" + createdTime + ", name=" + name + ", owner=" + owner + ", paths=" + + (paths == null ? null : paths.length) + ", permissions=" + Arrays.toString(permissions) + ", sortOrder=" + sortOrder + + ", updatedBy=" + updatedBy + ", updatedTime=" + updatedTime + ", virtualHost=" + virtualHost + ", docMeta=" + docMeta + + "]"; + } +} diff --git a/src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java b/src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java index 1f217737a4..e53ab1b22e 100644 --- a/src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java @@ -86,7 +86,7 @@ protected QueryBuilder convertFuzzyQuery(final QueryContext context, final Fuzzy final String field = getSearchField(context.getDefaultField(), term.field()); if (isRestrictedTagField(context, field)) { - // a tag value is a hash of the name: a fuzzy match has no use but to probe for tags the caller cannot see + // a tag value encodes the name and the owner of a tag: a fuzzy match on it would probe for tags the caller cannot see return buildHiddenTagQuery(context, field, term.text()); } diff --git a/src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java b/src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java index 5175d67b82..2583bf5683 100644 --- a/src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java @@ -77,7 +77,7 @@ protected QueryBuilder convertPrefixQuery(final QueryContext context, final Pref final String text = prefixQuery.getPrefix().text(); if (isRestrictedTagField(context, field)) { - // a tag value is a hash of the name: a prefix has no use but to probe for tags the caller cannot see + // a tag value encodes the name and the owner of a tag: a prefix on it would probe for tags the caller cannot see return buildHiddenTagQuery(context, field, text + "*"); } diff --git a/src/main/java/org/codelibs/fess/query/QueryCommand.java b/src/main/java/org/codelibs/fess/query/QueryCommand.java index 34b0264040..bc698cae5f 100644 --- a/src/main/java/org/codelibs/fess/query/QueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/QueryCommand.java @@ -18,9 +18,8 @@ import static org.codelibs.core.stream.StreamUtil.stream; import java.lang.Character.UnicodeBlock; -import java.util.Collections; -import java.util.Locale; -import java.util.Set; +import java.util.List; +import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -121,9 +120,10 @@ protected boolean isSearchField(final String field) { } /** - * Checks if a condition on the field must be limited to the tags that the caller can see. The {@code tag} field - * holds the values of every tag of a document, including tags whose label permissions do not include the caller, - * so a condition on it is restricted for every search except the admin search. + * Checks if a condition on the field must be limited to the tags that the caller can see. A value + * of the {@code tag} field decodes to the name and the owner of a tag, and the field of a document + * holds the values of every tag put on it, so a condition on it is restricted for every search + * except the admin search. * @param context The query context. * @param field The field name. * @return True if the field is the tag field and the condition must be restricted. @@ -134,34 +134,42 @@ protected boolean isRestrictedTagField(final QueryContext context, final String } /** - * Checks if the caller can see a tag. The visible tags are resolved once per query context. + * Checks if the caller can see a tag. Nothing is visible while user tags are disabled or to a + * caller who is not logged in. Each value is resolved once per query context. Only called for a + * field that {@link #isRestrictedTagField} restricts, so never for the admin search. * @param context The query context. * @param value The tag value. * @return True if the tag is visible to the caller. */ protected boolean isVisibleTag(final QueryContext context, final String value) { - Set valueSet = context.getVisibleTagValueSet(); - if (valueSet == null) { - valueSet = getVisibleTagValueSet(context); - context.setVisibleTagValueSet(valueSet); + if (!ComponentUtil.getFessConfig().isUserTagEnabled()) { + return false; } - return valueSet.contains(value); + final Map visibilityMap = context.getTagVisibilityMap(); + final Boolean cached = visibilityMap.get(value); + if (cached != null) { + return cached; + } + final boolean visible = resolveTagVisibility(context, value); + visibilityMap.put(value, visible); + return visible; } /** - * Resolves the values of the tags that the caller can see. + * Resolves whether the caller can see a tag. * @param context The query context. - * @return The visible tag values, or an empty set if they cannot be resolved. + * @param value The tag value. + * @return True if the tag is visible, false if it is not or cannot be resolved. */ - protected Set getVisibleTagValueSet(final QueryContext context) { + protected boolean resolveTagVisibility(final QueryContext context, final String value) { + final SearchRequestType searchRequestType = + context.getSearchRequestType() != null ? context.getSearchRequestType() : SearchRequestType.JSON; try { - final SearchRequestType searchRequestType = - context.getSearchRequestType() != null ? context.getSearchRequestType() : SearchRequestType.JSON; - final Locale locale = LaRequestUtil.getOptionalRequest().map(request -> request.getLocale()).orElse(null); - return ComponentUtil.getLabelTypeHelper().getTagValueSet(searchRequestType, locale != null ? locale : Locale.ROOT); + return ComponentUtil.getTagTypeHelper().getVisibleTagTypes(List.of(value), searchRequestType).containsKey(value); } catch (final RuntimeException e) { - logger.debug("Failed to resolve the visible tags; no tag condition matches.", e); - return Collections.emptySet(); + logger.warn("Failed to resolve the visibility of a tag; the tag condition matches nothing. searchRequestType={}", + searchRequestType, e); + return false; } } diff --git a/src/main/java/org/codelibs/fess/query/QueryFieldConfig.java b/src/main/java/org/codelibs/fess/query/QueryFieldConfig.java index a26e6f61b6..2dff089863 100644 --- a/src/main/java/org/codelibs/fess/query/QueryFieldConfig.java +++ b/src/main/java/org/codelibs/fess/query/QueryFieldConfig.java @@ -143,7 +143,6 @@ public void init() { fessConfig.getIndexFieldThumbnail(), // fessConfig.getIndexFieldClickCount(), // fessConfig.getIndexFieldFavoriteCount(), // - fessConfig.getIndexFieldTag(), // fessConfig.getIndexFieldConfigId(), // fessConfig.getIndexFieldLang(), // fessConfig.getIndexFieldHasCache()); diff --git a/src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java b/src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java index 2459ae443b..1de5cc25a4 100644 --- a/src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java @@ -75,7 +75,7 @@ protected QueryBuilder convertTermRangeQuery(final QueryContext context, final T final String field = getSearchField(context.getDefaultField(), termRangeQuery.getField()); if (isRestrictedTagField(context, field)) { - // a tag value is a hash of the name: a range has no use but to probe for tags the caller cannot see + // a tag value encodes the name and the owner of a tag: a range on it would probe for tags the caller cannot see return buildHiddenTagQuery(context, field, termRangeQuery.toString(field)); } diff --git a/src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java b/src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java index 07a34030ab..9d00b7bf88 100644 --- a/src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java +++ b/src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java @@ -81,7 +81,7 @@ protected QueryBuilder convertWildcardQuery(final QueryContext context, final Wi final FessConfig fessConfig = ComponentUtil.getFessConfig(); final String field = getSearchField(context.getDefaultField(), wildcardQuery.getField()); if (isRestrictedTagField(context, field)) { - // a tag value is a hash of the name: a pattern has no use but to probe for tags the caller cannot see + // a tag value encodes the name and the owner of a tag: a pattern on it would probe for tags the caller cannot see return buildHiddenTagQuery(context, field, wildcardQuery.getTerm().text()); } if (Constants.DEFAULT_FIELD.equals(field)) { diff --git a/src/main/java/org/codelibs/fess/rank/fusion/SemanticChunkSearcher.java b/src/main/java/org/codelibs/fess/rank/fusion/SemanticChunkSearcher.java index 6f22859b5a..dc15428703 100644 --- a/src/main/java/org/codelibs/fess/rank/fusion/SemanticChunkSearcher.java +++ b/src/main/java/org/codelibs/fess/rank/fusion/SemanticChunkSearcher.java @@ -940,5 +940,10 @@ public Float getMinScore() { public List getFilterQueries() { return parent.getFilterQueries(); } + + @Override + public String[] getResponseFields() { + return parent.getResponseFields(); + } } } diff --git a/src/main/java/org/codelibs/fess/util/ComponentUtil.java b/src/main/java/org/codelibs/fess/util/ComponentUtil.java index 47f8d6a511..48f6a95e95 100644 --- a/src/main/java/org/codelibs/fess/util/ComponentUtil.java +++ b/src/main/java/org/codelibs/fess/util/ComponentUtil.java @@ -86,7 +86,7 @@ import org.codelibs.fess.helper.SseResponseHelper; import org.codelibs.fess.helper.SuggestHelper; import org.codelibs.fess.helper.SystemHelper; -import org.codelibs.fess.helper.TagHelper; +import org.codelibs.fess.helper.TagTypeHelper; import org.codelibs.fess.helper.ThemeArtifactHelper; import org.codelibs.fess.helper.UserAgentHelper; import org.codelibs.fess.helper.UserInfoHelper; @@ -203,8 +203,6 @@ public final class ComponentUtil { private static final String SUGGEST_HELPER = "suggestHelper"; - private static final String TAG_HELPER = "tagHelper"; - private static final String SEARCH_ENGINE_CLIENT = "searchEngineClient"; private static final String DICTIONARY_MANAGER = "dictionaryManager"; @@ -247,6 +245,8 @@ public final class ComponentUtil { private static final String LABEL_TYPE_HELPER = "labelTypeHelper"; + private static final String TAG_TYPE_HELPER = "tagTypeHelper"; + private static final String QUERY_HELPER = "queryHelper"; private static final String QUERY_FIELD_CONFIG = "queryFieldConfig"; @@ -411,6 +411,14 @@ public static LabelTypeHelper getLabelTypeHelper() { return getComponent(LABEL_TYPE_HELPER); } + /** + * Gets the tag type helper component. + * @return The tag type helper. + */ + public static TagTypeHelper getTagTypeHelper() { + return getComponent(TAG_TYPE_HELPER); + } + /** * Gets the search log helper component. * @return The search log helper. @@ -642,14 +650,6 @@ public static SuggestHelper getSuggestHelper() { return getComponent(SUGGEST_HELPER); } - /** - * Gets the tag helper component. - * @return The tag helper. - */ - public static TagHelper getTagHelper() { - return getComponent(TAG_HELPER); - } - /** * Gets the role query helper component. * @return The role query helper. diff --git a/src/main/resources/app.xml b/src/main/resources/app.xml index e1dfd107bf..57184385b2 100644 --- a/src/main/resources/app.xml +++ b/src/main/resources/app.xml @@ -63,7 +63,7 @@ - + diff --git a/src/main/resources/esclient.xml b/src/main/resources/esclient.xml index da999ff4e9..550fd7e9d6 100644 --- a/src/main/resources/esclient.xml +++ b/src/main/resources/esclient.xml @@ -488,6 +488,9 @@ "fess_config.scheduled_job/scheduled_job" + + "fess_config.tag_type/tag_type" + "fess_config.thumbnail_queue/thumbnail_queue" diff --git a/src/main/resources/esflute_config.xml b/src/main/resources/esflute_config.xml index a601552481..12a2c3b2d7 100644 --- a/src/main/resources/esflute_config.xml +++ b/src/main/resources/esflute_config.xml @@ -30,6 +30,7 @@ + diff --git a/src/main/resources/fess_api.xml b/src/main/resources/fess_api.xml index 974466cb4a..550972a4f2 100644 --- a/src/main/resources/fess_api.xml +++ b/src/main/resources/fess_api.xml @@ -25,6 +25,7 @@ + diff --git a/src/main/resources/fess_config.properties b/src/main/resources/fess_config.properties index 46bcaf5494..45ea9f983b 100644 --- a/src/main/resources/fess_config.properties +++ b/src/main/resources/fess_config.properties @@ -585,7 +585,7 @@ index.field.created=created index.field.timestamp=timestamp # Field name for label in the index. index.field.label=label -# Field name for the tags of the document in the index: the values of the labels of the kind "tag" whose included paths list the document URL. +# Field name for the user tags of the document in the index. index.field.tag=tag # Field name for MIME type in the index. index.field.mimetype=mimetype @@ -674,7 +674,7 @@ index.dictionary.prefix= #> Document Management # Array-type fields for admin in the index. -index.admin.array.fields=lang,role,label,anchor,virtual_host,tag +index.admin.array.fields=lang,role,label,anchor,virtual_host # Date-type fields for admin in the index. index.admin.date.fields=expires,created,timestamp,last_modified # Integer-type fields for admin in the index. @@ -1131,12 +1131,20 @@ logging.chat.max.queue.size=10000 search.history.enabled=true # Maximum number of search history entries returned per user. search.history.size=10 -# Whether logged-in users can tag documents from the search screen. A tag is a label of the kind "tag": its name is the tag name, its included paths list the tagged URLs and its permissions list the users who added it. +# Whether logged-in users can tag documents. Each tag belongs to the user who created it. user.tag.enabled=false -# Maximum length of a tag name. +# Maximum length of a tag name, in code points. user.tag.name.max.length=50 -# Maximum number of tags on one document. -user.tag.max.document.tags=100 +# Maximum number of tags one user can own. +user.tag.max.tags=1000 +# Maximum number of URLs one tag can be put on. +user.tag.max.paths=10000 +# Maximum number of pending tag changes held in memory until they are applied to the documents. +user.tag.queue.max.size=10000 +# Number of URLs updated per bulk request when tag changes are applied to the documents. +user.tag.process.batch.size=100 +# Maximum number of tags visible to one user in a search. +user.tag.visible.max.size=1000 # ======================================================================================== # Web @@ -1254,6 +1262,8 @@ page.docboost.max.fetch.size=1000 page.keymatch.max.fetch.size=1000 # Maximum number of labeltype records to fetch per page. page.labeltype.max.fetch.size=1000 +# Maximum number of tagtype records to fetch per page. +page.tagtype.max.fetch.size=1000 # Maximum number of roletype records to fetch per page. page.roletype.max.fetch.size=1000 # Maximum number of user records to fetch per page. @@ -1514,6 +1524,8 @@ online.help.name.fileconfig=fileconfig online.help.name.fileauth=fileauth # Online help key for label type. online.help.name.labeltype=labeltype +# Online help key for tag type. +online.help.name.tagtype=tagtype # Online help key for duplicate host. online.help.name.duplicatehost=duplicatehost # Online help key for scheduler. @@ -1837,7 +1849,7 @@ rag.chat.response.language=browser # Index Export index.export.path=/var/lib/fess/export # Comma-separated document fields omitted from files written by the index export job. -index.export.exclude.fields=cache +index.export.exclude.fields=cache,tag # Number of documents fetched per scroll request by the index export job. index.export.scroll.size=100 # Output format for exported documents; only html and json are accepted, anything else fails the job. diff --git a/src/main/resources/fess_indices/fess_config.label_type/label_type.json b/src/main/resources/fess_indices/fess_config.label_type/label_type.json index 56893510bb..ffcbef3818 100644 --- a/src/main/resources/fess_indices/fess_config.label_type/label_type.json +++ b/src/main/resources/fess_indices/fess_config.label_type/label_type.json @@ -6,9 +6,6 @@ "value": { "type": "keyword" }, - "kind": { - "type": "keyword" - }, "includedPaths": { "type": "keyword" }, diff --git a/src/main/resources/fess_indices/fess_config.scheduled_job/scheduled_job.bulk b/src/main/resources/fess_indices/fess_config.scheduled_job/scheduled_job.bulk index d93ce0b290..86cb956683 100644 --- a/src/main/resources/fess_indices/fess_config.scheduled_job/scheduled_job.bulk +++ b/src/main/resources/fess_indices/fess_config.scheduled_job/scheduled_job.bulk @@ -26,3 +26,5 @@ {"name":"Log Notification","target":"all","cronExpression":"*/5 * * * *","scriptType":"javascript","scriptData":"return container.getComponent(\"logNotificationJob\").execute();","jobLogging":false,"crawler":false,"available":true,"sortOrder":13,"createdBy":"system","createdTime":0,"updatedBy":"system","updatedTime":0} {"index":{"_index":"fess_config.scheduled_job","_id":"content-chunk-vector-indexer"}} {"name":"Content Chunk Vector Indexer","target":"all","cronExpression":"0 13 * * *","scriptType":"javascript","scriptData":"return container.getComponent(\"chunkVectorJob\").logLevel(\"info\").sessionId(\"CHUNK\").execute(executor);","jobLogging":true,"crawler":false,"available":false,"sortOrder":14,"createdBy":"system","createdTime":0,"updatedBy":"system","updatedTime":0} +{"index":{"_index":"fess_config.scheduled_job","_id":"tag_updater"}} +{"name":"Tag Updater","target":"all","cronExpression":"","scriptType":"javascript","scriptData":"return container.getComponent(\"tagUpdaterJob\").execute();","jobLogging":false,"crawler":false,"available":true,"sortOrder":15,"createdBy":"system","createdTime":0,"updatedBy":"system","updatedTime":0} diff --git a/src/main/resources/fess_indices/fess_config.tag_type.json b/src/main/resources/fess_indices/fess_config.tag_type.json new file mode 100644 index 0000000000..8f94f93c21 --- /dev/null +++ b/src/main/resources/fess_indices/fess_config.tag_type.json @@ -0,0 +1,10 @@ +{ + "settings": { + "index": { + "refresh_interval": "1s", + "number_of_shards": 1, + "number_of_replicas": 0, + "auto_expand_replicas": "0-1" + } + } +} diff --git a/src/main/resources/fess_indices/fess_config.tag_type/alias/fess_config.json b/src/main/resources/fess_indices/fess_config.tag_type/alias/fess_config.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/main/resources/fess_indices/fess_config.tag_type/alias/fess_config.json @@ -0,0 +1 @@ +{} diff --git a/src/main/resources/fess_indices/fess_config.tag_type/tag_type.json b/src/main/resources/fess_indices/fess_config.tag_type/tag_type.json new file mode 100644 index 0000000000..d63771de37 --- /dev/null +++ b/src/main/resources/fess_indices/fess_config.tag_type/tag_type.json @@ -0,0 +1,34 @@ +{ + "properties": { + "name": { + "type": "keyword" + }, + "owner": { + "type": "keyword" + }, + "paths": { + "type": "keyword" + }, + "permissions": { + "type": "keyword" + }, + "virtualHost": { + "type": "keyword" + }, + "sortOrder": { + "type": "integer" + }, + "createdBy": { + "type": "keyword" + }, + "createdTime": { + "type": "long" + }, + "updatedBy": { + "type": "keyword" + }, + "updatedTime": { + "type": "long" + } + } +} diff --git a/src/main/resources/fess_job.xml b/src/main/resources/fess_job.xml index 495e738287..aa2100e26f 100644 --- a/src/main/resources/fess_job.xml +++ b/src/main/resources/fess_job.xml @@ -22,6 +22,8 @@ + + diff --git a/src/main/resources/fess_label.properties b/src/main/resources/fess_label.properties index 8c7472b920..6a51f09ed1 100644 --- a/src/main/resources/fess_label.properties +++ b/src/main/resources/fess_label.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=File System labels.menu_data_store=Data Store labels.menu_label_type=Label +labels.menu_tag_type=Tag labels.menu_key_match=Key Match labels.menu_boost_document_rule=Document Boost labels.menu_path_mapping=Path Mapping @@ -408,6 +409,7 @@ labels.group_details=Group Details labels.job_log_details=Job Log Details labels.key_match_details=Key Match Details labels.labeltype_details=Label Type Details +labels.tagtype_details=Tag Details labels.pathmap_details=Path Mapping Details labels.related_content_details=Related Content Details labels.related_query_details=Related Query Details @@ -432,6 +434,7 @@ labels.plugin_list=Plugin List labels.pathmap_list=Path Mapping List labels.log_file_list=Log File List labels.labeltype_list=Label Type List +labels.tagtype_list=Tag List labels.key_match_list=Key Match List labels.job_log_list=Job Log List labels.group_list=Group List @@ -513,12 +516,13 @@ labels.labeltype_configuration=Label labels.labeltype_title_details=Label labels.labeltype_name=Name labels.labeltype_value=Value -labels.labeltype_kind=Kind -labels.labeltype_kind_label=Label -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=A Tag is a tag that users add from the search screen: Name is the tag name, Included Paths lists the tagged URLs (one URL per line, exact match), and Permissions decides who can see it. A user who adds a tag is added to its Permissions. labels.labeltype_included_paths=Included Paths labels.labeltype_excluded_paths=Excluded Paths +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Name +labels.tagtype_owner=Owner +labels.tagtype_paths=Paths labels.roletype_configuration=Role labels.roletype_title_details=Role labels.roletype_name=Name diff --git a/src/main/resources/fess_label_de.properties b/src/main/resources/fess_label_de.properties index 46968eb9fe..aba0d94bca 100644 --- a/src/main/resources/fess_label_de.properties +++ b/src/main/resources/fess_label_de.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=Dateisystem labels.menu_data_store=Datenspeicher labels.menu_label_type=Label +labels.menu_tag_type=Tag labels.menu_key_match=Schlüsselübereinstimmung labels.menu_boost_document_rule=Dokument-Boost labels.menu_path_mapping=Pfadzuordnung @@ -408,6 +409,7 @@ labels.group_details=Gruppen-Details labels.job_log_details=Jobprotokoll-Details labels.key_match_details=Schlüsselübereinstimmungs-Details labels.labeltype_details=Label-Typ-Details +labels.tagtype_details=Tag-Details labels.pathmap_details=Pfadzuordnungs-Details labels.related_content_details=Verwandter-Inhalt-Details labels.related_query_details=Verwandte-Abfrage-Details @@ -432,6 +434,7 @@ labels.plugin_list=Plugin-Liste labels.pathmap_list=Pfadzuordnungs-Liste labels.log_file_list=Logdatei-Liste labels.labeltype_list=Label-Typ-Liste +labels.tagtype_list=Tag-Liste labels.key_match_list=Schlüsselübereinstimmungs-Liste labels.job_log_list=Job-Log-Liste labels.group_list=Gruppen-Liste @@ -513,12 +516,13 @@ labels.labeltype_configuration=Label labels.labeltype_title_details=Label labels.labeltype_name=Name labels.labeltype_value=Wert -labels.labeltype_kind=Art -labels.labeltype_kind_label=Label -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Ein Tag ist ein Tag, den Benutzer auf der Suchseite hinzufügen: Name ist der Tag-Name, Eingeschlossene Pfade listet die getaggten URLs auf (eine URL pro Zeile, exakte Übereinstimmung), und Berechtigungen legt fest, wer ihn sehen kann. Ein Benutzer, der einen Tag hinzufügt, wird zu dessen Berechtigungen hinzugefügt. labels.labeltype_included_paths=Eingeschlossene Pfade labels.labeltype_excluded_paths=Ausgeschlossene Pfade +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Name +labels.tagtype_owner=Besitzer +labels.tagtype_paths=Pfade labels.roletype_configuration=Rolle labels.roletype_title_details=Rolle labels.roletype_name=Name diff --git a/src/main/resources/fess_label_en.properties b/src/main/resources/fess_label_en.properties index c2b23f8e7a..e8d2613f41 100644 --- a/src/main/resources/fess_label_en.properties +++ b/src/main/resources/fess_label_en.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=File System labels.menu_data_store=Data Store labels.menu_label_type=Label +labels.menu_tag_type=Tag labels.menu_key_match=Key Match labels.menu_boost_document_rule=Document Boost labels.menu_path_mapping=Path Mapping @@ -408,6 +409,7 @@ labels.group_details=Group Details labels.job_log_details=Job Log Details labels.key_match_details=Key Match Details labels.labeltype_details=Label Type Details +labels.tagtype_details=Tag Details labels.pathmap_details=Path Mapping Details labels.related_content_details=Related Content Details labels.related_query_details=Related Query Details @@ -432,6 +434,7 @@ labels.plugin_list=Plugin List labels.pathmap_list=Path Mapping List labels.log_file_list=Log File List labels.labeltype_list=Label Type List +labels.tagtype_list=Tag List labels.key_match_list=Key Match List labels.job_log_list=Job Log List labels.group_list=Group List @@ -513,12 +516,13 @@ labels.labeltype_configuration=Label labels.labeltype_title_details=Label labels.labeltype_name=Name labels.labeltype_value=Value -labels.labeltype_kind=Kind -labels.labeltype_kind_label=Label -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=A Tag is a tag that users add from the search screen: Name is the tag name, Included Paths lists the tagged URLs (one URL per line, exact match), and Permissions decides who can see it. A user who adds a tag is added to its Permissions. labels.labeltype_included_paths=Included Paths labels.labeltype_excluded_paths=Excluded Paths +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Name +labels.tagtype_owner=Owner +labels.tagtype_paths=Paths labels.roletype_configuration=Role labels.roletype_title_details=Role labels.roletype_name=Name diff --git a/src/main/resources/fess_label_es.properties b/src/main/resources/fess_label_es.properties index 35108e371f..4410f6689e 100644 --- a/src/main/resources/fess_label_es.properties +++ b/src/main/resources/fess_label_es.properties @@ -148,6 +148,7 @@ labels.menu_web=Web labels.menu_file_system=Sistema de archivos labels.menu_data_store=Almacén de datos labels.menu_label_type=Etiqueta +labels.menu_tag_type=Etiqueta de usuario labels.menu_key_match=Coincidencia de clave labels.menu_boost_document_rule=Impulso de documento labels.menu_path_mapping=Mapeo de rutas @@ -407,6 +408,7 @@ labels.group_details=Group Detalles labels.job_log_details=Job Log Detalles labels.key_match_details=Key Match Detalles labels.labeltype_details=Labeltype Detalles +labels.tagtype_details=Detalles de la etiqueta de usuario labels.pathmap_details=Pathmap Detalles labels.related_content_details=Related Content Detalles labels.related_query_details=Related Query Detalles @@ -431,6 +433,7 @@ labels.plugin_list=Lista de complementos labels.pathmap_list=Lista de mapeo de rutas labels.log_file_list=Lista de archivos de registro labels.labeltype_list=Lista de tipos de etiquetas +labels.tagtype_list=Lista de etiquetas de usuario labels.key_match_list=Lista de coincidencias de claves labels.job_log_list=Lista de registros de trabajos labels.group_list=Lista de grupos @@ -512,12 +515,13 @@ labels.labeltype_configuration=Etiqueta labels.labeltype_title_details=Etiqueta labels.labeltype_name=Nombre labels.labeltype_value=Valor -labels.labeltype_kind=Tipo -labels.labeltype_kind_label=Etiqueta -labels.labeltype_kind_tag=Etiqueta de usuario -labels.labeltype_kind_help=Una Etiqueta de usuario es una etiqueta que los usuarios añaden desde la pantalla de búsqueda: Nombre es el nombre de la etiqueta, Rutas incluidas enumera las URL etiquetadas (una URL por línea, coincidencia exacta) y Permisos determina quién puede verla. El usuario que añade una etiqueta se agrega a sus Permisos. labels.labeltype_included_paths=Rutas incluidas labels.labeltype_excluded_paths=Rutas excluidas +labels.tagtype_configuration=Etiqueta de usuario +labels.tagtype_title_details=Etiqueta de usuario +labels.tagtype_name=Nombre +labels.tagtype_owner=Propietario +labels.tagtype_paths=Rutas labels.roletype_configuration=Rol labels.roletype_title_details=Rol labels.roletype_name=Nombre diff --git a/src/main/resources/fess_label_fr.properties b/src/main/resources/fess_label_fr.properties index 78b9548248..1529cccb2f 100644 --- a/src/main/resources/fess_label_fr.properties +++ b/src/main/resources/fess_label_fr.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=Système de fichiers labels.menu_data_store=Magasin de données labels.menu_label_type=Étiquette +labels.menu_tag_type=Tag labels.menu_key_match=Correspondance de clé labels.menu_boost_document_rule=Boost de document labels.menu_path_mapping=Mappage de chemin @@ -408,6 +409,7 @@ labels.group_details=Group Détails labels.job_log_details=Job Log Détails labels.key_match_details=Key Match Détails labels.labeltype_details=Labeltype Détails +labels.tagtype_details=Détails du tag labels.pathmap_details=Pathmap Détails labels.related_content_details=Related Content Détails labels.related_query_details=Related Query Détails @@ -432,6 +434,7 @@ labels.plugin_list=Liste des plugins labels.pathmap_list=Liste de mappage de chemins labels.log_file_list=Liste des fichiers journaux labels.labeltype_list=Liste des types d'étiquettes +labels.tagtype_list=Liste des tags labels.key_match_list=Liste de correspondance de clés labels.job_log_list=Liste des journaux de tâches labels.group_list=Liste des groupes @@ -513,12 +516,13 @@ labels.labeltype_configuration=Étiquette labels.labeltype_title_details=Étiquette labels.labeltype_name=Nom labels.labeltype_value=Valeur -labels.labeltype_kind=Type -labels.labeltype_kind_label=Étiquette -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Un Tag est un tag que les utilisateurs ajoutent depuis l'écran de recherche : Nom est le nom du tag, Chemins inclus liste les URL taguées (une URL par ligne, correspondance exacte) et Autorisations détermine qui peut le voir. Un utilisateur qui ajoute un tag est ajouté à ses Autorisations. labels.labeltype_included_paths=Chemins inclus labels.labeltype_excluded_paths=Chemins exclus +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Nom +labels.tagtype_owner=Propriétaire +labels.tagtype_paths=Chemins labels.roletype_configuration=Rôle labels.roletype_title_details=Rôle labels.roletype_name=Nom diff --git a/src/main/resources/fess_label_hi.properties b/src/main/resources/fess_label_hi.properties index 2bed7e97b1..9677e2bebe 100644 --- a/src/main/resources/fess_label_hi.properties +++ b/src/main/resources/fess_label_hi.properties @@ -149,6 +149,7 @@ labels.menu_web=वेब labels.menu_file_system=फ़ाइल सिस्टम labels.menu_data_store=डेटा स्टोर labels.menu_label_type=लेबल +labels.menu_tag_type=टैग labels.menu_key_match=की मैच labels.menu_boost_document_rule=दस्तावेज़ बूस्ट labels.menu_path_mapping=पथ मैपिंग @@ -408,6 +409,7 @@ labels.group_details=Group विवरण labels.job_log_details=Job Log विवरण labels.key_match_details=Key Match विवरण labels.labeltype_details=Labeltype विवरण +labels.tagtype_details=टैग विवरण labels.pathmap_details=Pathmap विवरण labels.related_content_details=Related Content विवरण labels.related_query_details=Related Query विवरण @@ -432,6 +434,7 @@ labels.plugin_list=प्लगइन सूची labels.pathmap_list=पथ मैपिंग सूची labels.log_file_list=लॉग फ़ाइल सूची labels.labeltype_list=लेबल प्रकार सूची +labels.tagtype_list=टैग सूची labels.key_match_list=की मैच सूची labels.job_log_list=जॉब लॉग सूची labels.group_list=समूह सूची @@ -513,12 +516,13 @@ labels.labeltype_configuration=लेबल labels.labeltype_title_details=लेबल labels.labeltype_name=नाम labels.labeltype_value=मान -labels.labeltype_kind=प्रकार -labels.labeltype_kind_label=लेबल -labels.labeltype_kind_tag=टैग -labels.labeltype_kind_help=टैग वह टैग है जिसे उपयोगकर्ता खोज स्क्रीन से जोड़ते हैं: नाम टैग का नाम है, शामिल पथ में टैग किए गए URL सूचीबद्ध होते हैं (प्रति पंक्ति एक URL, सटीक मिलान), और अनुमतियां तय करती हैं कि इसे कौन देख सकता है। टैग जोड़ने वाले उपयोगकर्ता को उसकी अनुमतियां में जोड़ दिया जाता है। labels.labeltype_included_paths=शामिल पथ labels.labeltype_excluded_paths=बहिष्कृत पथ +labels.tagtype_configuration=टैग +labels.tagtype_title_details=टैग +labels.tagtype_name=नाम +labels.tagtype_owner=स्वामी +labels.tagtype_paths=पथ labels.roletype_configuration=भूमिका labels.roletype_title_details=भूमिका labels.roletype_name=नाम diff --git a/src/main/resources/fess_label_id.properties b/src/main/resources/fess_label_id.properties index 9e69ce6baa..4d21e93c41 100644 --- a/src/main/resources/fess_label_id.properties +++ b/src/main/resources/fess_label_id.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=Sistem Berkas labels.menu_data_store=Penyimpanan Data labels.menu_label_type=Label +labels.menu_tag_type=Tag labels.menu_key_match=Pencocokan Kata Kunci labels.menu_boost_document_rule=Boost Dokumen labels.menu_path_mapping=Pemetaan Jalur @@ -408,6 +409,7 @@ labels.group_details=Group Detail labels.job_log_details=Job Log Detail labels.key_match_details=Key Match Detail labels.labeltype_details=Labeltype Detail +labels.tagtype_details=Detail Tag labels.pathmap_details=Pathmap Detail labels.related_content_details=Related Content Detail labels.related_query_details=Related Query Detail @@ -432,6 +434,7 @@ labels.plugin_list=Daftar plugin labels.pathmap_list=Daftar pemetaan jalur labels.log_file_list=Daftar file log labels.labeltype_list=Daftar tipe label +labels.tagtype_list=Daftar Tag labels.key_match_list=Daftar kecocokan kunci labels.job_log_list=Daftar log pekerjaan labels.group_list=Daftar grup @@ -513,12 +516,13 @@ labels.labeltype_configuration=Label labels.labeltype_title_details=Label labels.labeltype_name=Nama labels.labeltype_value=Nilai -labels.labeltype_kind=Jenis -labels.labeltype_kind_label=Label -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Tag adalah tag yang ditambahkan pengguna dari layar pencarian: Nama adalah nama tag, Jalur yang Disertakan berisi daftar URL yang diberi tag (satu URL per baris, kecocokan persis), dan Izin menentukan siapa yang dapat melihatnya. Pengguna yang menambahkan tag akan ditambahkan ke Izin tag tersebut. labels.labeltype_included_paths=Jalur yang Disertakan labels.labeltype_excluded_paths=Jalur yang Dikecualikan +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Nama +labels.tagtype_owner=Pemilik +labels.tagtype_paths=Jalur labels.roletype_configuration=Peran labels.roletype_title_details=Peran labels.roletype_name=Nama diff --git a/src/main/resources/fess_label_it.properties b/src/main/resources/fess_label_it.properties index 7c515e0f7d..ed1fe497a5 100644 --- a/src/main/resources/fess_label_it.properties +++ b/src/main/resources/fess_label_it.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=File system labels.menu_data_store=Data store labels.menu_label_type=Etichetta +labels.menu_tag_type=Tag labels.menu_key_match=Corrispondenza chiave labels.menu_boost_document_rule=Boost documento labels.menu_path_mapping=Mappatura percorso @@ -408,6 +409,7 @@ labels.group_details=Group Dettagli labels.job_log_details=Job Log Dettagli labels.key_match_details=Key Match Dettagli labels.labeltype_details=Labeltype Dettagli +labels.tagtype_details=Dettagli tag labels.pathmap_details=Pathmap Dettagli labels.related_content_details=Related Content Dettagli labels.related_query_details=Related Query Dettagli @@ -432,6 +434,7 @@ labels.plugin_list=Elenco plugin labels.pathmap_list=Elenco mappatura percorsi labels.log_file_list=Elenco file di log labels.labeltype_list=Elenco tipi etichetta +labels.tagtype_list=Elenco tag labels.key_match_list=Elenco corrispondenze chiave labels.job_log_list=Elenco log lavori labels.group_list=Elenco gruppi @@ -513,12 +516,13 @@ labels.labeltype_configuration=Etichetta labels.labeltype_title_details=Etichetta labels.labeltype_name=Nome labels.labeltype_value=Valore -labels.labeltype_kind=Tipo -labels.labeltype_kind_label=Etichetta -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Un Tag è un tag che gli utenti aggiungono dalla schermata di ricerca: Nome è il nome del tag, Percorsi inclusi elenca gli URL taggati (un URL per riga, corrispondenza esatta) e Permessi stabilisce chi può vederlo. L'utente che aggiunge un tag viene aggiunto ai suoi Permessi. labels.labeltype_included_paths=Percorsi inclusi labels.labeltype_excluded_paths=Percorsi esclusi +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Nome +labels.tagtype_owner=Proprietario +labels.tagtype_paths=Percorsi labels.roletype_configuration=Ruolo labels.roletype_title_details=Ruolo labels.roletype_name=Nome diff --git a/src/main/resources/fess_label_ja.properties b/src/main/resources/fess_label_ja.properties index b022853589..906abed49b 100644 --- a/src/main/resources/fess_label_ja.properties +++ b/src/main/resources/fess_label_ja.properties @@ -149,6 +149,7 @@ labels.menu_web=ウェブ labels.menu_file_system=ファイルシステム labels.menu_data_store=データストア labels.menu_label_type=ラベル +labels.menu_tag_type=タグ labels.menu_key_match=キーマッチ labels.menu_boost_document_rule=ドキュメントブースト labels.menu_path_mapping=パスマッピング @@ -408,6 +409,7 @@ labels.group_details=グループ詳細 labels.job_log_details=ジョブログ詳細 labels.key_match_details=キーマッチ詳細 labels.labeltype_details=ラベルタイプ詳細 +labels.tagtype_details=タグ詳細 labels.pathmap_details=パスマッピング詳細 labels.related_content_details=関連コンテンツ詳細 labels.related_query_details=関連クエリ詳細 @@ -432,6 +434,7 @@ labels.plugin_list=プラグイン一覧 labels.pathmap_list=パスマッピング一覧 labels.log_file_list=ログファイル一覧 labels.labeltype_list=ラベルタイプ一覧 +labels.tagtype_list=タグ一覧 labels.key_match_list=キーマッチ一覧 labels.job_log_list=ジョブログ一覧 labels.group_list=グループ一覧 @@ -510,12 +513,13 @@ labels.labeltype_configuration=ラベル labels.labeltype_title_details=ラベル labels.labeltype_name=名前 labels.labeltype_value=値 -labels.labeltype_kind=種類 -labels.labeltype_kind_label=ラベル -labels.labeltype_kind_tag=タグ -labels.labeltype_kind_help=タグは、ユーザーが検索画面から追加するタグです。名前はタグ名、対象とするパスはタグ付けされたURL (1行に1つのURL、完全一致)、パーミッションはタグを表示できるユーザーを指定します。タグを追加したユーザーは、そのタグのパーミッションに追加されます。 labels.labeltype_included_paths=対象とするパス labels.labeltype_excluded_paths=除外するパス +labels.tagtype_configuration=タグ +labels.tagtype_title_details=タグ +labels.tagtype_name=名前 +labels.tagtype_owner=所有者 +labels.tagtype_paths=パス labels.roletype_configuration=ロール labels.roletype_title_details=ロール labels.roletype_name=名前 diff --git a/src/main/resources/fess_label_ko.properties b/src/main/resources/fess_label_ko.properties index a73db3a970..09d827c906 100644 --- a/src/main/resources/fess_label_ko.properties +++ b/src/main/resources/fess_label_ko.properties @@ -149,6 +149,7 @@ labels.menu_web=웹 labels.menu_file_system=파일 시스템 labels.menu_data_store=데이터 저장소 labels.menu_label_type=라벨 +labels.menu_tag_type=태그 labels.menu_key_match=키 매치 labels.menu_boost_document_rule=문서 부스트 labels.menu_path_mapping=경로 매핑 @@ -408,6 +409,7 @@ labels.group_details=Group 상세 labels.job_log_details=Job Log 상세 labels.key_match_details=Key Match 상세 labels.labeltype_details=Labeltype 상세 +labels.tagtype_details=태그 상세 labels.pathmap_details=Pathmap 상세 labels.related_content_details=Related Content 상세 labels.related_query_details=Related Query 상세 @@ -432,6 +434,7 @@ labels.plugin_list=플러그인 목록 labels.pathmap_list=경로 매핑 목록 labels.log_file_list=로그 파일 목록 labels.labeltype_list=라벨 유형 목록 +labels.tagtype_list=태그 목록 labels.key_match_list=키 매치 목록 labels.job_log_list=작업 로그 목록 labels.group_list=그룹 목록 @@ -513,12 +516,13 @@ labels.labeltype_configuration=라벨 labels.labeltype_title_details=라벨 labels.labeltype_name=이름 labels.labeltype_value=값 -labels.labeltype_kind=종류 -labels.labeltype_kind_label=라벨 -labels.labeltype_kind_tag=태그 -labels.labeltype_kind_help=태그는 사용자가 검색 화면에서 추가하는 태그입니다. 이름은 태그 이름이고, 대상 경로는 태그가 지정된 URL 목록(한 줄에 하나의 URL, 정확히 일치)이며, 권한은 태그를 볼 수 있는 사용자를 결정합니다. 태그를 추가한 사용자는 해당 태그의 권한에 추가됩니다. labels.labeltype_included_paths=대상 경로 labels.labeltype_excluded_paths=제외 경로 +labels.tagtype_configuration=태그 +labels.tagtype_title_details=태그 +labels.tagtype_name=이름 +labels.tagtype_owner=소유자 +labels.tagtype_paths=경로 labels.roletype_configuration=역할 labels.roletype_title_details=역할 labels.roletype_name=이름 diff --git a/src/main/resources/fess_label_nl.properties b/src/main/resources/fess_label_nl.properties index 5b1659fe18..ed63c9765a 100644 --- a/src/main/resources/fess_label_nl.properties +++ b/src/main/resources/fess_label_nl.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=Bestandssysteem labels.menu_data_store=Gegevensopslag labels.menu_label_type=Label +labels.menu_tag_type=Tag labels.menu_key_match=Sleutelovereenkomst labels.menu_boost_document_rule=Documentboost labels.menu_path_mapping=Padtoewijzing @@ -408,6 +409,7 @@ labels.group_details=Groepdetails labels.job_log_details=Details taaklogboek labels.key_match_details=Details sleutelovereenkomst labels.labeltype_details=Details labeltype +labels.tagtype_details=Details tag labels.pathmap_details=Details padtoewijzing labels.related_content_details=Details gerelateerde inhoud labels.related_query_details=Details gerelateerde query @@ -432,6 +434,7 @@ labels.plugin_list=Pluginlijst labels.pathmap_list=Padtoewijzingslijst labels.log_file_list=Logbestandlijst labels.labeltype_list=Labeltypelijst +labels.tagtype_list=Taglijst labels.key_match_list=Sleutelovereenkomstlijst labels.job_log_list=Taakloglijst labels.group_list=Groeplijst @@ -513,12 +516,13 @@ labels.labeltype_configuration=Label labels.labeltype_title_details=Label labels.labeltype_name=Naam labels.labeltype_value=Waarde -labels.labeltype_kind=Soort -labels.labeltype_kind_label=Label -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Een Tag is een tag die gebruikers vanuit het zoekscherm toevoegen: Naam is de tagnaam, Inbegrepen paden bevat de getagde URL's (één URL per regel, exacte overeenkomst) en Machtigingen bepaalt wie de tag kan zien. Een gebruiker die een tag toevoegt, wordt aan de Machtigingen ervan toegevoegd. labels.labeltype_included_paths=Inbegrepen paden labels.labeltype_excluded_paths=Uitgesloten paden +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Naam +labels.tagtype_owner=Eigenaar +labels.tagtype_paths=Paden labels.roletype_configuration=Rol labels.roletype_title_details=Rol labels.roletype_name=Naam diff --git a/src/main/resources/fess_label_pl.properties b/src/main/resources/fess_label_pl.properties index 2c6e405185..68d3db60f0 100644 --- a/src/main/resources/fess_label_pl.properties +++ b/src/main/resources/fess_label_pl.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=System plików labels.menu_data_store=Magazyn danych labels.menu_label_type=Etykieta +labels.menu_tag_type=Tag labels.menu_key_match=Dopasowanie klucza labels.menu_boost_document_rule=Wzmocnienie dokumentu labels.menu_path_mapping=Mapowanie ścieżek @@ -408,6 +409,7 @@ labels.group_details=Group Szczegóły labels.job_log_details=Job Log Szczegóły labels.key_match_details=Key Match Szczegóły labels.labeltype_details=Labeltype Szczegóły +labels.tagtype_details=Szczegóły tagu labels.pathmap_details=Pathmap Szczegóły labels.related_content_details=Related Content Szczegóły labels.related_query_details=Related Query Szczegóły @@ -432,6 +434,7 @@ labels.plugin_list=Lista wtyczek labels.pathmap_list=Lista mapowania ścieżek labels.log_file_list=Lista plików dziennika labels.labeltype_list=Lista typów etykiet +labels.tagtype_list=Lista tagów labels.key_match_list=Lista dopasowania kluczy labels.job_log_list=Lista dzienników zadań labels.group_list=Lista grup @@ -513,12 +516,13 @@ labels.labeltype_configuration=Etykieta labels.labeltype_title_details=Etykieta labels.labeltype_name=Nazwa labels.labeltype_value=Wartość -labels.labeltype_kind=Rodzaj -labels.labeltype_kind_label=Etykieta -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Tag to tag dodawany przez użytkowników z ekranu wyszukiwania: Nazwa to nazwa tagu, Uwzględnione ścieżki zawierają oznaczone adresy URL (jeden URL w wierszu, dokładne dopasowanie), a Uprawnienia określają, kto może go zobaczyć. Użytkownik, który doda tag, zostaje dodany do jego Uprawnień. labels.labeltype_included_paths=Uwzględnione ścieżki labels.labeltype_excluded_paths=Wykluczone ścieżki +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Nazwa +labels.tagtype_owner=Właściciel +labels.tagtype_paths=Ścieżki labels.roletype_configuration=Rola labels.roletype_title_details=Rola labels.roletype_name=Nazwa diff --git a/src/main/resources/fess_label_pt_BR.properties b/src/main/resources/fess_label_pt_BR.properties index 1e15f4d975..036e826225 100644 --- a/src/main/resources/fess_label_pt_BR.properties +++ b/src/main/resources/fess_label_pt_BR.properties @@ -148,6 +148,7 @@ labels.menu_web=Web labels.menu_file_system=Sistema de arquivos labels.menu_data_store=Armazenamento de dados labels.menu_label_type=Rótulo +labels.menu_tag_type=Tag labels.menu_key_match=Correspondência de chave labels.menu_boost_document_rule=Regra de impulso de documento labels.menu_path_mapping=Mapeamento de caminho @@ -407,6 +408,7 @@ labels.group_details=Group Detalhes labels.job_log_details=Job Log Detalhes labels.key_match_details=Key Match Detalhes labels.labeltype_details=Labeltype Detalhes +labels.tagtype_details=Detalhes da tag labels.pathmap_details=Pathmap Detalhes labels.related_content_details=Related Content Detalhes labels.related_query_details=Related Query Detalhes @@ -431,6 +433,7 @@ labels.plugin_list=Lista de plugins labels.pathmap_list=Lista de mapeamento de caminhos labels.log_file_list=Lista de arquivos de log labels.labeltype_list=Lista de tipos de rótulo +labels.tagtype_list=Lista de tags labels.key_match_list=Lista de correspondência de chaves labels.job_log_list=Lista de logs de trabalho labels.group_list=Lista de grupos @@ -512,12 +515,13 @@ labels.labeltype_configuration=Rótulo labels.labeltype_title_details=Rótulo labels.labeltype_name=Nome labels.labeltype_value=Valor -labels.labeltype_kind=Tipo -labels.labeltype_kind_label=Rótulo -labels.labeltype_kind_tag=Tag -labels.labeltype_kind_help=Uma Tag é uma tag que os usuários adicionam pela tela de pesquisa: Nome é o nome da tag, Caminhos incluídos lista as URLs marcadas (uma URL por linha, correspondência exata) e Permissões define quem pode vê-la. O usuário que adiciona uma tag é incluído em suas Permissões. labels.labeltype_included_paths=Caminhos incluídos labels.labeltype_excluded_paths=Caminhos excluídos +labels.tagtype_configuration=Tag +labels.tagtype_title_details=Tag +labels.tagtype_name=Nome +labels.tagtype_owner=Proprietário +labels.tagtype_paths=Caminhos labels.roletype_configuration=Função labels.roletype_title_details=Função labels.roletype_name=Nome diff --git a/src/main/resources/fess_label_ru.properties b/src/main/resources/fess_label_ru.properties index 61a67dd998..2ba1c8bd25 100644 --- a/src/main/resources/fess_label_ru.properties +++ b/src/main/resources/fess_label_ru.properties @@ -149,6 +149,7 @@ labels.menu_web=Веб labels.menu_file_system=Файловая система labels.menu_data_store=Хранилище данных labels.menu_label_type=Метка +labels.menu_tag_type=Тег labels.menu_key_match=Совпадение ключей labels.menu_boost_document_rule=Ускорение документов labels.menu_path_mapping=Сопоставление путей @@ -408,6 +409,7 @@ labels.group_details=Group Детали labels.job_log_details=Job Log Детали labels.key_match_details=Key Match Детали labels.labeltype_details=Labeltype Детали +labels.tagtype_details=Сведения о теге labels.pathmap_details=Pathmap Детали labels.related_content_details=Related Content Детали labels.related_query_details=Related Query Детали @@ -432,6 +434,7 @@ labels.plugin_list=Список плагинов labels.pathmap_list=Список сопоставления путей labels.log_file_list=Список файлов журнала labels.labeltype_list=Список типов меток +labels.tagtype_list=Список тегов labels.key_match_list=Список соответствия ключей labels.job_log_list=Список журналов заданий labels.group_list=Список групп @@ -513,12 +516,13 @@ labels.labeltype_configuration=Метка labels.labeltype_title_details=Метка labels.labeltype_name=Имя labels.labeltype_value=Значение -labels.labeltype_kind=Тип -labels.labeltype_kind_label=Метка -labels.labeltype_kind_tag=Тег -labels.labeltype_kind_help=Тег — это тег, который пользователи добавляют на странице поиска: «Имя» — название тега, «Включенные пути» — список помеченных URL (по одному URL в строке, точное совпадение), а «Разрешения» определяют, кто может его видеть. Пользователь, добавивший тег, добавляется в его «Разрешения». labels.labeltype_included_paths=Включенные пути labels.labeltype_excluded_paths=Исключенные пути +labels.tagtype_configuration=Тег +labels.tagtype_title_details=Тег +labels.tagtype_name=Имя +labels.tagtype_owner=Владелец +labels.tagtype_paths=Пути labels.roletype_configuration=Роль labels.roletype_title_details=Роль labels.roletype_name=Имя diff --git a/src/main/resources/fess_label_tr.properties b/src/main/resources/fess_label_tr.properties index 63ce1d8642..ecfcae8543 100644 --- a/src/main/resources/fess_label_tr.properties +++ b/src/main/resources/fess_label_tr.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=Dosya Sistemi labels.menu_data_store=Veri Deposu labels.menu_label_type=Etiket +labels.menu_tag_type=Kullanıcı etiketi labels.menu_key_match=Anahtar Eşleşmesi labels.menu_boost_document_rule=Belge Artırma labels.menu_path_mapping=Yol Eşleştirme @@ -408,6 +409,7 @@ labels.group_details=Group Detaylar labels.job_log_details=Job Log Detaylar labels.key_match_details=Key Match Detaylar labels.labeltype_details=Labeltype Detaylar +labels.tagtype_details=Kullanıcı etiketi detayları labels.pathmap_details=Pathmap Detaylar labels.related_content_details=Related Content Detaylar labels.related_query_details=Related Query Detaylar @@ -432,6 +434,7 @@ labels.plugin_list=Eklenti listesi labels.pathmap_list=Yol eşleme listesi labels.log_file_list=Günlük dosyası listesi labels.labeltype_list=Etiket türü listesi +labels.tagtype_list=Kullanıcı etiketi listesi labels.key_match_list=Anahtar eşleşme listesi labels.job_log_list=İş günlüğü listesi labels.group_list=Grup listesi @@ -513,12 +516,13 @@ labels.labeltype_configuration=Etiket labels.labeltype_title_details=Etiket labels.labeltype_name=Ad labels.labeltype_value=Değer -labels.labeltype_kind=Tür -labels.labeltype_kind_label=Etiket -labels.labeltype_kind_tag=Kullanıcı etiketi -labels.labeltype_kind_help=Kullanıcı etiketi, kullanıcıların arama ekranından eklediği bir etikettir: Ad etiketin adıdır, Dahil Edilen Yollar etiketlenen URL'leri listeler (her satıra bir URL, tam eşleşme) ve İzinler onu kimin görebileceğini belirler. Etiket ekleyen kullanıcı, o etiketin İzinler alanına eklenir. labels.labeltype_included_paths=Dahil Edilen Yollar labels.labeltype_excluded_paths=Hariç Tutulan Yollar +labels.tagtype_configuration=Kullanıcı etiketi +labels.tagtype_title_details=Kullanıcı etiketi +labels.tagtype_name=Ad +labels.tagtype_owner=Sahip +labels.tagtype_paths=Yollar labels.roletype_configuration=Rol labels.roletype_title_details=Rol labels.roletype_name=Ad diff --git a/src/main/resources/fess_label_zh_CN.properties b/src/main/resources/fess_label_zh_CN.properties index f6accea4e4..0875f73845 100644 --- a/src/main/resources/fess_label_zh_CN.properties +++ b/src/main/resources/fess_label_zh_CN.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=文件系统 labels.menu_data_store=数据存储 labels.menu_label_type=标签 +labels.menu_tag_type=用户标签 labels.menu_key_match=关键词匹配 labels.menu_boost_document_rule=文档提升 labels.menu_path_mapping=路径映射 @@ -408,6 +409,7 @@ labels.group_details=Group 详情 labels.job_log_details=Job Log 详情 labels.key_match_details=Key Match 详情 labels.labeltype_details=Labeltype 详情 +labels.tagtype_details=用户标签详情 labels.pathmap_details=Pathmap 详情 labels.related_content_details=Related Content 详情 labels.related_query_details=Related Query 详情 @@ -432,6 +434,7 @@ labels.plugin_list=插件列表 labels.pathmap_list=路径映射列表 labels.log_file_list=日志文件列表 labels.labeltype_list=标签类型列表 +labels.tagtype_list=用户标签列表 labels.key_match_list=关键字匹配列表 labels.job_log_list=作业日志列表 labels.group_list=组列表 @@ -513,12 +516,13 @@ labels.labeltype_configuration=标签 labels.labeltype_title_details=标签 labels.labeltype_name=名称 labels.labeltype_value=值 -labels.labeltype_kind=类型 -labels.labeltype_kind_label=标签 -labels.labeltype_kind_tag=用户标签 -labels.labeltype_kind_help=用户标签是用户在搜索页面添加的标签:名称为标签名,包含的路径列出被添加标签的URL(每行一个URL,完全匹配),权限决定谁可以查看该标签。添加标签的用户会被加入该标签的权限中。 labels.labeltype_included_paths=包含的路径 labels.labeltype_excluded_paths=排除的路径 +labels.tagtype_configuration=用户标签 +labels.tagtype_title_details=用户标签 +labels.tagtype_name=名称 +labels.tagtype_owner=所有者 +labels.tagtype_paths=路径 labels.roletype_configuration=角色 labels.roletype_title_details=角色 labels.roletype_name=名称 diff --git a/src/main/resources/fess_label_zh_TW.properties b/src/main/resources/fess_label_zh_TW.properties index 5e1cbba1dd..f820136eba 100644 --- a/src/main/resources/fess_label_zh_TW.properties +++ b/src/main/resources/fess_label_zh_TW.properties @@ -149,6 +149,7 @@ labels.menu_web=Web labels.menu_file_system=檔案系統 labels.menu_data_store=資料儲存 labels.menu_label_type=標籤 +labels.menu_tag_type=使用者標籤 labels.menu_key_match=關鍵字匹配 labels.menu_boost_document_rule=文檔提升 labels.menu_path_mapping=路徑映射 @@ -408,6 +409,7 @@ labels.group_details=Group 詳情 labels.job_log_details=Job Log 詳情 labels.key_match_details=Key Match 詳情 labels.labeltype_details=Labeltype 詳情 +labels.tagtype_details=使用者標籤詳情 labels.pathmap_details=Pathmap 詳情 labels.related_content_details=Related Content 詳情 labels.related_query_details=Related Query 詳情 @@ -432,6 +434,7 @@ labels.plugin_list=外掛程式清單 labels.pathmap_list=路徑映射清單 labels.log_file_list=日誌檔案清單 labels.labeltype_list=標籤類型清單 +labels.tagtype_list=使用者標籤清單 labels.key_match_list=關鍵字配對清單 labels.job_log_list=作業日誌清單 labels.group_list=群組清單 @@ -513,12 +516,13 @@ labels.labeltype_configuration=標籤 labels.labeltype_title_details=標籤 labels.labeltype_name=名稱 labels.labeltype_value=值 -labels.labeltype_kind=類型 -labels.labeltype_kind_label=標籤 -labels.labeltype_kind_tag=使用者標籤 -labels.labeltype_kind_help=使用者標籤是使用者在搜尋畫面新增的標籤:名稱為標籤名稱,包含的路徑列出被加上標籤的URL(每行一個URL,完全符合),權限決定誰可以檢視該標籤。新增標籤的使用者會被加入該標籤的權限中。 labels.labeltype_included_paths=包含的路徑 labels.labeltype_excluded_paths=排除的路徑 +labels.tagtype_configuration=使用者標籤 +labels.tagtype_title_details=使用者標籤 +labels.tagtype_name=名稱 +labels.tagtype_owner=擁有者 +labels.tagtype_paths=路徑 labels.roletype_configuration=角色 labels.roletype_title_details=角色 labels.roletype_name=名稱 diff --git a/src/main/resources/fess_message.properties b/src/main/resources/fess_message.properties index c6023589ff..015507128c 100644 --- a/src/main/resources/fess_message.properties +++ b/src/main/resources/fess_message.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Failed to generate related queries: {0} errors.related_query_generation_disabled=Related queries cannot be generated because the search log or user info is disabled. errors.related_query_generation_in_progress=Related queries are already being generated. +errors.tagtype_invalid_name=Enter a tag name of 1 to {0} characters without control characters. +errors.tagtype_too_many_paths=A tag can have up to {0} paths. +errors.tagtype_already_exists=A tag with the same name and owner already exists. +errors.tagtype_changed_concurrently=The tag was changed by someone else. Reload it and try again. errors.property_required={0} is required. errors.property_type_integer={0} must be an integer. diff --git a/src/main/resources/fess_message_de.properties b/src/main/resources/fess_message_de.properties index 23945ed3c6..67eb425513 100644 --- a/src/main/resources/fess_message_de.properties +++ b/src/main/resources/fess_message_de.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Fehler beim Generieren verwandter Abfragen: {0} errors.related_query_generation_disabled=Verwandte Abfragen können nicht generiert werden, da das Suchprotokoll oder die Benutzerinformationen deaktiviert sind. errors.related_query_generation_in_progress=Verwandte Abfragen werden bereits generiert. +errors.tagtype_invalid_name=Geben Sie einen Tag-Namen mit 1 bis {0} Zeichen ohne Steuerzeichen ein. +errors.tagtype_too_many_paths=Ein Tag kann bis zu {0} Pfade haben. +errors.tagtype_already_exists=Ein Tag mit demselben Namen und Besitzer ist bereits vorhanden. +errors.tagtype_changed_concurrently=Der Tag wurde von einer anderen Person geändert. Laden Sie ihn neu und versuchen Sie es erneut. errors.property_required={0} ist erforderlich. errors.property_type_integer={0} muss eine ganze Zahl sein. diff --git a/src/main/resources/fess_message_en.properties b/src/main/resources/fess_message_en.properties index c6023589ff..015507128c 100644 --- a/src/main/resources/fess_message_en.properties +++ b/src/main/resources/fess_message_en.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Failed to generate related queries: {0} errors.related_query_generation_disabled=Related queries cannot be generated because the search log or user info is disabled. errors.related_query_generation_in_progress=Related queries are already being generated. +errors.tagtype_invalid_name=Enter a tag name of 1 to {0} characters without control characters. +errors.tagtype_too_many_paths=A tag can have up to {0} paths. +errors.tagtype_already_exists=A tag with the same name and owner already exists. +errors.tagtype_changed_concurrently=The tag was changed by someone else. Reload it and try again. errors.property_required={0} is required. errors.property_type_integer={0} must be an integer. diff --git a/src/main/resources/fess_message_es.properties b/src/main/resources/fess_message_es.properties index 8fa8fff3bb..53af424eab 100644 --- a/src/main/resources/fess_message_es.properties +++ b/src/main/resources/fess_message_es.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=No se pudieron generar las consultas relacionadas: {0} errors.related_query_generation_disabled=No se pueden generar consultas relacionadas porque el registro de búsqueda o la información de usuario están deshabilitados. errors.related_query_generation_in_progress=Ya se están generando consultas relacionadas. +errors.tagtype_invalid_name=Introduzca un nombre de etiqueta de 1 a {0} caracteres sin caracteres de control. +errors.tagtype_too_many_paths=Una etiqueta de usuario puede tener hasta {0} rutas. +errors.tagtype_already_exists=Ya existe una etiqueta de usuario con el mismo nombre y propietario. +errors.tagtype_changed_concurrently=Otra persona ha modificado la etiqueta de usuario. Vuelva a cargarla e inténtelo de nuevo. errors.property_required={0} es obligatorio. errors.property_type_integer={0} es un número. diff --git a/src/main/resources/fess_message_fr.properties b/src/main/resources/fess_message_fr.properties index 03696130b6..b87185fc89 100644 --- a/src/main/resources/fess_message_fr.properties +++ b/src/main/resources/fess_message_fr.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Échec de la génération des requêtes associées : {0} errors.related_query_generation_disabled=Impossible de générer des requêtes associées car le journal de recherche ou les informations utilisateur sont désactivés. errors.related_query_generation_in_progress=La génération des requêtes associées est déjà en cours. +errors.tagtype_invalid_name=Saisissez un nom de tag de 1 à {0} caractères sans caractères de contrôle. +errors.tagtype_too_many_paths=Un tag peut avoir au maximum {0} chemins. +errors.tagtype_already_exists=Un tag portant le même nom et le même propriétaire existe déjà. +errors.tagtype_changed_concurrently=Le tag a été modifié par quelqu'un d'autre. Rechargez-le et réessayez. errors.property_required={0} est requis. errors.property_type_integer={0} doit être un entier. diff --git a/src/main/resources/fess_message_hi.properties b/src/main/resources/fess_message_hi.properties index 955f5ea8cc..5e69e2abbd 100644 --- a/src/main/resources/fess_message_hi.properties +++ b/src/main/resources/fess_message_hi.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=संबंधित क्वेरी जनरेट करने में विफल: {0} errors.related_query_generation_disabled=खोज लॉग या उपयोगकर्ता जानकारी अक्षम होने के कारण संबंधित क्वेरी जनरेट नहीं की जा सकतीं। errors.related_query_generation_in_progress=संबंधित क्वेरी पहले से जनरेट की जा रही हैं। +errors.tagtype_invalid_name=नियंत्रण वर्णों के बिना 1 से {0} वर्णों का टैग नाम दर्ज करें। +errors.tagtype_too_many_paths=एक टैग में अधिकतम {0} पथ हो सकते हैं। +errors.tagtype_already_exists=समान नाम और स्वामी वाला टैग पहले से मौजूद है। +errors.tagtype_changed_concurrently=टैग को किसी और ने बदल दिया है। इसे फिर से लोड करें और पुनः प्रयास करें। errors.property_required={0} आवश्यक है। errors.property_type_integer={0} एक पूर्णांक होना चाहिए। diff --git a/src/main/resources/fess_message_id.properties b/src/main/resources/fess_message_id.properties index b32b6a2a34..7c9c3f57af 100644 --- a/src/main/resources/fess_message_id.properties +++ b/src/main/resources/fess_message_id.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Gagal membuat kueri terkait: {0} errors.related_query_generation_disabled=Kueri terkait tidak dapat dibuat karena log pencarian atau info pengguna dinonaktifkan. errors.related_query_generation_in_progress=Kueri terkait sedang dibuat. +errors.tagtype_invalid_name=Masukkan nama tag sepanjang 1 hingga {0} karakter tanpa karakter kontrol. +errors.tagtype_too_many_paths=Sebuah tag dapat memiliki hingga {0} jalur. +errors.tagtype_already_exists=Tag dengan nama dan pemilik yang sama sudah ada. +errors.tagtype_changed_concurrently=Tag telah diubah oleh orang lain. Muat ulang lalu coba lagi. errors.property_required={0} wajib diisi. errors.property_type_integer={0} harus berupa integer. diff --git a/src/main/resources/fess_message_it.properties b/src/main/resources/fess_message_it.properties index 6a9dc92488..195a0e98d8 100644 --- a/src/main/resources/fess_message_it.properties +++ b/src/main/resources/fess_message_it.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Impossibile generare le query correlate: {0} errors.related_query_generation_disabled=Impossibile generare query correlate perché il log di ricerca o le informazioni utente sono disabilitati. errors.related_query_generation_in_progress=La generazione delle query correlate è già in corso. +errors.tagtype_invalid_name=Inserire un nome di tag da 1 a {0} caratteri senza caratteri di controllo. +errors.tagtype_too_many_paths=Un tag può avere fino a {0} percorsi. +errors.tagtype_already_exists=Esiste già un tag con lo stesso nome e proprietario. +errors.tagtype_changed_concurrently=Il tag è stato modificato da un altro utente. Ricaricarlo e riprovare. errors.property_required={0} è obbligatorio. errors.property_type_integer={0} è un numero intero. diff --git a/src/main/resources/fess_message_ja.properties b/src/main/resources/fess_message_ja.properties index c1db0af3e9..a6b09bc440 100644 --- a/src/main/resources/fess_message_ja.properties +++ b/src/main/resources/fess_message_ja.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=このテーマはこのサーバーよ errors.failed_to_generate_related_query=関連クエリーの生成に失敗しました: {0} errors.related_query_generation_disabled=検索ログまたはユーザー情報が無効なため、関連クエリーを生成できません。 errors.related_query_generation_in_progress=関連クエリーの生成は既に実行中です。 +errors.tagtype_invalid_name=タグ名は制御文字を含めずに1~{0}文字で入力してください。 +errors.tagtype_too_many_paths=1つのタグに設定できるパスは{0}件までです。 +errors.tagtype_already_exists=同じ名前と所有者のタグがすでに存在します。 +errors.tagtype_changed_concurrently=タグが他のユーザーによって変更されました。再読み込みしてからやり直してください。 errors.property_required={0}は必須です。 errors.property_type_integer={0}は数値でなければなりません。 diff --git a/src/main/resources/fess_message_ko.properties b/src/main/resources/fess_message_ko.properties index 4202f6757f..90701be0a7 100644 --- a/src/main/resources/fess_message_ko.properties +++ b/src/main/resources/fess_message_ko.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=관련 쿼리를 생성하지 못했습니다: {0} errors.related_query_generation_disabled=검색 로그 또는 사용자 정보가 비활성화되어 있어 관련 쿼리를 생성할 수 없습니다. errors.related_query_generation_in_progress=관련 쿼리를 이미 생성하고 있습니다. +errors.tagtype_invalid_name=제어 문자 없이 1~{0}자의 태그 이름을 입력하십시오. +errors.tagtype_too_many_paths=하나의 태그에는 최대 {0}개의 경로를 지정할 수 있습니다. +errors.tagtype_already_exists=같은 이름과 소유자의 태그가 이미 존재합니다. +errors.tagtype_changed_concurrently=다른 사용자가 태그를 변경했습니다. 다시 불러온 후 다시 시도하십시오. errors.property_required={0}은(는) 필수입니다. errors.property_type_integer={0}은(는) 정수여야 합니다. diff --git a/src/main/resources/fess_message_nl.properties b/src/main/resources/fess_message_nl.properties index af2941789c..14b8230252 100644 --- a/src/main/resources/fess_message_nl.properties +++ b/src/main/resources/fess_message_nl.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Genereren van gerelateerde zoekopdrachten mislukt: {0} errors.related_query_generation_disabled=Gerelateerde zoekopdrachten kunnen niet worden gegenereerd omdat het zoeklogboek of de gebruikersinformatie is uitgeschakeld. errors.related_query_generation_in_progress=Gerelateerde zoekopdrachten worden al gegenereerd. +errors.tagtype_invalid_name=Voer een tagnaam van 1 tot {0} tekens in zonder besturingstekens. +errors.tagtype_too_many_paths=Een tag kan maximaal {0} paden hebben. +errors.tagtype_already_exists=Er bestaat al een tag met dezelfde naam en eigenaar. +errors.tagtype_changed_concurrently=De tag is door iemand anders gewijzigd. Laad de tag opnieuw en probeer het nogmaals. errors.property_required={0} is vereist. errors.property_type_integer={0} is een geheel getal. diff --git a/src/main/resources/fess_message_pl.properties b/src/main/resources/fess_message_pl.properties index bc0608f4b0..8127c3f879 100644 --- a/src/main/resources/fess_message_pl.properties +++ b/src/main/resources/fess_message_pl.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Nie udało się wygenerować powiązanych zapytań: {0} errors.related_query_generation_disabled=Nie można wygenerować powiązanych zapytań, ponieważ dziennik wyszukiwania lub informacje o użytkowniku są wyłączone. errors.related_query_generation_in_progress=Powiązane zapytania są już generowane. +errors.tagtype_invalid_name=Wprowadź nazwę tagu o długości od 1 do {0} znaków bez znaków sterujących. +errors.tagtype_too_many_paths=Tag może mieć maksymalnie {0} ścieżek. +errors.tagtype_already_exists=Tag o tej samej nazwie i tym samym właścicielu już istnieje. +errors.tagtype_changed_concurrently=Tag został zmieniony przez kogoś innego. Załaduj go ponownie i spróbuj jeszcze raz. errors.property_required={0} jest wymagane. errors.property_type_integer={0} jest liczbą całkowitą. diff --git a/src/main/resources/fess_message_pt_BR.properties b/src/main/resources/fess_message_pt_BR.properties index bf8844b7f8..3ac28cdaa9 100644 --- a/src/main/resources/fess_message_pt_BR.properties +++ b/src/main/resources/fess_message_pt_BR.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Falha ao gerar consultas relacionadas: {0} errors.related_query_generation_disabled=Não é possível gerar consultas relacionadas porque o log de pesquisa ou as informações do usuário estão desativados. errors.related_query_generation_in_progress=As consultas relacionadas já estão sendo geradas. +errors.tagtype_invalid_name=Digite um nome de tag de 1 a {0} caracteres sem caracteres de controle. +errors.tagtype_too_many_paths=Uma tag pode ter até {0} caminhos. +errors.tagtype_already_exists=Já existe uma tag com o mesmo nome e proprietário. +errors.tagtype_changed_concurrently=A tag foi alterada por outra pessoa. Recarregue-a e tente novamente. errors.property_required={0} é obrigatório. errors.property_type_integer={0} é um número. diff --git a/src/main/resources/fess_message_ru.properties b/src/main/resources/fess_message_ru.properties index 44db0cf7fc..cbc3d33fc0 100644 --- a/src/main/resources/fess_message_ru.properties +++ b/src/main/resources/fess_message_ru.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=Не удалось создать связанные запросы: {0} errors.related_query_generation_disabled=Невозможно создать связанные запросы, так как журнал поиска или сведения о пользователях отключены. errors.related_query_generation_in_progress=Связанные запросы уже создаются. +errors.tagtype_invalid_name=Введите имя тега длиной от 1 до {0} символов без управляющих символов. +errors.tagtype_too_many_paths=Тег может содержать не более {0} путей. +errors.tagtype_already_exists=Тег с таким же именем и владельцем уже существует. +errors.tagtype_changed_concurrently=Тег был изменен другим пользователем. Перезагрузите его и повторите попытку. errors.property_required={0} обязательно. errors.property_type_integer={0} должно быть целым числом. diff --git a/src/main/resources/fess_message_tr.properties b/src/main/resources/fess_message_tr.properties index e6eb264060..25455a100b 100644 --- a/src/main/resources/fess_message_tr.properties +++ b/src/main/resources/fess_message_tr.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=İlgili sorgular oluşturulamadı: {0} errors.related_query_generation_disabled=Arama günlüğü veya kullanıcı bilgisi devre dışı olduğundan ilgili sorgular oluşturulamıyor. errors.related_query_generation_in_progress=İlgili sorgular zaten oluşturuluyor. +errors.tagtype_invalid_name=Kontrol karakteri içermeyen, 1 ile {0} karakter arasında bir etiket adı girin. +errors.tagtype_too_many_paths=Bir kullanıcı etiketi en fazla {0} yola sahip olabilir. +errors.tagtype_already_exists=Aynı ada ve sahibe sahip bir kullanıcı etiketi zaten var. +errors.tagtype_changed_concurrently=Kullanıcı etiketi başka biri tarafından değiştirildi. Yeniden yükleyip tekrar deneyin. errors.property_required={0} gereklidir. errors.property_type_integer={0} bir tamsayı olmalıdır. diff --git a/src/main/resources/fess_message_zh_CN.properties b/src/main/resources/fess_message_zh_CN.properties index 2d7212bde2..d28b82d20a 100644 --- a/src/main/resources/fess_message_zh_CN.properties +++ b/src/main/resources/fess_message_zh_CN.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=生成相关查询失败:{0} errors.related_query_generation_disabled=由于搜索日志或用户信息已禁用,无法生成相关查询。 errors.related_query_generation_in_progress=相关查询正在生成中。 +errors.tagtype_invalid_name=请输入1到{0}个字符且不含控制字符的标签名称。 +errors.tagtype_too_many_paths=一个用户标签最多可以有{0}个路径。 +errors.tagtype_already_exists=已存在名称和所有者相同的用户标签。 +errors.tagtype_changed_concurrently=用户标签已被其他人更改。请重新加载后再试。 errors.property_required={0} 是必需的。 errors.property_type_integer={0} 是数字。 diff --git a/src/main/resources/fess_message_zh_TW.properties b/src/main/resources/fess_message_zh_TW.properties index 0db389a9a9..90e121c2fd 100644 --- a/src/main/resources/fess_message_zh_TW.properties +++ b/src/main/resources/fess_message_zh_TW.properties @@ -181,6 +181,10 @@ errors.theme_incompatible_fess_version=This theme requires a newer version of Fe errors.failed_to_generate_related_query=產生相關查詢失敗:{0} errors.related_query_generation_disabled=由於搜尋日誌或使用者資訊已停用,無法產生相關查詢。 errors.related_query_generation_in_progress=相關查詢正在產生中。 +errors.tagtype_invalid_name=請輸入1到{0}個字元且不含控制字元的標籤名稱。 +errors.tagtype_too_many_paths=一個使用者標籤最多可以有{0}個路徑。 +errors.tagtype_already_exists=已存在名稱和擁有者相同的使用者標籤。 +errors.tagtype_changed_concurrently=使用者標籤已被其他人變更。請重新載入後再試一次。 errors.property_required={0} 是必需的。 errors.property_type_integer={0} 是數字。 diff --git a/src/main/webapp/WEB-INF/env/crawler/resources/app.xml b/src/main/webapp/WEB-INF/env/crawler/resources/app.xml index f201aa3540..036aac3370 100644 --- a/src/main/webapp/WEB-INF/env/crawler/resources/app.xml +++ b/src/main/webapp/WEB-INF/env/crawler/resources/app.xml @@ -12,6 +12,8 @@ + + diff --git a/src/main/webapp/WEB-INF/view/admin/labeltype/admin_labeltype_details.jsp b/src/main/webapp/WEB-INF/view/admin/labeltype/admin_labeltype_details.jsp index a921435447..27365e9a85 100644 --- a/src/main/webapp/WEB-INF/view/admin/labeltype/admin_labeltype_details.jsp +++ b/src/main/webapp/WEB-INF/view/admin/labeltype/admin_labeltype_details.jsp @@ -66,15 +66,6 @@ ${fe:html(true)} ${f:h(value)} - - - - - - - ${f:br(f:h(includedPaths))} -
- -
- - - - - - -
-
diff --git a/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype.jsp b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype.jsp new file mode 100644 index 0000000000..a77bb3fa07 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype.jsp @@ -0,0 +1,170 @@ +<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> +${fe:html(true)} + + + <la:message key="labels.admin_brand_title"/> | <la:message + key="labels.tagtype_configuration"/> + + + +
+ + + + + +
+
+
+
+
+

+ +

+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+ <%-- Message --%> +
+ +
${msg}
+
+ +
+ +
show" id="listSearchForm"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
+
+
+ <%-- List --%> + +
+ +
+
+
+ +
+
+ "> + + + + + + + + + + + + + + +
${f:h(data.name)}${f:h(data.owner)}
+
+
+
+ <%-- Paging Info --%> +
+ +
+ <%-- Paging Navigation --%> +
+
    + +
  • + +
  • +
    + +
  • +
    + +
  • class="page-item active"> + ${p}
  • +
    + +
  • + +
  • +
    + +
  • +
    +
+
+
+ + + +
+
+
+
+
+
+
+ +
+ + +${fe:html(false)} diff --git a/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_details.jsp b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_details.jsp new file mode 100644 index 0000000000..fefd05dd46 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_details.jsp @@ -0,0 +1,101 @@ +<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> +${fe:html(true)} + + + <la:message key="labels.admin_brand_title"/> | <la:message + key="labels.tagtype_configuration"/> + + + +
+ + + + + +
+
+
+
+
+

+ +

+
+
+ +
+
+
+
+
+ + + + + + + +
+
+
card-successcard-dangercard-primary"> + <%-- Card Header --%> +
+ +
+ <%-- Card Body --%> +
+ <%-- Message --%> +
+ +
${msg}
+
+ +
+ <%-- Form Fields --%> + "> + + + + + + + + + + + + + + + + + + + + + + + + + + +
${f:h(name)}
${f:h(owner)}
${f:br(f:h(paths))}
${f:br(f:h(permissions))}
${f:h(virtualHost)}
${f:h(sortOrder)}
+
+ +
+
+
+
+
+
+ +
+ + +${fe:html(false)} diff --git a/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_edit.jsp b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_edit.jsp new file mode 100644 index 0000000000..ddac5d2e55 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/admin/tagtype/admin_tagtype_edit.jsp @@ -0,0 +1,121 @@ +<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> +${fe:html(true)} + + + <la:message key="labels.admin_brand_title"/> | <la:message + key="labels.tagtype_configuration"/> + + + +
+ + + + + +
+
+
+
+
+

+ +

+
+
+ +
+
+
+
+
+ + + + + + + +
+
+
card-success"> +
+ +
+
+
+ +
${msg}
+
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+
+
+
+
+ +
+ + +${fe:html(false)} diff --git a/src/main/webapp/WEB-INF/view/common/admin/sidebar.jsp b/src/main/webapp/WEB-INF/view/common/admin/sidebar.jsp index f8709aa2bf..e6caae912e 100644 --- a/src/main/webapp/WEB-INF/view/common/admin/sidebar.jsp +++ b/src/main/webapp/WEB-INF/view/common/admin/sidebar.jsp @@ -112,7 +112,7 @@ - + + + +