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