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}