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