001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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, 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.jlan;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsResource;
032import org.opencms.file.wrapper.CmsObjectWrapper;
033import org.opencms.file.wrapper.CmsWrappedResource;
034import org.opencms.lock.CmsLock;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsLog;
037import org.opencms.util.CmsUUID;
038
039import java.io.IOException;
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.List;
043import java.util.regex.Pattern;
044
045import org.apache.commons.logging.Log;
046
047import org.alfresco.jlan.server.filesys.AccessDeniedException;
048import org.alfresco.jlan.server.filesys.FileAttribute;
049import org.alfresco.jlan.server.filesys.FileInfo;
050import org.alfresco.jlan.server.filesys.NetworkFile;
051import org.alfresco.jlan.smb.SeekType;
052import org.alfresco.jlan.util.WildCard;
053
054/**
055 * This class represents a file for use by the JLAN server component. It currently just
056 * wraps an OpenCms resource.<p>
057 */
058public class CmsJlanNetworkFile extends NetworkFile {
059
060    /** The logger instance for this class. */
061    private static final Log LOG = CmsLog.getLog(CmsJlanNetworkFile.class);
062
063    /** The buffer used for reading/writing file contents. */
064    private CmsFileBuffer m_buffer = new CmsFileBuffer();
065
066    /** Flag which indicates whether the buffer has been initialized. */
067    private boolean m_bufferInitialized;
068
069    /** The CMS context to use. */
070    private CmsObjectWrapper m_cms;
071
072    /** The write count after which the file was last flushed. */
073    private int m_lastFlush;
074
075    /** The wrapped resource. */
076    private CmsResource m_resource;
077
078    /** Flag which indicates whether we need to unlock the resource. */
079    private boolean m_needToUnlock;
080
081    /** Creates a new network file instance.<p>
082     *
083     * @param cms the CMS object wrapper to use
084     * @param resource the actual CMS resource
085     * @param fullName the raw repository path
086     */
087    public CmsJlanNetworkFile(CmsObjectWrapper cms, CmsResource resource, String fullName) {
088
089        super(resource.getName());
090        m_resource = resource;
091        m_cms = cms;
092        updateFromResource();
093        setFullName(normalizeName(fullName));
094        setFileId(resource.getStructureId().hashCode());
095    }
096
097    /**
098     * @see org.alfresco.jlan.server.filesys.NetworkFile#closeFile()
099     */
100    @Override
101    public void closeFile() throws IOException {
102
103        if (hasDeleteOnClose()) {
104            delete();
105        } else {
106            flushFile();
107            if ((getWriteCount() > 0) && m_needToUnlock) {
108                try {
109                    m_cms.unlockResource(m_cms.getSitePath(m_resource));
110                    m_needToUnlock = false;
111                } catch (CmsException e) {
112                    LOG.error("Couldn't unlock file: " + m_resource.getRootPath());
113                }
114            }
115        }
116    }
117
118    /**
119     * Deletes the file.<p>
120     *
121     * @throws IOException if something goes wrong
122     */
123    public void delete() throws IOException {
124
125        try {
126            load(false);
127            ensureLock();
128            m_cms.deleteResource(m_cms.getSitePath(m_resource), CmsResource.DELETE_PRESERVE_SIBLINGS);
129            if (!m_resource.getState().isNew()) {
130                try {
131                    m_cms.unlockResource(m_cms.getSitePath(m_resource));
132                } catch (CmsException e) {
133                    LOG.warn(e.getLocalizedMessage(), e);
134                }
135            }
136        } catch (CmsException e) {
137            throw CmsJlanDiskInterface.convertCmsException(e);
138        }
139    }
140
141    /**
142     * @see org.alfresco.jlan.server.filesys.NetworkFile#flushFile()
143     */
144    @Override
145    public void flushFile() throws IOException {
146
147        int writeCount = getWriteCount();
148        try {
149            if (writeCount > m_lastFlush) {
150                CmsFile file = getFile();
151                if (file != null) {
152                    CmsWrappedResource wr = new CmsWrappedResource(file);
153                    String rootPath = m_cms.getRequestContext().addSiteRoot(
154                        CmsJlanDiskInterface.getCmsPath(getFullName()));
155                    wr.setRootPath(rootPath);
156                    file = wr.getFile();
157                    file.setContents(m_buffer.getContents());
158                    ensureLock();
159                    m_cms.writeFile(file);
160                }
161            }
162            m_lastFlush = writeCount;
163        } catch (CmsException e) {
164            LOG.error(e.getLocalizedMessage(), e);
165            throw new IOException(e);
166        }
167
168    }
169
170    /**
171     * Gets the file information record.<p>
172     *
173     * @return the file information for this file
174     *
175     * @throws IOException if reading the file information fails
176     */
177    public FileInfo getFileInfo() throws IOException {
178
179        try {
180            load(false);
181            if (m_resource.isFile()) {
182
183                //  Fill in a file information object for this file/directory
184
185                long flen = m_resource.getLength();
186
187                //long alloc = (flen + 512L) & 0xFFFFFFFFFFFFFE00L;
188                long alloc = flen;
189                int fattr = 0;
190                if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
191                    fattr += FileAttribute.ReadOnly;
192                }
193                //  Create the file information
194                FileInfo finfo = new FileInfo(m_resource.getName(), flen, fattr);
195                long fdate = m_resource.getDateLastModified();
196                finfo.setModifyDateTime(fdate);
197                finfo.setAllocationSize(alloc);
198                finfo.setFileId(m_resource.getStructureId().hashCode());
199                finfo.setCreationDateTime(m_resource.getDateCreated());
200                finfo.setChangeDateTime(fdate);
201                return finfo;
202            } else {
203
204                //  Fill in a file information object for this directory
205
206                int fattr = FileAttribute.Directory;
207                if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
208                    fattr += FileAttribute.ReadOnly;
209                }
210                // Can't use negative file size here, since this stops Windows 7 from connecting
211                FileInfo finfo = new FileInfo(m_resource.getName(), 1, fattr);
212                long fdate = m_resource.getDateLastModified();
213                finfo.setModifyDateTime(fdate);
214                finfo.setAllocationSize(1);
215                finfo.setFileId(m_resource.getStructureId().hashCode());
216                finfo.setCreationDateTime(m_resource.getDateCreated());
217                finfo.setChangeDateTime(fdate);
218                return finfo;
219
220            }
221        } catch (CmsException e) {
222            throw CmsJlanDiskInterface.convertCmsException(e);
223
224        }
225    }
226
227    /**
228     * Moves this file to a different path.<p>
229     *
230     * @param cmsNewPath the new path
231     * @throws CmsException if something goes wrong
232     */
233    public void moveTo(String cmsNewPath) throws CmsException {
234
235        ensureLock();
236        m_cms.moveResource(m_cms.getSitePath(m_resource), cmsNewPath);
237        CmsUUID id = m_resource.getStructureId();
238        CmsResource updatedRes = m_cms.readResource(id, CmsJlanDiskInterface.STANDARD_FILTER);
239        m_resource = updatedRes;
240        updateFromResource();
241    }
242
243    /**
244     * @see org.alfresco.jlan.server.filesys.NetworkFile#openFile(boolean)
245     */
246    @Override
247    public void openFile(boolean arg0) {
248
249        // not needed
250
251    }
252
253    /**
254     * @see org.alfresco.jlan.server.filesys.NetworkFile#readFile(byte[], int, int, long)
255     */
256    @Override
257    public int readFile(byte[] buffer, int length, int bufferOffset, long fileOffset) throws IOException {
258
259        try {
260            load(true);
261            int result = m_buffer.read(buffer, length, bufferOffset, (int)fileOffset);
262            return result;
263        } catch (CmsException e) {
264            throw CmsJlanDiskInterface.convertCmsException(e);
265        }
266    }
267
268    /**
269     * Collects all files matching the given name pattern and search attributes.<p>
270     *
271     * @param name the name pattern
272     * @param searchAttributes the search attributes
273     *
274     * @return the list of file objects which match the given parameters
275     *
276     * @throws IOException if something goes wrong
277     */
278    public List<CmsJlanNetworkFile> search(String name, int searchAttributes) throws IOException {
279
280        try {
281            load(false);
282            if (m_resource.isFolder()) {
283                List<CmsJlanNetworkFile> result = new ArrayList<CmsJlanNetworkFile>();
284                String regex = WildCard.convertToRegexp(name);
285                Pattern pattern = Pattern.compile(regex);
286                List<CmsResource> children = m_cms.getResourcesInFolder(
287                    m_cms.getSitePath(m_resource),
288                    CmsJlanDiskInterface.STANDARD_FILTER);
289                for (CmsResource child : children) {
290                    CmsJlanNetworkFile childFile = new CmsJlanNetworkFile(m_cms, child, getFullChildPath(child));
291                    if (!matchesSearchAttributes(searchAttributes)) {
292                        continue;
293                    }
294                    if (!pattern.matcher(child.getName()).matches()) {
295                        continue;
296                    }
297
298                    result.add(childFile);
299                }
300                return result;
301            } else {
302                throw new AccessDeniedException("Can't search a non-directory!");
303            }
304        } catch (CmsException e) {
305            throw CmsJlanDiskInterface.convertCmsException(e);
306        }
307    }
308
309    /**
310     * @see org.alfresco.jlan.server.filesys.NetworkFile#seekFile(long, int)
311     */
312    @Override
313    public long seekFile(long pos, int typ) throws IOException {
314
315        try {
316            load(true);
317            switch (typ) {
318
319                //  From current position
320
321                case SeekType.CurrentPos:
322                    m_buffer.seek(m_buffer.getPosition() + pos);
323                    break;
324
325                //  From end of file
326
327                case SeekType.EndOfFile:
328                    long newPos = m_buffer.getLength() + pos;
329                    m_buffer.seek(newPos);
330                    break;
331
332                //  From start of file
333
334                case SeekType.StartOfFile:
335                default:
336                    m_buffer.seek(pos);
337                    break;
338            }
339            return m_buffer.getPosition();
340        } catch (CmsException e) {
341            throw new IOException(e);
342        }
343    }
344
345    /**
346     * Sets the file information.<p>
347     *
348     * @param info the file information to set
349     */
350    public void setFileInformation(FileInfo info) {
351
352        if (info.hasSetFlag(FileInfo.FlagDeleteOnClose)) {
353            setDeleteOnClose(true);
354        }
355    }
356
357    /**
358     * @see org.alfresco.jlan.server.filesys.NetworkFile#truncateFile(long)
359     */
360    @Override
361    public void truncateFile(long size) throws IOException {
362
363        try {
364            load(true);
365            m_buffer.truncate((int)size);
366            incrementWriteCount();
367        } catch (CmsException e) {
368            throw CmsJlanDiskInterface.convertCmsException(e);
369        }
370    }
371
372    /**
373     * @see org.alfresco.jlan.server.filesys.NetworkFile#writeFile(byte[], int, int, long)
374     */
375    @Override
376    public void writeFile(byte[] data, int len, int pos, long offset) throws IOException {
377
378        try {
379            if (m_resource.isFolder()) {
380                throw new AccessDeniedException("Can't write data to folder!");
381            }
382            load(true);
383            m_buffer.seek(offset);
384            byte[] dataToWrite = Arrays.copyOfRange(data, pos, pos + len);
385            m_buffer.write(dataToWrite);
386            incrementWriteCount();
387        } catch (CmsException e) {
388            throw CmsJlanDiskInterface.convertCmsException(e);
389        }
390    }
391
392    /**
393     * Make sure that this resource is locked.<p>
394     *
395     * @throws CmsException if something goes wrong
396     */
397    protected void ensureLock() throws CmsException {
398
399        CmsLock lock = m_cms.getLock(m_resource);
400        if (lock.isUnlocked() || !lock.isLockableBy(m_cms.getRequestContext().getCurrentUser())) {
401            m_cms.lockResourceTemporary(m_cms.getSitePath(m_resource));
402            m_needToUnlock = true;
403        }
404    }
405
406    /**
407     * Gets the CmsFile instance for this file, or null if the file contents haven'T been loaded already.<p>
408     *
409     * @return the CmsFile instance
410     */
411    protected CmsFile getFile() {
412
413        if (m_resource instanceof CmsFile) {
414            return (CmsFile)m_resource;
415        }
416        return null;
417    }
418
419    /**
420     * Adds the name of a child resource to this file's path.<p>
421     *
422     * @param child the child resource
423     *
424     * @return the path of the child
425     */
426    protected String getFullChildPath(CmsResource child) {
427
428        String childName = child.getName();
429        String sep = getFullName().endsWith("\\") ? "" : "\\";
430        return getFullName() + sep + childName;
431    }
432
433    /**
434     * Loads the file data from the VFS.<p>
435     *
436     * @param needContent true if we need the file content to be loaded
437     *
438     * @throws IOException if an IO error happens
439     * @throws CmsException if a CMS operation fails
440     */
441    protected void load(boolean needContent) throws IOException, CmsException {
442
443        try {
444            if (m_resource.isFolder() && needContent) {
445                throw new AccessDeniedException("Operation not supported for directories!");
446            }
447            if (m_resource.isFile() && needContent && (!(m_resource instanceof CmsFile))) {
448                m_resource = m_cms.readFile(m_cms.getSitePath(m_resource), CmsJlanDiskInterface.STANDARD_FILTER);
449            }
450            if (!m_bufferInitialized && (getFile() != null)) {
451                // readResource may already have returned a CmsFile, this is why we need to initialize the buffer
452                // here and not in the if-block above
453                m_buffer.init(getFile().getContents());
454                m_bufferInitialized = true;
455            }
456        } catch (CmsException e) {
457            throw e;
458        }
459    }
460
461    /**
462     * Checks if this file matches the given search attributes.<p>
463     *
464     * @param attributes the search attributes
465     *
466     * @return true if this file matches the search attributes given
467     */
468    protected boolean matchesSearchAttributes(int attributes) {
469
470        if (isDirectory()) {
471            return (attributes & FileAttribute.Directory) != 0;
472        } else {
473            return true;
474        }
475    }
476
477    /**
478     * Copies state information from the internal CmsResource object to this object.<p>
479     */
480    protected void updateFromResource() {
481
482        setCreationDate(m_resource.getDateCreated());
483        int length = m_resource.getLength();
484        if (m_resource.isFolder()) {
485            length = 1;
486        }
487        setFileSize(length);
488        setModifyDate(m_resource.getDateLastModified());
489        setAttributes(m_resource.isFile() ? FileAttribute.Normal : FileAttribute.Directory);
490    }
491
492    /**
493     * Replace sequences of consecutive slashes/backslashes to a single backslash.<p>
494     *
495     * @param fullName the path to normalize
496     * @return the normalized path
497     */
498    private String normalizeName(String fullName) {
499
500        return fullName.replaceAll("[/\\\\]+", "\\\\");
501    }
502
503}