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;
046import org.ametys.web.search.solr.field.OrphanSearchField;
047import org.ametys.web.search.solr.field.PagesSearchField;
048
049/**
050 * Observes when a content is made orphan or reaffected to a page and update the index accordingly.
051 */
052public class ContentOrphanStatusPart2Observer extends AbstractLogEnabled implements Observer, Serviceable, Contextualizable
053{
054    /** The Solr indexer. */
055    protected SolrIndexer _solrIndexer;
056    /** The Ametys object resolver */
057    protected AmetysObjectResolver _resolver;
058    /** The page DAO */
059    protected PageDAO _pageDAO;
060    /** The avalon context */
061    protected Context _context;
062    
063    @Override
064    public void service(ServiceManager manager) throws ServiceException
065    {
066        _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE);
067        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
068        _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE);
069    }
070    
071    @Override
072    public void contextualize(Context context) throws ContextException
073    {
074        _context = context;
075    }
076    
077    @Override
078    public boolean supports(Event event)
079    {
080        return event.getId().equals(ObservationConstants.EVENT_ZONEITEM_DELETED)
081            || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_ADDED)
082            || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_MODIFIED)
083            || event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED)
084            || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED)
085            || event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED);
086    }
087    
088    @Override
089    public int getPriority(Event event)
090    {
091        // Will be processed AFTER live synchronization observers, to have up-to-date pages in live, in order to have correct indexation data
092        return Observer.MAX_PRIORITY + 4000;
093    }
094    
095    @Override
096    public void observe(Event event, Map<String, Object> transientVars) throws Exception
097    {
098        if (ObserverHelper.isNotSuspendedObservationForIndexation())
099        {
100            if (event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED) || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED))
101            {
102                Page page = (Page) event.getArguments().get(ObservationConstants.ARGS_PAGE);
103                if (page != null)
104                {
105                    boolean pageWasInLive = _pageWasInLive(transientVars);
106                    for (Content content : _pageDAO.getPageContents(page))
107                    {
108                        _updateContentProperties(content, page.getId(), pageWasInLive);   
109                    }
110                }
111            }
112            else if (event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED))
113            {
114                @SuppressWarnings("unchecked")
115                List<Content> contents = (List<Content>) event.getArguments().get(ObservationConstants.ARGS_PAGE_CONTENTS);
116                String pageId = (String) event.getArguments().get(ObservationConstants.ARGS_PAGE_ID);
117                if (contents != null)
118                {
119                    boolean pageWasInLive = _pageWasInLive(transientVars);
120                    for (Content content : contents)
121                    {
122                        _updateContentProperties(content, pageId, pageWasInLive);   
123                    }
124                }
125            }
126            else
127            {
128                ZoneType zoneType = (ZoneType) event.getArguments().get(ObservationConstants.ARGS_ZONE_TYPE);
129                if (zoneType == ZoneType.CONTENT)
130                {
131                    Content content = (Content) event.getArguments().get(ObservationConstants.ARGS_ZONE_ITEM_CONTENT);
132                    if (content != null)
133                    {
134                        _updateContentProperties(content, null, false);
135                    }
136                }
137            }
138        }
139    }
140    
141    private boolean _pageWasInLive(Map<String, Object> transientVars)
142    {
143        return (boolean) transientVars.getOrDefault(ContentOrphanStatusPart1Observer.PAGE_WAS_IN_LIVE_KEY, false);
144    }
145
146    /**
147     * Update content properties
148     * @param content the content 
149     * @param pageId the page id
150     * @param pageWasInLive if the page was in live before operation
151     * @throws Exception if an error occurred
152     */
153    protected void _updateContentProperties(Content content, String pageId, boolean pageWasInLive) throws Exception
154    {
155        // default workspace
156        _updateProperties(content, RepositoryConstants.DEFAULT_WORKSPACE);
157        
158        // live workspace
159        Request request = ContextHelper.getRequest(_context);
160        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
161        try
162        {
163            // Force the workspace.
164            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
165            
166            if (_resolver.hasAmetysObjectForId(content.getId()))
167            {
168                Content liveContent = _resolver.resolveById(content.getId(), null);
169                if (StringUtils.isBlank(pageId))
170                {
171                    _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE);
172                }
173                else
174                {
175                    boolean isPageInLive = _resolver.hasAmetysObjectForId(pageId);
176                    if (isPageInLive ^ pageWasInLive) // A XOR B : we update content properties in live workspace if page status has changed
177                    {
178                        _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE);
179                    }
180                }
181            } 
182        }
183        finally
184        {
185            // Restore workspace
186            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
187        }
188    }
189    
190    private void _updateProperties(Content content, String workspace) throws Exception
191    {
192        if (content instanceof WebContent)
193        {
194            // Update orphan status
195            _solrIndexer.updateSystemProperty(content, OrphanSearchField.NAME, workspace);
196            // Update pageIds
197            _solrIndexer.updateSystemProperty(content, PagesSearchField.NAME, workspace);
198        }
199    }
200}