001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 GmbH & Co. KG, 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.repository; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsProperty; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.CmsUser; 035import org.opencms.file.CmsVfsResourceAlreadyExistsException; 036import org.opencms.file.CmsVfsResourceNotFoundException; 037import org.opencms.file.types.A_CmsResourceTypeFolderBase; 038import org.opencms.file.types.CmsResourceTypeFolder; 039import org.opencms.file.wrapper.CmsObjectWrapper; 040import org.opencms.lock.CmsLock; 041import org.opencms.main.CmsException; 042import org.opencms.main.CmsLog; 043import org.opencms.main.OpenCms; 044import org.opencms.security.CmsSecurityException; 045import org.opencms.util.CmsFileUtil; 046import org.opencms.util.CmsResourceTranslator; 047import org.opencms.util.CmsStringUtil; 048 049import java.io.IOException; 050import java.io.InputStream; 051import java.util.ArrayList; 052import java.util.HashMap; 053import java.util.Iterator; 054import java.util.List; 055import java.util.Map; 056 057import org.apache.commons.logging.Log; 058 059import com.google.common.io.BaseEncoding; 060 061/** 062 * This is the session class to work with the {@link CmsRepository}.<p> 063 * 064 * You can get an instance of this class by calling 065 * {@link CmsRepository#login(String, String)}.<p> 066 * 067 * This class provides basic file and folder operations on the resources 068 * in the VFS of OpenCms.<p> 069 * 070 * @see A_CmsRepositorySession 071 * @see I_CmsRepositorySession 072 * 073 * @since 6.5.6 074 */ 075public class CmsRepositorySession extends A_CmsRepositorySession { 076 077 /** The log object for this class. */ 078 private static final Log LOG = CmsLog.getLog(CmsRepositorySession.class); 079 080 /** Default namespace for OpenCms properties. */ 081 public static final String PROPERTY_NAMESPACE = "http://opencms.org/ns/property"; 082 083 /** Prefix used for encoded property names outside the default property namespace. */ 084 public static final String EXTERNAL_PREFIX = "xDAV_"; 085 086 /** Base for the namespace encoding. */ 087 private static final BaseEncoding PROPERTY_NS_CODEC = BaseEncoding.base64Url().withPadChar('$'); 088 089 /** Repository-specific file translations to use (may be null). */ 090 private CmsResourceTranslator m_translation; 091 092 /** The initialized {@link CmsObjectWrapper}. */ 093 private final CmsObjectWrapper m_cms; 094 095 /** 096 * Constructor with an initialized {@link CmsObjectWrapper} and a 097 * {@link CmsRepositoryFilter} to use.<p> 098 * 099 * @param cms the initialized CmsObject 100 * @param filter the repository filter to use 101 * @param translation the repository specific file translations (may be null) 102 */ 103 public CmsRepositorySession(CmsObjectWrapper cms, CmsRepositoryFilter filter, CmsResourceTranslator translation) { 104 105 m_cms = cms; 106 setFilter(filter); 107 m_translation = translation; 108 } 109 110 /** 111 * Decodes the namespace URI. 112 * 113 * @param code the encoded namespace URI 114 * @return the decoded namespace URI 115 * @throws Exception if something goes wrong 116 */ 117 public static String decodeNamespace(String code) throws Exception { 118 119 code = code.replace("~", "_"); 120 return new String(PROPERTY_NS_CODEC.decode(code), "UTF-8"); 121 } 122 123 /** 124 * Encodes the namespace URI. 125 * 126 * @param data a namesapce URI 127 * @return the encoded namespace URI 128 * 129 * @throws Exception if something goes wrong 130 */ 131 public static String encodeNamespace(String data) throws Exception { 132 133 String s = BaseEncoding.base64Url().withPadChar('$').encode(data.getBytes("UTF-8")); 134 s = s.replace('_', '~'); 135 return s; 136 } 137 138 /** 139 * @see org.opencms.repository.I_CmsRepositorySession#copy(java.lang.String, java.lang.String, boolean) 140 */ 141 public void copy(String src, String dest, boolean overwrite, boolean shallow) throws CmsException { 142 143 src = validatePath(src); 144 dest = validatePath(dest); 145 146 if (LOG.isDebugEnabled()) { 147 LOG.debug(Messages.get().getBundle().key(Messages.LOG_COPY_ITEM_2, src, dest)); 148 } 149 150 // It is only possible in OpenCms to overwrite files. 151 // Folder are not possible to overwrite. 152 if (exists(dest)) { 153 154 if (overwrite) { 155 CmsResource srcRes = m_cms.readResource(src, CmsResourceFilter.DEFAULT); 156 CmsResource destRes = m_cms.readResource(dest, CmsResourceFilter.DEFAULT); 157 158 if ((srcRes.isFile()) && (destRes.isFile())) { 159 160 if (LOG.isDebugEnabled()) { 161 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DELETE_DEST_0)); 162 } 163 164 // delete existing resource 165 delete(dest); 166 } else { 167 168 if (LOG.isDebugEnabled()) { 169 LOG.debug(Messages.get().getBundle().key(Messages.ERR_OVERWRITE_0)); 170 } 171 172 // internal error (not possible) 173 throw new CmsException(Messages.get().container(Messages.ERR_OVERWRITE_0)); 174 } 175 } else { 176 177 if (LOG.isDebugEnabled()) { 178 LOG.debug(Messages.get().getBundle().key(Messages.ERR_DEST_EXISTS_0)); 179 } 180 181 throw new CmsVfsResourceAlreadyExistsException(Messages.get().container(Messages.ERR_DEST_EXISTS_0)); 182 } 183 } 184 185 // copy resource 186 if (shallow) { 187 m_cms.getRequestContext().setAttribute(A_CmsResourceTypeFolderBase.ATTR_SHALLOW_FOLDER_COPY, Boolean.TRUE); 188 } 189 try { 190 m_cms.copyResource(src, dest, CmsResource.COPY_PRESERVE_SIBLING); 191 } finally { 192 m_cms.getRequestContext().removeAttribute(A_CmsResourceTypeFolderBase.ATTR_SHALLOW_FOLDER_COPY); 193 } 194 195 // unlock destination resource 196 m_cms.unlockResource(dest); 197 } 198 199 /** 200 * @see org.opencms.repository.I_CmsRepositorySession#create(java.lang.String) 201 */ 202 public void create(String path) throws CmsException { 203 204 path = validatePath(path); 205 206 if (LOG.isDebugEnabled()) { 207 LOG.debug(Messages.get().getBundle().key(Messages.LOG_CREATE_ITEM_1, path)); 208 } 209 210 // create the folder 211 CmsResource res = m_cms.createResource(path, CmsResourceTypeFolder.RESOURCE_TYPE_ID); 212 213 // unlock new created folders if lock is not inherited 214 if (!m_cms.getLock(res).isInherited()) { 215 m_cms.unlockResource(path); 216 } 217 } 218 219 /** 220 * @see org.opencms.repository.I_CmsRepositorySession#delete(java.lang.String) 221 */ 222 public void delete(String path) throws CmsException { 223 224 path = validatePath(path); 225 226 if (LOG.isDebugEnabled()) { 227 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DELETE_ITEM_1, path)); 228 } 229 230 CmsRepositoryLockInfo lock = getLock(path); 231 232 // lock resource 233 m_cms.lockResource(path); 234 235 // delete resource 236 m_cms.deleteResource(path, CmsResource.DELETE_PRESERVE_SIBLINGS); 237 238 // if deleting items out of a xml page restore lock state after deleting 239 try { 240 if (lock == null) { 241 m_cms.unlockResource(path); 242 } 243 } catch (CmsException ex) { 244 // noop 245 } 246 } 247 248 /** 249 * @see org.opencms.repository.I_CmsRepositorySession#exists(java.lang.String) 250 */ 251 public boolean exists(String path) { 252 253 try { 254 path = validatePath(path); 255 return m_cms.existsResource(path); 256 } catch (CmsException ex) { 257 return false; 258 } 259 } 260 261 /** 262 * @see org.opencms.repository.I_CmsRepositorySession#getItem(java.lang.String) 263 */ 264 public I_CmsRepositoryItem getItem(String path) throws CmsException { 265 266 path = validatePath(path); 267 268 if (LOG.isDebugEnabled()) { 269 LOG.debug(Messages.get().getBundle().key(Messages.LOG_READ_ITEM_1, path)); 270 } 271 272 CmsResource res = m_cms.readResource(path, CmsResourceFilter.DEFAULT); 273 274 CmsRepositoryItem item = new CmsRepositoryItem(res, m_cms); 275 return item; 276 } 277 278 /** 279 * @see org.opencms.repository.I_CmsRepositorySession#getLock(java.lang.String) 280 */ 281 public CmsRepositoryLockInfo getLock(String path) { 282 283 try { 284 CmsRepositoryLockInfo lockInfo = new CmsRepositoryLockInfo(); 285 286 path = validatePath(path); 287 288 CmsResource res = m_cms.readResource(path, CmsResourceFilter.DEFAULT); 289 290 // check user locks 291 CmsLock cmsLock = m_cms.getLock(res); 292 if (!cmsLock.isUnlocked()) { 293 lockInfo.setPath(path); 294 295 CmsUser owner = m_cms.readUser(cmsLock.getUserId()); 296 if (owner != null) { 297 lockInfo.setUsername(owner.getName()); 298 lockInfo.setOwner(owner.getName() + "||" + owner.getEmail()); 299 } 300 return lockInfo; 301 } 302 303 return null; 304 } catch (CmsException ex) { 305 306 // error occurred while finding locks 307 // return null (no lock found) 308 return null; 309 } 310 } 311 312 /** 313 * @see org.opencms.repository.I_CmsRepositorySession#getProperties(java.lang.String) 314 */ 315 public Map<CmsPropertyName, String> getProperties(String path) throws CmsException { 316 317 Map<String, CmsProperty> props = m_cms.readProperties(path); 318 Map<CmsPropertyName, String> out = new HashMap<>(); 319 for (Map.Entry<String, CmsProperty> entry : props.entrySet()) { 320 String name = entry.getKey(); 321 CmsProperty prop = entry.getValue(); 322 if (name.startsWith(EXTERNAL_PREFIX)) { 323 try { 324 String remainder = name.substring(EXTERNAL_PREFIX.length()); 325 int pos = remainder.indexOf("_"); 326 String nsEncoded = remainder.substring(0, pos); 327 String actualName = remainder.substring(pos + 1); 328 String ns = decodeNamespace(nsEncoded); 329 CmsPropertyName pn = new CmsPropertyName(ns, actualName); 330 out.put(pn, prop.getStructureValue()); 331 } catch (Exception e) { 332 LOG.error(e.getLocalizedMessage(), e); 333 } 334 } else { 335 if (prop.getStructureValue() != null) { 336 String outName = name + ".s"; 337 out.put(new CmsPropertyName(PROPERTY_NAMESPACE, outName), prop.getStructureValue()); 338 } 339 if (prop.getResourceValue() != null) { 340 String outName = name + ".r"; 341 out.put(new CmsPropertyName(PROPERTY_NAMESPACE, outName), prop.getResourceValue()); 342 } 343 } 344 } 345 return out; 346 } 347 348 /** 349 * @see org.opencms.repository.I_CmsRepositorySession#list(java.lang.String) 350 */ 351 public List<I_CmsRepositoryItem> list(String path) throws CmsException { 352 353 List<I_CmsRepositoryItem> ret = new ArrayList<I_CmsRepositoryItem>(); 354 355 path = validatePath(path); 356 357 if (LOG.isDebugEnabled()) { 358 LOG.debug(Messages.get().getBundle().key(Messages.LOG_LIST_ITEMS_1, path)); 359 } 360 361 List<CmsResource> resources = m_cms.getResourcesInFolder(path, CmsResourceFilter.DEFAULT); 362 Iterator<CmsResource> iter = resources.iterator(); 363 while (iter.hasNext()) { 364 CmsResource res = iter.next(); 365 366 if (!isFiltered(m_cms.getRequestContext().removeSiteRoot(res.getRootPath()))) { 367 368 // open the original resource (for virtual files this is the resource in the VFS 369 // which the virtual resource is based on) 370 // this filters e.g. property files for resources that are filtered out and thus 371 // should not be displayed 372 try { 373 CmsResource org = m_cms.readResource(res.getStructureId(), CmsResourceFilter.DEFAULT); 374 if (!isFiltered(m_cms.getRequestContext().removeSiteRoot(org.getRootPath()))) { 375 ret.add(new CmsRepositoryItem(res, m_cms)); 376 } 377 } catch (CmsVfsResourceNotFoundException e) { 378 // Pure virtual resources with an ID that does not correspond to any real resource 379 ret.add(new CmsRepositoryItem(res, m_cms)); 380 } 381 } 382 } 383 384 if (LOG.isDebugEnabled()) { 385 LOG.debug(Messages.get().getBundle().key(Messages.LOG_LIST_ITEMS_SUCESS_1, Integer.valueOf(ret.size()))); 386 } 387 388 return ret; 389 } 390 391 /** 392 * @see org.opencms.repository.I_CmsRepositorySession#lock(java.lang.String, org.opencms.repository.CmsRepositoryLockInfo) 393 */ 394 public boolean lock(String path, CmsRepositoryLockInfo lock) throws CmsException { 395 396 path = validatePath(path); 397 398 if (LOG.isDebugEnabled()) { 399 LOG.debug(Messages.get().getBundle().key(Messages.LOG_LOCK_ITEM_1, path)); 400 } 401 402 m_cms.lockResource(path); 403 return true; 404 } 405 406 /** 407 * @see org.opencms.repository.I_CmsRepositorySession#move(java.lang.String, java.lang.String, boolean) 408 */ 409 public void move(String src, String dest, boolean overwrite) throws CmsException { 410 411 src = validatePath(src); 412 dest = validatePath(dest); 413 414 if (LOG.isDebugEnabled()) { 415 LOG.debug(Messages.get().getBundle().key(Messages.LOG_MOVE_ITEM_2, src, dest)); 416 } 417 418 // It is only possible in OpenCms to overwrite files. 419 // Folder are not possible to overwrite. 420 if (exists(dest)) { 421 422 if (overwrite) { 423 CmsResource srcRes = m_cms.readResource(src, CmsResourceFilter.DEFAULT); 424 CmsResource destRes = m_cms.readResource(dest, CmsResourceFilter.DEFAULT); 425 426 if ((srcRes.isFile()) && (destRes.isFile())) { 427 428 if (LOG.isDebugEnabled()) { 429 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DELETE_DEST_0)); 430 } 431 432 // delete existing resource 433 delete(dest); 434 } else { 435 436 if (LOG.isDebugEnabled()) { 437 LOG.debug(Messages.get().getBundle().key(Messages.ERR_OVERWRITE_0)); 438 } 439 440 throw new CmsException(Messages.get().container(Messages.ERR_OVERWRITE_0)); 441 } 442 } else { 443 444 if (LOG.isDebugEnabled()) { 445 LOG.debug(Messages.get().getBundle().key(Messages.ERR_DEST_EXISTS_0)); 446 } 447 448 throw new CmsVfsResourceAlreadyExistsException(Messages.get().container(Messages.ERR_DEST_EXISTS_0)); 449 } 450 } 451 452 // lock source resource 453 m_cms.lockResource(src); 454 455 // moving 456 m_cms.moveResource(src, dest); 457 458 // unlock destination resource 459 m_cms.unlockResource(dest); 460 } 461 462 /** 463 * @see org.opencms.repository.I_CmsRepositorySession#save(java.lang.String, java.io.InputStream, boolean) 464 */ 465 public void save(String path, InputStream inputStream, boolean overwrite) throws CmsException, IOException { 466 467 path = validatePath(path); 468 byte[] content = CmsFileUtil.readFully(inputStream); 469 470 try { 471 CmsFile file = m_cms.readFile(path, CmsResourceFilter.DEFAULT); 472 473 if (LOG.isDebugEnabled()) { 474 LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_ITEM_1, path)); 475 } 476 477 if (overwrite) { 478 479 file.setContents(content); 480 481 CmsLock lock = m_cms.getLock(file); 482 483 // lock resource 484 if (!lock.isInherited()) { 485 m_cms.lockResource(path); 486 } 487 488 // write file 489 m_cms.writeFile(file); 490 491 if (lock.isNullLock()) { 492 m_cms.unlockResource(path); 493 } 494 } else { 495 496 if (LOG.isDebugEnabled()) { 497 LOG.debug(Messages.get().getBundle().key(Messages.ERR_DEST_EXISTS_0)); 498 } 499 500 throw new CmsVfsResourceAlreadyExistsException(Messages.get().container(Messages.ERR_DEST_EXISTS_0)); 501 } 502 } catch (CmsVfsResourceNotFoundException ex) { 503 504 if (LOG.isDebugEnabled()) { 505 LOG.debug(Messages.get().getBundle().key(Messages.LOG_CREATE_ITEM_1, path)); 506 } 507 508 int type = OpenCms.getResourceManager().getDefaultTypeForName(path).getTypeId(); 509 510 // create the file 511 CmsResource res = m_cms.createResource(path, type, content, null); 512 513 // unlock file after creation if lock is not inherited 514 if (!m_cms.getLock(res).isInherited()) { 515 m_cms.unlockResource(path); 516 } 517 } 518 519 } 520 521 /** 522 * @see org.opencms.repository.I_CmsRepositorySession#unlock(java.lang.String) 523 */ 524 public void unlock(String path) { 525 526 try { 527 path = validatePath(path); 528 529 if (LOG.isDebugEnabled()) { 530 LOG.debug(Messages.get().getBundle().key(Messages.LOG_UNLOCK_ITEM_1, path)); 531 } 532 533 m_cms.unlockResource(path); 534 } catch (CmsException ex) { 535 536 if (LOG.isErrorEnabled()) { 537 LOG.error(Messages.get().getBundle().key(Messages.ERR_UNLOCK_FAILED_0), ex); 538 } 539 } 540 } 541 542 /** 543 * @see org.opencms.repository.I_CmsRepositorySession#updateProperties(java.lang.String, java.util.Map) 544 */ 545 public void updateProperties(String path, Map<CmsPropertyName, String> properties) throws CmsException { 546 547 Map<String, CmsProperty> propsToWrite = new HashMap<>(); 548 for (Map.Entry<CmsPropertyName, String> entry : properties.entrySet()) { 549 CmsPropertyName pn = entry.getKey(); 550 String value = entry.getValue(); 551 if (pn.getNamespace().equals(PROPERTY_NAMESPACE)) { 552 String baseName = pn.getName().substring(0, pn.getName().length() - 2); 553 if (!propsToWrite.containsKey(baseName)) { 554 CmsProperty prop = new CmsProperty(baseName, null, null); 555 propsToWrite.put(baseName, prop); 556 } 557 CmsProperty prop = propsToWrite.get(baseName); 558 if (pn.getName().endsWith(".s")) { 559 prop.setStructureValue(value); 560 } else if (pn.getName().endsWith(".r")) { 561 prop.setResourceValue(value); 562 } else { 563 LOG.error("Invalid name for repository property, must end with .s or .r"); 564 } 565 } else { 566 try { 567 String propName = EXTERNAL_PREFIX + encodeNamespace(pn.getNamespace()) + "_" + pn.getName(); 568 CmsProperty prop = new CmsProperty(propName, value, null); 569 propsToWrite.put(propName, prop); 570 } catch (Exception e) { 571 LOG.error(e.getLocalizedMessage(), e); 572 } 573 } 574 } 575 boolean needUnlock = false; 576 if (null == getLock(path)) { 577 m_cms.lockResource(path); 578 needUnlock = true; 579 } 580 try { 581 LOG.debug("Writing properties: " + propsToWrite); 582 m_cms.writeProperties(path, propsToWrite); 583 } finally { 584 if (needUnlock) { 585 m_cms.unlockResource(path); 586 } 587 } 588 } 589 590 /** 591 * Adds the site root to the path name and checks then if the path 592 * is filtered.<p> 593 * 594 * @see org.opencms.repository.A_CmsRepositorySession#isFiltered(java.lang.String) 595 */ 596 @Override 597 protected boolean isFiltered(String name) { 598 599 boolean ret = super.isFiltered(m_cms.getRequestContext().addSiteRoot(name)); 600 if (ret) { 601 602 if (LOG.isDebugEnabled()) { 603 LOG.debug(Messages.get().getBundle().key(Messages.ERR_ITEM_FILTERED_1, name)); 604 } 605 606 } 607 608 return ret; 609 } 610 611 /** 612 * Gets the resource translator to use for path translations. 613 * 614 * @return the resource translator to use 615 */ 616 private CmsResourceTranslator getEffectiveResourceTranslator() { 617 618 if (m_translation != null) { 619 return m_translation; 620 } 621 return m_cms.getRequestContext().getFileTranslator(); 622 } 623 624 /** 625 * Validates (translates) the given path and checks if it is filtered out.<p> 626 * 627 * @param path the path to validate 628 * 629 * @return the validated path 630 * 631 * @throws CmsSecurityException if the path is filtered out 632 */ 633 private String validatePath(String path) throws CmsSecurityException { 634 635 // Problems with spaces in new folders (default: "Neuer Ordner") 636 // Solution: translate this to a correct name. 637 CmsResourceTranslator translator = getEffectiveResourceTranslator(); 638 String ret = CmsStringUtil.translatePathComponents(translator, path); 639 640 // add site root only works correct if system folder ends with a slash 641 if (CmsResource.VFS_FOLDER_SYSTEM.equals(ret)) { 642 ret = ret.concat("/"); 643 } 644 645 // filter path 646 if (isFiltered(ret)) { 647 throw new CmsSecurityException(Messages.get().container(Messages.ERR_ITEM_FILTERED_1, path)); 648 } 649 650 return ret; 651 } 652}