/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA 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 OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.webgate.api;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import de.innovationgate.webgate.api.WGSessionContext.DocumentContext;
import de.innovationgate.webgate.api.WGStructEntry.SessionData;
import de.innovationgate.webgate.api.locking.ResourceIsLockedException;
/**
* <p>Represents an area of the content database, containing content documents.
* Areas are treated as separated regions of the content, where each has it's own access controlling.</p>
*/
public class WGArea extends WGSchemaDocument implements PageHierarchyNode {
public class SessionData {
private List backendEditors;
public List getBackendEditors() {
return backendEditors;
}
public void setBackendEditors(List backendEditors) {
this.backendEditors = backendEditors;
}
private List backendReaders;
public List getBackendReaders() {
return backendReaders;
}
public void setBackendReaders(List backendEditors) {
this.backendReaders = backendEditors;
}
}
public static final String META_READERS = "READERS";
public static final MetaInfo METAINFO_READERS = new MetaInfo(META_READERS, String.class, Collections.EMPTY_LIST);
static {
METAINFO_READERS.setMultiple(true);
METAINFO_READERS.setLuceneIndexType(MetaInfo.LUCENE_INDEXTYPE_FULLTEXT);
METAINFO_READERS.setInputConverter(new MetaConverter() {
public Object convert(WGDocument doc, MetaInfo metaInfo, Object value) throws WGAPIException {
List readers = (List) value;
// Test if the current user is contained in the list. If not, he is added to avoid self disclosure
if (!WGDatabase.anyoneAllowed(readers) && !doc.getDatabase().isMemberOfUserList(readers)) {
readers.add(doc.getDatabase().getSessionContext().getUser());
}
return readers;
}
});
};
public static final String META_EDITORS = "EDITORS";
public static final MetaInfo METAINFO_EDITORS = new MetaInfo(META_EDITORS, String.class, Collections.EMPTY_LIST);
static { METAINFO_EDITORS.setMultiple(true); };
public static final String META_SYSTEM = "SYSTEM";
public static final MetaInfo METAINFO_SYSTEM = new MetaInfo(META_SYSTEM, Boolean.class, Boolean.FALSE);
static {
METAINFO_SYSTEM.setExtdata(true);
}
private WGDocumentListCache rootEntries = null;
/**
* @throws WGAPIException
* @see de.innovationgate.webgate.api.WGDocument#WGDocument(WGDatabase, WGDocumentCore)
*/
public WGArea(WGDatabase db, WGDocumentCore doc) throws WGAPIException {
super(db, doc);
}
/**
* Returns the name of this area.
* @throws WGAPIException
*/
public String getName() throws WGAPIException {
return String.valueOf(this.getMetaData(META_NAME));
}
/**
* Returns a list of all root entries in this area.
* @return WGStructEntryList
* @throws WGSystemException
* @throws WGBackendException
*/
public synchronized WGStructEntryList getRootEntries() throws WGAPIException {
// Double checked cache (un-synchronized and synchronized)
List entries = getDatabase().fetchDocumentListCache(this.rootEntries, WGDocument.TYPE_STRUCTENTRY);
if (!isCachingEnabled() || entries == null) {
WGOperationKey op = db.obtainOperationKey(WGOperationKey.OP_STRUCT_ROOTS, getName());
synchronized (op) {
try {
op.setUsed(true);
entries = getDatabase().fetchDocumentListCache(this.rootEntries, WGDocument.TYPE_STRUCTENTRY);
if (!isCachingEnabled() || entries == null) {
entries = this.db.getRootEntries(this);
if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
this.rootEntries = WGDocumentListCache.buildFromDocuments(entries);
}
}
}
finally {
op.setUsed(false);
}
}
}
return WGStructEntryList.create(entries);
}
/**
* Returns a list of allowed content editors in this area.
* @return List
* @throws WGAPIException
*/
public List getEditors() throws WGAPIException {
List editors = (List) this.getMetaData(WGArea.META_EDITORS);
if (editors != null) {
return editors;
}
else {
return new ArrayList();
}
}
/**
* Returns a list of allowed readers of contents in this area. This is only supported on content stores of version 5.
* @return List
* @throws WGAPIException
*/
public List getReaders() throws WGAPIException {
List readers = (List) this.getMetaData(WGArea.META_READERS);
if (readers != null) {
return readers;
}
else {
return new ArrayList();
}
}
/**
* Sets the allowed editors for this area.
* @param list
* @throws WGAPIException
*/
public boolean setEditors(List list) throws WGAPIException {
return setMetaData(META_EDITORS, list);
}
/**
* Sets the allowed readers for this area. This is only supported on content stores of version 5.
* @param list
* @throws WGAPIException
*/
public boolean setReaders(List list) throws WGAPIException {
return setMetaData(META_READERS, list);
}
/**
* Tests if the current user is allowed to edit documents in this area.
* @return boolean
* @throws WGAPIException
*/
public boolean mayEditAreaChildren() throws WGAPIException {
if( this.db.getSessionContext().getAccessLevel() == WGDatabase.ACCESSLEVEL_MANAGER ){
return true;
}
List readers = (List) getEffectiveReaders();
if (!WGDatabase.anyoneAllowed(readers) && !this.db.isMemberOfUserList(readers)) {
return false;
}
List editors = (List) this.getEffectiveEditors();
if (!WGDatabase.anyoneAllowed(editors) && !this.db.isMemberOfUserList(editors)) {
return false;
}
return true;
}
/**
* Returns the list of currently effective readers. These may differ from the currently set readers in a yet unsaved document.
* @throws WGAPIException
*/
public List getEffectiveReaders() throws WGAPIException {
List readers = getSessionData().getBackendReaders();
if (readers == null) {
readers = getReaders();
}
return readers;
}
/**
* Returns the list of currently effective editors. These may differ from the currently set editors in a yet unsaved document.
* @throws WGAPIException
*/
public List getEffectiveEditors() throws WGAPIException {
List editors = getSessionData().getBackendEditors();
if (editors == null) {
editors = getEditors();
}
return editors;
}
/**
* Creates a new root entry for this area.
* @param contentType The content type for this new entry.
* @param title The title of this new entry.
* @return A newly created root entry
* @throws WGAPIException
*/
public WGStructEntry createRootEntry(WGContentType contentType, String title) throws WGAPIException {
return getDatabase().createStructEntry(this, contentType, title);
}
/**
* Creates a new root page, including struct entry and content.
* Both documents are already saved when the method exists.
* @param contentType The content type of the page
* @param title The title of the page that will be used for both struct entry
* @param language The language of the content to create. Leave null to use databases default language.
* @return The content of the created page
* @throws WGAPIException
*/
public WGContent createRootPage(WGContentType contentType, String title, String language) throws WGAPIException {
if (language == null) {
language = getDatabase().getDefaultLanguage();
}
WGLanguage lang = getDatabase().getLanguage(language);
performRootPageCreationCheck(contentType, lang);
WGStructEntry entry = createRootEntry(contentType, title);
entry.save();
WGContent content = entry.createContent(lang, title);
content.save();
return content;
}
/**
* Variant of {@link #createRootPage(WGContentType, String, String)} that always uses default language.
* @param contentType
* @param title
* @return The content of the created page
* @throws WGAPIException
*/
public WGContent createRootPage(WGContentType contentType, String title) throws WGAPIException {
return createRootPage(contentType, title, null);
}
/**
* @throws WGAPIException
* @see WGDocument#dropCache()
*/
public void dropCache() throws WGAPIException {
dropRelations();
super.dropCache();
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.WGDocument#dropRelations()
*/
protected void dropRelations() {
this.rootEntries = null;
}
/**
* Sets the name of this area.
* @throws WGAPIException
*/
protected boolean setName(String name) throws WGAPIException {
return setMetaData(META_NAME, name);
}
/*
* @see de.innovationgate.webgate.api.WGDocument#createClone(de.innovationgate.webgate.api.WGDatabase)
*/
public WGDocument createClone(WGDatabase db) throws WGAPIException {
WGArea newArea = db.createArea(getName());
try {
pushData(newArea);
if (newArea.save(getLastModified())) {
return newArea;
}
else {
return null;
}
} catch (WGAPIException e) {
// try to remove previous created area
try {
newArea.remove();
}
catch (WGAPIException e1) {
}
throw e;
}
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.WGDocument#pushData(de.innovationgate.webgate.api.WGDocument)
*/
public void pushData(WGDocument doc) throws WGAPIException {
super.pushData(doc);
WGArea area = (WGArea) doc;
area.setDescription(getDescription());
area.setEditors(new ArrayList(getEditors()));
area.setReaders(new ArrayList(getReaders()));
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.WGDocument#remove()
*/
public boolean remove() throws WGAPIException {
// We must explicitly call the removal check before innerRemove() because we are gonna delete document before we reach this method
performRemoveCheck();
if (!db.hasFeature(WGDatabase.FEATURE_AUTOCASCADES_DELETIONS) && db.getSessionContext().isCascadeDeletions()) {
Iterator doomed = getRootEntries().iterator();
while (doomed.hasNext()) {
WGStructEntry doomedEntry = (WGStructEntry) doomed.next();
if (!doomedEntry.isDeleted()) {
doomedEntry.remove();
}
}
}
return innerRemove();
}
public void performRemoveCheck() throws WGAPIException {
super.performRemoveCheck();
if (db.getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_MANAGER) {
throw new WGAuthorisationException("You are not authorized to delete areas in this database");
}
if (!getDatabase().isDoctypeModifiable(WGDocument.TYPE_AREA)) {
throw new WGAuthorisationException("Removing areas via WGAPI is not permitted in this database");
}
if (!mayEditAreaChildren()) {
throw new WGAuthorisationException("You are not authorized to delete this area");
}
// Perform removal check on all child entries
Iterator entries = getRootEntries().iterator();
while (entries.hasNext()) {
((WGStructEntry) entries.next()).performRemoveCheck();
}
}
/**
* Creates a new content object as root in this area. This is only usable in content stores without struct entries (e.g. all descendants of SimpleContentSource).
* @param key The key for the new content
* @param title The title of the new content
* @return Newly created content object
* @throws WGAPIException
*/
public WGContent createContent(Object key, String title) throws WGAPIException {
return getDatabase().createContent(this, key, title);
}
/**
* Creates a new content as root for this area. This is only usable in content stores without struct entries (e.g. all descendants of SimpleContentSource).
* @return Newly created root content.
* @throws WGAPIException
*/
public WGContent createContent() throws WGAPIException {
return createContent(null, "");
}
public Class getChildNodeType() {
return WGStructEntry.class;
}
public List getChildNodes() throws WGAPIException {
return new ArrayList(getRootEntries());
}
public PageHierarchyNode getParentNode() throws WGAPIException {
return getDatabase().getAllDocumentsHierarchy().getCollectionForType(WGDocument.TYPE_AREA);
}
public String getNodeKey() throws WGAPIException {
return getDocumentKey().toString();
}
public String getNodeTitle(String language) throws WGAPIException {
return getName();
}
public void performSaveCheck() throws ResourceIsLockedException, WGAPIException {
super.performSaveCheck();
List readers = (List) getEffectiveReaders();
if (!WGDatabase.anyoneAllowed(readers) && !this.db.isMemberOfUserList(readers)) {
throw new WGAuthorisationException("Updating areas via WGAPI is not permitted in this database");
}
List editors = (List) getEffectiveEditors();
if (!WGDatabase.anyoneAllowed(editors) && !this.db.isMemberOfUserList(editors)) {
throw new WGAuthorisationException("You are not authorized to modify this area");
}
if (!getDatabase().isDoctypeModifiable(WGDocument.TYPE_AREA)) {
throw new WGAuthorisationException("Updating areas via WGAPI is not permitted in this database");
}
}
/**
* Implements the visitor pattern on the page hierarchy of this area. The visitor visits all struct entries and all of their (non-archived) contents.
* @param visitor
* @throws WGAPIException
*/
public void visit(WGPageVisitor visitor) throws WGAPIException {
visitor.visit(this);
List<WGStructEntry> roots = getRootEntries().asList();
for (WGStructEntry root : roots) {
root.visit(visitor);
}
}
/**
* Checks if the current user may create a new root struct entry of the given content type. If so the method exits normally. Otherwise an exception is thrown.
* @param ct The content type to use.
* @throws WGAPIException If the user may not create the document. The exception informs about the reason.
*/
public void performRootCreationCheck(WGContentType ct) throws WGAPIException {
getDatabase().performStructCreationCheck(null, this, ct);
}
/**
* Checks if the current user may create a new root page with the given data. If so the method exits normally. Otherwise an exception is thrown.
* @param ct The content type to use for the struct entry
* @param lang The language to use for the content
* @throws WGAPIException If the user may not create the document. The exception informs about the reason.
*/
public void performRootPageCreationCheck(WGContentType ct, WGLanguage lang) throws WGAPIException {
performRootCreationCheck(ct);
getDatabase().performContentCreationCheck(null, lang);
}
@Override
public int getType() {
return WGDocument.TYPE_AREA;
}
public boolean isSystemArea() throws WGAPIException {
return (Boolean) getMetaData(META_SYSTEM);
}
public void setSystemArea(boolean system) throws WGAPIException {
setMetaData(META_SYSTEM, system);
}
private SessionData getSessionData() {
DocumentContext con = getDocumentSessionContext();
SessionData data = (SessionData) con.getCustomData();
if (data == null) {
data = new SessionData();
con.setCustomData(data);
}
return data;
}
protected void updateBackendCaches(WGDocumentCore core) {
super.updateBackendCaches(core);
try {
de.innovationgate.webgate.api.WGArea.SessionData sessionData = getSessionData();
sessionData.setBackendEditors((List) core.getMetaData(WGArea.META_EDITORS));
sessionData.setBackendReaders((List) core.getMetaData(WGArea.META_READERS));
}
catch (WGAPIException e) {
WGFactory.getLogger().error("Error updating area backend cache", e);
}
}
/**
* Tests if the current user may read contents in this area
* @throws WGAPIException
*/
public boolean mayReadContent() throws WGAPIException {
// Are hierarchical reader fields enabled ?
if (!getDatabase().isPageReadersEnabled()) {
return true;
}
List readers = getEffectiveReaders();
return (WGDatabase.anyoneAllowed(readers, true) || getDatabase().isMemberOfUserList(readers));
}
}