/**
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.portal.pom.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import org.exoplatform.portal.config.NoSuchDataException;
import org.exoplatform.portal.config.StaleModelException;
import org.exoplatform.portal.config.UserACL;
import org.exoplatform.portal.config.model.ApplicationState;
import org.exoplatform.portal.config.model.ApplicationType;
import org.exoplatform.portal.config.model.CloneApplicationState;
import org.exoplatform.portal.config.model.Container;
import org.exoplatform.portal.config.model.PersistentApplicationState;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.config.model.TransientApplicationState;
import org.exoplatform.portal.mop.Described;
import org.exoplatform.portal.mop.ProtectedContainer;
import org.exoplatform.portal.mop.ProtectedResource;
import org.exoplatform.portal.mop.redirects.Condition;
import org.exoplatform.portal.mop.redirects.DeviceProperty;
import org.exoplatform.portal.mop.redirects.Mappings;
import org.exoplatform.portal.mop.redirects.NodeMap;
import org.exoplatform.portal.mop.redirects.Redirect;
import org.exoplatform.portal.mop.redirects.Redirectable;
import org.exoplatform.portal.pom.config.POMSession;
import org.exoplatform.portal.pom.config.Utils;
import org.exoplatform.portal.pom.spi.portlet.Portlet;
import org.exoplatform.services.jcr.util.Text;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.mop.api.Attributes;
import org.gatein.mop.api.content.ContentType;
import org.gatein.mop.api.content.Customization;
import org.gatein.mop.api.workspace.ObjectType;
import org.gatein.mop.api.workspace.Site;
import org.gatein.mop.api.workspace.Templatized;
import org.gatein.mop.api.workspace.WorkspaceObject;
import org.gatein.mop.api.workspace.ui.UIBody;
import org.gatein.mop.api.workspace.ui.UIComponent;
import org.gatein.mop.api.workspace.ui.UIContainer;
import org.gatein.mop.api.workspace.ui.UIWindow;
import org.gatein.mop.core.util.Tools;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class Mapper {
/** . */
private static final Set<String> propertiesBlackList = Tools.set("jcr:uuid", "jcr:primaryType");
/** . */
private static final Set<String> portalPropertiesBlackList = Tools.set(MappedAttributes.LOCALE.getName(),
MappedAttributes.SKIN.getName());
/** . */
private static final Set<String> windowPropertiesBlackList = Tools.set(MappedAttributes.THEME.getName(),
MappedAttributes.TYPE.getName(), MappedAttributes.ICON.getName(), MappedAttributes.WIDTH.getName(),
MappedAttributes.HEIGHT.getName());
/** . */
private final POMSession session;
/** . */
private final Logger log = LoggerFactory.getLogger(Mapper.class);
public Mapper(POMSession session) {
this.session = session;
}
public PortalData load(Site src) {
String type = Mapper.getOwnerType(src.getObjectType());
Attributes attrs = src.getAttributes();
//
Templatized templarized = src.getRootNavigation().getTemplatized();
org.gatein.mop.api.workspace.Page template = templarized.getTemplate();
UIContainer srcLayout = template.getRootComponent();
//
Map<String, String> properties = new HashMap<String, String>();
load(attrs, properties, portalPropertiesBlackList);
//
List<ComponentData> layoutChildren = loadChildren(srcLayout);
ContainerData layout = load(srcLayout, layoutChildren);
//
List<String> accessPermissions = Collections.emptyList();
String editPermission = null;
if (src.isAdapted(ProtectedResource.class)) {
ProtectedResource pr = src.adapt(ProtectedResource.class);
accessPermissions = pr.getAccessPermissions();
editPermission = pr.getEditPermission();
}
//
List<RedirectData> redirects = null;
if (src.isAdapted(Redirectable.class)) {
Redirectable redirectAble = src.adapt(Redirectable.class);
redirects = loadRedirects(src, redirectAble.getRedirects());
}
Described described = src.adapt(Described.class);
//
return new PortalData(src.getObjectId(), src.getName(), type, attrs.getValue(MappedAttributes.LOCALE),
described.getName(), described.getDescription(), accessPermissions, editPermission,
Collections.unmodifiableMap(properties), attrs.getValue(MappedAttributes.SKIN), layout, redirects);
}
private List<RedirectData> loadRedirects(Site src, Map<String, Redirect> redirects) {
if (redirects == null || redirects.isEmpty()) {
return null;
} else {
List<RedirectData> redirectsData = new ArrayList<RedirectData>();
for (Redirect redirect : redirects.values()) {
RedirectData redirectDate = new RedirectData(src.getObjectId(), redirect.getSite(), redirect.getName(),
redirect.getEnabled(), buildConditionData(src, redirect.getConditions()), buildMappingsData(
src.getObjectId(), redirect.getMapping()));
redirectsData.add(redirectDate);
}
return redirectsData;
}
}
private List<RedirectConditionData> buildConditionData(Site src, Map<String, Condition> conditions) {
List<RedirectConditionData> conditionDatas = new ArrayList<RedirectConditionData>();
for (Condition condition : conditions.values()) {
RedirectConditionData conditionData = new RedirectConditionData(src.getObjectId(), null, condition.getName());
RedirectUserAgentConditionData userAgentConditionData = new RedirectUserAgentConditionData(src.getObjectId(), null);
userAgentConditionData.getUserAgentContains().addAll(condition.getUserAgentContains());
userAgentConditionData.getUserAgentDoesNotContain().addAll(condition.getUserAgentDoesNotContain());
conditionData.setUserAgentConditionData(userAgentConditionData);
for (DeviceProperty deviceProperty : condition.getDeviceProperties().values()) {
RedirectDevicePropertyConditionData propertyConditionData = new RedirectDevicePropertyConditionData(
src.getObjectId(), null, deviceProperty.getName());
if (deviceProperty.getEquals() != null) {
propertyConditionData.setEquals(deviceProperty.getEquals());
}
if (deviceProperty.getGreaterThan() != null) {
propertyConditionData.setGreaterThan(deviceProperty.getGreaterThan());
}
if (deviceProperty.getLessThan() != null) {
propertyConditionData.setLessThan(deviceProperty.getLessThan());
}
if (deviceProperty.getPattern() != null) {
propertyConditionData.setMatches(Pattern.compile(deviceProperty.getPattern()));
}
conditionData.getDevicePropertyConditionData().add(propertyConditionData);
}
conditionDatas.add(conditionData);
}
return conditionDatas;
}
private RedirectMappingsData buildMappingsData(String storageId, Mappings mappings) {
if (mappings != null) {
RedirectMappingsData redirectMappingsData = new RedirectMappingsData(storageId);
if (mappings.getUnresolvedNodeMatching() != null) {
redirectMappingsData.setUnresolvedNode(mappings.getUnresolvedNodeMatching());
}
if (mappings.getNodeNameMatching() != null) {
redirectMappingsData.setUseNodeNameMatching(mappings.getNodeNameMatching());
}
if (mappings.getNodeMap() != null) {
HashMap<String, String> map = new HashMap<String, String>();
for (NodeMap nodeMap : mappings.getNodeMap().values()) {
map.put(nodeMap.getOriginNode(), nodeMap.getRedirectNode());
}
redirectMappingsData.getMappings().putAll(map);
}
return redirectMappingsData;
} else {
return null;
}
}
public void save(PortalData src, Site dst) {
try {
if (src.getStorageId() != null && !src.getStorageId().equals(dst.getObjectId())) {
String msg = "Attempt to save a site " + src.getType() + "/" + src.getName() + " on the wrong target site "
+ dst.getObjectType() + "/" + dst.getName();
throw new IllegalArgumentException(msg);
}
//
Attributes attrs = dst.getAttributes();
attrs.setValue(MappedAttributes.LOCALE, src.getLocale());
attrs.setValue(MappedAttributes.SKIN, src.getSkin());
if (src.getProperties() != null) {
save(src.getProperties(), attrs, portalPropertiesBlackList);
}
ProtectedResource pr = dst.adapt(ProtectedResource.class);
pr.setAccessPermissions(src.getAccessPermissions());
pr.setEditPermission(src.getEditPermission());
Described described = dst.adapt(Described.class);
described.setName(src.getLabel());
described.setDescription(src.getDescription());
Redirectable redirectable = dst.adapt(Redirectable.class);
Map<String, Redirect> redirects = redirectable.getRedirects();
List<RedirectData> redirectsData = src.getRedirects();
redirects.clear(); // clear the redirects map since we need to rebuild it based on the new redirects
if (src.getRedirects() != null) {
for (RedirectData redirectData : redirectsData) {
Redirect redirect = redirectable.createRedirect();
redirects.put(redirectData.getRedirectName(), redirect);
redirect.setName(redirectData.getRedirectName());
redirect.setSite(redirectData.getRedirectSiteName());
redirect.setEnabled(redirectData.isEnabled());
if (redirectData.getConditions() != null) {
redirect.getConditions().clear(); // clear the map so that we can rebuild it
for (RedirectConditionData conditionData : redirectData.getConditions()) {
Condition condition = redirect.createCondition();
redirect.getConditions().put(conditionData.getRedirectName(), condition);
buildCondition(conditionData, condition);
}
}
if (redirectData.getMappings() != null) {
Mappings mappings = redirect.getMapping();
if (redirect.getMapping() == null) {
mappings = redirect.createMapping();
redirect.setMapping(mappings);
}
buildMappings(redirectData.getMappings(), mappings);
}
}
}
//
org.gatein.mop.api.workspace.Page templates = dst.getRootPage().getChild("templates");
org.gatein.mop.api.workspace.Page template = templates.getChild("default");
if (template == null) {
template = templates.addChild("default");
}
//
ContainerData srcContainer = src.getPortalLayout();
UIContainer dstContainer = template.getRootComponent();
// Workaround to have the real source container used as the model / UI layer lose this
// ID which lead to bugs
ContainerData realSrcContainer = new ContainerData(dstContainer.getObjectId(), srcContainer.getId(),
srcContainer.getName(), srcContainer.getIcon(), srcContainer.getTemplate(), srcContainer.getFactoryId(),
srcContainer.getTitle(), srcContainer.getDescription(), srcContainer.getWidth(), srcContainer.getHeight(),
srcContainer.getAccessPermissions(), srcContainer.getMoveAppsPermissions(),
srcContainer.getMoveContainersPermissions(), srcContainer.getChildren());
//
save(realSrcContainer, dstContainer);
saveChildren(realSrcContainer, dstContainer);
//
Templatized templatized = dst.getRootNavigation().getTemplatized();
if (templatized != null) {
templatized.setTemplate(template);
} else {
template.templatize(dst.getRootNavigation());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
private void buildCondition(RedirectConditionData redirectConditionData, Condition condition) {
condition.setName(redirectConditionData.getRedirectName());
if (redirectConditionData.getUserAgentConditionData() != null) {
condition.setUserAgentContains(redirectConditionData.getUserAgentConditionData().getUserAgentContains());
}
if (redirectConditionData.getUserAgentConditionData() != null) {
condition
.setUserAgentDoesNotContain(redirectConditionData.getUserAgentConditionData().getUserAgentDoesNotContain());
}
// if the device property conditions is empty and there are currently no stored device properties, then skip
// otherwise we need to either remove or add device properties
if (redirectConditionData.getDevicePropertyConditionData() != null || !condition.getDeviceProperties().isEmpty()) {
// A list of stored redirect names which have not been found in the current RedirectConditionData list.
//
// This is because during a save we receive a list of RedirectData, and we need to keep track if an existing
// stored redirect has been deleted or not.
Set<String> notFoundProperties = new HashSet<String>(condition.getDeviceProperties().keySet());
for (RedirectDevicePropertyConditionData propertyConditionData : redirectConditionData
.getDevicePropertyConditionData()) {
DeviceProperty deviceProperty = condition.getDeviceProperties().get(propertyConditionData.getPropertyName());
// if not found, then create it
if (deviceProperty == null) {
deviceProperty = condition.createDeviceProperty();
condition.getDeviceProperties().put(propertyConditionData.getPropertyName(), deviceProperty);
} else {
// if found, remove from the nonFound list
notFoundProperties.remove(propertyConditionData.getPropertyName());
}
deviceProperty.setName(propertyConditionData.getPropertyName());
if (propertyConditionData.getGreaterThan() != null) {
deviceProperty.setGreaterThan(propertyConditionData.getGreaterThan());
}
if (propertyConditionData.getLessThan() != null) {
deviceProperty.setLessThan(propertyConditionData.getLessThan());
}
if (propertyConditionData.getEquals() != null) {
deviceProperty.setEquals(propertyConditionData.getEquals());
}
if (propertyConditionData.getMatches() != null) {
deviceProperty.setPattern(propertyConditionData.getMatches().toString());
}
}
// remove all the device conditions which were not in the RedirectConditionData list
for (String devicePropertyName : notFoundProperties) {
condition.getDeviceProperties().remove(devicePropertyName);
}
}
}
private void buildMappings(RedirectMappingsData mappingsData, Mappings mappings) {
mappings.setNodeNameMatching(mappingsData.isUseNodeNameMatching());
mappings.setUnresolvedNodeMatching(mappingsData.getUnresolvedNode());
// if the mappings conditions is empty and there are currently no stored mappings, then skip
// otherwise we need to either remove or add mappings
if (!mappingsData.getMappings().isEmpty() || !mappings.getNodeMap().isEmpty()) {
// A list of stored node mappings which have not been found in the current RedirectMappingsData list.
//
// This is because during a save we receive a list of nodes from RedirectMappingsData, and we need to keep track
// if an existing stored redirect node has been deleted or not.
Set<String> notFoundNodeMappings = new HashSet<String>(mappings.getNodeMap().keySet());
for (String key : mappingsData.getMappings().keySet()) {
NodeMap nodeMap = mappings.getNodeMap().get(Text.escapeIllegalJcrChars(key));
if (nodeMap == null) {
nodeMap = mappings.createNode();
mappings.getNodeMap().put(Text.escapeIllegalJcrChars(key), nodeMap);
} else {
notFoundNodeMappings.remove(Text.escapeIllegalJcrChars(key));
}
nodeMap.setOriginNode(key);
nodeMap.setRedirectNode(mappingsData.getMappings().get(key));
}
// remove all the device conditions which were not in the RedirectConditionData list
for (String nodeName : notFoundNodeMappings) {
mappings.getNodeMap().remove(nodeName);
}
}
}
public PageData load(org.gatein.mop.api.workspace.Page src) {
Site site = src.getSite();
String ownerType = getOwnerType(site.getObjectType());
String ownerId = site.getName();
String name = src.getName();
List<ComponentData> children = loadChildren(src.getRootComponent());
List<String> moveAppsPermissions = null;
List<String> moveContainersPermissions = null;
if (src.isAdapted(ProtectedContainer.class)) {
ProtectedContainer pc = src.adapt(ProtectedContainer.class);
moveAppsPermissions = pc.getMoveAppsPermissions();
moveContainersPermissions = pc.getMoveContainersPermissions();
} else {
moveAppsPermissions = Collections.emptyList();
moveContainersPermissions = Collections.emptyList();
}
//
return new PageData(src.getObjectId(), null, name, null, null, null, null, null, null, null,
Collections.<String> emptyList(), children, ownerType, ownerId, null, false, moveAppsPermissions,
moveContainersPermissions);
}
private ContainerData load(UIContainer src, List<ComponentData> children) {
//
List<String> accessPermissions = Collections.emptyList();
if (src.isAdapted(ProtectedResource.class)) {
ProtectedResource pr = src.adapt(ProtectedResource.class);
accessPermissions = pr.getAccessPermissions();
}
List<String> moveAppsPermissions = null;
List<String> moveContainersPermissions = null;
if (src.isAdapted(ProtectedContainer.class)) {
ProtectedContainer pc = src.adapt(ProtectedContainer.class);
moveAppsPermissions = pc.getMoveAppsPermissions();
moveContainersPermissions = pc.getMoveContainersPermissions();
} else {
/* legacy mode */
moveAppsPermissions = ProtectedContainer.DEFAULT_MOVE_APPLICATIONS_PERMISSIONS;
moveContainersPermissions = ProtectedContainer.DEFAULT_MOVE_CONTAINERS_PERMISSIONS;
}
//
Described described = src.adapt(Described.class);
Attributes attrs = src.getAttributes();
return new ContainerData(src.getObjectId(), attrs.getValue(MappedAttributes.ID), attrs.getValue(MappedAttributes.NAME),
attrs.getValue(MappedAttributes.ICON), attrs.getValue(MappedAttributes.TEMPLATE),
attrs.getValue(MappedAttributes.FACTORY_ID), described.getName(), described.getDescription(),
attrs.getValue(MappedAttributes.WIDTH), attrs.getValue(MappedAttributes.HEIGHT),
Utils.safeImmutableList(accessPermissions), Utils.safeImmutableList(moveAppsPermissions),
Utils.safeImmutableList(moveContainersPermissions), children);
}
private List<ComponentData> loadChildren(UIContainer src) {
if (src == null)
throw new NoSuchDataException("Can not load children");
ArrayList<ComponentData> children = new ArrayList<ComponentData>();
for (UIComponent component : src.getComponents()) {
// Obtain a model object from the ui component
ComponentData mo;
if (component instanceof UIContainer) {
UIContainer srcContainer = (UIContainer) component;
Attributes attrs = srcContainer.getAttributes();
String type = attrs.getValue(MappedAttributes.TYPE);
if ("dashboard".equals(type)) {
Site owner = src.getPage().getSite();
TransientApplicationState<Portlet> state = new TransientApplicationState<Portlet>(
"dashboard/DashboardPortlet", null, getOwnerType(owner.getObjectType()), owner.getName());
//
boolean showInfoBar = attrs.getValue(MappedAttributes.SHOW_INFO_BAR, false);
boolean showMode = attrs.getValue(MappedAttributes.SHOW_MODE, false);
boolean showWindowState = attrs.getValue(MappedAttributes.SHOW_WINDOW_STATE, false);
String theme = attrs.getValue(MappedAttributes.THEME, null);
Described described = srcContainer.adapt(Described.class);
String id = attrs.getValue(MappedAttributes.ID, null);
String icon = attrs.getValue(MappedAttributes.ICON, null);
String width = attrs.getValue(MappedAttributes.WIDTH, null);
String height = attrs.getValue(MappedAttributes.HEIGHT, null);
//
List<String> a = Collections.singletonList(UserACL.EVERYONE);
if (srcContainer.isAdapted(ProtectedResource.class)) {
ProtectedResource pr = srcContainer.adapt(ProtectedResource.class);
a = pr.getAccessPermissions();
}
//
mo = new ApplicationData<Portlet>(srcContainer.getObjectId(), component.getName(), ApplicationType.PORTLET,
state, id, described.getName(), icon, described.getDescription(), showInfoBar, showWindowState,
showMode, theme, width, height, Collections.<String, String> emptyMap(), a);
} else {
List<ComponentData> dstChildren = loadChildren(srcContainer);
mo = load(srcContainer, dstChildren);
}
} else if (component instanceof UIWindow) {
UIWindow window = (UIWindow) component;
ApplicationData<?> application = load(window);
mo = application;
} else if (component instanceof UIBody) {
mo = new BodyData(component.getObjectId(), BodyType.PAGE);
} else {
throw new AssertionError();
}
// Add among children
children.add(mo);
}
return children;
}
public List<ModelChange> save(PageData src, Site site, String name) throws IllegalStateException {
org.gatein.mop.api.workspace.Page root = site.getRootPage();
org.gatein.mop.api.workspace.Page pages = root.getChild("pages");
org.gatein.mop.api.workspace.Page dst = pages.getChild(name);
//
LinkedList<ModelChange> changes = new LinkedList<ModelChange>();
//
if (dst == null) {
throw new NoSuchDataException("The page " + name + " not found");
} else {
changes.add(new ModelChange.Update(src));
}
//
UIContainer rootContainer = dst.getRootComponent();
// We are creating a new Page with the root container id as this one is lost
// in the model / ui layer. Not doing this cause a class cast exception later
// so it's likely the best fix we can do at the moment
PageData src2 = new PageData(rootContainer.getObjectId(), src.getId(), src.getName(), src.getIcon(), src.getTemplate(),
null, null, null, src.getWidth(), src.getHeight(), Collections.<String> emptyList(), src.getChildren(),
src.getOwnerType(), src.getOwnerId(), null, false, src.getMoveAppsPermissions(),
src.getMoveContainersPermissions());
//
LinkedList<ModelChange> childrenChanges = saveChildren(src2, rootContainer);
//
changes.addAll(childrenChanges);
//
return changes;
}
private void save(ContainerData src, UIContainer dst) {
ProtectedResource pr = dst.adapt(ProtectedResource.class);
pr.setAccessPermissions(src.getAccessPermissions());
ProtectedContainer pc = dst.adapt(ProtectedContainer.class);
pc.setMoveAppsPermissions(src.getMoveAppsPermissions());
pc.setMoveContainersPermissions(src.getMoveContainersPermissions());
Described described = dst.adapt(Described.class);
described.setName(src.getTitle());
described.setDescription(src.getDescription());
Attributes dstAttrs = dst.getAttributes();
dstAttrs.setValue(MappedAttributes.ID, src.getId());
dstAttrs.setValue(MappedAttributes.TYPE, src instanceof DashboardData ? "dashboard" : null);
dstAttrs.setValue(MappedAttributes.ICON, src.getIcon());
dstAttrs.setValue(MappedAttributes.TEMPLATE, src.getTemplate());
dstAttrs.setValue(MappedAttributes.FACTORY_ID, src.getFactoryId());
dstAttrs.setValue(MappedAttributes.WIDTH, src.getWidth());
dstAttrs.setValue(MappedAttributes.HEIGHT, src.getHeight());
dstAttrs.setValue(MappedAttributes.NAME, src.getName());
}
/*
* Performs routing of the corresponding save method
*/
private void save(ModelData src, WorkspaceObject dst, LinkedList<ModelChange> changes,
Map<String, String> hierarchyRelationships) {
if (src instanceof ContainerData) {
save((ContainerData) src, (UIContainer) dst);
saveChildren((ContainerData) src, (UIContainer) dst, changes, hierarchyRelationships);
} else if (src instanceof ApplicationData) {
save((ApplicationData<?>) src, (UIWindow) dst);
} else if (src instanceof BodyData) {
// Stateless
} else {
throw new AssertionError("Was not expecting child " + src);
}
}
private LinkedList<ModelChange> saveChildren(final ContainerData src, UIContainer dst) {
LinkedList<ModelChange> changes = new LinkedList<ModelChange>();
// The relationship in the hierarchy
// basically it's a map of the relationships between parent/child nodes
// that is helpful to detect move operations
// that we make immutable to avoid any bug
Map<String, String> hierarchyRelationships = new HashMap<String, String>();
build(src, hierarchyRelationships);
hierarchyRelationships = Collections.unmodifiableMap(hierarchyRelationships);
//
saveChildren(src, dst, changes, hierarchyRelationships);
//
return changes;
}
private void build(ContainerData parent, Map<String, String> hierarchyRelationships) {
String parentId = parent.getStorageId();
for (ModelData child : parent.getChildren()) {
String childId = child.getStorageId();
if (childId != null) {
if (hierarchyRelationships.containsKey(childId)) {
throw new AssertionError("The same object is present two times in the object hierarchy");
}
// Note that we are aware that parent id may be null
hierarchyRelationships.put(childId, parentId);
}
if (child instanceof ContainerData) {
build((ContainerData) child, hierarchyRelationships);
}
}
}
private void saveChildren(final ContainerData src, UIContainer dst, LinkedList<ModelChange> changes,
Map<String, String> hierarchyRelationships) {
final List<String> orders = new ArrayList<String>();
final Map<String, ModelData> modelObjectMap = new HashMap<String, ModelData>();
//
for (ModelData srcChild : src.getChildren()) {
String srcChildId = srcChild.getStorageId();
// Flag variable, become non null if and only if we are saving a transient dashboard
ApplicationData<?> transientDashboardData = null;
// Replace dashboard application by container if needed
// this should be removed once we make the dashboard as first class
// citizen of the portal
if (srcChild instanceof ApplicationData) {
ApplicationData<?> app = (ApplicationData<?>) srcChild;
// todo julien: shouldn't we be checking for WSRP as well here?
if (app.getType() == ApplicationType.PORTLET && app.getState() instanceof TransientApplicationState) {
TransientApplicationState<?> state = (TransientApplicationState<?>) app.getState();
String contentId = state.getContentId();
if ("dashboard/DashboardPortlet".equals(contentId)) {
DashboardData data;
if (app.getStorageId() != null) {
UIContainer dstDashboard = session.findObjectById(ObjectType.CONTAINER, app.getStorageId());
data = loadDashboard(dstDashboard);
// Update those attributes as we have to do it now, they don't exist in a container
// but do exist in a dashboard container
Attributes attrs = dstDashboard.getAttributes();
attrs.setValue(MappedAttributes.SHOW_INFO_BAR, app.isShowInfoBar());
attrs.setValue(MappedAttributes.SHOW_MODE, app.isShowApplicationMode());
attrs.setValue(MappedAttributes.SHOW_WINDOW_STATE, app.isShowApplicationState());
attrs.setValue(MappedAttributes.THEME, app.getTheme());
} else {
data = DashboardData.INITIAL_DASHBOARD;
transientDashboardData = (ApplicationData<?>) srcChild;
}
//
String icon = data.getIcon();
if (icon == null)
icon = app.getIcon();
String title = data.getTitle();
if (title == null)
title = app.getTitle();
String description = data.getDescription();
if (description == null)
description = app.getDescription();
String width = data.getWidth();
if (width == null)
width = app.getWidth();
String height = data.getHeight();
if (height == null)
height = app.getHeight();
data = new DashboardData(data.getStorageId(), data.getId(), data.getName(), icon, data.getTemplate(),
data.getFactoryId(), title, description, width, height, app.getAccessPermissions(),
data.getMoveAppsPermissions(), data.getMoveContainersPermissions(), data.getChildren());
//
srcChild = data;
}
}
}
//
UIComponent dstChild;
if (srcChildId != null) {
dstChild = session.findObjectById(ObjectType.COMPONENT, srcChildId);
if (dstChild == null) {
throw new StaleModelException("Could not find supposed present child with id " + srcChildId);
}
// julien : this can fail due to a bug in chromattic not implementing equals method properly
// and is replaced with the foreach below
/*
* if (!dst.contains(dstChild)) { throw new IllegalArgumentException("Attempt for updating a ui component " +
* session.pathOf(dstChild) + "that is not present in the target ui container " + session.pathOf(dst)); }
*/
boolean found = false;
for (UIComponent child : dst.getComponents()) {
if (child.getObjectId().equals(srcChildId)) {
found = true;
break;
}
}
//
if (!found) {
if (hierarchyRelationships.containsKey(srcChildId)) {
String srcId = hierarchyRelationships.get(srcChildId);
// It's a move operation, so we move the node first
dst.getComponents().add(dstChild);
//
changes.add(new ModelChange.Move(srcId, dst.getObjectId(), srcChildId));
} else {
throw new IllegalArgumentException("Attempt for updating a ui component " + session.pathOf(dstChild)
+ " that is not present in the target ui container " + session.pathOf(dst));
}
}
//
changes.add(new ModelChange.Update(srcChild));
} else {
String name = srcChild.getStorageName();
if (name == null) {
// We manufacture one name
name = UUID.randomUUID().toString();
}
if (srcChild instanceof ContainerData) {
dstChild = dst.add(ObjectType.CONTAINER, name);
} else if (srcChild instanceof ApplicationData) {
dstChild = dst.add(ObjectType.WINDOW, name);
} else if (srcChild instanceof BodyData) {
dstChild = dst.add(ObjectType.BODY, name);
} else {
throw new StaleModelException("Was not expecting child " + srcChild);
}
changes.add(new ModelChange.Create(dst.getObjectId(), srcChild));
}
//
if (transientDashboardData != null) {
Attributes attrs = dstChild.getAttributes();
attrs.setValue(MappedAttributes.SHOW_INFO_BAR, transientDashboardData.isShowInfoBar());
attrs.setValue(MappedAttributes.SHOW_MODE, transientDashboardData.isShowApplicationMode());
attrs.setValue(MappedAttributes.SHOW_WINDOW_STATE, transientDashboardData.isShowApplicationState());
attrs.setValue(MappedAttributes.THEME, transientDashboardData.getTheme());
}
save(srcChild, dstChild, changes, hierarchyRelationships);
//
String dstChildId = dstChild.getObjectId();
modelObjectMap.put(dstChildId, srcChild);
orders.add(dstChildId);
}
// Take care of move operation that could be seen as a remove otherwise
for (UIComponent dstChild : dst.getComponents()) {
String dstChildId = dstChild.getObjectId();
if (!modelObjectMap.containsKey(dstChildId)) {
String parentId = hierarchyRelationships.get(dstChildId);
if (parentId != null) {
// Get the new parent
UIContainer parent = session.findObjectById(ObjectType.CONTAINER, parentId);
// Perform the move
parent.getComponents().add(dstChild);
//
changes.add(new ModelChange.Move(dst.getObjectId(), parentId, dstChildId));
// julien : we do not need to create an update operation
// as later the update operation will be created when the
// object
// will be processed
} else if (hierarchyRelationships.containsKey(dstChildId)) {
// The dstChild is placed under transient Chromattic entity whose storageId == null. However,
// the hierachyRelationships contains dstChildId in key set, so we have to mark dstChild as
// moved object
modelObjectMap.put(dstChildId, null);
}
}
}
// Delete removed children
for (Iterator<UIComponent> i = dst.getComponents().iterator(); i.hasNext();) {
UIComponent dstChild = i.next();
String dstChildId = dstChild.getObjectId();
if (!modelObjectMap.containsKey(dstChildId)) {
i.remove();
changes.add(new ModelChange.Destroy(dstChildId));
}
}
// Now sort children according to the order provided by the container
// need to replace that with Collections.sort once the set(int index, E element) is implemented in Chromattic lists
UIComponent[] a = dst.getComponents().toArray(new UIComponent[dst.getComponents().size()]);
Arrays.sort(a, new Comparator<UIComponent>() {
public int compare(UIComponent o1, UIComponent o2) {
int i1 = orders.indexOf(o1.getObjectId());
int i2 = orders.indexOf(o2.getObjectId());
return i1 - i2;
}
});
for (int j = 0; j < a.length; j++) {
dst.getComponents().add(j, a[j]);
}
}
public <S> ApplicationData<S> load(UIWindow src) {
Attributes attrs = src.getAttributes();
//
Customization<?> customization = src.getCustomization();
//
ContentType<?> contentType = customization.getType();
//
String customizationid = customization.getId();
// julien: should type check that
ApplicationType<S> type = (ApplicationType<S>) ApplicationType.getType(contentType);
//
PersistentApplicationState<S> instanceState = new PersistentApplicationState<S>(customizationid);
//
HashMap<String, String> properties = new HashMap<String, String>();
load(attrs, properties, windowPropertiesBlackList);
//
List<String> accessPermissions = Collections.emptyList();
if (src.isAdapted(ProtectedResource.class)) {
ProtectedResource pr = src.adapt(ProtectedResource.class);
accessPermissions = pr.getAccessPermissions();
}
//
Described described = src.adapt(Described.class);
//
boolean showInfoBar = attrs.getValue(MappedAttributes.SHOW_INFO_BAR, false);
boolean showWindowState = attrs.getValue(MappedAttributes.SHOW_WINDOW_STATE, false);
boolean showMode = attrs.getValue(MappedAttributes.SHOW_MODE, false);
String theme = attrs.getValue(MappedAttributes.THEME, null);
//
return new ApplicationData<S>(src.getObjectId(), src.getName(), type, instanceState, null, described.getName(),
attrs.getValue(MappedAttributes.ICON), described.getDescription(), showInfoBar, showWindowState, showMode,
theme, attrs.getValue(MappedAttributes.WIDTH), attrs.getValue(MappedAttributes.HEIGHT),
Utils.safeImmutableMap(properties), Utils.safeImmutableList(accessPermissions));
}
public <S> void save(ApplicationData<S> src, UIWindow dst) {
ProtectedResource pr = dst.adapt(ProtectedResource.class);
pr.setAccessPermissions(src.getAccessPermissions());
Described described = dst.adapt(Described.class);
described.setName(src.getTitle());
described.setDescription(src.getDescription());
//
Attributes attrs = dst.getAttributes();
attrs.setValue(MappedAttributes.SHOW_INFO_BAR, src.isShowInfoBar());
attrs.setValue(MappedAttributes.SHOW_WINDOW_STATE, src.isShowApplicationState());
attrs.setValue(MappedAttributes.SHOW_MODE, src.isShowApplicationMode());
attrs.setValue(MappedAttributes.THEME, src.getTheme());
attrs.setValue(MappedAttributes.ICON, src.getIcon());
attrs.setValue(MappedAttributes.WIDTH, src.getWidth());
attrs.setValue(MappedAttributes.HEIGHT, src.getHeight());
save(src.getProperties(), attrs, windowPropertiesBlackList);
//
ApplicationState<S> instanceState = src.getState();
// We modify only transient portlet state
// and we ignore any persistent portlet state
if (instanceState instanceof TransientApplicationState) {
//
TransientApplicationState<S> transientState = (TransientApplicationState<S>) instanceState;
// Attempt to get a site from the instance state
Site site = null;
if (transientState.getOwnerType() != null && transientState.getOwnerId() != null) {
ObjectType<Site> siteType = parseSiteType(transientState.getOwnerType());
site = session.getWorkspace().getSite(siteType, transientState.getOwnerId());
}
// The current site
Site currentSite = dst.getPage().getSite();
// If it is the same site than the current page
// set null
if (site == dst.getPage().getSite()) {
site = null;
}
// The content id
String contentId = transientState.getContentId();
ContentType<S> contentType = src.getType().getContentType();
// The customization that we will inherit from if not null
Customization<?> customization = null;
// Destroy existing window previous customization
if (dst.getCustomization() != null) {
dst.getCustomization().destroy();
}
// If the existing customization is not null and matches the content id
Customization<S> dstCustomization;
if (customization != null && customization.getType().equals(contentType)
&& customization.getContentId().equals(contentId)) {
// Cast is ok as content type matches
@SuppressWarnings("unchecked")
Customization<S> bilto = (Customization<S>) customization;
// If it's a customization of the current site we extend it
if (bilto.getContext() == currentSite) {
dstCustomization = dst.customize(bilto);
} else {
// Otherwise we clone it propertly
S state = bilto.getVirtualState();
dstCustomization = dst.customize(contentType, contentId, state);
}
} else {
// Otherwise we create an empty customization
dstCustomization = dst.customize(contentType, contentId, null);
}
// At this point we have customized the window
// now if we have any additional state payload we must merge it
// with the current state
S state = ((TransientApplicationState<S>) instanceState).getContentState();
if (state != null) {
dstCustomization.setState(state);
}
} else if (instanceState instanceof CloneApplicationState) {
CloneApplicationState cloneState = (CloneApplicationState) instanceState;
//
Customization<?> customization = session.findCustomizationById(cloneState.getStorageId());
//
dst.customize(customization);
} else if (instanceState instanceof PersistentApplicationState) {
// Do nothing
} else {
throw new IllegalArgumentException("Cannot save application with state " + instanceState);
}
}
public DashboardData loadDashboard(UIContainer container) {
List<String> accessPermissions = Collections.emptyList();
if (container.isAdapted(ProtectedResource.class)) {
ProtectedResource pr = container.adapt(ProtectedResource.class);
accessPermissions = pr.getAccessPermissions();
}
List<String> moveAppsPermissions = null;
List<String> moveContainersPermissions = null;
if (container.isAdapted(ProtectedContainer.class)) {
ProtectedContainer pc = container.adapt(ProtectedContainer.class);
moveAppsPermissions = pc.getMoveAppsPermissions();
moveContainersPermissions = pc.getMoveContainersPermissions();
} else {
moveAppsPermissions = Collections.emptyList();
moveContainersPermissions = Collections.emptyList();
}
//
Described described = container.adapt(Described.class);
//
Attributes attrs = container.getAttributes();
List<ComponentData> children = loadChildren(container);
return new DashboardData(container.getObjectId(), attrs.getValue(MappedAttributes.ID),
attrs.getValue(MappedAttributes.NAME), attrs.getValue(MappedAttributes.ICON),
attrs.getValue(MappedAttributes.TEMPLATE), attrs.getValue(MappedAttributes.FACTORY_ID), described.getName(),
described.getDescription(), attrs.getValue(MappedAttributes.WIDTH), attrs.getValue(MappedAttributes.HEIGHT),
Utils.safeImmutableList(accessPermissions), moveAppsPermissions, moveContainersPermissions, children);
}
public void saveDashboard(DashboardData dashboard, UIContainer dst) {
save(dashboard, dst);
saveChildren(dashboard, dst);
}
private static void load(Attributes src, Map<String, String> dst, Set<String> blackList) {
for (String name : src.getKeys()) {
if (!blackList.contains(name) && !propertiesBlackList.contains(name)) {
Object value = src.getObject(name);
if (value instanceof String) {
dst.put(name, (String) value);
}
}
}
}
public static void save(Map<String, String> src, Attributes dst, Set<String> blackList) {
for (Map.Entry<String, String> property : src.entrySet()) {
String name = property.getKey();
if (!blackList.contains(name) && !propertiesBlackList.contains(name)) {
dst.setString(name, property.getValue());
}
}
}
public static String getOwnerType(ObjectType<? extends Site> siteType) {
if (siteType == ObjectType.PORTAL_SITE) {
return PortalConfig.PORTAL_TYPE;
} else if (siteType == ObjectType.GROUP_SITE) {
return PortalConfig.GROUP_TYPE;
} else if (siteType == ObjectType.USER_SITE) {
return PortalConfig.USER_TYPE;
} else {
throw new IllegalArgumentException("Invalid site type " + siteType);
}
}
public static ObjectType<Site> parseSiteType(String ownerType) {
if (ownerType.equals(PortalConfig.PORTAL_TYPE)) {
return ObjectType.PORTAL_SITE;
} else if (ownerType.equals(PortalConfig.GROUP_TYPE)) {
return ObjectType.GROUP_SITE;
} else if (ownerType.equals(PortalConfig.USER_TYPE)) {
return ObjectType.USER_SITE;
} else {
throw new IllegalArgumentException("Invalid owner type " + ownerType);
}
}
}