/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Franziska Haunolder, Edgar Mueller - initial API and implementation ******************************************************************************/ package org.eclipse.emf.emfstore.internal.client.properties; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.emfstore.internal.client.accesscontrol.AccessControlHelper; import org.eclipse.emf.emfstore.internal.client.model.ESWorkspaceProviderImpl; import org.eclipse.emf.emfstore.internal.client.model.ProjectSpace; import org.eclipse.emf.emfstore.internal.client.model.exceptions.EMFStorePropertiesOutdatedException; import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceImpl; import org.eclipse.emf.emfstore.internal.common.model.EMFStoreProperty; import org.eclipse.emf.emfstore.internal.common.model.EMFStorePropertyType; import org.eclipse.emf.emfstore.internal.common.model.PropertyStringValue; import org.eclipse.emf.emfstore.internal.server.exceptions.AccessControlException; import org.eclipse.emf.emfstore.server.exceptions.ESException; /** * This class is responsible for the modification of EMFStore based properties. <br/> * There are two kinds of properties, local and shared ones. * <ul> * <li>Local properties are project space specific and thus are not shared across project space boundaries.</li> * <li> * On the contrary, shared properties may be shared across project space boundaries via the synchronize call.<br/> * If a property is shared it may either be versioned or not. <br/> * Unversioned properties follow the principle 'last-write-wins' principle, i.e. if two callers modify the same property * the one that executed the call later will have overwritten the the value written by the first caller.<br/> * This is not the case with versioned properties.<br/> * If two callers modify a versioned property the latter one will receive an exception telling him that the property * that he has modify is outdated. The caller then should catch the exception and handle it appropriately, e.g. by * updating the state of the shared properties and then retransmitting his changes.</li> * </ul> * Shared and local properties both have their own namespace meaning that a * shared property named <code>"foo"</code> has nothing in common with a local * property called <code>"foo"</code>. * * @author haunolder * @author emueller **/ public final class PropertyManager { private final ProjectSpaceImpl projectSpace; private final Map<String, EMFStoreProperty> sharedProperties; private final Map<String, EMFStoreProperty> localProperties; /** * PropertyManager constructor. * * @param projectSpace * the project space that should get managed by the property * manager **/ public PropertyManager(ProjectSpace projectSpace) { this.projectSpace = (ProjectSpaceImpl) projectSpace; localProperties = createMap(EMFStorePropertyType.LOCAL); sharedProperties = createMap(EMFStorePropertyType.SHARED); } /** * Set a local property. If the property already exists it will be updated. * * @param propertyName * the name of the local property * @param value * the actual value of the property **/ public void setLocalProperty(String propertyName, EObject value) { EMFStoreProperty prop = findProperty(propertyName); if (prop == null) { prop = createProperty(propertyName, value, false); prop.setType(EMFStorePropertyType.LOCAL); projectSpace.getProperties().add(prop); } else { prop.setValue(value); } localProperties.put(propertyName, prop); projectSpace.saveProjectSpaceOnly(); } /** * Sets a local string property. If the property already exists it will be * updated. * * @param propertyName * the name of the local property * @param value * the value of the local property **/ public void setLocalStringProperty(String propertyName, String value) { final PropertyStringValue propertyValue = org.eclipse.emf.emfstore.internal.common.model.ModelFactory.eINSTANCE .createPropertyStringValue(); propertyValue.setValue(value); setLocalProperty(propertyName, propertyValue); } /** * Retrieves a local property. * * @param propertyName * the name of the local property * @return the local property **/ public EMFStoreProperty getLocalProperty(String propertyName) { return localProperties.get(propertyName); } /** * Retrieves a local string property. * * @param propertyName * the name of a local string property * @return the string value if it exists, otherwise <code>null</code> **/ public String getLocalStringProperty(String propertyName) { final EMFStoreProperty property = localProperties.get(propertyName); if (property == null || property.getValue() == null) { return null; } return ((PropertyStringValue) property.getValue()).getValue(); } /** * Sets the property with the given name to the given value. * * @param propertyName * the name of the property to be set * @param value * the actual value of the property */ public void setSharedVersionedProperty(String propertyName, EObject value) { setSharedProperty(propertyName, value, true); } /** * Set a shared string property. * * @param propertyName * the name of the shared property * @param string * the string value that should be set * **/ public void setSharedStringProperty(String propertyName, String string) { final PropertyStringValue propertyValue = org.eclipse.emf.emfstore.internal.common.model.ModelFactory.eINSTANCE .createPropertyStringValue(); propertyValue.setValue(string); setSharedProperty(propertyName, propertyValue, false); } /** * Set a versioned shared string property. * * @param propertyName * the name of the shared property * @param string * the string value that should be set * **/ public void setSharedVersionedStringProperty(String propertyName, String string) { final PropertyStringValue propertyValue = org.eclipse.emf.emfstore.internal.common.model.ModelFactory.eINSTANCE .createPropertyStringValue(); propertyValue.setValue(string); setSharedProperty(propertyName, propertyValue, true); } /** * Set a shared property. * * @param propertyName * the name of the shared property * @param value * the value of the shared property * @param isVersioned * whether the shared property should be versioned or not **/ private void setSharedProperty(String propertyName, EObject value, boolean isVersioned) { EMFStoreProperty prop = findProperty(propertyName); if (prop == null) { prop = createProperty(propertyName, value, isVersioned); prop.setType(EMFStorePropertyType.SHARED); projectSpace.getProperties().add(prop); } else { prop.setValue(value); } projectSpace.getChangedSharedProperties().add(prop); projectSpace.saveProjectSpaceOnly(); } /** * Set a shared property. * * @param propertyName * the name of the shared property * @param value * the value of the shared property **/ public void setSharedProperty(String propertyName, EObject value) { setSharedProperty(propertyName, value, false); } /** * Updates a shared versioned property within the project space to the one * given, i.e. the name of the property is first used to look it up within * the project space. If found, the value and version attributes are * updated, otherwise the property will be created. * * @param property * the updated property */ private void updateProperty(EMFStoreProperty property) { EMFStoreProperty prop = findProperty(property.getKey()); if (prop == null) { prop = createProperty(property.getKey(), property.getValue(), property.getVersion() != 0); prop.setType(EMFStorePropertyType.SHARED); projectSpace.getProperties().add(prop); } else { prop.setValue(property.getValue()); } prop.setVersion(property.getVersion()); sharedProperties.put(property.getKey(), prop); projectSpace.saveProjectSpaceOnly(); } /** * Retrieves the shared property with the given name. * * @param propertyName * the name of the shared property * @return value the actual value of the shared property **/ public EMFStoreProperty getSharedProperty(String propertyName) { return sharedProperties.get(propertyName); } /** * Retrieves a shared string property. * * @param propertyName * of the shared property as String * @return the string value if it exists, otherwise <code>null</code> **/ public String getSharedStringProperty(String propertyName) { final EMFStoreProperty property = sharedProperties.get(propertyName); if (property == null || property.getValue() == null) { return null; } return ((PropertyStringValue) property.getValue()).getValue(); } /** * Transmit changed shared properties to the server. Clears the * changedSharedProperties List and fills shareProperties with the actual * properties from the server. * * @throws AccessControlException * if the caller has no write access to the project space * @throws ESException * if the project space being manipulated is not yet shared or * an error occurs within EMFStore * @throws EMFStorePropertiesOutdatedException * if any changed property is outdated **/ public void synchronizeSharedProperties() throws AccessControlException, ESException, EMFStorePropertiesOutdatedException { // check if project is shared, if not throw checked exception if it is // shared if (projectSpace.getUsersession() == null) { throw new ESException("Project has not been shared yet."); } new AccessControlHelper(projectSpace.getUsersession()).checkWriteAccess(projectSpace.getProjectId()); final List<EMFStoreProperty> changedProperties = new ArrayList<EMFStoreProperty>( projectSpace.getChangedSharedProperties()); final List<EMFStoreProperty> rejectedProperties = ESWorkspaceProviderImpl .getInstance() .getConnectionManager() .setEMFProperties(projectSpace.getUsersession().getSessionId(), changedProperties, projectSpace.getProjectId()); // setEMFProperties returns us a list of properties as found one the // server, // i.e. we have to deal with different object identities final List<EMFStoreProperty> nonRejectedProperties = filterNonRejected(changedProperties, rejectedProperties); projectSpace.getChangedSharedProperties().removeAll(nonRejectedProperties); // update properties to reflect current state on server final List<EMFStoreProperty> sharedProperties = ESWorkspaceProviderImpl.getInstance().getConnectionManager() .getEMFProperties(projectSpace.getUsersession().getSessionId(), projectSpace.getProjectId()); for (final EMFStoreProperty prop : sharedProperties) { updateProperty(prop); } if (rejectedProperties.size() > 0) { throw new EMFStorePropertiesOutdatedException(rejectedProperties); } } /** * Filters a list of changed properties to find only those that have not * been rejected. * * @param changedProperties * the list of changed properties * @param rejectedProperties * the list containing all properties that have been rejected * @return a list of properties that have not been rejected */ private List<EMFStoreProperty> filterNonRejected(List<EMFStoreProperty> changedProperties, List<EMFStoreProperty> rejectedProperties) { final List<EMFStoreProperty> result = new ArrayList<EMFStoreProperty>(); for (final EMFStoreProperty changed : changedProperties) { boolean isNotRejected = true; for (final EMFStoreProperty rejected : rejectedProperties) { if (changed.getKey().equals(rejected.getKey())) { isNotRejected = false; break; } } // a found property has been rejected, so we only pay attention to // those if (isNotRejected) { result.add(changed); } } return result; } /** * Creates a map based on the project properties of the given {@link EMFStorePropertyType}. * * @param type * the {@link EMFStorePropertyType} of the properties that should * be contained in the map */ private Map<String, EMFStoreProperty> createMap(EMFStorePropertyType type) { final Map<String, EMFStoreProperty> map = new LinkedHashMap<String, EMFStoreProperty>(); final EList<EMFStoreProperty> properties = projectSpace.getProperties(); for (final EMFStoreProperty prop : properties) { if (prop.getType() == type) { map.put(prop.getKey(), prop); } } return map; } /** * Creates a property with the given key and value. * * @param key * the name of the property * @param value * the actual value of the property * @return the newly created property */ private EMFStoreProperty createProperty(String key, EObject value, boolean isVersioned) { final EMFStoreProperty prop = org.eclipse.emf.emfstore.internal.common.model.ModelFactory.eINSTANCE .createEMFStoreProperty(); prop.setKey(key); prop.setValue(value); if (isVersioned) { prop.setVersion(EMFStoreProperty.VERSIONED); } return prop; } /** * Returns the property with the given name if it is contained in properties * map of the project space. * * @param propertyName * the name of the property * @return the property or <code>null</code> if no such property has been * found */ private EMFStoreProperty findProperty(String propertyName) { EMFStoreProperty property = localProperties.get(propertyName); if (property == null) { property = sharedProperties.get(propertyName); } if (property == null) { // actually we should never get here for (final EMFStoreProperty p : projectSpace.getProperties()) { if (p.getKey().equals(propertyName)) { property = p; } } if (property != null) { if (property.getType() == EMFStorePropertyType.LOCAL) { localProperties.put(propertyName, property); } else { sharedProperties.put(propertyName, property); } } } return property; } }