001/*
002 *  Copyright 2015 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.web.indexing.observation;
017
018import java.util.List;
019import java.util.Map;
020
021import org.apache.avalon.framework.context.Context;
022import org.apache.avalon.framework.context.ContextException;
023import org.apache.avalon.framework.context.Contextualizable;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.cocoon.components.ContextHelper;
028import org.apache.cocoon.environment.Request;
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.cms.content.indexing.solr.SolrIndexer;
032import org.ametys.cms.content.indexing.solr.observation.ObserverHelper;
033import org.ametys.cms.repository.Content;
034import org.ametys.core.observation.Event;
035import org.ametys.core.observation.Observer;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.RepositoryConstants;
038import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
039import org.ametys.runtime.plugin.component.AbstractLogEnabled;
040import org.ametys.web.ObservationConstants;
041import org.ametys.web.WebConstants;
042import org.ametys.web.repository.content.WebContent;
043import org.ametys.web.repository.page.Page;
044import org.ametys.web.repository.page.PageDAO;
045import org.ametys.web.repository.page.ZoneItem.ZoneType;
046
047/**
048 * Observes when a content is made orphan or reaffected to a page and update the index accordingly.
049 */
050public class ContentOrphanStatusPart2Observer extends AbstractLogEnabled implements Observer, Serviceable, Contextualizable
051{
052    /** The Solr indexer. */
053    protected SolrIndexer _solrIndexer;
054    /** The Ametys object resolver */
055    protected AmetysObjectResolver _resolver;
056    /** The page DAO */
057    protected PageDAO _pageDAO;
058    /** The avalon context */
059    protected Context _context;
060    
061    @Override
062    public void service(ServiceManager manager) throws ServiceException
063    {
064        _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE);
065        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
066        _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE);
067    }
068    
069    @Override
070    public void contextualize(Context context) throws ContextException
071    {
072        _context = context;
073    }
074    
075    @Override
076    public boolean supports(Event event)
077    {
078        return event.getId().equals(ObservationConstants.EVENT_ZONEITEM_DELETED)
079            || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_ADDED)
080            || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_MODIFIED)
081            || event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED)
082            || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED)
083            || event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED);
084    }
085    
086    @Override
087    public int getPriority(Event event)
088    {
089        // Will be processed AFTER live synchronization observers, to have up-to-date pages in live, in order to have correct indexation data
090        return Observer.MAX_PRIORITY + 4000;
091    }
092    
093    @Override
094    public void observe(Event event, Map<String, Object> transientVars) throws Exception
095    {
096        if (ObserverHelper.isNotSuspendedObservationForIndexation())
097        {
098            if (event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED) || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED))
099            {
100                Page page = (Page) event.getArguments().get(ObservationConstants.ARGS_PAGE);
101                if (page != null)
102                {
103                    boolean pageWasInLive = _pageWasInLive(transientVars);
104                    for (Content content : _pageDAO.getPageContents(page))
105                    {
106                        _updateContentProperties(content, page.getId(), pageWasInLive);   
107                    }
108                }
109            }
110            else if (event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED))
111            {
112                @SuppressWarnings("unchecked")
113                List<Content> contents = (List<Content>) event.getArguments().get(ObservationConstants.ARGS_PAGE_CONTENTS);
114                String pageId = (String) event.getArguments().get(ObservationConstants.ARGS_PAGE_ID);
115                if (contents != null)
116                {
117                    boolean pageWasInLive = _pageWasInLive(transientVars);
118                    for (Content content : contents)
119                    {
120                        _updateContentProperties(content, pageId, pageWasInLive);   
121                    }
122                }
123            }
124            else
125            {
126                ZoneType zoneType = (ZoneType) event.getArguments().get(ObservationConstants.ARGS_ZONE_TYPE);
127                if (zoneType == ZoneType.CONTENT)
128                {
129                    Content content = (Content) event.getArguments().get(ObservationConstants.ARGS_ZONE_ITEM_CONTENT);
130                    if (content != null)
131                    {
132                        _updateContentProperties(content, null, false);
133                    }
134                }
135            }
136        }
137    }
138    
139    private boolean _pageWasInLive(Map<String, Object> transientVars)
140    {
141        return (boolean) transientVars.getOrDefault(ContentOrphanStatusPart1Observer.PAGE_WAS_IN_LIVE_KEY, false);
142    }
143
144    /**
145     * Update content properties
146     * @param content the content 
147     * @param pageId the page id
148     * @param pageWasInLive if the page was in live before operation
149     * @throws Exception if an error occurred
150     */
151    protected void _updateContentProperties(Content content, String pageId, boolean pageWasInLive) throws Exception
152    {
153        // default workspace
154        _updateProperties(content, RepositoryConstants.DEFAULT_WORKSPACE);
155        
156        // live workspace
157        Request request = ContextHelper.getRequest(_context);
158        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
159        try
160        {
161            // Force the workspace.
162            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
163            
164            if (_resolver.hasAmetysObjectForId(content.getId()))
165            {
166                Content liveContent = _resolver.resolveById(content.getId(), null);
167                if (StringUtils.isBlank(pageId))
168                {
169                    _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE);
170                }
171                else
172                {
173                    boolean isPageInLive = _resolver.hasAmetysObjectForId(pageId);
174                    if (isPageInLive ^ pageWasInLive) // A XOR B : we update content properties in live workspace if page status has changed
175                    {
176                        _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE);
177                    }
178                }
179            } 
180        }
181        finally
182        {
183            // Restore workspace
184            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
185        }
186    }
187    
188    private void _updateProperties(Content content, String workspace) throws Exception
189    {
190        if (content instanceof WebContent)
191        {
192            // Update orphan status
193            _solrIndexer.updateSystemProperty(content, "orphan", workspace);
194            // Update pageIds
195            _solrIndexer.updateSystemProperty(content, "pages", workspace);
196        }
197    }
198}