001/*
002 *  Copyright 2017 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.cms.indexing.explorer;
017
018import java.util.Map;
019
020import org.apache.avalon.framework.context.Context;
021import org.apache.avalon.framework.context.ContextException;
022import org.apache.avalon.framework.context.Contextualizable;
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026import org.apache.cocoon.components.ContextHelper;
027import org.apache.cocoon.environment.Request;
028import org.apache.commons.lang.BooleanUtils;
029
030import org.ametys.cms.content.indexing.solr.SolrIndexer;
031import org.ametys.cms.content.indexing.solr.observation.ObserverHelper;
032import org.ametys.cms.indexing.IndexingException;
033import org.ametys.cms.repository.RequestAttributeWorkspaceSelector;
034import org.ametys.core.observation.AsyncObserver;
035import org.ametys.core.observation.Event;
036import org.ametys.plugins.explorer.ObservationConstants;
037import org.ametys.plugins.explorer.resources.Resource;
038import org.ametys.plugins.explorer.resources.ResourceCollection;
039import org.ametys.plugins.repository.AmetysObject;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.runtime.plugin.component.AbstractLogEnabled;
042
043/**
044 * Abstract observer in charge of indexing resources when created, modified, moved...
045 */
046public abstract class AbstractSolrIndexResourceObserver extends AbstractLogEnabled implements AsyncObserver, Contextualizable, Serviceable
047{
048    /** Request attribute disabling content indexation when set to <code>true</code>. */
049    public static final String DISABLE_INDEXING_KEY = SolrIndexResourceObserver.class.getName() + "$disableIndexing";
050    
051    /** The Solr indexer. */
052    protected SolrIndexer _solrIndexer;
053    
054    /** The AmetysObject resolver. */
055    protected AmetysObjectResolver _resolver;
056    
057    /** The component context. */
058    protected Context _context;
059    
060    @Override
061    public void contextualize(Context context) throws ContextException
062    {
063        _context = context;
064    }
065    
066    @Override
067    public void service(ServiceManager serviceManager) throws ServiceException
068    {
069        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
070        _solrIndexer = (SolrIndexer) serviceManager.lookup(SolrIndexer.ROLE);
071    }
072    
073    @Override
074    public boolean supports(Event event)
075    {
076        // If the resource is moved or renamed, the document stays the same, only its data changes
077        // (there is no need to unindex a document and index a new one).
078        return event.getId().equals(ObservationConstants.EVENT_RESOURCE_CREATED)
079            || event.getId().equals(ObservationConstants.EVENT_RESOURCE_UPDATED)
080            || event.getId().equals(ObservationConstants.EVENT_RESOURCE_RENAMED)
081            || event.getId().equals(ObservationConstants.EVENT_RESOURCE_MOVED)
082            || event.getId().equals(ObservationConstants.EVENT_COLLECTION_RENAMED)
083            || event.getId().equals(ObservationConstants.EVENT_COLLECTION_MOVED);
084    }
085    
086    @Override
087    public int getPriority(Event event)
088    {
089        return MAX_PRIORITY + 3000;
090    }
091    
092    @Override
093    public void observe(Event event, Map<String, Object> transientVars) throws Exception
094    {
095        Request request = ContextHelper.getRequest(_context);
096        Boolean disableIndexingAttr = (Boolean) request.getAttribute(DISABLE_INDEXING_KEY);
097
098        if (ObserverHelper.isNotSuspendedObservationForIndexation() && BooleanUtils.isNotTrue(disableIndexingAttr))
099        {
100            String[] workspaces = getWorkspacesToIndex();
101            for (String workspace : workspaces)
102            {
103                _index(event, workspace);
104            }
105        }
106    }
107    
108    /**
109     * Get the list of workspace to index
110     * @return An array of workspace names
111     */
112    protected abstract String[] getWorkspacesToIndex();
113    
114    /**
115     * Perform the indexation process
116     * @param event The current event
117     * @param workspaceName The workspace name for the indexation
118     * @throws IndexingException if an error occurs during indexation.
119     */
120    protected void _index(Event event, String workspaceName) throws IndexingException
121    {
122        Request request = ContextHelper.getRequest(_context);
123        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
124        
125        try
126        {
127            // Force the workspace.
128            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
129            
130            if (event.getId().equals(ObservationConstants.EVENT_RESOURCE_CREATED))
131            {
132                // Resources were created: index each one.
133                @SuppressWarnings("unchecked")
134                Map<String, Resource> resources = (Map<String, Resource>) event.getArguments().get(ObservationConstants.ARGS_RESOURCES);
135                if (resources != null)
136                {
137                    for (String resourceId : resources.keySet())
138                    {
139                        Resource resource = _resolver.resolveById(resourceId);
140                        if (isHandledResource(resource))
141                        {
142                            onResourceCreated(resource, workspaceName);
143                        }
144                    }
145                    
146                    _solrIndexer.commit(workspaceName);
147                }
148            }
149            else if (event.getId().equals(ObservationConstants.EVENT_RESOURCE_UPDATED)
150                    || event.getId().equals(ObservationConstants.EVENT_RESOURCE_RENAMED)
151                    || event.getId().equals(ObservationConstants.EVENT_RESOURCE_MOVED))
152            {
153                // A resource was modified, renamed or moved: reindex the corresponding document.
154                String resourceId = (String) event.getArguments().get(ObservationConstants.ARGS_ID);
155                Resource resource = _resolver.resolveById(resourceId);
156                if (isHandledResource(resource))
157                {
158                    onResourceUpdated(resource, workspaceName);
159                    _solrIndexer.commit(workspaceName);
160                }
161            }
162            else if (event.getId().equals(ObservationConstants.EVENT_COLLECTION_RENAMED)
163                    || event.getId().equals(ObservationConstants.EVENT_COLLECTION_MOVED))
164            {
165                // A collection was moved: recursively reindex all resources in it.
166                String collectionId = (String) event.getArguments().get(ObservationConstants.ARGS_ID);
167                ResourceCollection resourceCollection = _resolver.resolveById(collectionId);
168                
169                if (isHandledResource(resourceCollection))
170                {
171                    onCollectionRenamedOrMoved(resourceCollection, workspaceName);
172                    _solrIndexer.commit(workspaceName);
173                }
174            }
175        }
176        catch (Exception e)
177        {
178            String error = String.format("Failed to index some resources in workspace %s", workspaceName);
179            getLogger().error(error, e);
180            throw new IndexingException(error, e);
181        }
182        finally
183        {
184            // Restore context
185            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
186        }
187    }
188
189    /**
190     * Returns true if it is a {@link Resource} or {@link ResourceCollection} handled by this observer
191     * @param object The resource or collection
192     * @return true if it is handled by this observer
193     */
194    protected abstract boolean isHandledResource(AmetysObject object);
195    
196    /**
197     * Method called when a resource handled by this observer is created. Typically for indexing the created resource.
198     * @param resource The created resource
199     * @param workspaceName The workspace name
200     * @throws Exception if an error occurs
201     */
202    protected abstract void onResourceCreated(Resource resource, String workspaceName) throws Exception;
203    
204    /**
205     * Method called when a resource handled by this observer is updated, moved or renamed. Typically for indexing the updated resource.
206     * @param resource The updated resource
207     * @param workspaceName The workspace name
208     * @throws Exception if an error occurs
209     */
210    protected abstract void onResourceUpdated(Resource resource, String workspaceName) throws Exception;
211    
212    /**
213    * Method called when a resource collection handled by this observer is renamed or moved. Typically for indexing the children of the resource collection.
214    * @param resourceCollection The resource collection
215     * @param workspaceName The workspace name
216    * @throws Exception if an error occurs
217    */
218    protected abstract void onCollectionRenamedOrMoved(ResourceCollection resourceCollection, String workspaceName) throws Exception;
219}