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.core.observation.AsyncObserver;
034import org.ametys.core.observation.Event;
035import org.ametys.plugins.explorer.ObservationConstants;
036import org.ametys.plugins.explorer.resources.Resource;
037import org.ametys.plugins.explorer.resources.ResourceCollection;
038import org.ametys.plugins.repository.AmetysObject;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
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            }
147            else if (event.getId().equals(ObservationConstants.EVENT_RESOURCE_UPDATED)
148                    || event.getId().equals(ObservationConstants.EVENT_RESOURCE_RENAMED)
149                    || event.getId().equals(ObservationConstants.EVENT_RESOURCE_MOVED))
150            {
151                // A resource was modified, renamed or moved: reindex the corresponding document.
152                String resourceId = (String) event.getArguments().get(ObservationConstants.ARGS_ID);
153                Resource resource = _resolver.resolveById(resourceId);
154                if (isHandledResource(resource))
155                {
156                    onResourceUpdated(resource, workspaceName);
157                }
158            }
159            else if (event.getId().equals(ObservationConstants.EVENT_COLLECTION_RENAMED)
160                    || event.getId().equals(ObservationConstants.EVENT_COLLECTION_MOVED))
161            {
162                // A collection was moved: recursively reindex all resources in it.
163                String collectionId = (String) event.getArguments().get(ObservationConstants.ARGS_ID);
164                ResourceCollection resourceCollection = _resolver.resolveById(collectionId);
165                
166                if (isHandledResource(resourceCollection))
167                {
168                    onCollectionRenamedOrMoved(resourceCollection, workspaceName);
169                }
170            }
171        }
172        catch (Exception e)
173        {
174            String error = String.format("Failed to index some resources in workspace %s", workspaceName);
175            getLogger().error(error, e);
176            throw new IndexingException(error, e);
177        }
178        finally
179        {
180            // Restore context
181            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
182        }
183    }
184
185    /**
186     * Returns true if it is a {@link Resource} or {@link ResourceCollection} handled by this observer
187     * @param object The resource or collection
188     * @return true if it is handled by this observer
189     */
190    protected abstract boolean isHandledResource(AmetysObject object);
191    
192    /**
193     * Method called when a resource handled by this observer is created. Typically for indexing the created resource.
194     * @param resource The created resource
195     * @param workspaceName The workspace name
196     * @throws Exception if an error occurs
197     */
198    protected abstract void onResourceCreated(Resource resource, String workspaceName) throws Exception;
199    
200    /**
201     * Method called when a resource handled by this observer is updated, moved or renamed. Typically for indexing the updated resource.
202     * @param resource The updated resource
203     * @param workspaceName The workspace name
204     * @throws Exception if an error occurs
205     */
206    protected abstract void onResourceUpdated(Resource resource, String workspaceName) throws Exception;
207    
208    /**
209    * Method called when a resource collection handled by this observer is renamed or moved. Typically for indexing the children of the resource collection.
210    * @param resourceCollection The resource collection
211     * @param workspaceName The workspace name
212    * @throws Exception if an error occurs
213    */
214    protected abstract void onCollectionRenamedOrMoved(ResourceCollection resourceCollection, String workspaceName) throws Exception;
215}