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}