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