Index: main/plugin-user-directory/plugin.xml
===================================================================
--- main/plugin-user-directory/plugin.xml	(revision 42112)
+++ main/plugin-user-directory/plugin.xml	(working copy)
@@ -245,6 +245,18 @@
                 <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
                            id="org.ametys.plugins.userdirectory.observation.SynchronizeUserContentValidationObserver"
                            class="org.ametys.plugins.userdirectory.observation.SynchronizeUserContentValidationObserver"/>
+                
+                <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
+                           id="org.ametys.plugins.userdirectory.observation.LuceneUserIndexPageObserver"
+                           class="org.ametys.plugins.userdirectory.observation.LuceneUserIndexPageObserver"/>
+                
+                <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
+                           id="org.ametys.plugins.userdirectory.observation.SynchronizeUserPageChangeObserver"
+                           class="org.ametys.plugins.userdirectory.observation.SynchronizeUserPageChangeObserver"/>
+                           
+                <extension point="org.ametys.cms.observation.ObserverExtensionPoint"
+                           id="org.ametys.plugins.userdirectory.observation.InvalidateCacheOnUserPageModificationObserver"
+                           class="org.ametys.plugins.userdirectory.observation.InvalidateCacheOnUserPageModificationObserver"/>
                            
             </extensions>
         </feature>
@@ -904,4 +916,16 @@
                 </extension>
             </extensions>
         </feature>
