001/*
002 * #%L
003 * Netarchivesuite - harvester
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.harvester.webinterface;
025
026import java.util.ArrayList;
027import java.util.Date;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031
032import javax.servlet.ServletRequest;
033import javax.servlet.jsp.PageContext;
034
035
036import dk.netarkivet.common.exceptions.ArgumentNotValid;
037import dk.netarkivet.common.exceptions.ForwardedToErrorPage;
038import dk.netarkivet.common.utils.DomainUtils;
039import dk.netarkivet.common.utils.I18n;
040import dk.netarkivet.common.webinterface.HTMLUtils;
041import dk.netarkivet.harvester.datamodel.Domain;
042import dk.netarkivet.harvester.datamodel.DomainConfiguration;
043import dk.netarkivet.harvester.datamodel.DomainDAO;
044import dk.netarkivet.harvester.datamodel.HarvestDefinitionDAO;
045import dk.netarkivet.harvester.datamodel.PartialHarvest;
046import dk.netarkivet.harvester.datamodel.Schedule;
047import dk.netarkivet.harvester.datamodel.ScheduleDAO;
048import dk.netarkivet.harvester.datamodel.SparseDomainConfiguration;
049import dk.netarkivet.harvester.datamodel.SparsePartialHarvest;
050import dk.netarkivet.harvester.datamodel.extendedfield.ExtendedFieldTypes;
051
052/**
053 * This class contains the methods for updating data for selective harvests.
054 */
055@SuppressWarnings({"unchecked"})
056public final class SelectiveHarvestUtil {
057    /**
058     * Utility class. No instances.
059     */
060    private SelectiveHarvestUtil() {
061    }
062
063    /**
064     * Update or create a partial harvest definition.
065     *
066     * @param context JSP context of this call. Contains parameters as described in
067     * Definitions-edit-selective-harvest.jsp
068     * @param i18n Translation information.
069     * @param unknownDomains List to which unknown legal domains are added.
070     * @param illegalDomains List to which illegal domains are added,
071     */
072    public static void processRequest(PageContext context, I18n i18n, List<String> unknownDomains,
073            List<String> illegalDomains) {
074        ArgumentNotValid.checkNotNull(context, "PageContext context");
075        ArgumentNotValid.checkNotNull(i18n, "I18n i18n");
076        ArgumentNotValid.checkNotNull(unknownDomains, "List unknownDomains");
077        ArgumentNotValid.checkNotNull(illegalDomains, "List illegalDomains");
078
079        // Was the set next date button pressed?
080        boolean setNextDateOnly = HTMLUtils.parseOptionalBoolean(context, Constants.NEXTDATE_SUBMIT, false);
081        if (setNextDateOnly) {
082            HTMLUtils.forwardOnEmptyParameter(context, Constants.NEXTDATE_PARAM, Constants.NEXTDATE_PARAM);
083            HTMLUtils.forwardOnEmptyParameter(context, Constants.HARVEST_ID, Constants.HARVEST_ID);
084
085            // If the override date is set, parse it and set the override date.
086            Date date = HTMLUtils.parseOptionalDate(context, Constants.NEXTDATE_PARAM, I18n.getString(
087                    dk.netarkivet.harvester.Constants.TRANSLATIONS_BUNDLE, context.getResponse().getLocale(),
088                    "harvestdefinition.schedule.edit.timeformat"), null);
089            long harvestId = HTMLUtils.parseOptionalLong(context, Constants.HARVEST_ID, -1L);
090
091            HarvestDefinitionDAO.getInstance().updateNextdate(harvestId, date);
092
093            return; // nothin' more to do!
094        }
095
096        ServletRequest request = context.getRequest();
097        if (request.getParameter(Constants.UPDATE_PARAM) == null) {
098            return; // nothing to do.
099        }
100
101        String deleteConfig = request.getParameter(Constants.DELETECONFIG_PARAM);
102        // Case where we are removing a configuration
103        // In this we make the delete, and then return;
104        if (deleteConfig != null) {
105            HTMLUtils.forwardOnEmptyParameter(context, Constants.DELETECONFIG_PARAM);
106            deleteConfig(context, i18n, deleteConfig);
107            return;
108        }
109
110        PartialHarvest hdd = updateHarvestDefinition(context, i18n, unknownDomains, illegalDomains);
111
112        ExtendedFieldValueDefinition.processRequest(context, i18n, hdd, ExtendedFieldTypes.HARVESTDEFINITION);
113        HarvestDefinitionDAO.getInstance().update(hdd);
114
115        boolean changed = false;
116
117        // If the override date is set, parse it and set the override date.
118        Date date = HTMLUtils.parseOptionalDate(context, Constants.NEXTDATE_PARAM, I18n.getString(
119                dk.netarkivet.harvester.Constants.TRANSLATIONS_BUNDLE, context.getResponse().getLocale(),
120                "harvestdefinition.schedule.edit.timeformat"), null);
121        if (date != null) {
122            hdd.setNextDate(date);
123            changed |= true;
124        }
125
126        // Case where we are adding domains that didn't exist before
127        // This uses two parameters because it has an input field and a submit
128        // button.
129        if (request.getParameter(Constants.ADDDOMAINS_PARAM) != null) {
130            HTMLUtils.forwardOnMissingParameter(context, Constants.UNKNOWN_DOMAINS_PARAM);
131            changed |= addDomainsToHarvest(hdd, request.getParameter(Constants.UNKNOWN_DOMAINS_PARAM));
132        }
133
134        if (changed) {
135            HarvestDefinitionDAO.getInstance().update(hdd);
136        }
137
138    }
139
140    /**
141     * Updates the harvest definition with posted values.
142     *
143     * @param context The context that the web request processing happens in
144     * @param i18n Translation information for this site section.
145     * @param unknownDomains List to add unknown but legal domains to.
146     * @param illegalDomains List to add illegal domains to.
147     * @return The updated harvest definition. This object holds an edition that is legal to use for further updates
148     * (adding or deleting domains)
149     */
150    private static PartialHarvest updateHarvestDefinition(PageContext context, I18n i18n, List<String> unknownDomains,
151            List<String> illegalDomains) {
152        ServletRequest request = context.getRequest();
153        HTMLUtils.forwardOnEmptyParameter(context, Constants.HARVEST_PARAM, Constants.SCHEDULE_PARAM);
154        String name = request.getParameter(Constants.HARVEST_PARAM);
155        String oldname = request.getParameter(Constants.HARVEST_OLD_PARAM);
156        if (oldname == null) {
157            oldname = "";
158        }
159
160        HTMLUtils.forwardOnMissingParameter(context, Constants.COMMENTS_PARAM, Constants.DOMAINLIST_PARAM,
161                Constants.AUDIENCE_PARAM);
162        String scheduleName = request.getParameter(Constants.SCHEDULE_PARAM);
163        Schedule sched = ScheduleDAO.getInstance().read(scheduleName);
164        if (sched == null) {
165            HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;unknown.schedule.0", scheduleName);
166            throw new ForwardedToErrorPage("Schedule '" + scheduleName + "' not found");
167        }
168
169        String comments = request.getParameter(Constants.COMMENTS_PARAM);
170        String audience = request.getParameter(Constants.AUDIENCE_PARAM);
171
172        List<DomainConfiguration> dc = getDomainConfigurations(request.getParameterMap());
173        addDomainsToConfigurations(dc, request.getParameter(Constants.DOMAINLIST_PARAM), unknownDomains, illegalDomains);
174
175        // If necessary create harvest from scratch
176        HarvestDefinitionDAO hddao = HarvestDefinitionDAO.getInstance();
177        if ((request.getParameter(Constants.CREATENEW_PARAM) != null)) {
178            if (hddao.exists(name)) {
179                HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;harvest.definition.0.already.exists", name);
180                throw new ForwardedToErrorPage("A harvest definition " + "called '" + name + "' already exists");
181            }
182            PartialHarvest hdd = new PartialHarvest(dc, sched, name, comments, audience);
183            hdd.setActive(false);
184            hddao.create(hdd);
185            return hdd;
186        } else {
187            long edition = HTMLUtils.parseOptionalLong(context, Constants.EDITION_PARAM, Constants.NO_EDITION);
188            PartialHarvest hdd;
189            if (oldname.equals(name)) {
190                hdd = (PartialHarvest) hddao.getHarvestDefinition(name);
191            } else {
192                if (hddao.exists(name)) {
193                    HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;harvest.definition.0.already.exists", name);
194                    throw new ForwardedToErrorPage("A harvest definition " + "called '" + name + "' already exists");
195                } else {
196                    hdd = (PartialHarvest) hddao.getHarvestDefinition(oldname);
197                    hdd.setName(name);
198                }
199            }
200            if (hdd.getEdition() != edition) {
201                HTMLUtils.forwardWithRawErrorMessage(context, i18n, "errormsg;harvest.definition.changed.0.retry.1",
202                        "<br/><a href=\"Definitions-edit-selective-harvest.jsp?" + Constants.HARVEST_PARAM + "="
203                                + HTMLUtils.encodeAndEscapeHTML(name) + "\">", "</a>");
204                throw new ForwardedToErrorPage("Harvest definition '" + name + "' has changed");
205            }
206            // update the harvest definition
207            hdd.setDomainConfigurations(dc);
208            hdd.setSchedule(sched);
209            hdd.setComments(comments);
210            hdd.setAudience(audience);
211            hddao.update(hdd);
212            return hdd;
213        }
214    }
215
216    /**
217     * Delete a domain configuration from a harvestdefinition.
218     *
219     * @param context The web server context for the JSP page.
220     * @param i18n Translation information for this site section.
221     * @param deleteConfig the configuration to delete, in the form of a domain name, a colon, a configuration name.
222     */
223    private static void deleteConfig(PageContext context, I18n i18n, String deleteConfig) {
224        HTMLUtils.forwardOnEmptyParameter(context, Constants.HARVEST_PARAM, Constants.SCHEDULE_PARAM);
225        ServletRequest request = context.getRequest();
226        String name = request.getParameter(Constants.HARVEST_PARAM);
227        HarvestDefinitionDAO hddao = HarvestDefinitionDAO.getInstance();
228        if (!hddao.exists(name)) {
229            HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;harvestdefinition.0.does.not.exist", name);
230            throw new ForwardedToErrorPage("Harvestdefinition '" + name + "' does not exist");
231        }
232        SparsePartialHarvest sph = hddao.getSparsePartialHarvest(name);
233
234        String[] domainConfigPair = deleteConfig.split(":", 2);
235        if (domainConfigPair.length < 2) {
236            HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;malformed.domain.config.pair.0", deleteConfig);
237            throw new ForwardedToErrorPage("Malformed domain-config pair " + deleteConfig);
238        }
239        String domainName = domainConfigPair[0];
240        String configName = domainConfigPair[1];
241        SparseDomainConfiguration key = new SparseDomainConfiguration(domainName, configName);
242
243        hddao.removeDomainConfiguration(sph.getOid(), key);
244    }
245
246    /**
247     * Extract domain configuration list from a map of parameters. All key that starts with Constants.DOMAIN_IDENTIFIER
248     * are treated as a concatenation of : DOMAIN_IDENTIFIER + domain name. The corresponding value in the map is
249     * treated as the configuration name Entries that do not match this pattern are ignored.
250     *
251     * @param configurations a mapping (domain to its configurations)
252     * @return a list of domain configurations
253     */
254    private static List<DomainConfiguration> getDomainConfigurations(Map<String, String[]> configurations) {
255        List<DomainConfiguration> dcList = new ArrayList<DomainConfiguration>();
256
257        for (Map.Entry<String, String[]> param : configurations.entrySet()) {
258            if (param.getKey().startsWith(Constants.DOMAIN_IDENTIFIER)) {
259                String domainName = param.getKey().substring(Constants.DOMAIN_IDENTIFIER.length());
260                Domain domain = DomainDAO.getInstance().read(domainName);
261                for (String configurationName : param.getValue()) {
262                    dcList.add(domain.getConfiguration(configurationName));
263                }
264            }
265        }
266        return dcList;
267    }
268
269    /**
270     * Given a list of domain configurations and a list of domains, add the default configurations for the domains to
271     * the configuration list. If any of the domains are unknown, their names are instead appended to the argument
272     * unknownDomains (with newline separation)
273     *
274     * @param dcList the initial list of configurations
275     * @param extraDomains the domains to be added to dcList with default configurations
276     * @param unknownDomains a list to add unknown, legal domains to
277     * @param illegalDomains a list to add illegal domains to
278     */
279    private static void addDomainsToConfigurations(List<DomainConfiguration> dcList, String extraDomains,
280            List<String> unknownDomains, List<String> illegalDomains) {
281        String[] domains = extraDomains.split("\\s+");
282        DomainDAO ddao = DomainDAO.getInstance();
283        for (String domain : domains) {
284            domain = domain.trim();
285            if (domain.length() > 0) {
286                if (ddao.exists(domain)) {
287                    Domain d = ddao.read(domain);
288                    if (!dcList.contains(d.getDefaultConfiguration())) {
289                        dcList.add(d.getDefaultConfiguration());
290                    }
291                } else {
292                    if (DomainUtils.isValidDomainName(domain)) {
293                        unknownDomains.add(domain);
294                    } else {
295                        illegalDomains.add(domain);
296                    }
297                }
298            }
299        }
300    }
301
302    /**
303     * Given a harvest and list of domains, this method creates all the specified domains and adds them to the harvest
304     * with their default configuration.
305     *
306     * @param hdd The harvest definition to change.
307     * @param domains a whitespace-separated list of domains to create and add to harvest
308     * @return True if changes were made to hdd.
309     */
310    private static boolean addDomainsToHarvest(PartialHarvest hdd, String domains) {
311        String[] domainsS = domains.split("\\s");
312        List<DomainConfiguration> configurations = new ArrayList<DomainConfiguration>();
313        for (String domainName : domainsS) {
314            if (domainName != null && !domainName.isEmpty()) {
315                if (DomainUtils.isValidDomainName(domainName)) {
316                    Domain domain = Domain.getDefaultDomain(domainName);
317                    DomainDAO.getInstance().create(domain);
318                    configurations.add(domain.getDefaultConfiguration());
319                }
320            }
321        }
322        if (configurations.size() > 0) {
323            Iterator<DomainConfiguration> existingConfigurations = hdd.getDomainConfigurations();
324            while (existingConfigurations.hasNext()) {
325                configurations.add(existingConfigurations.next());
326            }
327            hdd.setDomainConfigurations(configurations);
328            return true;
329        } else {
330            return false;
331        }
332    }
333}