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 */ 020 021package org.nuxeo.ecm.platform.tag; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Calendar; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.stream.Collectors; 032 033import org.nuxeo.ecm.core.api.CoreSession; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.IdRef; 036import org.nuxeo.ecm.core.api.IterableQueryResult; 037import org.nuxeo.ecm.core.api.NuxeoException; 038import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 039import org.nuxeo.ecm.core.query.sql.NXQL; 040 041/** 042 * Implementation of tag service based on SQL relations 043 * 044 * @deprecated since 9.3, use {@link FacetedTagService} instead 045 */ 046@Deprecated 047public class RelationTagService extends AbstractTagService { 048 049 @Override 050 public boolean hasFeature(Feature feature) { 051 switch (feature) { 052 case TAGS_BELONG_TO_DOCUMENT: 053 return false; 054 default: 055 throw new UnsupportedOperationException(feature.name()); 056 } 057 } 058 059 @Override 060 public boolean supportsTag(CoreSession session, String docId) { 061 return true; 062 } 063 064 @Override 065 public void doTag(CoreSession session, String docId, String label, String username) { 066 if (session.getDocument(new IdRef(docId)).isProxy()) { // Tags are disabled on proxies 067 throw new NuxeoException("Adding tags is not allowed on proxies"); 068 } 069 // Find tag 070 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, label); 071 String tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 072 Calendar date = Calendar.getInstance(); 073 if (tagId == null) { 074 // no tag found, create it 075 DocumentModel tag = session.createDocumentModel(null, label, TagConstants.TAG_DOCUMENT_TYPE); 076 tag.setPropertyValue("dc:created", date); 077 tag.setPropertyValue(TagConstants.TAG_LABEL_FIELD, label); 078 tag = session.createDocument(tag); 079 tagId = tag.getId(); 080 } 081 // Check if tagging already exists for user. 082 res = getItems(PAGE_PROVIDERS.GET_FIRST_TAGGING_FOR_DOC_AND_TAG.name(), session, docId, tagId); 083 084 if (res != null && !res.isEmpty()) { 085 // tagging already exists 086 return; 087 } 088 // Add tagging to the document. 089 DocumentModel tagging = session.createDocumentModel(null, label, TagConstants.TAGGING_DOCUMENT_TYPE); 090 tagging.setPropertyValue("dc:created", date); 091 tagging.setPropertyValue("dc:creator", username); 092 093 tagging.setPropertyValue(TagConstants.TAGGING_SOURCE_FIELD, docId); 094 tagging.setPropertyValue(TagConstants.TAGGING_TARGET_FIELD, tagId); 095 session.createDocument(tagging); 096 session.save(); 097 } 098 099 @Override 100 public void doUntag(CoreSession session, String docId, String label) { 101 IdRef ref = new IdRef(docId); 102 if (session.exists(ref) && session.getDocument(ref).isProxy()) { // Tags are disabled on proxies 103 throw new NuxeoException("Removing tags is not allowed on proxies"); 104 } 105 String tagId = null; 106 if (label != null) { 107 // Find tag 108 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, 109 label); 110 tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 111 if (tagId == null) { 112 // tag not found 113 return; 114 } 115 } 116 // Find taggings for user. 117 Set<String> taggingIds = new HashSet<>(); 118 String query = String.format("SELECT ecm:uuid FROM Tagging WHERE relation:source = '%s'", docId); 119 if (tagId != null) { 120 query += String.format(" AND relation:target = '%s'", tagId); 121 } 122 try (IterableQueryResult res = session.queryAndFetch(query, NXQL.NXQL)) { 123 for (Map<String, Serializable> map : res) { 124 taggingIds.add((String) map.get(NXQL.ECM_UUID)); 125 } 126 } 127 // Remove taggings 128 for (String taggingId : taggingIds) { 129 session.removeDocument(new IdRef(taggingId)); 130 } 131 if (!taggingIds.isEmpty()) { 132 session.save(); 133 } 134 } 135 136 @Override 137 public Set<String> doGetTags(CoreSession session, String docId) { 138 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENT.name(), session, docId); 139 if (res == null) { 140 return Collections.emptySet(); 141 } 142 return res.stream().map(m -> (String) m.get(TagConstants.TAG_LABEL_FIELD)).collect(Collectors.toSet()); 143 } 144 145 @Override 146 public void doCopyTags(CoreSession session, String srcDocId, String dstDocId, boolean removeExistingTags) { 147 if (removeExistingTags) { 148 doUntag(session, dstDocId, null); 149 } 150 Set<String> existingTags = new HashSet<>(); 151 List<Map<String, Serializable>> dstTagsRes = getItems(PAGE_PROVIDERS.GET_TAGS_TO_COPY_FOR_DOCUMENT.name(), 152 session, dstDocId); 153 if (dstTagsRes != null) { 154 for (Map<String, Serializable> map : dstTagsRes) { 155 existingTags.add(String.format("%s/%s", map.get("tag:label"), map.get("dc:creator"))); 156 } 157 } 158 159 List<Map<String, Serializable>> srcTagsRes = getItems(PAGE_PROVIDERS.GET_TAGS_TO_COPY_FOR_DOCUMENT.name(), 160 session, srcDocId); 161 if (srcTagsRes != null) { 162 boolean docCreated = false; 163 for (Map<String, Serializable> map : srcTagsRes) { 164 String key = String.format("%s/%s", map.get("tag:label"), map.get("dc:creator")); 165 if (!existingTags.contains(key)) { 166 DocumentModel tagging = session.createDocumentModel(null, (String) map.get("tag:label"), 167 TagConstants.TAGGING_DOCUMENT_TYPE); 168 tagging.setPropertyValue("dc:created", map.get("dc:created")); 169 tagging.setPropertyValue("dc:creator", map.get("dc:creator")); 170 tagging.setPropertyValue(TagConstants.TAGGING_SOURCE_FIELD, dstDocId); 171 tagging.setPropertyValue(TagConstants.TAGGING_TARGET_FIELD, map.get("relation:target")); 172 session.createDocument(tagging); 173 docCreated = true; 174 } 175 } 176 if (docCreated) { 177 session.save(); 178 } 179 } 180 } 181 182 @Override 183 public List<String> doGetTagDocumentIds(CoreSession session, String label) { 184 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENTS_FOR_TAG.name(), session, label); 185 if (res == null) { 186 return Collections.emptyList(); 187 } 188 return res.stream().map(m -> (String) m.get(TagConstants.TAGGING_SOURCE_FIELD)).collect(Collectors.toList()); 189 } 190 191 @Override 192 public Set<String> doGetTagSuggestions(CoreSession session, String label) { 193 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_TAG_SUGGESTIONS.name(), session, label); 194 if (res == null) { 195 return Collections.emptySet(); 196 } 197 return res.stream().map(m -> (String) m.get(TagConstants.TAG_LABEL_FIELD)).collect(Collectors.toSet()); 198 } 199 200 /** 201 * @since 8.4 202 */ 203 @Override 204 public boolean canUntag(CoreSession session, String docId, String label) { 205 boolean canUntag = super.canUntag(session, docId, label); 206 if (!canUntag) { 207 // Else check if desired tag was created by current user 208 UnrestrictedCanRemoveTagging r = new UnrestrictedCanRemoveTagging(session, docId, label); 209 r.runUnrestricted(); 210 canUntag = r.canUntag; 211 } 212 return canUntag; 213 } 214 215 protected static class UnrestrictedCanRemoveTagging extends UnrestrictedSessionRunner { 216 217 private final String docId; 218 219 private final String label; 220 221 private boolean canUntag; 222 223 protected UnrestrictedCanRemoveTagging(CoreSession session, String docId, String label) { 224 super(session); 225 this.docId = docId; 226 this.label = cleanLabel(label, true, false); 227 this.canUntag = false; 228 } 229 230 @Override 231 public void run() { 232 String tagId = null; 233 if (label != null) { 234 // Find tag 235 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, 236 label); 237 tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 238 if (tagId == null) { 239 // tag not found - so user can untag 240 canUntag = true; 241 return; 242 } 243 } 244 // Find creators of tag(s). 245 Set<String> creators = new HashSet<>(); 246 String query = String.format("SELECT DISTINCT dc:creator FROM Tagging WHERE relation:source = '%s'", docId); 247 if (tagId != null) { 248 query += String.format(" AND relation:target = '%s'", tagId); 249 } 250 try (IterableQueryResult res = session.queryAndFetch(query, NXQL.NXQL)) { 251 for (Map<String, Serializable> map : res) { 252 creators.add((String) map.get("dc:creator")); 253 } 254 } 255 // Check if user can untag 256 // - in case of one tag, check if creators contains user 257 // - in case of all tags, check if user is the only creator 258 canUntag = creators.size() == 1 && creators.contains(originatingUsername); 259 } 260 } 261 262 @Override 263 public List<Tag> getTagCloud(CoreSession session, String docId, String username, Boolean normalize) { 264 UnrestrictedGetDocumentCloud r = new UnrestrictedGetDocumentCloud(session, docId, username, normalize); 265 r.runUnrestricted(); 266 return r.cloud; 267 } 268 269 protected static class UnrestrictedGetDocumentCloud extends UnrestrictedSessionRunner { 270 271 protected final String docId; 272 273 protected final String username; 274 275 protected final List<Tag> cloud; 276 277 protected final Boolean normalize; 278 279 protected UnrestrictedGetDocumentCloud(CoreSession session, String docId, String username, Boolean normalize) { 280 super(session); 281 this.docId = docId; 282 this.username = cleanUsername(username); 283 this.normalize = normalize; 284 this.cloud = new ArrayList<>(); 285 } 286 287 @Override 288 public void run() { 289 List<Map<String, Serializable>> res; 290 if (docId == null) { 291 if (username == null) { 292 res = getItems(PAGE_PROVIDERS.GET_ALL_TAGS.name(), session); 293 } else { 294 res = getItems(PAGE_PROVIDERS.GET_ALL_TAGS_FOR_USER.name(), session, username); 295 } 296 } else { 297 // find all docs under docid 298 String path = session.getDocument(new IdRef(docId)).getPathAsString(); 299 path = path.replace("'", ""); 300 List<String> docIds = new ArrayList<>(); 301 docIds.add(docId); 302 List<Map<String, Serializable>> docRes = getItems(PAGE_PROVIDERS.GET_TAGGED_DOCUMENTS_UNDER.name(), 303 session, path); 304 if (docRes != null) { 305 for (Map<String, Serializable> map : docRes) { 306 docIds.add((String) map.get(NXQL.ECM_UUID)); 307 } 308 } 309 310 if (username == null) { 311 res = getItems(PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENTS.name(), session, docIds); 312 } else { 313 res = getItems(PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENTS_AND_USER.name(), session, docIds, username); 314 } 315 } 316 317 int min = 999999, max = 0; 318 if (res != null) { 319 for (Map<String, Serializable> map : res) { 320 String label = (String) map.get(TagConstants.TAG_LABEL_FIELD); 321 int weight = ((Long) map.get(TagConstants.TAGGING_SOURCE_FIELD)).intValue(); 322 if (weight == 0) { 323 // shouldn't happen 324 continue; 325 } 326 if (weight > max) { 327 max = weight; 328 } 329 if (weight < min) { 330 min = weight; 331 } 332 Tag weightedTag = new Tag(label, weight); 333 cloud.add(weightedTag); 334 } 335 } 336 if (normalize != null) { 337 normalizeCloud(cloud, min, max, !normalize.booleanValue()); 338 } 339 } 340 341 } 342 343 public static void normalizeCloud(List<Tag> cloud, int min, int max, boolean linear) { 344 if (min == max) { 345 for (Tag tag : cloud) { 346 tag.setWeight(100); 347 } 348 return; 349 } 350 double nmin; 351 double diff; 352 if (linear) { 353 nmin = min; 354 diff = max - min; 355 } else { 356 nmin = Math.log(min); 357 diff = Math.log(max) - nmin; 358 } 359 for (Tag tag : cloud) { 360 long weight = tag.getWeight(); 361 double norm; 362 if (linear) { 363 norm = (weight - nmin) / diff; 364 } else { 365 norm = (Math.log(weight) - nmin) / diff; 366 } 367 tag.setWeight(Math.round(100 * norm)); 368 } 369 } 370 371}