Author: nzamosenchuk
Date: 2009-11-11 08:02:31 -0500 (Wed, 11 Nov 2009)
New Revision: 560
Modified:
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/core/observation/ObservationManagerRegistry.java
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoader.java
jcr/branches/1.12.0-JBC/component/core/src/test/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoaderTest.java
Log:
EXOJCR-204: Updated functionality for node-type filtering
Modified:
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/core/observation/ObservationManagerRegistry.java
===================================================================
---
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/core/observation/ObservationManagerRegistry.java 2009-11-11
12:53:06 UTC (rev 559)
+++
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/core/observation/ObservationManagerRegistry.java 2009-11-11
13:02:31 UTC (rev 560)
@@ -61,7 +61,7 @@
protected WorkspacePersistentDataManager workspaceDataManager;
public ObservationManagerRegistry(WorkspacePersistentDataManager
workspaceDataManager,
- SessionRegistry sessionRegistry, NamespaceAccessor namespaceAccessor)
+ SessionRegistry sessionRegistry, NamespaceAccessor namespaceAccessor,
NodeTypeDataManager nodeTypeDataManager)
{
this.sessionRegistry = sessionRegistry;
this.listenersMap = new HashMap<EventListener, ListenerCriteria>();
@@ -119,17 +119,5 @@
{
return nodeTypeDataManager;
}
-
- // TODO: remove hardcode
- public WorkspacePersistentDataManager getWorkspaceDataManager()
- {
- return workspaceDataManager;
- }
-
- // TODO: remove hardcode
- public LocationFactory getLocationFactory()
- {
- return new LocationFactory(namespaceAccessor);
- }
}
Modified:
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoader.java
===================================================================
---
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoader.java 2009-11-11
12:53:06 UTC (rev 559)
+++
jcr/branches/1.12.0-JBC/component/core/src/main/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoader.java 2009-11-11
13:02:31 UTC (rev 560)
@@ -18,15 +18,20 @@
*/
package org.exoplatform.services.jcr.impl.storage.jbosscache;
+import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
+import org.exoplatform.services.jcr.core.NamespaceAccessor;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeData;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.QPath;
+import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.observation.EventImpl;
import org.exoplatform.services.jcr.impl.core.observation.ListenerCriteria;
import org.exoplatform.services.jcr.impl.core.observation.ObservationManagerRegistry;
import org.exoplatform.services.jcr.impl.util.EntityCollection;
+import org.exoplatform.services.jcr.storage.WorkspaceDataContainer;
+import org.exoplatform.services.jcr.storage.WorkspaceStorageConnection;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.jboss.cache.Fqn;
@@ -60,17 +65,23 @@
private ObservationManagerRegistry observationManagerRegistry = null;
+ private WorkspaceDataContainer dataContainer = null;
+
+ private LocationFactory locationFactory = null;
+
/**
- *
+ * Init the loader DataContainer with given WorkspaceDataContainer instance.
+ *
+ * @param dataContainer WorkspaceDataContainer
*/
- public ObservationCacheLoader()
- {
- }
-
@Inject
- public void insertObservationRegistry(ObservationManagerRegistry
observationManagerRegistry)
+ public void injectDependencies(ObservationManagerRegistry observationManagerRegistry,
+ WorkspaceDataContainer dataContainer, NamespaceAccessor namespaceAccessor)
+ throws RepositoryConfigurationException
{
+ this.dataContainer = dataContainer;
this.observationManagerRegistry = observationManagerRegistry;
+ this.locationFactory = new LocationFactory(namespaceAccessor);
}
/**
@@ -85,15 +96,12 @@
// get UserID from list
String sessionId = "";
- // extract real list from list of modification
- // real - means list with out fake modifications containing UserID & etc
-
// for each listener
// for each element from real list post and event, if filtering ok
if (observationManagerRegistry != null)
{
-
+ WorkspaceStorageConnection storageConnection = dataContainer.openConnection();
EventListenerIterator eventListeners =
observationManagerRegistry.getEventListeners();
while (eventListeners.hasNext())
{
@@ -120,15 +128,12 @@
// check UUID
result &= isIdentifierMatch(criteria, item);
// check NodeType
- // TODO: FIX nodetype checks
- //result &= isNodeTypeMatch(criteria, item, changesLog);
+ result &= isNodeTypeMatch(criteria, item, modifications,
storageConnection);
// check session
result &= isSessionMatch(criteria, sessionId);
if (result)
{
- String path =
-
observationManagerRegistry.getLocationFactory().createJCRPath(item.getQPath()).getAsString(
- false);
+ String path =
locationFactory.createJCRPath(item.getQPath()).getAsString(false);
events.add(new EventImpl(eventType, path, userId));
}
} // it wasn't able to retrive item's data
@@ -136,24 +141,35 @@
// if this is session
else if (isSessionSubtree(m))
{
- if (m.getType() == ModificationType.PUT_KEY_VALUE){
+ if (m.getType() == ModificationType.PUT_KEY_VALUE)
+ {
if (m.getKey().equals(JBossCacheStorage.SESSION_ID))
sessionId = (String)m.getValue();
else if (m.getKey().equals(JBossCacheStorage.USER_ID))
- userId = (String)m.getValue();}
- else if (m.getType() == ModificationType.REMOVE_NODE){
- sessionId = "";
- userId = "";
- }
+ userId = (String)m.getValue();
+ }
+ else if (m.getType() == ModificationType.REMOVE_DATA)
+ {
+ sessionId = "";
+ userId = "";
+ }
}
}
if (events.size() > 0)
{
// no events - no onEvent() action
- listener.onEvent(events);
+ try
+ {
+ listener.onEvent(events);
+ }
+ catch (Exception e)
+ {
+ log.error("Onservation listener (" + listener + ") in
error, skipping.", e);
+ }
}
}
+ storageConnection.close();
}
}
@@ -245,24 +261,21 @@
return itemPath.isDescendantOf(criteria.getAbsPath(), !criteria.isDeep());
}
- // shoud be changed in order to obtain parent's data elsewhere. Cache is not
accessible from cacheloader.put(List)
- @Deprecated
- private boolean isNodeTypeMatch(ListenerCriteria criteria, ItemData item,
List<Modification> modifications)
- throws RepositoryException
+ // should be changed in order to obtain parent's data elsewhere. Cache is not
accessible from cacheloader.put(List)
+ private boolean isNodeTypeMatch(ListenerCriteria criteria, ItemData item,
List<Modification> modifications,
+ WorkspaceStorageConnection storageConnection) throws RepositoryException
{
if (criteria.getNodeTypeName() == null)
return true;
- // TODO: IT IS IMPOSSIBLE TO ACCESS CACHE ON CACHELOADER.PUT()!!!
- NodeData node =
-
(NodeData)observationManagerRegistry.getWorkspaceDataManager().getItemData(item.getParentIdentifier());
+ NodeData node =
(NodeData)storageConnection.getItemData(item.getParentIdentifier());
if (node == null)
{
// check if parent exists in changes log
for (int i = modifications.size() - 1; i >= 0; i--)
{
ItemData data = getItemData(modifications.get(i));
- if (data.getIdentifier().equals(item.getParentIdentifier()))
+ if (data != null &&
data.getIdentifier().equals(item.getParentIdentifier()))
{
// parent found
node = (NodeData)data;
@@ -280,19 +293,8 @@
for (int i = 0; i < criteria.getNodeTypeName().length; i++)
{
- NodeTypeData criteriaNT =
-
observationManagerRegistry.getNodeTypeDataManager().getNodeType(criteria.getNodeTypeName()[i]);
- InternalQName[] testQNames;
- if (criteriaNT.isMixin())
- {
- testQNames = node.getMixinTypeNames();
- }
- else
- {
- testQNames = new InternalQName[1];
- testQNames[0] = node.getPrimaryTypeName();
- }
- if
(observationManagerRegistry.getNodeTypeDataManager().isNodeType(criteriaNT.getName(),
testQNames))
+ if
(observationManagerRegistry.getNodeTypeDataManager().isNodeType(criteria.getNodeTypeName()[i],
+ node.getPrimaryTypeName(), node.getMixinTypeNames()))
return true;
}
return false;
Modified:
jcr/branches/1.12.0-JBC/component/core/src/test/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoaderTest.java
===================================================================
---
jcr/branches/1.12.0-JBC/component/core/src/test/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoaderTest.java 2009-11-11
12:53:06 UTC (rev 559)
+++
jcr/branches/1.12.0-JBC/component/core/src/test/java/org/exoplatform/services/jcr/impl/storage/jbosscache/ObservationCacheLoaderTest.java 2009-11-11
13:02:31 UTC (rev 560)
@@ -19,27 +19,46 @@
package org.exoplatform.services.jcr.impl.storage.jbosscache;
import org.exoplatform.services.jcr.access.AccessControlList;
+import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.NamespaceAccessor;
+import org.exoplatform.services.jcr.core.nodetype.ItemDefinitionData;
+import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData;
+import org.exoplatform.services.jcr.core.nodetype.NodeTypeData;
+import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataImpl;
+import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
+import org.exoplatform.services.jcr.core.nodetype.NodeTypeValue;
+import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData;
+import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionDatas;
+import org.exoplatform.services.jcr.dataflow.PlainChangesLog;
import org.exoplatform.services.jcr.datamodel.InternalQName;
+import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPath;
+import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.observation.ListenerCriteria;
import org.exoplatform.services.jcr.impl.core.observation.ObservationManagerRegistry;
import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
+import org.exoplatform.services.jcr.impl.storage.inmemory.InmemoryContainerImpl;
+import org.exoplatform.services.jcr.storage.WorkspaceStorageConnection;
import org.jboss.cache.Modification;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import javax.jcr.InvalidItemStateException;
import javax.jcr.NamespaceException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
@@ -56,6 +75,10 @@
private ObservationManagerRegistry registry;
+ private DummyWorkspaceStorageConnection workspaceStorageConnection;
+
+ private NamespaceAccessor accessor;
+
private LocationFactory lf;
@Override
@@ -63,8 +86,8 @@
{
super.setUp();
loader = new ObservationCacheLoader();
- registry = new DummyObservationRegistry();
- loader.insertObservationRegistry(registry);
+ registry = new ObservationManagerRegistry(null, null, null,new
DummyNodeTypeDataManager());
+ workspaceStorageConnection = new DummyWorkspaceStorageConnection();
final Map<String, String> ns = new HashMap<String, String>();
@@ -77,9 +100,8 @@
ns.put("nt", "http://nt");
nss.put("http://nt", "nt");
- this.lf = new LocationFactory(new NamespaceAccessor()
+ accessor = new NamespaceAccessor()
{
-
public String getNamespaceURIByPrefix(String prefix) throws NamespaceException,
RepositoryException
{
return ns.get(prefix);
@@ -94,7 +116,10 @@
{
return ns.keySet().toArray(new String[ns.keySet().size()]);
}
- });
+ };
+
+ lf = new LocationFactory(accessor);
+ loader.injectDependencies(registry, new
DummyWorkspaceDataContainer(workspaceStorageConnection), accessor);
}
public void testFilterByUUID() throws Exception
@@ -220,58 +245,314 @@
public void testFilterByNodeType() throws Exception
{
- QPath node1path = QPath.parse("[]:1[]node:1");
- NodeData newNode =
- new TransientNodeData(node1path, Constants.SYSTEM_UUID, 1,
Constants.NT_UNSTRUCTURED, new InternalQName[0], 0,
+ String parent1uuid = "uuid1";
+ String parent2uuid = "uuid2";
+
+ QPath node1path = QPath.parse("[]:1[]node1:1");
+ NodeData parentNode1 =
+ new TransientNodeData(node1path, parent1uuid, 1, Constants.NT_UNSTRUCTURED, new
InternalQName[0], 0,
Constants.ROOT_UUID, new AccessControlList());
- NodeData newNode2 =
- new TransientNodeData(node1path, Constants.SYSTEM_UUID, 1,
Constants.NT_RESOURCE, new InternalQName[0], 0,
+ workspaceStorageConnection.add(parentNode1);
+
+ QPath node2path = QPath.parse("[]:1[]node2:1");
+ NodeData parentNode2 =
+ new TransientNodeData(node2path, parent2uuid, 1, Constants.NT_RESOURCE, new
InternalQName[0], 0,
Constants.ROOT_UUID, new AccessControlList());
+ workspaceStorageConnection.add(parentNode2);
+
+ QPath child1path = QPath.parse("[]:1[]child1:1");
+ NodeData childNode1 =
+ new TransientNodeData(child1path, "child1", 1,
Constants.NT_UNSTRUCTURED, new InternalQName[0], 0,
+ parent1uuid, new AccessControlList());
+
+ QPath child2path = QPath.parse("[]:1[]child2:1");
+ NodeData childNode2 =
+ new TransientNodeData(child2path, "child2", 1,
Constants.NT_UNSTRUCTURED, new InternalQName[0], 0,
+ parent2uuid, new AccessControlList());
+
DummyListener listener = new DummyListener();
// listening only to NT_RESOURCE nodes
registry.addEventListener(listener, new ListenerCriteria(Event.NODE_ADDED,
- lf.parseAbsPath("/").getInternalPath(), true, null, new
InternalQName[]{Constants.NT_RESOURCE}, false, "session1"));
+ lf.parseAbsPath("/").getInternalPath(), true, null, new
InternalQName[]{Constants.NT_RESOURCE}, false,
+ "session1"));
List<Modification> modifications = new
ArrayList<Modification>(setSession("session1", "admin"));
- modifications.addAll(addNode(newNode));
- modifications.addAll(addNode(newNode2));
+ modifications.addAll(addNode(childNode1));
+ modifications.addAll(addNode(childNode2));
modifications.addAll(removeSession());
loader.put(modifications);
// one event expected
- assertEvent(Event.NODE_ADDED, node1path, listener, "admin");
+ assertEvent(Event.NODE_ADDED, child2path, listener, "admin");
}
-
+
@Override
protected void tearDown() throws Exception
{
super.tearDown();
}
- class DummyObservationRegistry extends ObservationManagerRegistry
+ class DummyListener implements EventListener
{
- public DummyObservationRegistry()
+ public List<Event> eventList = new ArrayList<Event>();
+
+ public void onEvent(EventIterator events)
{
- super(null, null, null);
+ while (events.hasNext())
+ {
+ eventList.add(events.nextEvent());
+ }
}
- public LocationFactory getLocationFactory()
- {
- return lf;
- }
}
- class DummyListener implements EventListener
+ class DummyNodeTypeDataManager implements NodeTypeDataManager
{
- public List<Event> eventList = new ArrayList<Event>();
- public void onEvent(EventIterator events)
+ public NodeDefinitionData[] getAllChildNodeDefinitions(InternalQName...
nodeTypeNames)
{
- while (events.hasNext())
+ return null;
+ }
+
+ public List<NodeTypeData> getAllNodeTypes()
+ {
+ return null;
+ }
+
+ public PropertyDefinitionData[] getAllPropertyDefinitions(InternalQName...
nodeTypeNames)
+ {
+ return null;
+ }
+
+ public NodeDefinitionData getChildNodeDefinition(InternalQName nodeName,
InternalQName... nodeTypeNames)
+ throws RepositoryException
+ {
+ return null;
+ }
+
+ public NodeDefinitionData getChildNodeDefinition(InternalQName nodeName,
InternalQName primaryNodeType,
+ InternalQName[] mixinTypes) throws RepositoryException
+ {
+ return null;
+ }
+
+ public Set<InternalQName> getDeclaredSubtypes(InternalQName nodeTypeName)
+ {
+ return null;
+ }
+
+ public List<ItemDefinitionData> getManadatoryItemDefs(InternalQName
primaryNodeType, InternalQName[] mixinTypes)
+ throws RepositoryException
+ {
+ return null;
+ }
+
+ public NodeTypeData getNodeType(InternalQName typeName)
+ {
+ return null;
+ }
+
+ public PropertyDefinitionDatas getPropertyDefinitions(InternalQName propertyName,
InternalQName... nodeTypeNames)
+ throws RepositoryException
+ {
+ return null;
+ }
+
+ public PropertyDefinitionDatas getPropertyDefinitions(InternalQName propertyName,
InternalQName primaryNodeType,
+ InternalQName[] mixinTypes) throws RepositoryException
+ {
+ return null;
+ }
+
+ public Set<InternalQName> getSubtypes(InternalQName nodeTypeName)
+ {
+ return null;
+ }
+
+ public Set<InternalQName> getSupertypes(InternalQName nodeTypeName)
+ {
+ return null;
+ }
+
+ public boolean isChildNodePrimaryTypeAllowed(InternalQName childNodeTypeName,
InternalQName parentNodeType,
+ InternalQName[] parentMixinNames) throws RepositoryException
+ {
+ return false;
+ }
+
+ public boolean isNodeType(InternalQName testTypeName, InternalQName... typeNames)
+ {
+ return false;
+ }
+
+ public boolean isNodeType(InternalQName testTypeName, InternalQName
primaryNodeType, InternalQName[] mixinNames)
+ {
+ if (testTypeName.equals(primaryNodeType))
{
- eventList.add(events.nextEvent());
+ return true;
}
+ for (InternalQName name : mixinNames)
+ {
+ if (testTypeName.equals(name))
+ {
+ return true;
+ }
+ }
+ return false;
}
+ public boolean isOrderableChildNodesSupported(InternalQName primaryNodeType,
InternalQName[] mixinTypes)
+ throws RepositoryException
+ {
+ return false;
+ }
+
+ public List<NodeTypeData> registerNodeTypes(InputStream xml, int
alreadyExistsBehaviour, String contentType)
+ throws RepositoryException
+ {
+ return null;
+ }
+
+ public List<NodeTypeData> registerNodeTypes(List<NodeTypeValue>
ntValues, int alreadyExistsBehaviour)
+ throws RepositoryException
+ {
+ return null;
+ }
+
+ public PlainChangesLog setPrimaryType(NodeData nodeData, InternalQName
nodeTypeName) throws RepositoryException
+ {
+ return null;
+ }
+
+ public void unregisterNodeType(InternalQName nodeTypeName) throws
RepositoryException
+ {
+ }
+
+ public PlainChangesLog updateNodeType(NodeTypeData ancestorDefinition, NodeTypeData
recipientDefinition,
+ Map<InternalQName, NodeTypeData> volatileNodeTypes) throws
ConstraintViolationException, RepositoryException
+ {
+ return null;
+ }
+
}
+ class DummyWorkspaceDataContainer extends InmemoryContainerImpl
+ {
+
+ private DummyWorkspaceStorageConnection storageConnection;
+
+ public DummyWorkspaceDataContainer(DummyWorkspaceStorageConnection
storageConnection) throws RepositoryException
+ {
+ super(new WorkspaceEntry("", ""));
+ this.storageConnection = storageConnection;
+ }
+
+ public WorkspaceStorageConnection openConnection()
+ {
+ return storageConnection;
+ }
+ }
+
+ class DummyWorkspaceStorageConnection implements WorkspaceStorageConnection
+ {
+
+ private Map<String, ItemData> storage = new HashMap<String,
ItemData>();
+
+ public ItemData getItemData(String identifier) throws RepositoryException,
IllegalStateException
+ {
+ return storage.get(identifier);
+ }
+
+ public void add(NodeData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ storage.put(data.getIdentifier(), data);
+ }
+
+ public void add(PropertyData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ storage.put(data.getIdentifier(), data);
+ }
+
+ public void addSessionInfo(String sessionId, String userId) throws
RepositoryException
+ {
+ }
+
+ public void close() throws IllegalStateException, RepositoryException
+ {
+ }
+
+ public void commit() throws IllegalStateException, RepositoryException
+ {
+ }
+
+ public void delete(NodeData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ }
+
+ public void delete(PropertyData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ }
+
+ public List<NodeData> getChildNodesData(NodeData parent) throws
RepositoryException, IllegalStateException
+ {
+ return null;
+ }
+
+ public List<PropertyData> getChildPropertiesData(NodeData parent) throws
RepositoryException,
+ IllegalStateException
+ {
+ return null;
+ }
+
+ public ItemData getItemData(NodeData parentData, QPathEntry name) throws
RepositoryException,
+ IllegalStateException
+ {
+ return null;
+ }
+
+ public List<PropertyData> getReferencesData(String nodeIdentifier) throws
RepositoryException,
+ IllegalStateException, UnsupportedOperationException
+ {
+ return null;
+ }
+
+ public boolean isOpened()
+ {
+ return false;
+ }
+
+ public List<PropertyData> listChildPropertiesData(NodeData parent) throws
RepositoryException,
+ IllegalStateException
+ {
+ return null;
+ }
+
+ public void removeSessionInfo() throws RepositoryException
+ {
+ }
+
+ public void rename(NodeData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ }
+
+ public void rollback() throws IllegalStateException, RepositoryException
+ {
+ }
+
+ public void update(NodeData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ }
+
+ public void update(PropertyData data) throws RepositoryException,
UnsupportedOperationException,
+ InvalidItemStateException, IllegalStateException
+ {
+ }
+
+ }
+
}