/*******************************************************************************
* 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.jdbc;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import de.bannkreis.groq.Groq;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGBackendException;
import de.innovationgate.webgate.api.WGIllegalArgumentException;
import de.innovationgate.webgate.api.WGPortlet;
import de.innovationgate.webgate.api.WGPortletDefaultBean;
import de.innovationgate.webgate.api.WGPortletRegistry;
import de.innovationgate.webgate.api.WGSystemException;
import de.innovationgate.webgate.api.WGUserProfile;
import de.innovationgate.wga.common.ImmutableObject;
import de.innovationgate.wga.common.beans.csconfig.v1.Version;
public class PortletRegistryImplV4 extends WGPortletRegistry implements ImmutableObject {
public static final DecimalFormat PORTLETINDEX_FORMAT = new DecimalFormat("000");
public static final Pattern PATTERN_OLD_STYLE_PORTLETKEY = Pattern.compile("^p\\d+$");
public static String buildNamespacePrefix(String dbkey) {
dbkey = dbkey.toLowerCase();
String prefix = StringUtils.left(dbkey, 1) + StringUtils.right(dbkey, 2);
if (prefix.length() < 3) {
prefix += StringUtils.repeat("x", 3 - prefix.length());
}
// Escape non alphanumeric characters as x
char[] chars = prefix.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (!Character.isLetterOrDigit(chars[i])) {
chars[i] = 'x';
}
}
return new String(chars);
}
private WGDatabaseImpl _parent;
private UserProfile _profile;
private WGDocumentImpl _docImpl;
public PortletRegistryImplV4(WGDatabaseImpl parent, WGDocumentImpl docImpl) {
_parent = parent;
_docImpl = docImpl;
_profile = (UserProfile) docImpl.getNativeObject();
}
public synchronized void insertPortlet(WGPortlet portlet) throws WGAPIException {
if (portlet.isRoot()) {
throw new WGIllegalArgumentException("Cannot insert a root portlet");
}
List portletKeys = getPortletIds(portlet.getApplicationDb());
if (portlet.getParentPortletKey() != null && !isRootPortletKey(portlet.getParentPortletKey()) && !portletKeys.contains(portlet.getParentPortletKey())) {
throw new IllegalStateException("Cannot register portlet for parent '" + portlet.getParentPortletKey() + "' because the parent portlet is not registered");
}
synchronized (this) {
// Generate portlet key
String childPortletKey = generateNewPortletKey(portlet.getApplicationDb());
// Register portlet
WGPortletDefaultBean portletBean = (WGPortletDefaultBean) portlet;
insertPortletId(portletBean, childPortletKey);
_profile.getPortletkeys().add(serializePortlet(portlet));
}
}
public WGPortlet getPortlet(String appDb, String key) throws WGAPIException {
if (isRootPortletKey(key)) {
return getRootPortlet(appDb);
}
Iterator<String> regs = _profile.getPortletkeys().iterator();
while (regs.hasNext()) {
String reg = (String) regs.next();
WGPortlet portlet = deserializePortlet(reg, appDb);
// We do not need to check appDb here bc. in this portlet registry impl the appDb is implicitly contained in the portlet key
if (portlet.getKey().equals(key)) {
return portlet;
}
}
return null;
}
private boolean isRootPortletKey(String key) {
if (key.equals("")) {
return true;
}
if (key.length() == 6) {
try {
String indexStr = key.substring(3);
Number index = PORTLETINDEX_FORMAT.parse(indexStr);
if (index.intValue() == 0) {
return true;
}
}
catch (Exception e) {
}
}
return false;
}
public List<String> getPortletIds(String appDb) throws WGAPIException {
Iterator<String> regs = _profile.getPortletkeys().iterator();
List<String> keys = new ArrayList<String>();
while (regs.hasNext()) {
String reg = (String) regs.next();
WGPortlet portlet = deserializePortlet(reg, appDb);
if (doesPortletKeyMatchAppDb(appDb, portlet)) {
keys.add(portlet.getKey());
}
}
return keys;
}
private boolean doesPortletKeyMatchAppDb(String appDb, WGPortlet portlet) {
boolean matchesAppDb = false;
String expectedKeyPrefix = buildNamespacePrefix(appDb);
if (portlet.getKey().startsWith(expectedKeyPrefix)) {
matchesAppDb = true;
}
// Check for old style portlet registrations - Here we cannot determine if the portletkey matches any application
else if (PATTERN_OLD_STYLE_PORTLETKEY.matcher(portlet.getKey()).matches()) {
matchesAppDb = true;
}
return matchesAppDb;
}
public void removePortlet(WGPortlet portlet) throws WGAPIException {
if (portlet.isRoot()) {
throw new WGIllegalArgumentException("Cannot remove the root portlet");
}
// First unregister child portlets
Iterator<WGPortlet> children = getChildPortlets(portlet).iterator();
while (children.hasNext()) {
WGPortlet child = (WGPortlet) children.next();
removePortlet(child);
}
// Cleanup all items
clearItems(portlet);
// Remove registration
Iterator<String> regs = _profile.getPortletkeys().iterator();
while (regs.hasNext()) {
String reg = (String) regs.next();
WGPortlet p = deserializePortlet(reg, portlet.getApplicationDb());
// We do not need to check appDb here bc. in this portlet registry impl the appDb is implicitly contained in the portlet key
if (p.getKey().equals(portlet.getKey())) {
regs.remove();
}
}
}
private synchronized String generateNewPortletKey(String appDb) throws WGAPIException {
String charPart = buildNamespacePrefix(appDb);
// Find the last portlet key with this prefix
int portletIndex = 0;
List portletKeys = getPortletIds(appDb);
if (!portletKeys.isEmpty()) {
Iterator iter = portletKeys.iterator();
while (iter.hasNext()) {
String pkey = (String) iter.next();
if (pkey.startsWith(charPart)) {
int tempValue = Integer.parseInt(pkey.substring(charPart.length()));
if (portletIndex < tempValue) {
portletIndex = tempValue;
}
}
}
}
portletIndex++;
return charPart + PORTLETINDEX_FORMAT.format(portletIndex);
}
private String serializePortlet(WGPortlet portlet) {
List elements = new ArrayList();
elements.add(portlet.getKey());
elements.add(portlet.getParentPortletKey());
if (portlet.getDesignDb() != null) {
elements.add(portlet.getDesignDb() + "/" + portlet.getDesign());
}
else {
elements.add(portlet.getDesign());
}
elements.add(portlet.getName());
return WGUtils.serializeCollection(elements, "|", null, true);
}
private WGPortletDefaultBean deserializePortlet(String portletRegistration, String appDb) {
// getting Indizes of delimiters
List regElements = WGUtils.deserializeCollection(portletRegistration, "|");
if (regElements.size() < 4) {
throw new IllegalArgumentException("Portlet registration elements are " + regElements.size() + ", must be at least 4: " + portletRegistration);
}
// separate string into components
String key = (String) regElements.get(0);
String parentPortletId = (String) regElements.get(1);
WGPortletDefaultBean portlet = createPortletDefaultBean(key, appDb, parentPortletId);
String db = null;
String design = (String) regElements.get(2);
int slashPos = design.indexOf("/");
// Extract database if available
if (slashPos != -1) {
db = design.substring(0, slashPos);
design = design.substring(slashPos + 1);
}
String title = (String) regElements.get(3);
portlet.setDesignDb(db);
portlet.setDesign(design);
portlet.setName(title);
return portlet;
}
@Override
public void updatePortlet(WGPortlet portletToUpdate) throws WGIllegalArgumentException {
if (portletToUpdate.isRoot()) {
throw new WGIllegalArgumentException("Cannot update the root portlet");
}
List regs = _profile.getPortletkeys();
for (int idx=0; idx < regs.size(); idx++) {
String reg = (String) regs.get(idx);
WGPortlet portlet = deserializePortlet(reg, portletToUpdate.getApplicationDb());
// We do not need to check appDb here bc. in this portlet registry impl the appDb is implicitly contained in the portlet key
if (portlet.getKey().equals(portletToUpdate.getKey())) {
regs.set(idx, serializePortlet(portletToUpdate));
return;
}
}
throw new WGIllegalArgumentException("No portlet of id '" + portletToUpdate.getKey() + "' has yet been registered");
}
@Override
public List<WGPortlet> getChildPortlets(WGPortlet parent) throws WGAPIException {
List<WGPortlet> children = new ArrayList<WGPortlet>();
List regs = _profile.getPortletkeys();
for (int idx=0; idx < regs.size(); idx++) {
String reg = (String) regs.get(idx);
WGPortletDefaultBean portlet = deserializePortlet(reg, parent.getApplicationDb());
if (parent.getKey().equals(portlet.getParentPortletKey())) {
children.add(portlet);
}
}
return children;
}
@Override
public WGPortlet getRootPortlet(String dbKey) throws WGAPIException {
String rootNS = determineRootPortletNamespace(dbKey);
return createPortletDefaultBean(rootNS, dbKey, null);
}
private String determineRootPortletNamespace(String dbKey) throws WGAPIException {
UserProfileItem wgaCreationVersion = (UserProfileItem) _profile.getItems().get(WGUserProfile.ITEM_WGACREATIONVERSION);
if (wgaCreationVersion != null) {
Version version = new Version(wgaCreationVersion.getText());
// From WGA Version 4.1 on the root portlet has a defined namespace, based on the dbkey of the main context, plus index number 000.
if (version.isAtLeast(4, 1)) {
String nsPrefix = buildNamespacePrefix(dbKey);
return nsPrefix + "000";
}
}
// For earlier WGA versions the root portlet has an empty string namespace
// (which is problematic when multiple portlet application dbs share the same perso db)
return "";
}
@Override
public void prepareNewProfile(WGUserProfile profile) throws WGAPIException {
}
@Override
public List<String> getItemNames(WGPortlet portlet) {
String itemPrefix = getItemPrefix(portlet);
List<String> list = Groq
.selectFrom(_profile.getItems().keySet())
.whereStartsWith(itemPrefix)
.substring(itemPrefix.length());
return list;
}
@Override
public Object getItemValue(WGPortlet portlet, String name) throws WGSystemException, WGBackendException {
return _docImpl.getItemValue(getItemPrefix(portlet) + name);
}
@Override
public void removeItem(WGPortlet portlet, String name) throws WGIllegalArgumentException {
_docImpl.removeItem(getItemPrefix(portlet) + name);
}
@Override
public void setItemValue(WGPortlet portlet, String name, Object value) throws WGIllegalArgumentException, WGSystemException {
_docImpl.setItemValue(getItemPrefix(portlet) + name, value);
}
private String getItemPrefix(WGPortlet portlet) {
return portlet.getKey() + "_";
}
@Override
public boolean hasItem(WGPortlet portlet, String name) throws WGAPIException {
return _docImpl.hasItem(getItemPrefix(portlet) + name);
}
@Override
public WGPortlet createPortlet(String appDb, WGPortlet parent) {
return createPortletDefaultBean(null, appDb, (parent != null ? parent.getKey() : null));
}
@Override
public Set<String> getAppDbs() {
Set<String> apps = new HashSet<String>();
List regs = _profile.getPortletkeys();
boolean oldStylePresent = false;
for (int idx=0; idx < regs.size(); idx++) {
String reg = (String) regs.get(idx);
WGPortletDefaultBean portlet = deserializePortlet(reg, "");
if (!PATTERN_OLD_STYLE_PORTLETKEY.matcher(portlet.getKey()).matches()) {
String app = portlet.getKey().substring(0, 3);
apps.add(app);
}
else {
oldStylePresent = true;
}
}
return apps;
}
}