001/*
002 *  Copyright 2016 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.documents;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Set;
022
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.components.ContextHelper;
028import org.apache.cocoon.environment.Request;
029
030import org.ametys.cms.fo.ForceDefaultRepositoryWorkspaceCallableDecorator;
031import org.ametys.core.right.RightManager.RightResult;
032import org.ametys.core.ui.Callable;
033import org.ametys.core.user.UserIdentity;
034import org.ametys.plugins.explorer.ExplorerNode;
035import org.ametys.plugins.explorer.ObservationConstants;
036import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
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.AmetysRepositoryException;
041import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
042import org.ametys.plugins.workspaces.AbstractWorkspaceModule;
043import org.ametys.plugins.workspaces.documents.jcr.FolderFactory;
044import org.ametys.plugins.workspaces.project.objects.Project;
045import org.ametys.plugins.workspaces.util.StatisticColumn;
046import org.ametys.plugins.workspaces.util.StatisticsColumnType;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.web.repository.page.ModifiablePage;
049import org.ametys.web.repository.page.ModifiableZone;
050import org.ametys.web.repository.page.ModifiableZoneItem;
051import org.ametys.web.repository.page.ZoneItem.ZoneType;
052
053import com.google.common.collect.ImmutableSet;
054
055/**
056 * Helper component for managing documents
057 */
058public class DocumentWorkspaceModule extends AbstractWorkspaceModule
059{
060    /** The id of document module */
061    public static final String DOCUMENT_MODULE_ID = DocumentWorkspaceModule.class.getName();
062    
063    /** Workspaces documents node name */
064    public static final String WORKSPACES_DOCUMENTS_NODE_NAME = "documents";
065
066    private static final String __DOCUMENT_NUMBER_HEADER_ID = WORKSPACES_DOCUMENTS_NODE_NAME + "$document_number";
067 
068    private WorkspaceExplorerTrashDAO _workspacesExplorerTrashDAO;
069
070    @Override
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        super.service(manager);
074        _workspacesExplorerTrashDAO = (WorkspaceExplorerTrashDAO) manager.lookup(WorkspaceExplorerTrashDAO.ROLE);
075    }
076    
077    @Override
078    public void contextualize(Context context) throws ContextException
079    {
080        _context = context;
081    }
082    
083    @Override
084    public String getId()
085    {
086        return DOCUMENT_MODULE_ID;
087    }
088    
089    @Override
090    public String getModuleName()
091    {
092        return WORKSPACES_DOCUMENTS_NODE_NAME;
093    }
094    
095    public int getOrder()
096    {
097        return ORDER_DOCUMENTS;
098    }
099    
100    @Override
101    protected String getModulePageName()
102    {
103        return "documents";
104    }
105    
106    public I18nizableText getModuleTitle()
107    {
108        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_DOCUMENT_LABEL");
109    }
110    public I18nizableText getModuleDescription()
111    {
112        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_DOCUMENT_DESCRIPTION");
113    }
114    @Override
115    protected I18nizableText getModulePageTitle()
116    {
117        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_DOCUMENTS_TITLE");
118    }
119    
120    @Override
121    protected void initializeModulePage(ModifiablePage documentPage)
122    {
123        ModifiableZone defaultZone = documentPage.createZone("default");
124        
125        String serviceId = "org.ametys.plugins.workspaces.module.Document";
126        ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem();
127        defaultZoneItem.setType(ZoneType.SERVICE);
128        defaultZoneItem.setServiceId(serviceId);
129        
130        ModifiableModelAwareDataHolder serviceDataHolder = defaultZoneItem.getServiceParameters();
131        serviceDataHolder.setValue("xslt", _getDefaultXslt(serviceId));
132    }
133
134    /**
135     * Get the URI of a folder in project'site
136     * @param project The project
137     * @param collectionId The id of collection
138     * @return The thread uri
139     */
140    public String getFolderUri(Project project, String collectionId)
141    {
142        String moduleUrl = getModuleUrl(project);
143        if (moduleUrl != null)
144        {
145            StringBuilder sb = new StringBuilder();
146            sb.append(moduleUrl);
147            sb.append("#").append(collectionId);
148            
149            return sb.toString();
150        }
151        
152        return null;
153    }
154    
155    /**
156     * Retrieves the set of general rights used in the document module for the current user
157     * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not.
158     */
159    @Callable (rights = Callable.NO_CHECK_REQUIRED, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
160    public Map<String, Object> getModuleBaseRights()
161    {
162        Request request = ContextHelper.getRequest(_context);
163        
164        String projectName = (String) request.getAttribute("projectName");
165        Project project = _projectManager.getProject(projectName);
166        
167        ModifiableResourceCollection documentRoot = getModuleRoot(project, false);
168        
169        Map<String, Object> rightsData = new HashMap<>();
170        UserIdentity user = _currentUserProvider.getUser();
171        
172        // Add file / folder
173        rightsData.put("add-file", _rightManager.hasRight(user, "Plugin_Explorer_File_Add", documentRoot) == RightResult.RIGHT_ALLOW);
174        rightsData.put("add-folder", _rightManager.hasRight(user, "Plugin_Explorer_Folder_Add", documentRoot) == RightResult.RIGHT_ALLOW);
175        rightsData.put("add-cmis-folder", _rightManager.hasRight(user, "Plugin_Explorer_CMIS_Add", documentRoot) == RightResult.RIGHT_ALLOW);
176        
177        // Tags
178        rightsData.put("add-tag", _projectRightHelper.canAddTag(project));
179        rightsData.put("remove-tag", _projectRightHelper.canRemoveTag(project));
180        
181        return rightsData;
182    }
183
184    @Override
185    public Set<String> getAllowedEventTypes()
186    {
187        return ImmutableSet.of(ObservationConstants.EVENT_RESOURCE_CREATED,
188                org.ametys.plugins.workspaces.ObservationConstants.EVENT_RESOURCE_COMMENTED,
189                ObservationConstants.EVENT_RESOURCE_RENAMED,
190                ObservationConstants.EVENT_RESOURCE_UPDATED,
191                ObservationConstants.EVENT_RESOURCE_DELETED);
192    }
193
194    @Override
195    public Map<String, Object> _getInternalStatistics(Project project, boolean isActive)
196    {
197        if (isActive)
198        {
199            ModifiableResourceCollection documentRoot = getModuleRoot(project, false);
200            long fileNumber = documentRoot == null ? 0 : documentRoot.getChildren()
201                    .stream()
202                    .mapToLong(child -> _getFileNumber(child))
203                    .sum();
204            return Map.of(__DOCUMENT_NUMBER_HEADER_ID, fileNumber);
205        }
206        else
207        {
208            return Map.of(__DOCUMENT_NUMBER_HEADER_ID, __SIZE_INACTIVE);
209        }
210    }
211
212    /**
213     * Get the size of a resource (file or folder)
214     * @param document The resource
215     * @return The size in bytes
216     */
217    public long getRessourceSize(AmetysObject document)
218    {
219        if (document instanceof Resource)
220        {
221            Resource file = (Resource) document;
222            return file.getLength();
223        }
224        else if (document instanceof ResourceCollection)
225        {
226            ResourceCollection folder = (ResourceCollection) document;
227            
228            return folder.getChildren()
229                .stream()
230                .mapToLong(child -> getRessourceSize(child))
231                .sum();
232        }
233        return 0;
234    }
235    
236    private long _getFileNumber(AmetysObject document)
237    {
238        if (document instanceof Resource)
239        {
240            return 1;
241        }
242        else if (document instanceof ResourceCollection)
243        {
244            ResourceCollection folder = (ResourceCollection) document;
245            return folder.getChildren()
246                .stream()
247                .mapToLong(child -> _getFileNumber(child))
248                .sum();
249        }
250        return 0;
251    }
252
253    @Override
254    public List<StatisticColumn> _getInternalStatisticModel()
255    {
256
257        return List.of(new StatisticColumn(__DOCUMENT_NUMBER_HEADER_ID, new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_FILE_NUMBER"))
258                .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements")
259                .withType(StatisticsColumnType.LONG)
260                .withGroup(GROUP_HEADER_ELEMENTS_ID));
261    }
262    
263    @Override
264    public long getModuleSize(Project project)
265    {
266        ModifiableResourceCollection collection = getModuleRoot(project, false);
267        if (collection == null)
268        {
269            return 0;
270        }
271        Long usedTrashStorageSpace = _workspacesExplorerTrashDAO.getUsedTrashStorageSpace(project);
272        
273        Long usedStorageSpace = getModuleRoot(project, false).getChildren()
274            .stream()
275            .mapToLong(child -> getRessourceSize(child))
276            .sum();
277        
278        return usedStorageSpace + usedTrashStorageSpace;
279    }
280
281    @Override
282    protected boolean _showModuleSize()
283    {
284        return true;
285    }
286
287    @Override
288    public Set<String> getAllEventTypes()
289    {
290        return Set.of(ObservationConstants.EVENT_RESOURCE_CREATED,
291                      org.ametys.plugins.workspaces.ObservationConstants.EVENT_RESOURCE_COMMENTED,
292                      ObservationConstants.EVENT_RESOURCE_DELETED,
293                      ObservationConstants.EVENT_RESOURCE_MOVED,
294                      ObservationConstants.EVENT_RESOURCE_RENAMED,
295                      ObservationConstants.EVENT_RESOURCE_UPDATED);
296    }
297
298    @Override
299    public ModifiableResourceCollection getModuleRoot(Project project, boolean create)
300    {
301        try
302        {
303            ExplorerNode projectRootNode = project.getExplorerRootNode();
304            
305            if (projectRootNode instanceof ModifiableResourceCollection projectRootNodeRc)
306            {
307                return _getAmetysObject(projectRootNodeRc, getModuleName(), FolderFactory.FOLDER_NODETYPE, create);
308            }
309            else
310            {
311                throw new IllegalArgumentException("Error getting the " + getModuleName() + " root node : it should be a ModifiableResourceCollection and not " + projectRootNode.getClass());
312            }
313        }
314        catch (AmetysRepositoryException e)
315        {
316            throw new AmetysRepositoryException("Error getting the " + getModuleName() + " root node.", e);
317        }
318    }
319    
320    /**
321     * Get the URI of a fileId in project'site
322     * @param project The project
323     * @param fileId The id of the file
324     * @return The fileId uri
325     */
326    public String getFileUri(Project project, String fileId)
327    {
328        String moduleUrl = getModuleUrl(project);
329        if (moduleUrl != null)
330        {
331            StringBuilder sb = new StringBuilder();
332            sb.append(moduleUrl);
333            sb.append("?route=file-").append(fileId);
334            
335            return sb.toString();
336        }
337        
338        return null;
339    }
340}