001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: RelationActionsBean.java 28951 2008-01-11 13:35:15Z tdelprat $ 018 */ 019 020package org.nuxeo.ecm.platform.relations.web.listener.ejb; 021 022import static org.jboss.seam.ScopeType.CONVERSATION; 023 024import java.io.Serializable; 025import java.security.Principal; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Comparator; 029import java.util.List; 030import java.util.Map; 031 032import javax.faces.event.ActionEvent; 033 034import org.apache.commons.lang.StringUtils; 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.jboss.seam.ScopeType; 038import org.jboss.seam.annotations.Factory; 039import org.jboss.seam.annotations.In; 040import org.jboss.seam.annotations.Name; 041import org.jboss.seam.annotations.Scope; 042import org.jboss.seam.contexts.Context; 043import org.jboss.seam.contexts.Contexts; 044import org.jboss.seam.faces.FacesMessages; 045import org.jboss.seam.international.StatusMessage; 046import org.nuxeo.ecm.core.api.CoreSession; 047import org.nuxeo.ecm.core.api.DocumentModel; 048import org.nuxeo.ecm.platform.relations.api.DocumentRelationManager; 049import org.nuxeo.ecm.platform.relations.api.Graph; 050import org.nuxeo.ecm.platform.relations.api.Node; 051import org.nuxeo.ecm.platform.relations.api.QNameResource; 052import org.nuxeo.ecm.platform.relations.api.RelationManager; 053import org.nuxeo.ecm.platform.relations.api.Resource; 054import org.nuxeo.ecm.platform.relations.api.ResourceAdapter; 055import org.nuxeo.ecm.platform.relations.api.Statement; 056import org.nuxeo.ecm.platform.relations.api.Subject; 057import org.nuxeo.ecm.platform.relations.api.exceptions.RelationAlreadyExistsException; 058import org.nuxeo.ecm.platform.relations.api.impl.LiteralImpl; 059import org.nuxeo.ecm.platform.relations.api.impl.QNameResourceImpl; 060import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl; 061import org.nuxeo.ecm.platform.relations.api.util.RelationConstants; 062import org.nuxeo.ecm.platform.relations.jena.JenaGraph; 063import org.nuxeo.ecm.platform.relations.web.NodeInfo; 064import org.nuxeo.ecm.platform.relations.web.NodeInfoImpl; 065import org.nuxeo.ecm.platform.relations.web.StatementInfo; 066import org.nuxeo.ecm.platform.relations.web.StatementInfoComparator; 067import org.nuxeo.ecm.platform.relations.web.StatementInfoImpl; 068import org.nuxeo.ecm.platform.relations.web.listener.RelationActions; 069import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 070import org.nuxeo.ecm.platform.ui.web.invalidations.AutomaticDocumentBasedInvalidation; 071import org.nuxeo.ecm.platform.ui.web.invalidations.DocumentContextBoundActionBean; 072import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor; 073 074/** 075 * Seam component that manages statements involving current document as well as creation, edition and deletion of 076 * statements involving current document. 077 * <p> 078 * Current document is the subject of the relation. The predicate is resolved thanks to a list of predicates URIs. The 079 * object is resolved using a type (literal, resource, qname resource), an optional namespace (for qname resources) and 080 * a value. 081 * 082 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 083 */ 084@Name("relationActions") 085@Scope(CONVERSATION) 086@AutomaticDocumentBasedInvalidation 087public class RelationActionsBean extends DocumentContextBoundActionBean implements RelationActions, Serializable { 088 089 private static final long serialVersionUID = 2336539966097558178L; 090 091 private static final Log log = LogFactory.getLog(RelationActionsBean.class); 092 093 protected static boolean includeStatementsInEvents = false; 094 095 @In(create = true, required = false) 096 protected transient CoreSession documentManager; 097 098 @In(create = true) 099 protected RelationManager relationManager; 100 101 @In(create = true) 102 protected DocumentRelationManager documentRelationManager; 103 104 @In(create = true) 105 protected NavigationContext navigationContext; 106 107 @In(create = true) 108 protected transient ResourcesAccessor resourcesAccessor; 109 110 @In(create = true, required = false) 111 protected FacesMessages facesMessages; 112 113 @In(required = false) 114 protected transient Principal currentUser; 115 116 // statements lists 117 protected List<Statement> incomingStatements; 118 119 protected List<StatementInfo> incomingStatementsInfo; 120 121 protected List<Statement> outgoingStatements; 122 123 protected List<StatementInfo> outgoingStatementsInfo; 124 125 // fields for relation creation 126 127 protected String predicateUri; 128 129 protected String objectType; 130 131 protected String objectLiteralValue; 132 133 protected String objectUri; 134 135 protected String objectDocumentUid; 136 137 protected String objectDocumentTitle; 138 139 protected String comment; 140 141 protected Boolean showCreateForm = false; 142 143 // popupDisplayed flag for preventing relation_search content view execution 144 // until search button clicked 145 protected Boolean popupDisplayed = false; 146 147 @Override 148 public DocumentModel getDocumentModel(Node node) { 149 if (node.isQNameResource()) { 150 QNameResource resource = (QNameResource) node; 151 Map<String, Object> context = Collections.<String, Object> singletonMap( 152 ResourceAdapter.CORE_SESSION_CONTEXT_KEY, documentManager); 153 Object o = relationManager.getResourceRepresentation(resource.getNamespace(), resource, context); 154 if (o instanceof DocumentModel) { 155 return (DocumentModel) o; 156 } 157 } 158 return null; 159 } 160 161 // XXX AT: for BBB when repo name was not included in the resource uri 162 @Deprecated 163 private static QNameResource getOldDocumentResource(DocumentModel document) { 164 QNameResource documentResource = null; 165 if (document != null) { 166 documentResource = new QNameResourceImpl(RelationConstants.DOCUMENT_NAMESPACE, document.getId()); 167 } 168 return documentResource; 169 } 170 171 @Override 172 public QNameResource getDocumentResource(DocumentModel document) { 173 QNameResource documentResource = null; 174 if (document != null) { 175 documentResource = (QNameResource) relationManager.getResource(RelationConstants.DOCUMENT_NAMESPACE, 176 document, null); 177 } 178 return documentResource; 179 } 180 181 protected List<StatementInfo> getStatementsInfo(List<Statement> statements) { 182 if (statements == null) { 183 return null; 184 } 185 List<StatementInfo> infoList = new ArrayList<StatementInfo>(); 186 for (Statement statement : statements) { 187 Subject subject = statement.getSubject(); 188 // TODO: filter on doc visibility (?) 189 NodeInfo subjectInfo = new NodeInfoImpl(subject, getDocumentModel(subject), true); 190 Resource predicate = statement.getPredicate(); 191 Node object = statement.getObject(); 192 NodeInfo objectInfo = new NodeInfoImpl(object, getDocumentModel(object), true); 193 StatementInfo info = new StatementInfoImpl(statement, subjectInfo, new NodeInfoImpl(predicate), objectInfo); 194 infoList.add(info); 195 } 196 return infoList; 197 } 198 199 protected void resetEventContext() { 200 Context evtCtx = Contexts.getEventContext(); 201 if (evtCtx != null) { 202 evtCtx.remove("currentDocumentIncomingRelations"); 203 evtCtx.remove("currentDocumentOutgoingRelations"); 204 } 205 } 206 207 @Override 208 @Factory(value = "currentDocumentIncomingRelations", scope = ScopeType.EVENT) 209 public List<StatementInfo> getIncomingStatementsInfo() { 210 if (incomingStatementsInfo != null) { 211 return incomingStatementsInfo; 212 } 213 DocumentModel currentDoc = getCurrentDocument(); 214 Resource docResource = getDocumentResource(currentDoc); 215 if (docResource == null) { 216 incomingStatements = Collections.emptyList(); 217 incomingStatementsInfo = Collections.emptyList(); 218 } else { 219 Graph graph = relationManager.getGraphByName(RelationConstants.GRAPH_NAME); 220 incomingStatements = graph.getStatements(null, null, docResource); 221 if (graph instanceof JenaGraph) { 222 // add old statements, BBB 223 Resource oldDocResource = getOldDocumentResource(currentDoc); 224 incomingStatements.addAll(graph.getStatements(null, null, oldDocResource)); 225 } 226 incomingStatementsInfo = getStatementsInfo(incomingStatements); 227 // sort by modification date, reverse 228 Comparator<StatementInfo> comp = Collections.reverseOrder(new StatementInfoComparator()); 229 Collections.sort(incomingStatementsInfo, comp); 230 } 231 return incomingStatementsInfo; 232 } 233 234 @Override 235 @Factory(value = "currentDocumentOutgoingRelations", scope = ScopeType.EVENT) 236 public List<StatementInfo> getOutgoingStatementsInfo() { 237 if (outgoingStatementsInfo != null) { 238 return outgoingStatementsInfo; 239 } 240 DocumentModel currentDoc = getCurrentDocument(); 241 Resource docResource = getDocumentResource(currentDoc); 242 if (docResource == null) { 243 outgoingStatements = Collections.emptyList(); 244 outgoingStatementsInfo = Collections.emptyList(); 245 } else { 246 Graph graph = relationManager.getGraphByName(RelationConstants.GRAPH_NAME); 247 outgoingStatements = graph.getStatements(docResource, null, null); 248 if (graph instanceof JenaGraph) { 249 // add old statements, BBB 250 Resource oldDocResource = getOldDocumentResource(currentDoc); 251 outgoingStatements.addAll(graph.getStatements(oldDocResource, null, null)); 252 } 253 outgoingStatementsInfo = getStatementsInfo(outgoingStatements); 254 // sort by modification date, reverse 255 Comparator<StatementInfo> comp = Collections.reverseOrder(new StatementInfoComparator()); 256 Collections.sort(outgoingStatementsInfo, comp); 257 } 258 return outgoingStatementsInfo; 259 } 260 261 @Override 262 public void resetStatements() { 263 incomingStatements = null; 264 incomingStatementsInfo = null; 265 outgoingStatements = null; 266 outgoingStatementsInfo = null; 267 } 268 269 // getters & setters for creation items 270 271 @Override 272 public String getComment() { 273 return comment; 274 } 275 276 @Override 277 public void setComment(String comment) { 278 this.comment = comment; 279 } 280 281 @Override 282 public String getObjectDocumentTitle() { 283 return objectDocumentTitle; 284 } 285 286 @Override 287 public void setObjectDocumentTitle(String objectDocumentTitle) { 288 this.objectDocumentTitle = objectDocumentTitle; 289 } 290 291 @Override 292 public String getObjectDocumentUid() { 293 return objectDocumentUid; 294 } 295 296 @Override 297 public void setObjectDocumentUid(String objectDocumentUid) { 298 this.objectDocumentUid = objectDocumentUid; 299 } 300 301 @Override 302 public String getObjectLiteralValue() { 303 return objectLiteralValue; 304 } 305 306 @Override 307 public void setObjectLiteralValue(String objectLiteralValue) { 308 this.objectLiteralValue = objectLiteralValue; 309 } 310 311 @Override 312 public String getObjectType() { 313 return objectType; 314 } 315 316 @Override 317 public void setObjectType(String objectType) { 318 this.objectType = objectType; 319 } 320 321 @Override 322 public String getObjectUri() { 323 return objectUri; 324 } 325 326 @Override 327 public void setObjectUri(String objectUri) { 328 this.objectUri = objectUri; 329 } 330 331 @Override 332 public String getPredicateUri() { 333 return predicateUri; 334 } 335 336 @Override 337 public void setPredicateUri(String predicateUri) { 338 this.predicateUri = predicateUri; 339 } 340 341 @Override 342 public String addStatement() { 343 resetEventContext(); 344 345 Node object = null; 346 if (objectType.equals("literal")) { 347 objectLiteralValue = objectLiteralValue.trim(); 348 object = new LiteralImpl(objectLiteralValue); 349 } else if (objectType.equals("uri")) { 350 objectUri = objectUri.trim(); 351 object = new ResourceImpl(objectUri); 352 } else if (objectType.equals("document")) { 353 objectDocumentUid = objectDocumentUid.trim(); 354 String repositoryName = navigationContext.getCurrentServerLocation().getName(); 355 String localName = repositoryName + "/" + objectDocumentUid; 356 object = new QNameResourceImpl(RelationConstants.DOCUMENT_NAMESPACE, localName); 357 } 358 try { 359 documentRelationManager.addRelation(documentManager, getCurrentDocument(), object, predicateUri, false, 360 includeStatementsInEvents, StringUtils.trim(comment)); 361 facesMessages.add(StatusMessage.Severity.INFO, 362 resourcesAccessor.getMessages().get("label.relation.created")); 363 resetCreateFormValues(); 364 } catch (RelationAlreadyExistsException e) { 365 facesMessages.add(StatusMessage.Severity.WARN, 366 resourcesAccessor.getMessages().get("label.relation.already.exists")); 367 } 368 resetStatements(); 369 return null; 370 } 371 372 @Override 373 public void toggleCreateForm(ActionEvent event) { 374 showCreateForm = !showCreateForm; 375 } 376 377 private void resetCreateFormValues() { 378 predicateUri = ""; 379 objectType = ""; 380 objectLiteralValue = ""; 381 objectUri = ""; 382 objectDocumentUid = ""; 383 objectDocumentTitle = ""; 384 comment = ""; 385 showCreateForm = false; 386 popupDisplayed = false; 387 } 388 389 @Override 390 public String deleteStatement(StatementInfo stmtInfo) { 391 resetEventContext(); 392 documentRelationManager.deleteRelation(documentManager, stmtInfo.getStatement()); 393 facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("label.relation.deleted")); 394 resetStatements(); 395 return null; 396 } 397 398 @Override 399 public Boolean getShowCreateForm() { 400 return showCreateForm; 401 } 402 403 @Override 404 protected void resetBeanCache(DocumentModel newCurrentDocumentModel) { 405 resetStatements(); 406 } 407 408 public Boolean getPopupDisplayed() { 409 return popupDisplayed; 410 } 411 412 public void setPopupDisplayed(Boolean popupDisplayed) { 413 this.popupDisplayed = popupDisplayed; 414 } 415 416}