/*
* Copyright (C) 2003-2010 eXo Platform SAS.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see<http://www.gnu.org/licenses/>.
*/
package org.exoplatform.wiki.mow.core.api.wiki;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.jcr.Node;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.version.Version;
import org.chromattic.api.ChromatticSession;
import org.chromattic.api.DuplicateNameException;
import org.chromattic.api.RelationshipType;
import org.chromattic.api.annotations.Create;
import org.chromattic.api.annotations.Destroy;
import org.chromattic.api.annotations.ManyToOne;
import org.chromattic.api.annotations.MappedBy;
import org.chromattic.api.annotations.Name;
import org.chromattic.api.annotations.OneToMany;
import org.chromattic.api.annotations.OneToOne;
import org.chromattic.api.annotations.Owner;
import org.chromattic.api.annotations.Path;
import org.chromattic.api.annotations.PrimaryType;
import org.chromattic.api.annotations.Property;
import org.chromattic.api.annotations.WorkspaceName;
import org.chromattic.ext.ntdef.NTFolder;
import org.chromattic.ext.ntdef.Resource;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.wiki.chromattic.ext.ntdef.NTVersion;
import org.exoplatform.wiki.chromattic.ext.ntdef.VersionableMixin;
import org.exoplatform.wiki.mow.api.Page;
import org.exoplatform.wiki.mow.api.Wiki;
import org.exoplatform.wiki.mow.api.WikiNodeType;
import org.exoplatform.wiki.mow.core.api.MOWService;
import org.exoplatform.wiki.resolver.TitleResolver;
import org.exoplatform.wiki.service.PermissionType;
import org.exoplatform.wiki.service.WikiService;
import org.exoplatform.wiki.utils.Utils;
/**
* Created by The eXo Platform SAS
* Author : viet.nguyen
* viet.nguyen@exoplatform.com
* Mar 26, 2010
*/
@PrimaryType(name = WikiNodeType.WIKI_PAGE)
public abstract class PageImpl extends NTFolder implements Page {
private MOWService mowService;
private WikiService wService;
/**
* caching related pages for performance
*/
private List<PageImpl> relatedPages = null;
private boolean isMinorEdit = false;
public void setMOWService(MOWService mowService) {
this.mowService = mowService;
}
public void setWikiService(WikiService wService) {
this.wService = wService;
}
public ChromatticSession getChromatticSession() {
return mowService.getSession();
}
public Session getJCRSession() {
return getChromatticSession().getJCRSession();
}
public WikiService getWikiService(){
return wService;
}
private Node getJCRPageNode() throws Exception {
return (Node) getChromatticSession().getJCRSession().getItem(getPath());
}
@Name
public abstract String getName();
public abstract void setName(String name);
@Path
public abstract String getPath();
@WorkspaceName
public abstract String getWorkspace();
@OneToOne
@Owner
@MappedBy(WikiNodeType.Definition.CONTENT)
protected abstract AttachmentImpl getContentByChromattic();
protected abstract void setContentByChromattic(AttachmentImpl content);
@Create
protected abstract AttachmentImpl createContent();
public AttachmentImpl getContent() {
AttachmentImpl content = getContentByChromattic();
if (content == null) {
content = createContent();
setContentByChromattic(content);
}
return content;
}
@Property(name = WikiNodeType.Definition.TITLE)
public abstract String getTitleByChromattic();
public abstract void setTitleByChromattic(String title);
public String getTitle() {
String title = getTitleByChromattic();
return (title != null) ? title : getName();
}
public void setTitle(String title) {
setTitleByChromattic(title);
}
@Property(name = WikiNodeType.Definition.SYNTAX)
public abstract String getSyntax();
public abstract void setSyntax(String syntax);
@Property(name = WikiNodeType.Definition.COMMENT)
public abstract String getComment();
public abstract void setComment(String comment);
@Property(name = WikiNodeType.Definition.OWNER)
public abstract String getOwner();
public abstract void setOwner(String owner);
@Property(name = WikiNodeType.Definition.AUTHOR)
public abstract String getAuthor();
@Property(name = WikiNodeType.Definition.CREATED_DATE)
public abstract Date getCreatedDate();
public abstract void setCreatedDate(Date date);
@Property(name = WikiNodeType.Definition.UPDATED_DATE)
public abstract Date getUpdatedDate();
@Property(name = WikiNodeType.Definition.URL)
public abstract String getURL();
public abstract void setURL(String url);
@OneToOne(type = RelationshipType.EMBEDDED)
@Owner
public abstract MovedMixin getMovedMixin();
public abstract void setMovedMixin(MovedMixin move);
@OneToOne(type = RelationshipType.EMBEDDED)
@Owner
public abstract RemovedMixin getRemovedMixin();
public abstract void setRemovedMixin(RemovedMixin remove);
@OneToOne(type = RelationshipType.EMBEDDED)
@Owner
public abstract RenamedMixin getRenamedMixin();
public abstract void setRenamedMixin(RenamedMixin mix);
@OneToOne(type = RelationshipType.EMBEDDED)
@Owner
public abstract WatchedMixin getWatchedMixin();
public abstract void setWatchedMixin(WatchedMixin mix);
@Create
protected abstract WatchedMixin createWatchedMixin();
public void makeWatched() {
WatchedMixin watchedMixin = getWatchedMixin();
if (watchedMixin == null) {
watchedMixin = createWatchedMixin();
setWatchedMixin(watchedMixin);
}
}
@OneToOne(type = RelationshipType.EMBEDDED)
@Owner
public abstract VersionableMixin getVersionableMixinByChromattic();
protected abstract void setVersionableMixinByChromattic(VersionableMixin mix);
@Create
protected abstract VersionableMixin createVersionableMixin();
public VersionableMixin getVersionableMixin() {
VersionableMixin versionableMixin = getVersionableMixinByChromattic();
if (versionableMixin == null) {
versionableMixin = createVersionableMixin();
setVersionableMixinByChromattic(versionableMixin);
}
return versionableMixin;
}
public void makeVersionable() {
getVersionableMixin();
}
//TODO: replace by @Checkin when Chromattic support
public NTVersion checkin() throws Exception {
getChromatticSession().save();
Node pageNode = getJCRPageNode();
Version newVersion = pageNode.checkin();
NTVersion ntVersion = getChromatticSession().findByNode(NTVersion.class, newVersion);
return ntVersion;
}
//TODO: replace by @Checkout when Chromattic support
public void checkout() throws Exception {
Node pageNode = getJCRPageNode();
pageNode.checkout();
}
//TODO: replace by @Restore when Chromattic support
public void restore(String versionName, boolean removeExisting) throws Exception {
Node pageNode = getJCRPageNode();
pageNode.restore(versionName, removeExisting);
}
@Create
public abstract AttachmentImpl createAttachment();
public AttachmentImpl createAttachment(String fileName, Resource contentResource) throws Exception {
if (fileName == null) {
throw new NullPointerException();
}
Iterator<AttachmentImpl> attIter= getAttachments().iterator();
while (attIter.hasNext()) {
AttachmentImpl att = attIter.next();
if (att.getName().equals(fileName)) {
att.remove();
}
}
AttachmentImpl file = createAttachment();
file.setName(TitleResolver.getId(fileName, false));
addAttachment(file);
if (fileName.lastIndexOf(".") > 0) {
file.setTitle(fileName.substring(0, fileName.lastIndexOf(".")));
file.setFileType(fileName.substring(fileName.lastIndexOf(".")));
} else
file.setTitle(fileName);
if (contentResource != null) {
file.setContentResource(contentResource);
}
getChromatticSession().save();
return file;
}
@OneToMany
public abstract Collection<AttachmentImpl> getAttachmentsByChromattic();
public Collection<AttachmentImpl> getAttachments() {
return getAttachmentsByChromattic();
}
public Collection<AttachmentImpl> getAttachmentsExcludeContent() {
Collection<AttachmentImpl> attachments = getAttachmentsByChromattic();
List<AttachmentImpl> atts = new ArrayList<AttachmentImpl>(attachments.size());
for (AttachmentImpl attachment : attachments) {
if (!WikiNodeType.Definition.CONTENT.equals(attachment.getName())) {
atts.add(attachment);
}
}
return atts;
}
public AttachmentImpl getAttachment(String attachmentId) {
for (AttachmentImpl att : getAttachments()) {
if (att.getName().equals(attachmentId)) {
return att;
}
}
return null;
}
public void addAttachment(AttachmentImpl attachment) throws DuplicateNameException {
getAttachments().add(attachment);
}
public void removeAttachment(String attachmentId){
AttachmentImpl attachment = getAttachment(attachmentId);
if(attachment != null){
attachment.remove();
}
}
@ManyToOne
public abstract PageImpl getParentPage();
public abstract void setParentPage(PageImpl page);
@ManyToOne
public abstract Trash getTrash();
public abstract void setTrash(Trash trash);
@OneToMany
protected abstract Map<String, PageImpl> getChildrenContainer();
public Map<String, PageImpl> getChildPages() throws Exception {
Map<String, PageImpl> result = new HashMap<String, PageImpl>();
Iterator<Entry<String, PageImpl>> iter = getChildrenContainer().entrySet().iterator();
while (iter.hasNext()) {
Entry<String, PageImpl> entry = iter.next();
PageImpl page = entry.getValue();
if (page != null && page.hasPermission(PermissionType.VIEWPAGE)) {
result.put(page.getName(), page);
}
}
return result;
}
@Property(name = WikiNodeType.Definition.OVERRIDEPERMISSION)
public abstract boolean getOverridePermission();
public abstract void setOverridePermission(boolean isOverridePermission);
public boolean hasPermission(PermissionType permissionType) throws Exception {
String[] permission = new String[] {};
if (PermissionType.VIEWPAGE.equals(permissionType)) {
permission = new String[] { org.exoplatform.services.jcr.access.PermissionType.READ };
} else if (PermissionType.EDITPAGE.equals(permissionType)) {
permission = new String[] { org.exoplatform.services.jcr.access.PermissionType.ADD_NODE,
org.exoplatform.services.jcr.access.PermissionType.REMOVE,
org.exoplatform.services.jcr.access.PermissionType.SET_PROPERTY };
}
ExtendedNode pageNode = (ExtendedNode) getJCRPageNode();
AccessControlList acl = pageNode.getACL();
ConversationState conversationState = ConversationState.getCurrent();
Identity user = null;
if (conversationState != null) {
user = conversationState.getIdentity();
} else {
user = new Identity(IdentityConstants.ANONIM);
}
return Utils.hasPermission(acl, permission, user);
}
public HashMap<String, String[]> getPagePermission() throws Exception {
ExtendedNode pageNode = (ExtendedNode) getJCRPageNode();
HashMap<String, String[]> perm = new HashMap<String, String[]>();
AccessControlList acl = pageNode.getACL();
List<AccessControlEntry> aceList = acl.getPermissionEntries();
for (int i = 0, length = aceList.size(); i < length; i++) {
AccessControlEntry ace = aceList.get(i);
String[] nodeActions = perm.get(ace.getIdentity());
List<String> actions = null;
if (nodeActions != null) {
actions = new ArrayList<String>(Arrays.asList(nodeActions));
} else {
actions = new ArrayList<String>();
}
actions.add(ace.getPermission());
perm.put(ace.getIdentity(), actions.toArray(new String[5]));
}
return perm;
}
public void setPagePermission(HashMap<String, String[]> permissions) throws Exception {
getChromatticSession().save();
ExtendedNode pageNode = (ExtendedNode) getJCRPageNode();
if (pageNode.canAddMixin("exo:privilegeable")) {
pageNode.addMixin("exo:privilegeable");
}
if (permissions != null && permissions.size() > 0) {
pageNode.setPermissions(permissions);
} else {
pageNode.clearACL();
pageNode.setPermission(IdentityConstants.ANY, org.exoplatform.services.jcr.access.PermissionType.ALL);
}
}
public void setNonePermission() throws Exception {
setPagePermission(null);
}
/*public void addWikiPage(PageImpl wikiPage) throws DuplicateNameException {
getChildPages().add(wikiPage);
}*/
protected void addPage(String pageName, PageImpl page) {
if (pageName == null) {
throw new NullPointerException();
}
if (page == null) {
throw new NullPointerException();
}
Map<String, PageImpl> children = getChildrenContainer();
if (children.containsKey(pageName)) {
throw new IllegalStateException();
}
children.put(pageName, page);
}
public void addWikiPage(PageImpl page) {
if (page == null) {
throw new NullPointerException();
}
addPage(page.getName(), page);
}
public void addPublicPage(PageImpl page) throws Exception {
addWikiPage(page);
page.setNonePermission();
}
public PageImpl getWikiPage(String pageId) throws Exception{
if(WikiNodeType.Definition.WIKI_HOME_NAME.equalsIgnoreCase(pageId)){
return this;
}
Iterator<PageImpl> iter = getChildPages().values().iterator();
while(iter.hasNext()) {
PageImpl page = (PageImpl)iter.next() ;
if (pageId.equals(page.getName())) return page ;
}
return null ;
}
public Wiki getWiki() {
WikiHome wikiHome = getWikiHome();
if (wikiHome != null) {
PortalWiki portalWiki = wikiHome.getPortalWiki();
GroupWiki groupWiki = wikiHome.getGroupWiki();
UserWiki userWiki = wikiHome.getUserWiki();
if (portalWiki != null) {
return portalWiki;
} else if (groupWiki != null) {
return groupWiki;
} else {
return userWiki;
}
}
return null;
}
public WikiHome getWikiHome() {
PageImpl parent = this.getParentPage();
if (this instanceof WikiHome) {
parent = this;
} else
while (parent != null && !(parent instanceof WikiHome)) {
parent = parent.getParentPage();
}
return (WikiHome) parent;
}
public boolean isMinorEdit() {
return isMinorEdit;
}
public void setMinorEdit(boolean isMinorEdit) {
this.isMinorEdit = isMinorEdit;
}
@Destroy
public abstract void remove();
/**
* add a related page
* @param page
* @return uuid of node of related page if add successfully. <br>
* null if add failed.
* @throws NullPointerException if the param is null
* @throws Exception when any error occurs.
*/
public synchronized String addRelatedPage(PageImpl page) throws Exception {
Map<String, Value> referredUUIDs = getReferredUUIDs();
Session jcrSession = getJCRSession();
Node myJcrNode = (Node) jcrSession.getItem(getPath());
Node referredJcrNode = (Node) jcrSession.getItem(page.getPath());
String referedUUID = referredJcrNode.getUUID();
if (referredUUIDs.containsKey(referedUUID)) {
return null;
}
Value value2Add = jcrSession.getValueFactory().createValue(referredJcrNode);
referredUUIDs.put(referedUUID, value2Add);
myJcrNode.setProperty(WikiNodeType.Definition.RELATION,
referredUUIDs.values().toArray(new Value[referredUUIDs.size()]));
myJcrNode.save();
// cache a related page.
if (relatedPages != null) relatedPages.add(page);
return referedUUID;
}
public List<PageImpl> getRelatedPages() throws Exception {
if (relatedPages == null) {
relatedPages = new ArrayList<PageImpl>();
Iterator<Entry<String, Value>> refferedIter = getReferredUUIDs().entrySet().iterator();
ChromatticSession chSession = getChromatticSession();
while (refferedIter.hasNext()) {
Entry<String, Value> entry = refferedIter.next();
PageImpl page = chSession.findById(PageImpl.class, entry.getValue().getString());
relatedPages.add(page);
}
}
return new ArrayList<PageImpl>(relatedPages);
}
/**
* remove a specified related page.
* @param page
* @return uuid of node if related page is removed successfully <br>
* null if removing failed.
* @throws Exception when an error is thrown.
*/
public synchronized String removeRelatedPage(PageImpl page) throws Exception {
Map<String, Value> referedUUIDs = getReferredUUIDs();
Session jcrSession = getJCRSession();
Node referredJcrNode = (Node) jcrSession.getItem(page.getPath());
Node myJcrNode = (Node) jcrSession.getItem(getPath());
String referredUUID = referredJcrNode.getUUID();
if (!referedUUIDs.containsKey(referredUUID)) {
return null;
}
referedUUIDs.remove(referredUUID);
myJcrNode.setProperty(WikiNodeType.Definition.RELATION,
referedUUIDs.values().toArray(new Value[referedUUIDs.size()]));
myJcrNode.save();
// remove page from cache
if (relatedPages != null) relatedPages.remove(page);
return referredUUID;
}
/**
* get reference uuids of current page
* @return Map<String, Value> map of referred uuids of current page
* @throws Exception when an error is thrown.
*/
public Map<String, Value> getReferredUUIDs() throws Exception {
Session jcrSession = getJCRSession();
Node myJcrNode = (Node) jcrSession.getItem(getPath());
Map<String, Value> referedUUIDs = new HashMap<String, Value>();
if (myJcrNode.hasProperty(WikiNodeType.Definition.RELATION)) {
Value[] values = myJcrNode.getProperty(WikiNodeType.Definition.RELATION).getValues();
if (values != null && values.length > 0) {
for (Value value : values) {
referedUUIDs.put(value.getString(), value);
}
}
}
return referedUUIDs;
}
public synchronized void removeAllRelatedPages() throws Exception {
Session jcrSession = getJCRSession();
Node myJcrNode = (Node) jcrSession.getItem(getPath());
myJcrNode.setProperty(WikiNodeType.Definition.RELATION, (Value[]) null);
myJcrNode.save();
// clear related pages in cache.
if (relatedPages != null) relatedPages.clear();
}
}