001/* 002 * (C) Copyright 2009-2016 Nuxeo SA (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 * Radu Darlea 018 * Catalin Baican 019 * Florent Guillaume 020 */ 021 022package org.nuxeo.ecm.platform.tag; 023 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.Calendar; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Map; 032import java.util.Set; 033 034import org.nuxeo.ecm.core.api.CoreSession; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.DocumentRef; 037import org.nuxeo.ecm.core.api.DocumentSecurityException; 038import org.nuxeo.ecm.core.api.IdRef; 039import org.nuxeo.ecm.core.api.IterableQueryResult; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 042import org.nuxeo.ecm.core.api.event.DocumentEventTypes; 043import org.nuxeo.ecm.core.api.security.SecurityConstants; 044import org.nuxeo.ecm.core.event.Event; 045import org.nuxeo.ecm.core.event.EventService; 046import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 047import org.nuxeo.ecm.core.query.sql.NXQL; 048import org.nuxeo.ecm.platform.query.api.PageProvider; 049import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 050import org.nuxeo.ecm.platform.query.api.PageProviderService; 051import org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider; 052import org.nuxeo.runtime.api.Framework; 053import org.nuxeo.runtime.model.DefaultComponent; 054 055/** 056 * The implementation of the tag service. 057 */ 058public class TagServiceImpl extends DefaultComponent implements TagService { 059 060 public static final String NXTAG = TagQueryMaker.NXTAG; 061 062 protected enum PAGE_PROVIDERS { 063 // 064 GET_DOCUMENT_IDS_FOR_TAG, 065 // 066 GET_FIRST_TAGGING_FOR_DOC_AND_TAG_AND_USER, 067 // 068 GET_FIRST_TAGGING_FOR_DOC_AND_TAG, 069 // 070 GET_TAGS_FOR_DOCUMENT, 071 // core version: should keep on querying VCS 072 GET_TAGS_FOR_DOCUMENT_CORE, 073 // 074 GET_DOCUMENTS_FOR_TAG, 075 // 076 GET_TAGS_FOR_DOCUMENT_AND_USER, 077 // core version: should keep on querying VCS 078 GET_TAGS_FOR_DOCUMENT_AND_USER_CORE, 079 // 080 GET_DOCUMENTS_FOR_TAG_AND_USER, 081 // 082 GET_TAGS_TO_COPY_FOR_DOCUMENT, 083 // 084 GET_TAG_SUGGESTIONS, 085 // 086 GET_TAG_SUGGESTIONS_FOR_USER, 087 // 088 GET_TAGGED_DOCUMENTS_UNDER, 089 // 090 GET_ALL_TAGS, 091 // 092 GET_ALL_TAGS_FOR_USER, 093 // 094 GET_TAGS_FOR_DOCUMENTS, 095 // 096 GET_TAGS_FOR_DOCUMENTS_AND_USER, 097 } 098 099 @Override 100 public boolean isEnabled() { 101 return true; 102 } 103 104 protected static String cleanLabel(String label, boolean allowEmpty, boolean allowPercent) { 105 if (label == null) { 106 if (allowEmpty) { 107 return null; 108 } 109 throw new NuxeoException("Invalid empty tag"); 110 } 111 label = label.toLowerCase(); // lowercase 112 label = label.replace(" ", ""); // no spaces 113 label = label.replace("\\", ""); // dubious char 114 label = label.replace("'", ""); // dubious char 115 if (!allowPercent) { 116 label = label.replace("%", ""); // dubious char 117 } 118 if (label.length() == 0) { 119 throw new NuxeoException("Invalid empty tag"); 120 } 121 return label; 122 } 123 124 protected static String cleanUsername(String username) { 125 return username == null ? null : username.replace("'", ""); 126 } 127 128 @Override 129 public void tag(CoreSession session, String docId, String label, String username) { 130 UnrestrictedAddTagging r = new UnrestrictedAddTagging(session, docId, label, username); 131 r.runUnrestricted(); 132 fireUpdateEvent(session, docId); 133 } 134 135 protected void fireUpdateEvent(CoreSession session, String docId) { 136 DocumentRef documentRef = new IdRef(docId); 137 if (session.exists(documentRef)) { 138 DocumentModel documentModel = session.getDocument(documentRef); 139 DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), documentModel); 140 Event event = ctx.newEvent(DocumentEventTypes.DOCUMENT_TAG_UPDATED); 141 Framework.getLocalService(EventService.class).fireEvent(event); 142 } 143 } 144 145 protected static class UnrestrictedAddTagging extends UnrestrictedSessionRunner { 146 private final String docId; 147 148 private final String label; 149 150 private final String username; 151 152 protected UnrestrictedAddTagging(CoreSession session, String docId, String label, String username) { 153 super(session); 154 this.docId = docId; 155 this.label = cleanLabel(label, false, false); 156 this.username = cleanUsername(username); 157 } 158 159 @Override 160 public void run() { 161 // Find tag 162 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, 163 label); 164 String tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 165 Calendar date = Calendar.getInstance(); 166 if (tagId == null) { 167 // no tag found, create it 168 DocumentModel tag = session.createDocumentModel(null, label, TagConstants.TAG_DOCUMENT_TYPE); 169 tag.setPropertyValue("dc:created", date); 170 tag.setPropertyValue(TagConstants.TAG_LABEL_FIELD, label); 171 tag = session.createDocument(tag); 172 tagId = tag.getId(); 173 } 174 // Check if tagging already exists for user. 175 if (username != null) { 176 res = getItems(PAGE_PROVIDERS.GET_FIRST_TAGGING_FOR_DOC_AND_TAG_AND_USER.name(), session, docId, tagId, 177 username); 178 } else { 179 res = getItems(PAGE_PROVIDERS.GET_FIRST_TAGGING_FOR_DOC_AND_TAG.name(), session, docId, tagId); 180 } 181 if (res != null && !res.isEmpty()) { 182 // tagging already exists 183 return; 184 } 185 // Add tagging to the document. 186 DocumentModel tagging = session.createDocumentModel(null, label, TagConstants.TAGGING_DOCUMENT_TYPE); 187 tagging.setPropertyValue("dc:created", date); 188 if (username != null) { 189 tagging.setPropertyValue("dc:creator", username); 190 } 191 tagging.setPropertyValue(TagConstants.TAGGING_SOURCE_FIELD, docId); 192 tagging.setPropertyValue(TagConstants.TAGGING_TARGET_FIELD, tagId); 193 session.createDocument(tagging); 194 session.save(); 195 } 196 197 } 198 199 @Override 200 public void untag(CoreSession session, String docId, String label, String username) 201 throws DocumentSecurityException { 202 // There's two allowed cases here: 203 // - document doesn't exist, we're here after documentRemoved event 204 // - regular case: check if user can remove this tag on document 205 if (!session.exists(new IdRef(docId)) || canUntag(session, docId, label)) { 206 UnrestrictedRemoveTagging r = new UnrestrictedRemoveTagging(session, docId, label, username); 207 r.runUnrestricted(); 208 if (label != null) { 209 fireUpdateEvent(session, docId); 210 } 211 } else { 212 String principalName = session.getPrincipal().getName(); 213 throw new DocumentSecurityException("User '" + principalName + "' is not allowed to remove tag '" + label 214 + "' on document '" + docId + "'"); 215 } 216 } 217 218 protected static class UnrestrictedRemoveTagging extends UnrestrictedSessionRunner { 219 220 private final String docId; 221 222 private final String label; 223 224 private final String username; 225 226 protected UnrestrictedRemoveTagging(CoreSession session, String docId, String label, String username) { 227 super(session); 228 this.docId = docId; 229 this.label = cleanLabel(label, true, false); 230 this.username = cleanUsername(username); 231 } 232 233 @Override 234 public void run() { 235 String tagId = null; 236 if (label != null) { 237 // Find tag 238 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, 239 label); 240 tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 241 if (tagId == null) { 242 // tag not found 243 return; 244 } 245 } 246 // Find taggings for user. 247 Set<String> taggingIds = new HashSet<>(); 248 String query = String.format("SELECT ecm:uuid FROM Tagging WHERE relation:source = '%s'", docId); 249 if (tagId != null) { 250 query += String.format(" AND relation:target = '%s'", tagId); 251 } 252 if (username != null) { 253 query += String.format(" AND dc:creator = '%s'", username); 254 } 255 try (IterableQueryResult res = session.queryAndFetch(query, NXQL.NXQL)) { 256 for (Map<String, Serializable> map : res) { 257 taggingIds.add((String) map.get(NXQL.ECM_UUID)); 258 } 259 } 260 // Remove taggings 261 for (String taggingId : taggingIds) { 262 session.removeDocument(new IdRef(taggingId)); 263 } 264 if (!taggingIds.isEmpty()) { 265 session.save(); 266 } 267 } 268 269 } 270 271 /** 272 * @since 8.4 273 */ 274 @Override 275 public boolean canUntag(CoreSession session, String docId, String label) { 276 if (session.hasPermission(new IdRef(docId), SecurityConstants.WRITE)) { 277 // If user has WRITE permission, user can remove any tags 278 return true; 279 } 280 // Else check if desired tag was created by current user 281 UnrestrictedCanRemoveTagging r = new UnrestrictedCanRemoveTagging(session, docId, label); 282 r.runUnrestricted(); 283 return r.canUntag; 284 } 285 286 protected static class UnrestrictedCanRemoveTagging extends UnrestrictedSessionRunner { 287 288 private final String docId; 289 290 private final String label; 291 292 private boolean canUntag; 293 294 protected UnrestrictedCanRemoveTagging(CoreSession session, String docId, String label) { 295 super(session); 296 this.docId = docId; 297 this.label = cleanLabel(label, true, false); 298 this.canUntag = false; 299 } 300 301 @Override 302 public void run() { 303 String tagId = null; 304 if (label != null) { 305 // Find tag 306 List<Map<String, Serializable>> res = getItems(PAGE_PROVIDERS.GET_DOCUMENT_IDS_FOR_TAG.name(), session, 307 label); 308 tagId = (res != null && !res.isEmpty()) ? (String) res.get(0).get(NXQL.ECM_UUID) : null; 309 if (tagId == null) { 310 // tag not found - so user can untag 311 canUntag = true; 312 return; 313 } 314 } 315 // Find creators of tag(s). 316 Set<String> creators = new HashSet<>(); 317 String query = String.format("SELECT DISTINCT dc:creator FROM Tagging WHERE relation:source = '%s'", 318 docId); 319 if (tagId != null) { 320 query += String.format(" AND relation:target = '%s'", tagId); 321 } 322 try (IterableQueryResult res = session.queryAndFetch(query, NXQL.NXQL)) { 323 for (Map<String, Serializable> map : res) { 324 creators.add((String) map.get("dc:creator")); 325 } 326 } 327 // Check if user can untag 328 // - in case of one tag, check if creators contains user 329 // - in case of all tags, check if user is the only creator 330 canUntag = creators.size() == 1 && creators.contains(originatingUsername); 331 } 332 333 } 334 335 @Override 336 public List<Tag> getDocumentTags(CoreSession session, String docId, String username) { 337 return getDocumentTags(session, docId, username, true); 338 } 339 340 @Override 341 public List<Tag> getDocumentTags(CoreSession session, String docId, String username, boolean useCore) { 342 UnrestrictedGetDocumentTags r = new UnrestrictedGetDocumentTags(session, docId, username, useCore); 343 r.runUnrestricted(); 344 return r.tags; 345 } 346 347 protected static class UnrestrictedGetDocumentTags extends UnrestrictedSessionRunner { 348 349 protected final String docId; 350 351 protected final String username; 352 353 protected final List<Tag> tags; 354 355 protected final boolean useCore; 356 357 protected UnrestrictedGetDocumentTags(CoreSession session, String docId, String username, boolean useCore) { 358 super(session); 359 this.docId = docId; 360 this.username = cleanUsername(username); 361 this.useCore = useCore; 362 this.tags = new ArrayList<>(); 363 } 364 365 @Override 366 public void run() { 367 List<Map<String, Serializable>> res; 368 if (username == null) { 369 String ppName = PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENT.name(); 370 if (useCore) { 371 ppName = PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENT_CORE.name(); 372 } 373 res = getItems(ppName, session, docId); 374 } else { 375 String ppName = PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENT_AND_USER.name(); 376 if (useCore) { 377 ppName = PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENT_AND_USER_CORE.name(); 378 } 379 res = getItems(ppName, session, docId, username); 380 } 381 if (res != null) { 382 for (Map<String, Serializable> map : res) { 383 String label = (String) map.get(TagConstants.TAG_LABEL_FIELD); 384 tags.add(new Tag(label, 0)); 385 } 386 } 387 } 388 389 } 390 391 @Override 392 public void removeTags(CoreSession session, String docId) { 393 untag(session, docId, null, null); 394 } 395 396 @Override 397 public void copyTags(CoreSession session, String srcDocId, String dstDocId) { 398 copyTags(session, srcDocId, dstDocId, false); 399 } 400 401 protected void copyTags(CoreSession session, String srcDocId, String dstDocId, boolean removeExistingTags) { 402 if (removeExistingTags) { 403 removeTags(session, dstDocId); 404 } 405 406 UnrestrictedCopyTags r = new UnrestrictedCopyTags(session, srcDocId, dstDocId); 407 r.runUnrestricted(); 408 } 409 410 protected static class UnrestrictedCopyTags extends UnrestrictedSessionRunner { 411 412 protected final String srcDocId; 413 414 protected final String dstDocId; 415 416 protected UnrestrictedCopyTags(CoreSession session, String srcDocId, String dstDocId) { 417 super(session); 418 this.srcDocId = srcDocId; 419 this.dstDocId = dstDocId; 420 } 421 422 @Override 423 public void run() { 424 Set<String> existingTags = new HashSet<>(); 425 List<Map<String, Serializable>> dstTagsRes = getItems(PAGE_PROVIDERS.GET_TAGS_TO_COPY_FOR_DOCUMENT.name(), 426 session, dstDocId); 427 if (dstTagsRes != null) { 428 for (Map<String, Serializable> map : dstTagsRes) { 429 existingTags.add(String.format("%s/%s", map.get("tag:label"), map.get("dc:creator"))); 430 } 431 } 432 433 List<Map<String, Serializable>> srcTagsRes = getItems(PAGE_PROVIDERS.GET_TAGS_TO_COPY_FOR_DOCUMENT.name(), 434 session, srcDocId); 435 if (srcTagsRes != null) { 436 boolean docCreated = false; 437 for (Map<String, Serializable> map : srcTagsRes) { 438 String key = String.format("%s/%s", map.get("tag:label"), map.get("dc:creator")); 439 if (!existingTags.contains(key)) { 440 DocumentModel tagging = session.createDocumentModel(null, (String) map.get("tag:label"), 441 TagConstants.TAGGING_DOCUMENT_TYPE); 442 tagging.setPropertyValue("dc:created", map.get("dc:created")); 443 tagging.setPropertyValue("dc:creator", map.get("dc:creator")); 444 tagging.setPropertyValue(TagConstants.TAGGING_SOURCE_FIELD, dstDocId); 445 tagging.setPropertyValue(TagConstants.TAGGING_TARGET_FIELD, map.get("relation:target")); 446 session.createDocument(tagging); 447 docCreated = true; 448 } 449 } 450 if (docCreated) { 451 session.save(); 452 } 453 } 454 } 455 456 } 457 458 @Override 459 public void replaceTags(CoreSession session, String srcDocId, String dstDocId) { 460 copyTags(session, srcDocId, dstDocId, true); 461 } 462 463 @Override 464 public List<String> getTagDocumentIds(CoreSession session, String label, String username) { 465 UnrestrictedGetTagDocumentIds r = new UnrestrictedGetTagDocumentIds(session, label, username); 466 r.runUnrestricted(); 467 return r.docIds; 468 } 469 470 protected static class UnrestrictedGetTagDocumentIds extends UnrestrictedSessionRunner { 471 472 protected final String label; 473 474 protected final String username; 475 476 protected final List<String> docIds; 477 478 protected UnrestrictedGetTagDocumentIds(CoreSession session, String label, String username) { 479 super(session); 480 this.label = cleanLabel(label, false, false); 481 this.username = cleanUsername(username); 482 this.docIds = new ArrayList<>(); 483 } 484 485 @Override 486 public void run() { 487 List<Map<String, Serializable>> res; 488 if (username == null) { 489 res = getItems(PAGE_PROVIDERS.GET_DOCUMENTS_FOR_TAG.name(), session, label); 490 } else { 491 res = getItems(PAGE_PROVIDERS.GET_DOCUMENTS_FOR_TAG_AND_USER.name(), session, label, username); 492 } 493 if (res != null) { 494 for (Map<String, Serializable> map : res) { 495 docIds.add((String) map.get(TagConstants.TAGGING_SOURCE_FIELD)); 496 } 497 } 498 } 499 500 } 501 502 @Override 503 public List<Tag> getTagCloud(CoreSession session, String docId, String username, Boolean normalize) { 504 UnrestrictedGetDocumentCloud r = new UnrestrictedGetDocumentCloud(session, docId, username, normalize); 505 r.runUnrestricted(); 506 return r.cloud; 507 } 508 509 protected static class UnrestrictedGetDocumentCloud extends UnrestrictedSessionRunner { 510 511 protected final String docId; 512 513 protected final String username; 514 515 protected final List<Tag> cloud; 516 517 protected final Boolean normalize; 518 519 protected UnrestrictedGetDocumentCloud(CoreSession session, String docId, String username, Boolean normalize) { 520 super(session); 521 this.docId = docId; 522 this.username = cleanUsername(username); 523 this.normalize = normalize; 524 this.cloud = new ArrayList<>(); 525 } 526 527 @Override 528 public void run() { 529 List<Map<String, Serializable>> res; 530 if (docId == null) { 531 if (username == null) { 532 res = getItems(PAGE_PROVIDERS.GET_ALL_TAGS.name(), session); 533 } else { 534 res = getItems(PAGE_PROVIDERS.GET_ALL_TAGS_FOR_USER.name(), session, username); 535 } 536 } else { 537 // find all docs under docid 538 String path = session.getDocument(new IdRef(docId)).getPathAsString(); 539 path = path.replace("'", ""); 540 List<String> docIds = new ArrayList<>(); 541 docIds.add(docId); 542 List<Map<String, Serializable>> docRes = getItems(PAGE_PROVIDERS.GET_TAGGED_DOCUMENTS_UNDER.name(), 543 session, path); 544 if (docRes != null) { 545 for (Map<String, Serializable> map : docRes) { 546 docIds.add((String) map.get(NXQL.ECM_UUID)); 547 } 548 } 549 550 if (username == null) { 551 res = getItems(PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENTS.name(), session, docIds); 552 } else { 553 res = getItems(PAGE_PROVIDERS.GET_TAGS_FOR_DOCUMENTS_AND_USER.name(), session, docIds, username); 554 } 555 } 556 557 int min = 999999, max = 0; 558 if (res != null) { 559 for (Map<String, Serializable> map : res) { 560 String label = (String) map.get(TagConstants.TAG_LABEL_FIELD); 561 int weight = ((Long) map.get(TagConstants.TAGGING_SOURCE_FIELD)).intValue(); 562 if (weight == 0) { 563 // shouldn't happen 564 continue; 565 } 566 if (weight > max) { 567 max = weight; 568 } 569 if (weight < min) { 570 min = weight; 571 } 572 Tag weightedTag = new Tag(label, weight); 573 cloud.add(weightedTag); 574 } 575 } 576 if (normalize != null) { 577 normalizeCloud(cloud, min, max, !normalize.booleanValue()); 578 } 579 } 580 581 } 582 583 public static void normalizeCloud(List<Tag> cloud, int min, int max, boolean linear) { 584 if (min == max) { 585 for (Tag tag : cloud) { 586 tag.setWeight(100); 587 } 588 return; 589 } 590 double nmin; 591 double diff; 592 if (linear) { 593 nmin = min; 594 diff = max - min; 595 } else { 596 nmin = Math.log(min); 597 diff = Math.log(max) - nmin; 598 } 599 for (Tag tag : cloud) { 600 long weight = tag.getWeight(); 601 double norm; 602 if (linear) { 603 norm = (weight - nmin) / diff; 604 } else { 605 norm = (Math.log(weight) - nmin) / diff; 606 } 607 tag.setWeight(Math.round(100 * norm)); 608 } 609 } 610 611 @Override 612 public List<Tag> getSuggestions(CoreSession session, String label, String username) { 613 UnrestrictedGetTagSuggestions r = new UnrestrictedGetTagSuggestions(session, label, username); 614 r.runUnrestricted(); 615 return r.tags; 616 } 617 618 protected static class UnrestrictedGetTagSuggestions extends UnrestrictedSessionRunner { 619 620 protected final String label; 621 622 protected final String username; 623 624 protected final List<Tag> tags; 625 626 protected UnrestrictedGetTagSuggestions(CoreSession session, String label, String username) { 627 super(session); 628 label = cleanLabel(label, false, true); 629 if (!label.contains("%")) { 630 label += "%"; 631 } 632 this.label = label; 633 this.username = cleanUsername(username); 634 this.tags = new ArrayList<>(); 635 } 636 637 @Override 638 public void run() { 639 List<Map<String, Serializable>> res; 640 if (username == null) { 641 res = getItems(PAGE_PROVIDERS.GET_TAG_SUGGESTIONS.name(), session, label); 642 } else { 643 res = getItems(PAGE_PROVIDERS.GET_TAG_SUGGESTIONS_FOR_USER.name(), session, label, username); 644 } 645 if (res != null) { 646 for (Map<String, Serializable> map : res) { 647 String label = (String) map.get(TagConstants.TAG_LABEL_FIELD); 648 tags.add(new Tag(label, 0)); 649 } 650 } 651 // XXX should sort on tag weight 652 Collections.sort(tags, Tag.LABEL_COMPARATOR); 653 } 654 655 } 656 657 /** 658 * Returns results from calls to {@link CoreSession#queryAndFetch(String, String, Object...)} using page providers. 659 * 660 * @since 6.0 661 */ 662 @SuppressWarnings("unchecked") 663 protected static List<Map<String, Serializable>> getItems(String pageProviderName, CoreSession session, 664 Object... params) { 665 PageProviderService ppService = Framework.getService(PageProviderService.class); 666 if (ppService == null) { 667 throw new RuntimeException("Missing PageProvider service"); 668 } 669 Map<String, Serializable> props = new HashMap<>(); 670 // first retrieve potential props from definition 671 PageProviderDefinition def = ppService.getPageProviderDefinition(pageProviderName); 672 if (def != null) { 673 Map<String, String> defProps = def.getProperties(); 674 if (defProps != null) { 675 props.putAll(defProps); 676 } 677 } 678 props.put(CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY, (Serializable) session); 679 PageProvider<Map<String, Serializable>> pp = (PageProvider<Map<String, Serializable>>) ppService.getPageProvider( 680 pageProviderName, null, null, null, props, params); 681 if (pp == null) { 682 throw new NuxeoException("Page provider not found: " + pageProviderName); 683 } 684 return pp.getCurrentPage(); 685 } 686 687}