001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.ade.sitemap; 029 030import static org.opencms.ade.sitemap.shared.I_CmsAliasConstants.PARAM_IMPORTFILE; 031import static org.opencms.ade.sitemap.shared.I_CmsAliasConstants.PARAM_SEPARATOR; 032import static org.opencms.ade.sitemap.shared.I_CmsAliasConstants.PARAM_SITEROOT; 033 034import org.opencms.db.CmsAlias; 035import org.opencms.db.CmsAliasManager; 036import org.opencms.db.CmsRewriteAlias; 037import org.opencms.file.CmsObject; 038import org.opencms.file.CmsResource; 039import org.opencms.file.CmsResourceFilter; 040import org.opencms.gwt.shared.alias.CmsAliasEditValidationReply; 041import org.opencms.gwt.shared.alias.CmsAliasEditValidationRequest; 042import org.opencms.gwt.shared.alias.CmsAliasImportResult; 043import org.opencms.gwt.shared.alias.CmsAliasSaveValidationRequest; 044import org.opencms.gwt.shared.alias.CmsAliasTableRow; 045import org.opencms.gwt.shared.alias.CmsRewriteAliasTableRow; 046import org.opencms.main.CmsException; 047import org.opencms.main.OpenCms; 048import org.opencms.util.CmsUUID; 049 050import java.util.ArrayList; 051import java.util.HashSet; 052import java.util.List; 053import java.util.Locale; 054import java.util.Set; 055 056import org.apache.commons.fileupload2.core.DiskFileItem; 057import org.apache.commons.fileupload2.core.DiskFileItemFactory; 058import org.apache.commons.fileupload2.jakarta.servlet5.JakartaServletDiskFileUpload; 059 060import com.google.common.collect.Sets; 061 062import jakarta.servlet.http.HttpServletRequest; 063import jakarta.servlet.http.HttpServletResponse; 064 065/** 066 * Helper class used by a service to edit or import aliases for a whole site.<p> 067 */ 068public class CmsAliasBulkEditHelper { 069 070 /** The current CMS context. */ 071 private CmsObject m_cms; 072 073 /** Flag to indicate whether the validation was successful. */ 074 private boolean m_hasErrors; 075 076 /** 077 * Creates a new helper object.<p> 078 * 079 * @param cms the current CMS context 080 */ 081 public CmsAliasBulkEditHelper(CmsObject cms) { 082 083 m_cms = cms; 084 } 085 086 /** 087 * Imports uploaded aliases from a request.<p> 088 * 089 * @param request the request containing the uploaded aliases 090 * @param response the response 091 * @throws Exception if something goes wrong 092 */ 093 public void importAliases(HttpServletRequest request, HttpServletResponse response) throws Exception { 094 095 DiskFileItemFactory factory = DiskFileItemFactory.builder().get(); 096 JakartaServletDiskFileUpload upload = new JakartaServletDiskFileUpload(factory); 097 @SuppressWarnings("unchecked") 098 List<DiskFileItem> items = upload.parseRequest(request); 099 byte[] data = null; 100 String siteRoot = null; 101 String separator = ","; 102 for (DiskFileItem fileItem : items) { 103 String name = fileItem.getFieldName(); 104 if (PARAM_IMPORTFILE.equals(name)) { 105 data = fileItem.get(); 106 } else if (PARAM_SITEROOT.equals(name)) { 107 siteRoot = new String(fileItem.get(), request.getCharacterEncoding()); 108 } else if (PARAM_SEPARATOR.equals(name)) { 109 separator = new String(fileItem.get(), request.getCharacterEncoding()); 110 } 111 } 112 List<CmsAliasImportResult> result = new ArrayList<CmsAliasImportResult>(); 113 if ((siteRoot != null) && (data != null)) { 114 result = OpenCms.getAliasManager().importAliases(m_cms, data, siteRoot, separator); 115 } 116 String key = CmsVfsSitemapService.addAliasImportResult(result); 117 // only respond with a key, then the client can get the data for the key via GWT-RPC 118 response.getWriter().print(key); 119 } 120 121 /** 122 * Saves alias changes to the database.<p> 123 * 124 * @param saveRequest an object containing the alias changes to save 125 * 126 * @return a validation error if the alias data is invalid, or null otherwise 127 * 128 * @throws CmsException if something goes wrong 129 */ 130 public CmsAliasEditValidationReply saveAliases(CmsAliasSaveValidationRequest saveRequest) throws CmsException { 131 132 CmsAliasEditValidationReply reply = validateAliases(saveRequest); 133 if (m_hasErrors) { 134 return reply; 135 } else { 136 List<CmsRewriteAliasTableRow> rewriteData = saveRequest.getRewriteData(); 137 OpenCms.getAliasManager().saveRewriteAliases( 138 m_cms, 139 m_cms.getRequestContext().getSiteRoot(), 140 convertRewriteData(rewriteData)); 141 Set<CmsUUID> allTouchedIds = new HashSet<CmsUUID>(); 142 List<CmsAliasTableRow> rows = saveRequest.getEditedData(); 143 for (CmsAliasTableRow row : rows) { 144 if (row.isEdited()) { 145 allTouchedIds.add(row.getStructureId()); 146 if (row.getOriginalStructureId() != null) { 147 allTouchedIds.add(row.getOriginalStructureId()); 148 } 149 } 150 } 151 allTouchedIds.addAll(saveRequest.getDeletedIds()); 152 CmsAliasManager aliasManager = OpenCms.getAliasManager(); 153 String siteRoot = saveRequest.getSiteRoot(); 154 List<CmsAlias> aliases = aliasManager.getAliasesForSite(m_cms, siteRoot); 155 Set<CmsAlias> aliasSet = new HashSet<CmsAlias>(); 156 Set<CmsAlias> editedAliasSet = new HashSet<CmsAlias>(); 157 aliasSet.addAll(aliases); 158 for (CmsAliasTableRow row : rows) { 159 CmsAlias editedAlias = new CmsAlias(row.getStructureId(), siteRoot, row.getAliasPath(), row.getMode()); 160 editedAliasSet.add(editedAlias); 161 } 162 Set<CmsAlias> toDelete = Sets.difference(aliasSet, editedAliasSet); 163 toDelete = filterStructureId(toDelete, allTouchedIds); 164 Set<CmsAlias> toAdd = Sets.difference(editedAliasSet, aliasSet); 165 toAdd = filterStructureId(toAdd, allTouchedIds); 166 167 aliasManager.updateAliases(m_cms, toDelete, toAdd); 168 return null; 169 } 170 } 171 172 /** 173 * Validates the alias data.<p> 174 * 175 * @param validationRequest an object containing the alias data to validate 176 * 177 * @return the validation result 178 */ 179 public CmsAliasEditValidationReply validateAliases(CmsAliasEditValidationRequest validationRequest) { 180 181 List<CmsAliasTableRow> editedData = validationRequest.getEditedData(); 182 CmsAliasTableRow newEntry = validationRequest.getNewEntry(); 183 if (newEntry != null) { 184 newEntry.setKey((new CmsUUID()).toString()); 185 editedData.add(newEntry); 186 187 } 188 CmsObject cms = m_cms; 189 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 190 for (CmsAliasTableRow row : editedData) { 191 row.clearErrors(); 192 validateSingleAliasRow(cms, row); 193 } 194 Set<String> foundAliasPaths = new HashSet<String>(); 195 Set<String> duplicateAliasPaths = new HashSet<String>(); 196 for (CmsAliasTableRow row : editedData) { 197 String aliasPath = row.getAliasPath(); 198 if (foundAliasPaths.contains(aliasPath)) { 199 duplicateAliasPaths.add(aliasPath); 200 } 201 foundAliasPaths.add(aliasPath); 202 } 203 for (CmsAliasTableRow row : editedData) { 204 if (duplicateAliasPaths.contains(row.getAliasPath())) { 205 if (row.getPathError() == null) { 206 row.setAliasError(messageDuplicateAliasPath(locale)); 207 m_hasErrors = true; 208 } 209 } 210 } 211 CmsAliasEditValidationReply result = new CmsAliasEditValidationReply(); 212 List<CmsAliasTableRow> changedRows = new ArrayList<CmsAliasTableRow>(); 213 for (CmsAliasTableRow row : editedData) { 214 if (row.isChanged()) { 215 changedRows.add(row); 216 } 217 } 218 if (newEntry != null) { 219 changedRows.remove(newEntry); 220 } 221 result.setChangedRows(changedRows); 222 result.setValidatedNewEntry(newEntry); 223 return result; 224 } 225 226 /** 227 * Filters all aliases from a set whose structure id is in a given set of structure ids.<p> 228 * 229 * @param aliases the aliases to filter 230 * @param structureIds the structure ids for which we want the aliases 231 * 232 * @return the filtered structure ids 233 */ 234 protected Set<CmsAlias> filterStructureId(Set<CmsAlias> aliases, Set<CmsUUID> structureIds) { 235 236 Set<CmsAlias> result = new HashSet<CmsAlias>(); 237 for (CmsAlias alias : aliases) { 238 if (structureIds.contains(alias.getStructureId())) { 239 result.add(alias); 240 } 241 } 242 return result; 243 } 244 245 /** 246 * Converts rewrite alias table rows to rewrite alias objects.<p> 247 * 248 * @param rewriteData the rewrite data 249 * 250 * @return the converted rewrite aliases 251 */ 252 private List<CmsRewriteAlias> convertRewriteData(List<CmsRewriteAliasTableRow> rewriteData) { 253 254 String siteRoot = m_cms.getRequestContext().getSiteRoot(); 255 List<CmsRewriteAlias> result = new ArrayList<CmsRewriteAlias>(); 256 for (CmsRewriteAliasTableRow row : rewriteData) { 257 CmsRewriteAlias alias = new CmsRewriteAlias( 258 row.getId(), 259 siteRoot, 260 row.getPatternString(), 261 row.getReplacementString(), 262 row.getMode()); 263 result.add(alias); 264 } 265 return result; 266 } 267 268 /** 269 * Message accessor. 270 * 271 * @param locale the locale for messages 272 * 273 * @return the message string 274 */ 275 private String messageDuplicateAliasPath(Locale locale) { 276 277 return Messages.get().getBundle(locale).key(Messages.ERR_ALIAS_DUPLICATE_ALIAS_PATH_0); 278 } 279 280 /** 281 * Message accessor. 282 * 283 * @param locale the locale for messages 284 * 285 * @return the message string 286 */ 287 private String messageInvalidAliasPath(Locale locale) { 288 289 return Messages.get().getBundle(locale).key(Messages.ERR_ALIAS_INVALID_ALIAS_PATH_0); 290 } 291 292 /** 293 * Message accessor. 294 * 295 * @param locale the locale for messages 296 * 297 * @return the message string 298 */ 299 private String messageResourceNotFound(Locale locale) { 300 301 return Messages.get().getBundle(locale).key(Messages.ERR_ALIAS_RESOURCE_NOT_FOUND_0); 302 303 } 304 305 /** 306 * Validates a single alias row.<p> 307 * 308 * @param cms the current CMS context 309 * @param row the row to validate 310 */ 311 private void validateSingleAliasRow(CmsObject cms, CmsAliasTableRow row) { 312 313 Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 314 if (row.getStructureId() == null) { 315 String path = row.getResourcePath(); 316 try { 317 CmsResource resource = cms.readResource(path, CmsResourceFilter.ALL); 318 row.setStructureId(resource.getStructureId()); 319 if (row.getOriginalStructureId() == null) { 320 row.setOriginalStructureId(resource.getStructureId()); 321 } 322 } catch (CmsException e) { 323 row.setPathError(messageResourceNotFound(locale)); 324 m_hasErrors = true; 325 } 326 } 327 if (!CmsAlias.ALIAS_PATTERN.matcher(row.getAliasPath()).matches()) { 328 row.setAliasError(messageInvalidAliasPath(locale)); 329 m_hasErrors = true; 330 } 331 } 332}