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