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