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                threadsList.add(getThreadData(thread, includeChildren));
123            }
124        }
125        
126        result.put("threads", threadsList);
127        return result;
128    }
129    
130    /**
131     * Add a thread
132     * @param name The desired name for the thread
133     * @param description The thread description
134     * @return The result map with id, parentId and name keys
135     * @throws IllegalAccessException If the user has no sufficient rights
136     * @throws AmetysRepositoryException If a repository error occurred
137     */
138    @Callable
139    public Map<String, Object> addThread(String name, String description) throws IllegalAccessException, AmetysRepositoryException
140    {
141        Request request = ContextHelper.getRequest(_context);
142        String projectName = (String) request.getAttribute("projectName");
143        
144        Project project = _projectManager.getProject(projectName);
145        
146        WorkspaceModule module = _moduleEP.getModule(ThreadWorkspaceModule.THREAD_MODULE_ID);
147        ModifiableResourceCollection threadRoot = module.getModuleRoot(project, false);
148        
149        assert threadRoot != null;
150        
151        return addThread(threadRoot.getId(), name, description);
152    }
153    
154    @Override
155    protected void setPostContent(JCRPost post, String content)
156    {
157        try
158        {
159            _htmlTransformer.transform(content, post.getContent());
160        }
161        catch (IOException e)
162        {
163            throw new AmetysRepositoryException("Failed to transform post content into rich text", e);
164        }
165    }
166    
167    @Override
168    protected String getPostContent(JCRPost post) throws AmetysRepositoryException
169    {
170        Source contentSource = null;
171        try
172        {
173            Map<String, Object> parameters = new HashMap<>();
174            parameters.put("source", post.getContent().getInputStream());
175            contentSource = _sourceResolver.resolveURI("cocoon://_plugins/" + _pluginName + "/convert/html2html", null, parameters);
176            return IOUtils.toString(contentSource.getInputStream(), "UTF-8");
177        }
178        catch (IOException e)
179        {
180            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
181        }
182        finally
183        {
184            _sourceResolver.release(contentSource);
185        }
186    }
187    
188    @Override
189    protected String getPostContentForEditing(JCRPost post) throws AmetysRepositoryException
190    {
191        try
192        {
193            StringBuilder sb = new StringBuilder();
194            _htmlTransformer.transformForEditing(post.getContent(), sb);
195            return sb.toString();
196        }
197        catch (IOException e)
198        {
199            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
200        }
201    }
202
203    /**
204     * Get the number of threads in the project
205     * @param project The project
206     * @return The number of threads, or null if the module is not activated
207     */
208    public Long getThreadsCount(Project project)
209    {
210        Function<Project, ResourceCollection> getModuleRoot = proj -> _moduleEP.getModule(ThreadWorkspaceModule.THREAD_MODULE_ID).getModuleRoot(proj, false);
211        Function<ResourceCollection, Long> countThreads = root -> root.getChildren().stream()
212                .filter(JCRThread.class::isInstance)
213                .count();
214        
215        return Optional.ofNullable(project)
216                .map(getModuleRoot)
217                .map(countThreads)
218                .orElse(null);
219    }
220}