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