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.plugins.workspaces.threads;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.function.Function;
025
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.io.IOUtils;
034import org.apache.commons.lang.IllegalClassException;
035import org.apache.excalibur.source.Source;
036import org.apache.excalibur.source.SourceResolver;
037
038import org.ametys.core.ui.Callable;
039import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
040import org.ametys.plugins.explorer.resources.ResourceCollection;
041import org.ametys.plugins.explorer.threads.actions.ThreadDAO;
042import org.ametys.plugins.explorer.threads.jcr.JCRPost;
043import org.ametys.plugins.explorer.threads.jcr.JCRThread;
044import org.ametys.plugins.repository.AmetysObject;
045import org.ametys.plugins.repository.AmetysRepositoryException;
046import org.ametys.plugins.workspaces.html.HTMLTransformer;
047import org.ametys.plugins.workspaces.project.ProjectManager;
048import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
049import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
050import org.ametys.plugins.workspaces.project.objects.Project;
051import org.ametys.runtime.plugin.component.PluginAware;
052
053/**
054 * DAO for manipulating thread of a project
055 *
056 */
057public class WorkspaceThreadDAO extends ThreadDAO implements PluginAware, Contextualizable
058{
059    /** Avalon Role */
060    @SuppressWarnings("hiding")
061    public static final String ROLE = WorkspaceThreadDAO.class.getName();
062    
063    private HTMLTransformer _htmlTransformer;
064    private SourceResolver _sourceResolver;
065    private String _pluginName;
066    private ProjectManager _projectManager;
067
068    private Context _context;
069
070    private WorkspaceModuleExtensionPoint _moduleEP;
071
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        super.service(manager);
076        _htmlTransformer = (HTMLTransformer) manager.lookup(HTMLTransformer.ROLE);
077        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
078        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
079        _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
080    }
081    
082    @Override
083    public void setPluginInfo(String pluginName, String featureName, String id)
084    {
085        _pluginName = pluginName;
086    }
087
088    @Override
089    public void contextualize(Context context) throws ContextException
090    {
091        _context = context;
092    }
093    
094    /**
095     * Get the list of threads of a project
096     * @param includeChildren True to also include threads messages
097     * @return The list of threads
098     */
099    @Callable
100    public Map<String, Object> getThreadsList(boolean includeChildren)
101    {
102        Request request = ContextHelper.getRequest(_context);
103        String projectName = (String) request.getAttribute("projectName");
104        
105        Project project = _projectManager.getProject(projectName);
106        
107        WorkspaceModule module = _moduleEP.getModule(ThreadWorkspaceModule.THREAD_MODULE_ID);
108        ModifiableResourceCollection threadRoot = module.getModuleRoot(project, false);
109        
110        Map<String, Object> result = new HashMap<>();
111        List<Map<String, Object>> threadsList = new ArrayList<>();
112        if (threadRoot != null)
113        {
114            for (AmetysObject ametysObject : threadRoot.getChildren())
115            {
116                if (!(ametysObject instanceof JCRThread))
117                {
118                    throw new IllegalClassException(JCRThread.class, ametysObject.getClass());
119                }
120                
121                JCRThread thread = (JCRThread) ametysObject;
122                if (_rightManager.currentUserHasReadAccess(thread))
123                {
124                    threadsList.add(getThreadData(thread, includeChildren));
125                }
126            }
127        }
128        
129        result.put("threads", threadsList);
130        return result;
131    }
132    
133    /**
134     * Add a thread
135     * @param name The desired name for the thread
136     * @param description The thread description
137     * @return The result map with id, parentId and name keys
138     * @throws IllegalAccessException If the user has no sufficient rights
139     * @throws AmetysRepositoryException If a repository error occurred
140     */
141    @Callable
142    public Map<String, Object> addThread(String name, String description) throws IllegalAccessException, AmetysRepositoryException
143    {
144        Request request = ContextHelper.getRequest(_context);
145        String projectName = (String) request.getAttribute("projectName");
146        
147        Project project = _projectManager.getProject(projectName);
148        
149        WorkspaceModule module = _moduleEP.getModule(ThreadWorkspaceModule.THREAD_MODULE_ID);
150        ModifiableResourceCollection threadRoot = module.getModuleRoot(project, false);
151        
152        assert threadRoot != null;
153        
154        return addThread(threadRoot.getId(), name, description);
155    }
156    
157    @Override
158    protected void setPostContent(JCRPost post, String content)
159    {
160        try
161        {
162            _htmlTransformer.transform(content, post.getContent());
163        }
164        catch (IOException e)
165        {
166            throw new AmetysRepositoryException("Failed to transform post content into rich text", e);
167        }
168    }
169    
170    @Override
171    protected String getPostContent(JCRPost post) throws AmetysRepositoryException
172    {
173        Source contentSource = null;
174        try
175        {
176            Map<String, Object> parameters = new HashMap<>();
177            parameters.put("source", post.getContent().getInputStream());
178            contentSource = _sourceResolver.resolveURI("cocoon://_plugins/" + _pluginName + "/convert/html2html", null, parameters);
179            return IOUtils.toString(contentSource.getInputStream(), "UTF-8");
180        }
181        catch (IOException e)
182        {
183            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
184        }
185        finally
186        {
187            _sourceResolver.release(contentSource);
188        }
189    }
190    
191    @Override
192    protected String getPostContentForEditing(JCRPost post) throws AmetysRepositoryException
193    {
194        try
195        {
196            StringBuilder sb = new StringBuilder();
197            _htmlTransformer.transformForEditing(post.getContent(), sb);
198            return sb.toString();
199        }
200        catch (IOException e)
201        {
202            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
203        }
204    }
205
206    /**
207     * Get the number of threads in the project
208     * @param project The project
209     * @return The number of threads, or null if the module is not activated
210     */
211    public Long getThreadsCount(Project project)
212    {
213        Function<Project, ResourceCollection> getModuleRoot = proj -> _moduleEP.getModule(ThreadWorkspaceModule.THREAD_MODULE_ID).getModuleRoot(proj, false);
214        Function<ResourceCollection, Long> countThreads = root -> root.getChildren().stream()
215                .filter(JCRThread.class::isInstance)
216                .count();
217        
218        return Optional.ofNullable(project)
219                .map(getModuleRoot)
220                .map(countThreads)
221                .orElse(null);
222    }
223}