+        
+        <feature name="user.directory.sitemap.inputdata.cache.policy">
+            <extensions>
+                <extension point="org.ametys.web.cache.pageelement.PageElementCachePolicyExtensionPoint/inputData"
+                           id="org.ametys.plugins.userdirectory.cachepolicy.UserDirectoryVirtualPagesCachePolicy.inputData"
+                           class="org.ametys.plugins.userdirectory.cachepolicy.UserDirectoryVirtualPagesCachePolicy"/>
+                
+                <extension point="org.ametys.web.cache.pageelement.PageElementCachePolicyExtensionPoint/zoneItem"
+                           id="org.ametys.plugins.userdirectory.cachepolicy.UserDirectoryVirtualPagesCachePolicy.zoneItem"
+                           class="org.ametys.plugins.userdirectory.cachepolicy.UserDirectoryVirtualPagesCachePolicy"/>
+            </extensions>
+        </feature>
 </plugin>
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/cachepolicy/UserDirectoryVirtualPagesCachePolicy.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/cachepolicy/UserDirectoryVirtualPagesCachePolicy.java	(revision 0)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/cachepolicy/UserDirectoryVirtualPagesCachePolicy.java	(revision 0)
@@ -0,0 +1,135 @@
+/*
+ *  Copyright 2016 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.userdirectory.cachepolicy;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.repository.SynchronizableContent;
+import org.ametys.plugins.userdirectory.component.UserDirectoryPageHandler;
+import org.ametys.plugins.userdirectory.population.Population;
+import org.ametys.web.ObservationConstants;
+import org.ametys.web.cache.pageelement.PageElementCachePolicy;
+import org.ametys.web.inputdata.SitemapInputData;
+import org.ametys.web.repository.site.Site;
+
+/**
+ * Cache policy for the sitemap, handling user directory virtual pages linked with contents.
+ * Used for the Sitemap InputData as well as for the Sitemap service, even if the page element cache is not the same.
+ */
+public class UserDirectoryVirtualPagesCachePolicy extends AbstractLogEnabled implements Serviceable, PageElementCachePolicy
+{
+    
+    private static final Set<String> _USER_DIRECTORY_PE_TYPES = new HashSet<String>();
+    static
+    {
+        // Used for the Sitemap InputData as well as for the Sitemap service.
+        _USER_DIRECTORY_PE_TYPES.add(SitemapInputData.class.getName());
+        _USER_DIRECTORY_PE_TYPES.add("SERVICE:org.ametys.web.service.SitemapService");
+        _USER_DIRECTORY_PE_TYPES.add("CONTENT");
+    }
+    
+    /** The user directory page handler */
+    protected UserDirectoryPageHandler _uDPageHandler;
+    
+    @Override
+    public void service(ServiceManager manager) throws ServiceException
+    {
+        _uDPageHandler = (UserDirectoryPageHandler) manager.lookup(UserDirectoryPageHandler.ROLE);
+    }
+    
+    @Override
+    public Set<String> getPageElementTypes()
+    {
+        return _USER_DIRECTORY_PE_TYPES;
+    }
+    
+    @Override
+    public final PolicyResult shouldClearCache(String workspace, Site site, String pageElementType, Event event)
+    {
+        String id = event.getId();
+        
+        if (_getRemovingCacheEventIds(workspace).contains(id))
+        {
+            Object object = event.getTarget();
+            
+            // The target must be an user content and the site must possess an user directory root
+            // in one of its sitemaps to be invalidated.
+            if (object instanceof SynchronizableContent<?>)
+            {
+                SynchronizableContent syncContent = (SynchronizableContent) object;
+                try
+                {
+                    if (syncContent.getNode().hasProperty(Population.USER_POPULATION_PROPERTY))
+                    {
+                        String populationId = syncContent.getNode().getProperty(Population.USER_POPULATION_PROPERTY).getString();
+                        if (_uDPageHandler.getUserDirectoryRootPages(populationId) != null)
+                        {
+                            return PolicyResult.REMOVE;
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    getLogger().error("An error occurred : can't get the population id from content : " + syncContent.getId());
+                    return PolicyResult.KEEP;
+                }
+            }
+        }
+        
+        return PolicyResult.KEEP;
+    }
+    
+    @Override
+    public final PolicyResult shouldClearCache(String workspace, Site site, String pageElementType, String elementId, Event event)
+    {
+        // Never called because the first-level method never returns NEED_INFORMATION.
+        throw new UnsupportedOperationException("Should never be called.");
+    }
+    
+    /**
+     * Returns all event ids for which the cache should be removed.
+     * @param workspace the current JCR workspace.
+     * @return all event ids for which the cache should be removed.
+     */
+    protected List<String> _getRemovingCacheEventIds(String workspace)
+    {
+        if ("default".equals(workspace))
+        {
+            return Arrays.asList(org.ametys.cms.observation.ObservationConstants.CONTENT_ADDED,
+                                 org.ametys.cms.observation.ObservationConstants.CONTENT_MODIFIED,
+                                 org.ametys.cms.observation.ObservationConstants.CONTENT_DELETED);
+        }
+        else if ("live".equals(workspace))
+        {
+            return Arrays.asList(org.ametys.cms.observation.ObservationConstants.CONTENT_ADDED,
+                                 org.ametys.cms.observation.ObservationConstants.CONTENT_VALIDATED,
+                                 org.ametys.cms.observation.ObservationConstants.CONTENT_DELETED,
+                                 ObservationConstants.CONTENT_UNPUBLISHED);
+        }
+        
+        return Collections.emptyList();
+    }
+}
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/InvalidateCacheOnUserPageModificationObserver.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/InvalidateCacheOnUserPageModificationObserver.java	(revision 0)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/InvalidateCacheOnUserPageModificationObserver.java	(revision 0)
@@ -0,0 +1,73 @@
+/*
+ *  Copyright 2016 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.userdirectory.observation;
+
+import javax.jcr.Session;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.plugins.repository.jcr.JCRAmetysObject;
+import org.ametys.web.cache.AbstractSiteCacheObserver;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.page.ZoneItem;
+import org.ametys.web.repository.site.Site;
+
+/**
+ * {@link Observer} for observing page creation or modification in order to
+ * invalidate cache on front-office.
+ */
+public class InvalidateCacheOnUserPageModificationObserver extends AbstractSiteCacheObserver
+{
+    @Override
+    public boolean supports(Event event)
+    {
+        return event.getId().equals(ObservationConstants.USER_DIRECTORY_PAGE_CHANGED);
+    }
+    
+    @Override
+    protected Site _getSite(Event event)
+    {
+        return _getPage(event).getSite();
+    }
+    
+    private Page _getPage (Event event)
+    {
+        if (event.getTarget() instanceof ZoneItem)
+        {
+            return ((ZoneItem) event.getTarget()).getZone().getPage();
+        }
+        else
+        {
+            return (Page) event.getTarget();
+        }
+    }
+    
+    @Override
+    protected void _internalObserve(Event event, Site site, Session liveSession) throws Exception
+    {
+        Page page = _getPage (event);
+        
+        if (page instanceof JCRAmetysObject)
+        {
+            if (getLogger().isInfoEnabled())
+            {
+                getLogger().info("Page modification: " + event + ", invalidating cache");
+            }
+            
+            _cachePolicy.invalidateCacheOnPageModification(page);
+        }
+    }
+}
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/LuceneUserIndexPageObserver.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/LuceneUserIndexPageObserver.java	(revision 0)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/LuceneUserIndexPageObserver.java	(revision 0)
@@ -0,0 +1,96 @@
+/*
+ *  Copyright 2010 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.userdirectory.observation;
+
+import java.io.File;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.FSDirectory;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.lucene.AbstractSitemapLuceneObserver;
+import org.ametys.web.lucene.IndexerHelper;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.sitemap.Sitemap;
+
+/**
+ * {@link Observer} for observing page creation or modification in order to
+ * synchronize lucene index associated.
+ */
+public class LuceneUserIndexPageObserver extends AbstractSitemapLuceneObserver
+{
+    @Override
+    public boolean supports(Event event)
+    {
+        String id = event.getId();
+        return id.equals(ObservationConstants.USER_DIRECTORY_PAGE_CHANGED); 
+    }
+    
+    /**
+     * Retrieves the page in default workspace from the event
+     * @param event The event
+     * @return The page
+     * @throws Exception if failed to get the page
+     */
+    protected Page _getPage(Event event) throws Exception
+    {
+        // Async observer cannot directly use the event target because its session might not be alive anymore.
+        Object[] eventArgs = event.getArguments();
+        String pageId = eventArgs != null ? (String) eventArgs[0] : null;
+        
+        if (StringUtils.isEmpty(pageId))
+        {
+            throw new RuntimeException("This event expect at least the event target id as the first argument : " + event);
+        }
+        
+        return _getPageInLive(pageId);
+    }
+    
+    @Override
+    protected Sitemap _getSitemap(Event event) throws Exception
+    {
+        Page page = _getPage(event);
+        
+        return page == null ? null : page.getSitemap();
+    }
+    
+    @Override
+    protected void _updateIndex(final IndexWriter indexWriter, final Event event) throws Exception
+    {
+        Page livePage = _getPage(event);
+        
+        if (livePage != null)
+        {
+            IndexReader indexReader = null;
+            try
+            {
+                File indexDir = IndexerHelper.getIndexDir(_cocoonContext, livePage.getSiteName(), livePage.getSitemapName());
+                indexReader = IndexReader.open(FSDirectory.open(indexDir));
+                IndexerHelper.indexPage(livePage, indexReader, indexWriter, _pageIndexer, _resourceIndexer, true);
+            }
+            finally
+            {
+                if (indexReader != null)
+                {
+                    indexReader.close();
+                }
+            }
+        }
+    }
+}
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/ObservationConstants.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/ObservationConstants.java	(revision 40174)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/ObservationConstants.java	(working copy)
@@ -27,6 +27,9 @@
     /** Event id when an user directory root page is updated. */
     public static final String USER_DIRECTORY_ROOT_UPDATED = "user.directory.root.updated";
     
+    /** Event id when an user directory root page is updated. */
+    public static final String USER_DIRECTORY_PAGE_CHANGED = "user.directory.page.changed";
+    
     /** Event id when an user content is created during import process. */
     public static final String USER_DIRECTORY_IMPORT_CONTENT_CREATED = "user.directory.import.content.created";
     
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/SynchronizeUserPageChangeObserver.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/SynchronizeUserPageChangeObserver.java	(revision 0)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/observation/SynchronizeUserPageChangeObserver.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2010 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.userdirectory.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.ametys.cms.observation.Event;
+import org.ametys.cms.observation.Observer;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.skin.Skin;
+import org.ametys.web.synchronization.AbstractSynchronizePageObserver;
+
+/**
+ * {@link Observer} for observing structural page changes (page added, page type changed) in order to synchronize live workspace.
+ */
+public class SynchronizeUserPageChangeObserver extends AbstractSynchronizePageObserver
+{
+    @Override
+    protected boolean _internalSupport(Event event, Page target)
+    {
+        String id = event.getId();
+        return id.equals(ObservationConstants.USER_DIRECTORY_PAGE_CHANGED);
+    }
+    
+    @Override
+    protected void _internalObservePage(Event event, Page target, Skin skin, Session liveSession) throws RepositoryException
+    {
+        _synchronizeComponent.synchronizePage(target, skin, liveSession);
+        
+        if (liveSession.hasPendingChanges())
+        {
+            liveSession.save();
+        }
+    }
+}
Index: main/plugin-user-directory/src/org/ametys/plugins/userdirectory/population/AbstractPopulation.java
===================================================================
--- main/plugin-user-directory/src/org/ametys/plugins/userdirectory/population/AbstractPopulation.java	(revision 42112)
+++ main/plugin-user-directory/src/org/ametys/plugins/userdirectory/population/AbstractPopulation.java	(working copy)
@@ -950,7 +950,7 @@
                     }
                 }
 
-                _observationManager.notify(new Event(_currentUserProvider.getUser(), org.ametys.web.ObservationConstants.PAGE_CHANGED, rootPage, new Object[]{rootPage.getId()}));
+                _observationManager.notify(new Event(_currentUserProvider.getUser(), org.ametys.plugins.userdirectory.observation.ObservationConstants.USER_DIRECTORY_PAGE_CHANGED, rootPage, new Object[]{rootPage.getId()}));
             }
         }
         catch (ServiceException e)
