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.CmsResource;
031import org.opencms.file.CmsResourceFilter;
032import org.opencms.file.CmsVfsResourceAlreadyExistsException;
033import org.opencms.file.CmsVfsResourceNotFoundException;
034import org.opencms.file.wrapper.CmsObjectWrapper;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsLog;
037import org.opencms.main.OpenCms;
038import org.opencms.security.CmsSecurityException;
039import org.opencms.util.CmsStringUtil;
040import org.opencms.util.I_CmsRegexSubstitution;
041
042import java.io.FileNotFoundException;
043import java.io.IOException;
044import java.util.ArrayList;
045import java.util.Collections;
046import java.util.List;
047import java.util.regex.Matcher;
048import java.util.regex.Pattern;
049
050import org.apache.commons.logging.Log;
051
052import org.alfresco.jlan.server.SrvSession;
053import org.alfresco.jlan.server.core.DeviceContext;
054import org.alfresco.jlan.server.filesys.AccessDeniedException;
055import org.alfresco.jlan.server.filesys.DiskInterface;
056import org.alfresco.jlan.server.filesys.FileExistsException;
057import org.alfresco.jlan.server.filesys.FileInfo;
058import org.alfresco.jlan.server.filesys.FileOpenParams;
059import org.alfresco.jlan.server.filesys.FileStatus;
060import org.alfresco.jlan.server.filesys.NetworkFile;
061import org.alfresco.jlan.server.filesys.SearchContext;
062import org.alfresco.jlan.server.filesys.TreeConnection;
063import org.alfresco.jlan.util.WildCard;
064import org.springframework.extensions.config.ConfigElement;
065
066import com.google.common.base.Joiner;
067
068/**
069 * OpenCms implementation of the JLAN DiskInterface interface.<p>
070 *
071 * This class, together with the CmsJlanNetworkFile class, contains the main repository access functionality.<p>
072 */
073public class CmsJlanDiskInterface implements DiskInterface {
074
075    /** Attribute to control whether we need the filesize or not when reading a resource. */
076    public static final String NO_FILESIZE_REQUIRED = "NO_FILESIZE_REQUIRED";
077
078    /** The standard resource filter used for reading resources. */
079    public static final CmsResourceFilter STANDARD_FILTER = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED;
080
081    /** The logger instance for this class. */
082    private static final Log LOG = CmsLog.getLog(CmsJlanDiskInterface.class);
083
084    /**
085     * Tries to convert a CmsException to the matching exception type from JLAN.<p>
086     *
087     * @param e the exception to convert
088     * @return the converted exception
089     */
090    public static IOException convertCmsException(CmsException e) {
091
092        LOG.error(e.getLocalizedMessage(), e);
093        if (e instanceof CmsSecurityException) {
094            return new AccessDeniedException(e.getMessage(), e);
095        } else if (e instanceof CmsVfsResourceAlreadyExistsException) {
096            return new FileExistsException("File exists: " + e);
097        } else if (e instanceof CmsVfsResourceNotFoundException) {
098            return new FileNotFoundException("File does not exist: " + e);
099        } else {
100            return new IOException(e);
101        }
102    }
103
104    /**
105     * Converts a CIFS path to an OpenCms path by converting backslashes to slashes and translating special characters in the file name.<p>
106     *
107     * @param path the path to transform
108     * @return the OpenCms path for the given path
109     */
110    protected static String getCmsPath(String path) {
111
112        String slashPath = path.replace('\\', '/');
113
114        // split path into components, translate each of them separately, then combine them again at the end
115        String[] segments = slashPath.split("/");
116        List<String> nonEmptySegments = new ArrayList<String>();
117        for (String segment : segments) {
118            if (segment.length() > 0) {
119                String translatedSegment = "*".equals(segment)
120                ? "*"
121                : OpenCms.getResourceManager().getFileTranslator().translateResource(segment);
122                nonEmptySegments.add(translatedSegment);
123            }
124        }
125        String result = "/" + Joiner.on("/").join(nonEmptySegments);
126        return result;
127    }
128
129    /**
130     * @see org.alfresco.jlan.server.filesys.DiskInterface#closeFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile)
131     */
132    public void closeFile(SrvSession session, TreeConnection connection, NetworkFile file) throws IOException {
133
134        file.close();
135    }
136
137    /**
138     * @see org.alfresco.jlan.server.core.DeviceInterface#createContext(java.lang.String, org.springframework.extensions.config.ConfigElement)
139     */
140    public DeviceContext createContext(String shareName, ConfigElement args) {
141
142        return null; // not used, since the repository creates the device context
143
144    }
145
146    /**
147     * @see org.alfresco.jlan.server.filesys.DiskInterface#createDirectory(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.FileOpenParams)
148     */
149    public void createDirectory(SrvSession session, TreeConnection connection, FileOpenParams params)
150    throws IOException {
151
152        internalCreateFile(session, connection, params, "folder");
153    }
154
155    /**
156     * @see org.alfresco.jlan.server.filesys.DiskInterface#createFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.FileOpenParams)
157     */
158    public NetworkFile createFile(SrvSession session, TreeConnection connection, FileOpenParams params)
159    throws IOException {
160
161        return internalCreateFile(session, connection, params, null);
162    }
163
164    /**
165     * @see org.alfresco.jlan.server.filesys.DiskInterface#deleteDirectory(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String)
166     */
167    public void deleteDirectory(SrvSession session, TreeConnection connection, String path) throws IOException {
168
169        deleteFile(session, connection, path);
170    }
171
172    /**
173     * @see org.alfresco.jlan.server.filesys.DiskInterface#deleteFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String)
174     */
175    public void deleteFile(SrvSession session, TreeConnection connection, String path) throws IOException {
176
177        // note: deletion of a file may not necessarily go through this method, instead the client program may open the
178        // file, set a "delete on close" flag, and then close it.
179        try {
180            CmsJlanNetworkFile file = getFileForPath(session, connection, path);
181            if (file == null) {
182                // Only log a warning, since if the file doesn't exist, it doesn't really need to be deleted anymore
183                LOG.warn("Couldn't delete file " + path + " because it doesn't exist anymore.");
184            } else {
185                file.delete();
186            }
187        } catch (CmsException e) {
188            throw convertCmsException(e);
189
190        }
191    }
192
193    /**
194     * @see org.alfresco.jlan.server.filesys.DiskInterface#fileExists(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String)
195     */
196    public int fileExists(SrvSession session, TreeConnection connection, String path) {
197
198        try {
199            CmsObjectWrapper cms = getCms(session, connection);
200            cms.getRequestContext().setAttribute(NO_FILESIZE_REQUIRED, Boolean.TRUE);
201            CmsJlanNetworkFile file = getFileForPath(cms, session, connection, path);
202            if (file == null) {
203                return FileStatus.NotExist;
204            } else {
205                return file.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists;
206            }
207        } catch (Exception e) {
208            System.out.println(e);
209            return FileStatus.NotExist;
210        }
211    }
212
213    /**
214     * @see org.alfresco.jlan.server.filesys.DiskInterface#flushFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile)
215     */
216    public void flushFile(SrvSession session, TreeConnection connection, NetworkFile file) throws IOException {
217
218        file.flushFile();
219
220    }
221
222    /**
223     * @see org.alfresco.jlan.server.filesys.DiskInterface#getFileInformation(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String)
224     */
225    public FileInfo getFileInformation(SrvSession session, TreeConnection connection, String path) throws IOException {
226
227        try {
228            if (path == null) {
229                throw new FileNotFoundException("file not found: " + path);
230            }
231            CmsJlanNetworkFile file = getFileForPath(session, connection, path);
232            if (file == null) {
233                return null;
234                //throw new FileNotFoundException("path not found: " + path);
235            } else {
236                return file.getFileInfo();
237            }
238        } catch (CmsException e) {
239            throw convertCmsException(e);
240        }
241    }
242
243    /**
244     * @see org.alfresco.jlan.server.filesys.DiskInterface#isReadOnly(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.core.DeviceContext)
245     */
246    public boolean isReadOnly(SrvSession session, DeviceContext context) {
247
248        return false;
249    }
250
251    /**
252     * @see org.alfresco.jlan.server.filesys.DiskInterface#openFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.FileOpenParams)
253     */
254    public NetworkFile openFile(SrvSession session, TreeConnection connection, FileOpenParams params)
255    throws IOException {
256
257        String path = params.getPath();
258        String cmsPath = getCmsPath(path);
259        // TODO: Check access control
260        try {
261            CmsObjectWrapper cms = getCms(session, connection);
262            CmsResource resource = cms.readResource(cmsPath, STANDARD_FILTER);
263
264            return new CmsJlanNetworkFile(cms, resource, path);
265        } catch (CmsException e) {
266            throw convertCmsException(e);
267        }
268
269    }
270
271    /**
272     * @see org.alfresco.jlan.server.filesys.DiskInterface#readFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile, byte[], int, int, long)
273     */
274    public int readFile(
275        SrvSession sess,
276        TreeConnection tree,
277        NetworkFile file,
278        byte[] buf,
279        int bufPos,
280        int siz,
281        long filePos)
282    throws java.io.IOException {
283
284        //    Check if the file is a directory
285
286        if (file.isDirectory()) {
287            throw new AccessDeniedException();
288        }
289
290        //  Read the file
291
292        int rdlen = file.readFile(buf, siz, bufPos, filePos);
293
294        //  If we have reached end of file return a zero length read
295
296        if (rdlen < 0) {
297            rdlen = 0;
298        }
299
300        //  Return the actual read length
301
302        return rdlen;
303    }
304
305    /**
306     * @see org.alfresco.jlan.server.filesys.DiskInterface#renameFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String, java.lang.String)
307     */
308    public void renameFile(SrvSession session, TreeConnection connection, String oldName, String newName)
309    throws IOException {
310
311        String cmsNewPath = getCmsPath(newName);
312        try {
313            CmsJlanNetworkFile file = getFileForPath(session, connection, oldName);
314            file.moveTo(cmsNewPath);
315        } catch (CmsException e) {
316            throw convertCmsException(e);
317        }
318    }
319
320    /**
321     * @see org.alfresco.jlan.server.filesys.DiskInterface#seekFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile, long, int)
322     */
323    public long seekFile(SrvSession session, TreeConnection connection, NetworkFile file, long pos, int seekMode)
324    throws IOException {
325
326        return file.seekFile(pos, seekMode);
327    }
328
329    /**
330     * @see org.alfresco.jlan.server.filesys.DiskInterface#setFileInformation(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String, org.alfresco.jlan.server.filesys.FileInfo)
331     */
332    public void setFileInformation(SrvSession session, TreeConnection connection, String path, FileInfo info)
333    throws IOException {
334
335        try {
336            CmsObjectWrapper cms = getCms(session, connection);
337            String cmsPath = getCmsPath(path);
338            CmsResource resource = cms.readResource(cmsPath, STANDARD_FILTER);
339            CmsJlanNetworkFile file = new CmsJlanNetworkFile(cms, resource, path);
340            file.setFileInformation(info);
341        } catch (CmsException e) {
342            throw convertCmsException(e);
343        }
344    }
345
346    /**
347     * @see org.alfresco.jlan.server.filesys.DiskInterface#startSearch(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, java.lang.String, int)
348     */
349    public SearchContext startSearch(
350        SrvSession session,
351        TreeConnection connection,
352        String searchPath,
353        int searchAttributes) {
354
355        try {
356
357            String cmsPath = getCmsPath(searchPath);
358            if (cmsPath.endsWith("/")) {
359                cmsPath = cmsPath + "*";
360            }
361            String name = CmsResource.getName(cmsPath);
362            String parent = CmsResource.getParentFolder(cmsPath);
363
364            if (WildCard.containsWildcards(name)) {
365                CmsJlanNetworkFile parentFile = getFileForPath(session, connection, parent);
366                return new CmsJlanSearch(parentFile.search(name, searchAttributes));
367            } else {
368                CmsJlanNetworkFile file = getFileForPath(session, connection, cmsPath);
369                return new CmsJlanSearch(Collections.singletonList(file));
370            }
371        } catch (Exception e) {
372            throw new RuntimeException(e);
373        }
374    }
375
376    /**
377     * @see org.alfresco.jlan.server.core.DeviceInterface#treeClosed(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection)
378     */
379    public void treeClosed(SrvSession sess, TreeConnection tree) {
380
381        // ignore
382
383    }
384
385    /**
386     * @see org.alfresco.jlan.server.core.DeviceInterface#treeOpened(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection)
387     */
388    public void treeOpened(SrvSession arg0, TreeConnection arg1) {
389
390        // ignore
391    }
392
393    /**
394     * @see org.alfresco.jlan.server.filesys.DiskInterface#truncateFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile, long)
395     */
396    public void truncateFile(SrvSession session, TreeConnection connection, NetworkFile file, long size)
397    throws IOException {
398
399        file.truncateFile(size);
400    }
401
402    /**
403     * @see org.alfresco.jlan.server.filesys.DiskInterface#writeFile(org.alfresco.jlan.server.SrvSession, org.alfresco.jlan.server.filesys.TreeConnection, org.alfresco.jlan.server.filesys.NetworkFile, byte[], int, int, long)
404     */
405    public int writeFile(
406        SrvSession session,
407        TreeConnection connection,
408        NetworkFile file,
409        byte[] data,
410        int bufferOffset,
411        int length,
412        long fileOffset)
413    throws IOException {
414
415        if (file.isDirectory()) {
416            throw new AccessDeniedException("Can't write data to a directory!");
417        }
418        file.writeFile(data, length, bufferOffset, fileOffset);
419        return length;
420    }
421
422    /**
423     * Creates a CmsObjectWrapper for the current session.<p>
424     *
425     * @param session the current session
426     * @param connection the tree connection
427     *
428     * @return the correctly configured CmsObjectWrapper for this session
429     *
430     * @throws CmsException if something goes wrong
431     */
432    protected CmsObjectWrapper getCms(SrvSession session, TreeConnection connection) throws CmsException {
433
434        CmsJlanRepository repository = ((CmsJlanDeviceContext)connection.getContext()).getRepository();
435        CmsObjectWrapper result = repository.getCms(session, connection);
436        return result;
437    }
438
439    /**
440     * Helper method to get a network file object given a path.<p>
441     *
442     * @param cms the CMS context wrapper
443     * @param session the current session
444     * @param connection the current connection
445     * @param path the file path
446     *
447     * @return the network file object for the given path
448     * @throws CmsException if something goes wrong
449     */
450    protected CmsJlanNetworkFile getFileForPath(
451        CmsObjectWrapper cms,
452        SrvSession session,
453        TreeConnection connection,
454        String path)
455    throws CmsException {
456
457        try {
458            String cmsPath = getCmsPath(path);
459            CmsResource resource = cms.readResource(cmsPath, STANDARD_FILTER);
460            CmsJlanNetworkFile result = new CmsJlanNetworkFile(cms, resource, path);
461            return result;
462        } catch (CmsVfsResourceNotFoundException e) {
463            return null;
464        }
465    }
466
467    /**
468     * Helper method to get a network file object given a path.<p>
469     *
470     * @param session the current session
471     * @param connection the current connection
472     * @param path the file path
473     *
474     * @return the network file object for the given path
475     * @throws CmsException if something goes wrong
476     */
477    protected CmsJlanNetworkFile getFileForPath(SrvSession session, TreeConnection connection, String path)
478    throws CmsException {
479
480        CmsObjectWrapper cms = getCms(session, connection);
481        return getFileForPath(cms, session, connection, path);
482    }
483
484    /**
485     * Internal method for creating a new file.<p>
486     *
487     * @param session the session
488     * @param connection the tree connection
489     * @param params the parameters for opening the file
490     * @param typeName the name of the resource type for the new file
491     *
492     * @return a NetworkFile instance representing the newly created file
493     *
494     * @throws IOException if something goes wrong
495     */
496    protected NetworkFile internalCreateFile(
497        SrvSession session,
498        TreeConnection connection,
499        FileOpenParams params,
500        String typeName)
501    throws IOException {
502
503        String path = params.getPath();
504        String cmsPath = getCmsPath(path);
505        try {
506            CmsObjectWrapper cms = getCms(session, connection);
507            if (typeName == null) {
508                typeName = OpenCms.getResourceManager().getDefaultTypeForName(cmsPath).getTypeName();
509            }
510            CmsResource createdResource = cms.createResource(
511                cmsPath,
512                OpenCms.getResourceManager().getResourceType(typeName).getTypeId());
513            tryUnlock(cms, cmsPath);
514            CmsJlanNetworkFile result = new CmsJlanNetworkFile(cms, createdResource, path);
515            result.setFullName(params.getPath());
516            return result;
517        } catch (CmsVfsResourceAlreadyExistsException e) {
518            throw new FileExistsException("File exists: " + path);
519        } catch (CmsException e) {
520            throw new IOException(e);
521        }
522
523    }
524
525    /**
526     * Translates the last path segment of a path using the configured OpenCms file translations.<p>
527     *
528     * @param path the path for which the last segment should be translated
529     *
530     * @return the path with the translated last segment
531     */
532    protected String translateName(String path) {
533
534        return CmsStringUtil.substitute(Pattern.compile("/([^/]+)$"), path, new I_CmsRegexSubstitution() {
535
536            public String substituteMatch(String text, Matcher matcher) {
537
538                String name = text.substring(matcher.start(1), matcher.end(1));
539                return "/" + OpenCms.getResourceManager().getFileTranslator().translateResource(name);
540            }
541        });
542    }
543
544    /**
545     * Tries to unlock the file at the given path.<p>
546     *
547     * @param cms the CMS context wrapper
548     * @param path the path of the resource to unlock
549     */
550    private void tryUnlock(CmsObjectWrapper cms, String path) {
551
552        try {
553            cms.unlockResource(path);
554        } catch (Throwable e) {
555            LOG.info(e.getLocalizedMessage(), e);
556        }
557
558    }
559
560}