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.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.IdRef; 043import org.nuxeo.ecm.core.api.NuxeoException; 044import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 045import org.nuxeo.ecm.core.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 Log log = LogFactory.getLog(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 * @since 9.10 063 */ 064 public static final String DISABLE_VERSIONING = "tag.facet.disable.versioning"; 065 066 @Override 067 public boolean hasFeature(Feature feature) { 068 switch (feature) { 069 case TAGS_BELONG_TO_DOCUMENT: 070 return true; 071 default: 072 throw new UnsupportedOperationException(feature.name()); 073 } 074 } 075 076 @Override 077 public boolean supportsTag(CoreSession session, String docId) { 078 return session.getDocument(new IdRef(docId)).hasFacet(TAG_FACET); 079 } 080 081 protected void saveDocument(CoreSession session, DocumentModel doc) { 082 doc.putContextData(VersioningService.DISABLE_AUTO_CHECKOUT, Boolean.TRUE); 083 doc.putContextData(DISABLE_VERSIONING, Boolean.TRUE); 084 doc.putContextData(DISABLE_DUBLINCORE_LISTENER, Boolean.TRUE); 085 doc.putContextData(DISABLE_AUDIT_LOGGER, Boolean.TRUE); 086 session.saveDocument(doc); 087 } 088 089 @Override 090 public void doTag(CoreSession session, String docId, String label, String username) { 091 DocumentModel docModel = session.getDocument(new IdRef(docId)); 092 if (docModel.isProxy()) { 093 throw new NuxeoException("Adding tags is not allowed on proxies"); 094 } 095 List<Map<String, Serializable>> tags = getTags(docModel); 096 if (tags.stream().noneMatch(t -> label.equals(t.get(LABEL_PROPERTY)))) { 097 Map<String, Serializable> tag = new HashMap<>(); 098 tag.put(LABEL_PROPERTY, label); 099 tag.put(USERNAME_PROPERTY, username); 100 tags.add(tag); 101 setTags(docModel, tags); 102 saveDocument(session, docModel); 103 } 104 } 105 106 @Override 107 public void doUntag(CoreSession session, String docId, String label) { 108 DocumentModel docModel = session.getDocument(new IdRef(docId)); 109 if (docModel.isProxy()) { 110 throw new NuxeoException("Removing tags is not allowed on proxies"); 111 } 112 if (docModel.hasFacet(TAG_FACET)) { 113 // If label is null, all the tags are removed 114 if (label == null) { 115 if (!getTags(docModel).isEmpty()) { 116 setTags(docModel, new ArrayList<>()); 117 saveDocument(session, docModel); 118 } 119 } else { 120 List<Map<String, Serializable>> tags = getTags(docModel); 121 Map<String, Serializable> tag = tags.stream() 122 .filter(t -> label.equals(t.get(LABEL_PROPERTY))) 123 .findFirst() 124 .orElse(null); 125 if (tag != null) { 126 tags.remove(tag); 127 setTags(docModel, tags); 128 saveDocument(session, docModel); 129 } 130 } 131 } 132 } 133 134 @Override 135 public boolean canUntag(CoreSession session, String docId, String label) { 136 boolean canUntag = super.canUntag(session, docId, label); 137 if (!canUntag) { 138 // Check also if the current user is the one who applied the tag 139 DocumentModel docModel = session.getDocument(new IdRef(docId)); 140 Map<String, Serializable> tag = getTags(docModel).stream() 141 .filter(t -> label.equals(t.get(LABEL_PROPERTY))) 142 .findFirst() 143 .orElse(null); 144 if (tag != null) { 145 String username = session.getPrincipal().getName(); 146 canUntag = username.equals(tag.get(USERNAME_PROPERTY)); 147 } 148 } 149 return canUntag; 150 } 151 152 @Override 153 public Set<String> doGetTags(CoreSession session, String docId) { 154 DocumentModel docModel = session.getDocument(new IdRef(docId)); 155 List<Map<String, Serializable>> tags = getTags(docModel); 156 return tags.stream().map(t -> (String) t.get(LABEL_PROPERTY)).collect(Collectors.toSet()); 157 } 158 159 @Override 160 public void doCopyTags(CoreSession session, String srcDocId, String dstDocId, boolean removeExistingTags) { 161 DocumentModel srcDocModel = session.getDocument(new IdRef(srcDocId)); 162 DocumentModel dstDocModel = session.getDocument(new IdRef(dstDocId)); 163 164 if (!dstDocModel.isProxy()) { 165 List<Map<String, Serializable>> srcTags = getTags(srcDocModel); 166 List<Map<String, Serializable>> dstTags; 167 if (removeExistingTags) { 168 dstTags = srcTags; 169 } else { 170 dstTags = getTags(dstDocModel); 171 for (Map<String, Serializable> tag : srcTags) { 172 if (dstTags.stream().noneMatch(t -> tag.get(LABEL_PROPERTY).equals(t.get(LABEL_PROPERTY)))) { 173 dstTags.add(tag); 174 } 175 } 176 } 177 setTags(dstDocModel, dstTags); 178 saveDocument(session, dstDocModel); 179 } 180 } 181 182 @Override 183 public List<String> doGetTagDocumentIds(CoreSession session, String label) { 184 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_FACETED_TAG.name(), session, 185 label); 186 if (res == null) { 187 return Collections.emptyList(); 188 } 189 return res.stream().map(m -> (String) m.get(ECM_UUID)).collect(Collectors.toList()); 190 } 191 192 @Override 193 public Set<String> doGetTagSuggestions(CoreSession session, String label) { 194 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_FACETED_TAG_SUGGESTIONS.name(), session, 195 label); 196 if (res == null) { 197 return Collections.emptySet(); 198 } 199 return res.stream().map(m -> (String) m.get(TagConstants.TAG_LIST + "/*1/label")).collect(Collectors.toSet()); 200 } 201 202 @Override 203 public List<Tag> getTagCloud(CoreSession session, String docId, String username, Boolean normalize) { 204 return Collections.emptyList(); 205 } 206 207 @SuppressWarnings("unchecked") 208 protected List<Map<String, Serializable>> getTags(DocumentModel docModel) { 209 try { 210 return (List<Map<String, Serializable>>) docModel.getPropertyValue(TAG_LIST); 211 } catch (PropertyNotFoundException e) { 212 if (log.isWarnEnabled()) { 213 log.warn(String.format( 214 "Getting tags on %s failed since %s is missing on %s document type. This operation will be ignored.", 215 docModel.getPathAsString(), TAG_FACET, docModel.getType())); 216 } 217 return new ArrayList<>(); 218 } 219 } 220 221 protected void setTags(DocumentModel docModel, List<Map<String, Serializable>> tags) { 222 try { 223 if (docModel.isVersion()) { 224 docModel.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE); 225 } 226 docModel.setPropertyValue(TAG_LIST, (Serializable) tags); 227 } catch (PropertyNotFoundException e) { 228 if (log.isWarnEnabled()) { 229 log.warn(String.format( 230 "Setting tags on %s failed since %s is missing on %s document type. This operation will be ignored.", 231 docModel.getPathAsString(), TAG_FACET, docModel.getType())); 232 } 233 } 234 } 235}