001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.file.types;
029
030import org.opencms.db.CmsSecurityManager;
031import org.opencms.file.CmsDataNotImplementedException;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProperty;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.CmsVfsException;
037import org.opencms.file.CmsVfsResourceNotFoundException;
038import org.opencms.lock.CmsLockType;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsIllegalArgumentException;
041import org.opencms.main.OpenCms;
042import org.opencms.util.CmsFileUtil;
043import org.opencms.util.CmsStringUtil;
044
045import java.util.List;
046
047/**
048 * Resource type descriptor for the type "folder".<p>
049 *
050 * @since 6.0.0
051 */
052public abstract class A_CmsResourceTypeFolderBase extends A_CmsResourceType {
053
054    /** Attribute to control shallow copying. */
055    public static final String ATTR_SHALLOW_FOLDER_COPY = "shallow_folder_copy";
056
057    /** The serial version id. */
058    private static final long serialVersionUID = -698470184142645873L;
059
060    /**
061     * Default constructor, used to initialize member variables.<p>
062     */
063    public A_CmsResourceTypeFolderBase() {
064
065        super();
066    }
067
068    /**
069     * @see org.opencms.file.types.I_CmsResourceType#chtype(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int)
070     */
071    @Override
072    public void chtype(CmsObject cms, CmsSecurityManager securityManager, CmsResource filename, int newType)
073    throws CmsException, CmsDataNotImplementedException {
074
075        if (!OpenCms.getResourceManager().getResourceType(newType).isFolder()) {
076            // it is not possible to change the type of a folder to a file type
077            throw new CmsDataNotImplementedException(
078                Messages.get().container(Messages.ERR_CHTYPE_FOLDER_1, cms.getSitePath(filename)));
079        }
080        super.chtype(cms, securityManager, filename, newType);
081    }
082
083    /**
084     * @see org.opencms.file.types.I_CmsResourceType#copyResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, java.lang.String, CmsResource.CmsResourceCopyMode)
085     */
086    @Override
087    public void copyResource(
088        CmsObject cms,
089        CmsSecurityManager securityManager,
090        CmsResource source,
091        String destination,
092        CmsResource.CmsResourceCopyMode siblingMode)
093    throws CmsIllegalArgumentException, CmsException {
094
095        // first validate the destination name
096        destination = validateFoldername(destination);
097
098        // collect all resources in the folder (but exclude deleted ones)
099        List<CmsResource> resources = securityManager.readChildResources(
100            cms.getRequestContext(),
101            source,
102            CmsResourceFilter.IGNORE_EXPIRATION,
103            true,
104            true);
105
106        // handle the folder itself
107        super.copyResource(cms, securityManager, source, destination, siblingMode);
108        Boolean shallow = (Boolean)(cms.getRequestContext().getAttribute(ATTR_SHALLOW_FOLDER_COPY));
109        if ((shallow == null) || !shallow.booleanValue()) {
110            // now walk through all sub-resources in the folder
111            for (int i = 0; i < resources.size(); i++) {
112                CmsResource childResource = resources.get(i);
113                String childDestination = destination.concat(childResource.getName());
114                // handle child resources
115                getResourceType(
116                    childResource).copyResource(cms, securityManager, childResource, childDestination, siblingMode);
117            }
118        }
119    }
120
121    /**
122     * @see org.opencms.file.types.I_CmsResourceType#createResource(org.opencms.file.CmsObject, CmsSecurityManager, java.lang.String, byte[], List)
123     */
124    @Override
125    public CmsResource createResource(
126        CmsObject cms,
127        CmsSecurityManager securityManager,
128        String resourcename,
129        byte[] content,
130        List<CmsProperty> properties)
131    throws CmsException {
132
133        resourcename = validateFoldername(resourcename);
134        return super.createResource(cms, securityManager, resourcename, content, properties);
135    }
136
137    /**
138     * @see org.opencms.file.types.I_CmsResourceType#getLoaderId()
139     */
140    @Override
141    public int getLoaderId() {
142
143        // folders have no loader
144        return -1;
145    }
146
147    /**
148     * @see org.opencms.file.types.A_CmsResourceType#isFolder()
149     */
150    @Override
151    public boolean isFolder() {
152
153        return true;
154    }
155
156    /**
157     * @see org.opencms.file.types.I_CmsResourceType#moveResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, java.lang.String)
158     */
159    @Override
160    public void moveResource(
161        CmsObject cms,
162        CmsSecurityManager securityManager,
163        CmsResource resource,
164        String destination)
165    throws CmsException, CmsIllegalArgumentException {
166
167        String dest = cms.getRequestContext().addSiteRoot(destination);
168        if (!CmsResource.isFolder(dest)) {
169            // ensure folder name end's with a / (required for the following comparison)
170            dest = dest.concat("/");
171        }
172        if (resource.getRootPath().equals(dest)) {
173            // move to target with same name is not allowed
174            throw new CmsVfsException(
175                org.opencms.file.Messages.get().container(org.opencms.file.Messages.ERR_MOVE_SAME_NAME_1, destination));
176        }
177        if (dest.startsWith(resource.getRootPath())) {
178            // move of folder inside itself is not allowed
179            throw new CmsVfsException(
180                org.opencms.file.Messages.get().container(
181                    org.opencms.file.Messages.ERR_MOVE_SAME_FOLDER_2,
182                    cms.getSitePath(resource),
183                    destination));
184        }
185
186        // check the destination
187        try {
188            String destinationWithoutTrailingSlash = dest;
189            if (destinationWithoutTrailingSlash.length() > 1) {
190                // we also want to catch the case where a file (not folder) with the destination path already exists
191                destinationWithoutTrailingSlash = CmsFileUtil.removeTrailingSeparator(dest);
192            }
193            securityManager.readResource(cms.getRequestContext(), destinationWithoutTrailingSlash, CmsResourceFilter.ALL);
194            throw new CmsVfsException(
195                org.opencms.file.Messages.get().container(
196                    org.opencms.file.Messages.ERR_OVERWRITE_RESOURCE_2,
197                    cms.getRequestContext().removeSiteRoot(resource.getRootPath()),
198                    destinationWithoutTrailingSlash));
199        } catch (CmsVfsResourceNotFoundException e) {
200            // ok
201        }
202
203        // first validate the destination name
204        dest = validateFoldername(dest);
205        String targetName = CmsResource.getName(destination).replace("/", "");
206        CmsResource.checkResourceName(targetName);
207
208        securityManager.moveResource(cms.getRequestContext(), resource, dest);
209    }
210
211    /**
212     * @see org.opencms.file.types.I_CmsResourceType#replaceResource(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
213     */
214    @Override
215    public void replaceResource(
216        CmsObject cms,
217        CmsSecurityManager securityManager,
218        CmsResource resource,
219        int type,
220        byte[] content,
221        List<CmsProperty> properties)
222    throws CmsException, CmsDataNotImplementedException {
223
224        if (type != getTypeId()) {
225            // it is not possible to replace a folder with a different type
226            throw new CmsDataNotImplementedException(
227                Messages.get().container(Messages.ERR_REPLACE_RESOURCE_FOLDER_1, cms.getSitePath(resource)));
228        }
229        // properties of a folder can be replaced, content is ignored
230        super.replaceResource(cms, securityManager, resource, getTypeId(), null, properties);
231    }
232
233    /**
234     * @see org.opencms.file.types.I_CmsResourceType#setDateExpired(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
235     */
236    @Override
237    public void setDateExpired(
238        CmsObject cms,
239        CmsSecurityManager securityManager,
240        CmsResource resource,
241        long dateLastModified,
242        boolean recursive)
243    throws CmsException {
244
245        // handle the folder itself
246        super.setDateExpired(cms, securityManager, resource, dateLastModified, recursive);
247
248        if (recursive) {
249            // collect all resources in the folder (but exclude deleted ones)
250            List<CmsResource> resources = securityManager.readChildResources(
251                cms.getRequestContext(),
252                resource,
253                CmsResourceFilter.IGNORE_EXPIRATION,
254                true,
255                true);
256
257            // now walk through all sub-resources in the folder
258            for (int i = 0; i < resources.size(); i++) {
259                CmsResource childResource = resources.get(i);
260                // handle child resources
261                getResourceType(
262                    childResource).setDateExpired(cms, securityManager, childResource, dateLastModified, recursive);
263            }
264        }
265    }
266
267    /**
268     * @see org.opencms.file.types.I_CmsResourceType#setDateLastModified(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
269     */
270    @Override
271    public void setDateLastModified(
272        CmsObject cms,
273        CmsSecurityManager securityManager,
274        CmsResource resource,
275        long dateLastModified,
276        boolean recursive)
277    throws CmsException {
278
279        // handle the folder itself
280        super.setDateLastModified(cms, securityManager, resource, dateLastModified, recursive);
281
282        if (recursive) {
283            // collect all resources in the folder (but exclude deleted ones)
284            List<CmsResource> resources = securityManager.readChildResources(
285                cms.getRequestContext(),
286                resource,
287                CmsResourceFilter.IGNORE_EXPIRATION,
288                true,
289                true);
290
291            // now walk through all sub-resources in the folder
292            for (int i = 0; i < resources.size(); i++) {
293                CmsResource childResource = resources.get(i);
294                // handle child resources
295                getResourceType(childResource).setDateLastModified(
296                    cms,
297                    securityManager,
298                    childResource,
299                    dateLastModified,
300                    recursive);
301            }
302        }
303    }
304
305    /**
306     * @see org.opencms.file.types.I_CmsResourceType#setDateReleased(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, long, boolean)
307     */
308    @Override
309    public void setDateReleased(
310        CmsObject cms,
311        CmsSecurityManager securityManager,
312        CmsResource resource,
313        long dateLastModified,
314        boolean recursive)
315    throws CmsException {
316
317        // handle the folder itself
318        super.setDateReleased(cms, securityManager, resource, dateLastModified, recursive);
319
320        if (recursive) {
321            // collect all resources in the folder (but exclude deleted ones)
322            List<CmsResource> resources = securityManager.readChildResources(
323                cms.getRequestContext(),
324                resource,
325                CmsResourceFilter.IGNORE_EXPIRATION,
326                true,
327                true);
328
329            // now walk through all sub-resources in the folder
330            for (int i = 0; i < resources.size(); i++) {
331                CmsResource childResource = resources.get(i);
332                // handle child resources
333                getResourceType(
334                    childResource).setDateReleased(cms, securityManager, childResource, dateLastModified, recursive);
335            }
336        }
337    }
338
339    /**
340     * @see org.opencms.file.types.A_CmsResourceType#undelete(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, boolean)
341     */
342    @Override
343    public void undelete(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, boolean recursive)
344    throws CmsException {
345
346        // handle the folder itself
347        super.undelete(cms, securityManager, resource, recursive);
348
349        if (recursive) {
350            // collect all resources in the folder (but exclude deleted ones)
351            List<CmsResource> resources = securityManager.readChildResources(
352                cms.getRequestContext(),
353                resource,
354                CmsResourceFilter.ALL,
355                true,
356                true);
357
358            // now walk through all sub-resources in the folder
359            for (int i = 0; i < resources.size(); i++) {
360                CmsResource childResource = resources.get(i);
361                // handle child resources
362                getResourceType(childResource).undelete(cms, securityManager, childResource, recursive);
363            }
364        }
365    }
366
367    /**
368     * @see org.opencms.file.types.I_CmsResourceType#undoChanges(org.opencms.file.CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
369     */
370    @Override
371    public void undoChanges(
372        CmsObject cms,
373        CmsSecurityManager securityManager,
374        CmsResource resource,
375        CmsResource.CmsResourceUndoMode mode)
376    throws CmsException {
377
378        boolean recursive = mode.isRecursive();
379        if (mode == CmsResource.UNDO_MOVE_CONTENT) {
380            // undo move only?
381            String originalPath = securityManager.resourceOriginalPath(cms.getRequestContext(), resource);
382            if (originalPath.equals(resource.getRootPath())) {
383                // resource not moved
384                recursive = false;
385            }
386        }
387
388        List<CmsResource> resources = null;
389        if (recursive) { // recursive?
390            // collect all resources in the folder (including deleted ones)
391            resources = securityManager.readChildResources(
392                cms.getRequestContext(),
393                resource,
394                CmsResourceFilter.ALL,
395                true,
396                true);
397        }
398
399        // handle the folder itself, undo move op
400        super.undoChanges(cms, securityManager, resource, mode);
401
402        // the folder may have been moved back to its original position
403        CmsResource undoneResource2 = securityManager.readResource(
404            cms.getRequestContext(),
405            resource.getStructureId(),
406            CmsResourceFilter.ALL);
407        boolean isMoved = !undoneResource2.getRootPath().equals(resource.getRootPath());
408
409        if (recursive && (resources != null)) { // recursive?
410            // now walk through all sub-resources in the folder, and undo first
411            for (int i = 0; i < resources.size(); i++) {
412                CmsResource childResource = resources.get(i);
413
414                I_CmsResourceType type = getResourceType(childResource);
415
416                if (isMoved) {
417                    securityManager.lockResource(cms.getRequestContext(), childResource, CmsLockType.EXCLUSIVE);
418                }
419                if (!childResource.getState().isNew()) {
420                    // can not use undo for new resources
421                    if (childResource.isFolder()) {
422                        // recurse into this method for subfolders
423                        type.undoChanges(cms, securityManager, childResource, mode);
424                    } else if (!childResource.getState().isNew()) {
425                        // undo changes for changed files
426                        securityManager.undoChanges(cms.getRequestContext(), childResource, mode);
427                    } else {
428                        // undo move for new files? move with the folder
429                        if (mode.isUndoMove()) {
430                            String newPath = cms.getRequestContext().removeSiteRoot(
431                                securityManager.readResource(
432                                    cms.getRequestContext(),
433                                    resource.getStructureId(),
434                                    CmsResourceFilter.ALL).getRootPath() + childResource.getName());
435                            type.moveResource(cms, securityManager, childResource, newPath);
436                        }
437                    }
438                } else if (isMoved) {
439                    // we still need to update the resource path for new resources
440                    String newPath = cms.getRequestContext().removeSiteRoot(
441                        securityManager.readResource(
442                            cms.getRequestContext(),
443                            resource.getStructureId(),
444                            CmsResourceFilter.ALL).getRootPath() + childResource.getName());
445                    type.moveResource(cms, securityManager, childResource, newPath);
446                }
447            }
448
449            // now iterate again all sub-resources in the folder, and actualize the relations
450            for (int i = 0; i < resources.size(); i++) {
451                CmsResource childResource = resources.get(i);
452                updateRelationForUndo(cms, securityManager, childResource);
453            }
454        }
455    }
456
457    /**
458     * Checks if there are at least one character in the folder name,
459     * also ensures that it starts and ends with a '/'.<p>
460     *
461     * @param resourcename folder name to check (complete path)
462     *
463     * @return the validated folder name
464     *
465     * @throws CmsIllegalArgumentException if the folder name is empty or <code>null</code>
466     */
467    private String validateFoldername(String resourcename) throws CmsIllegalArgumentException {
468
469        if (CmsStringUtil.isEmpty(resourcename)) {
470            throw new CmsIllegalArgumentException(
471                org.opencms.db.Messages.get().container(org.opencms.db.Messages.ERR_BAD_RESOURCENAME_1, resourcename));
472        }
473        if (!CmsResource.isFolder(resourcename)) {
474            resourcename = resourcename.concat("/");
475        }
476        if (resourcename.charAt(0) != '/') {
477            resourcename = "/".concat(resourcename);
478        }
479        return resourcename;
480    }
481}