001/* 002 * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Funsho David 018 */ 019 020package org.nuxeo.ecm.platform.tag; 021 022import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE; 023import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_UUID; 024import static org.nuxeo.ecm.platform.audit.service.NXAuditEventsService.DISABLE_AUDIT_LOGGER; 025import static org.nuxeo.ecm.platform.dublincore.listener.DublinCoreListener.DISABLE_DUBLINCORE_LISTENER; 026import static org.nuxeo.ecm.platform.tag.TagConstants.TAG_FACET; 027import static org.nuxeo.ecm.platform.tag.TagConstants.TAG_LIST; 028 029import java.io.Serializable; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.stream.Collectors; 037 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.DocumentRef; 043import org.nuxeo.ecm.core.api.IdRef; 044import org.nuxeo.ecm.core.api.NuxeoException; 045import org.nuxeo.ecm.core.api.versioning.VersioningService; 046 047/** 048 * Implementation of the tag service based on facet 049 * 050 * @since 9.3 051 */ 052public class FacetedTagService extends AbstractTagService { 053 054 private static final Logger log = LogManager.getLogger(FacetedTagService.class); 055 056 public static final String LABEL_PROPERTY = "label"; 057 058 public static final String USERNAME_PROPERTY = "username"; 059 060 /** 061 * Context data to disable versioning, used by NoVersioningFacetedTagFilter. 062 * 063 * @since 9.10 064 */ 065 public static final String DISABLE_VERSIONING = "tag.facet.disable.versioning"; 066 067 @Override 068 public boolean hasFeature(Feature feature) { 069 switch (feature) { 070 case TAGS_BELONG_TO_DOCUMENT: 071 return true; 072 default: 073 throw new UnsupportedOperationException(feature.name()); 074 } 075 } 076 077 @Override 078 public boolean supportsTag(CoreSession session, String docId) { 079 return session.getDocument(new IdRef(docId)).hasFacet(TAG_FACET); 080 } 081 082 protected void saveDocument(CoreSession session, DocumentModel doc) { 083 doc.putContextData(VersioningService.DISABLE_AUTO_CHECKOUT, Boolean.TRUE); 084 doc.putContextData(DISABLE_VERSIONING, Boolean.TRUE); 085 doc.putContextData(DISABLE_DUBLINCORE_LISTENER, Boolean.TRUE); 086 doc.putContextData(DISABLE_AUDIT_LOGGER, Boolean.TRUE); 087 session.saveDocument(doc); 088 } 089 090 @Override 091 public void doTag(CoreSession session, String docId, String label, String username) { 092 DocumentModel docModel = session.getDocument(new IdRef(docId)); 093 if (docModel.isProxy()) { 094 throw new NuxeoException("Adding tags is not allowed on proxies"); 095 } 096 List<Map<String, Serializable>> tags = getTags(docModel); 097 if (tags.stream().noneMatch(t -> label.equals(t.get(LABEL_PROPERTY)))) { 098 Map<String, Serializable> tag = new HashMap<>(); 099 tag.put(LABEL_PROPERTY, label); 100 tag.put(USERNAME_PROPERTY, username); 101 tags.add(tag); 102 setTags(docModel, tags); 103 saveDocument(session, docModel); 104 } 105 } 106 107 @Override 108 public void doUntag(CoreSession session, String docId, String label) { 109 DocumentRef docRef = new IdRef(docId); 110 if (!session.exists(docRef)) { 111 return; 112 } 113 DocumentModel docModel = session.getDocument(docRef); 114 if (docModel.isProxy()) { 115 throw new NuxeoException("Removing tags is not allowed on proxies"); 116 } 117 if (docModel.hasFacet(TAG_FACET)) { 118 // If label is null, all the tags are removed 119 if (label == null) { 120 if (!getTags(docModel).isEmpty()) { 121 setTags(docModel, new ArrayList<>()); 122 saveDocument(session, docModel); 123 } 124 } else { 125 List<Map<String, Serializable>> tags = getTags(docModel); 126 Map<String, Serializable> tag = tags.stream() 127 .filter(t -> label.equals(t.get(LABEL_PROPERTY))) 128 .findFirst() 129 .orElse(null); 130 if (tag != null) { 131 tags.remove(tag); 132 setTags(docModel, tags); 133 saveDocument(session, docModel); 134 } 135 } 136 } 137 } 138 139 @Override 140 public boolean canUntag(CoreSession session, String docId, String label) { 141 boolean canUntag = super.canUntag(session, docId, label); 142 if (!canUntag) { 143 // Check also if the current user is the one who applied the tag 144 DocumentModel docModel = session.getDocument(new IdRef(docId)); 145 Map<String, Serializable> tag = getTags(docModel).stream() 146 .filter(t -> label.equals(t.get(LABEL_PROPERTY))) 147 .findFirst() 148 .orElse(null); 149 if (tag != null) { 150 String username = session.getPrincipal().getName(); 151 canUntag = username.equals(tag.get(USERNAME_PROPERTY)); 152 } 153 } 154 return canUntag; 155 } 156 157 @Override 158 public Set<String> doGetTags(CoreSession session, String docId) { 159 DocumentRef docRef = new IdRef(docId); 160 if (!session.exists(docRef)) { 161 return Collections.emptySet(); 162 } 163 DocumentModel docModel = session.getDocument(docRef); 164 List<Map<String, Serializable>> tags = getTags(docModel); 165 return tags.stream().map(t -> (String) t.get(LABEL_PROPERTY)).collect(Collectors.toSet()); 166 } 167 168 @Override 169 public void doCopyTags(CoreSession session, String srcDocId, String dstDocId, boolean removeExistingTags) { 170 DocumentModel srcDocModel = session.getDocument(new IdRef(srcDocId)); 171 DocumentModel dstDocModel = session.getDocument(new IdRef(dstDocId)); 172 173 if (!dstDocModel.isProxy()) { 174 List<Map<String, Serializable>> srcTags = getTags(srcDocModel); 175 List<Map<String, Serializable>> dstTags; 176 if (removeExistingTags) { 177 dstTags = srcTags; 178 } else { 179 dstTags = getTags(dstDocModel); 180 for (Map<String, Serializable> tag : srcTags) { 181 if (dstTags.stream().noneMatch(t -> tag.get(LABEL_PROPERTY).equals(t.get(LABEL_PROPERTY)))) { 182 dstTags.add(tag); 183 } 184 } 185 } 186 setTags(dstDocModel, dstTags); 187 saveDocument(session, dstDocModel); 188 } 189 } 190 191 @Override 192 public List<String> doGetTagDocumentIds(CoreSession session, String label) { 193 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_FACETED_TAG.name(), session, 194 label); 195 if (res == null) { 196 return Collections.emptyList(); 197 } 198 return res.stream().map(m -> (String) m.get(ECM_UUID)).collect(Collectors.toList()); 199 } 200 201 @Override 202 public Set<String> doGetTagSuggestions(CoreSession session, String label) { 203 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_FACETED_TAG_SUGGESTIONS.name(), session, 204 label); 205 if (res == null) { 206 return Collections.emptySet(); 207 } 208 return res.stream().map(m -> (String) m.get(TagConstants.TAG_LIST + "/*1/label")).collect(Collectors.toSet()); 209 } 210 211 @Override 212 public List<Tag> getTagCloud(CoreSession session, String docId, String username, Boolean normalize) { 213 return Collections.emptyList(); 214 } 215 216 @SuppressWarnings("unchecked") 217 protected List<Map<String, Serializable>> getTags(DocumentModel docModel) { 218 if (docModel.hasFacet(TAG_FACET)) { 219 return (List<Map<String, Serializable>>) docModel.getPropertyValue(TAG_LIST); 220 } else { 221 return new ArrayList<>(); 222 } 223 } 224 225 protected void setTags(DocumentModel docModel, List<Map<String, Serializable>> tags) { 226 if (!docModel.hasFacet(TAG_FACET)) { 227 throw new NuxeoException(String.format("Document %s of type %s doesn't have the %s facet", docModel, 228 docModel.getType(), TAG_FACET)); 229 } 230 if (docModel.isVersion()) { 231 docModel.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE); 232 } 233 docModel.setPropertyValue(TAG_LIST, (Serializable) tags); 234 } 235}