nl.strohalm.cyclos.services.ads.AdImportServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.ads.AdImportServiceImpl.java

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.services.ads;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import nl.strohalm.cyclos.dao.ads.imports.AdImportDAO;
import nl.strohalm.cyclos.dao.ads.imports.ImportedAdCategoryDAO;
import nl.strohalm.cyclos.dao.ads.imports.ImportedAdDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.MemberUser;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.accounts.Currency;
import nl.strohalm.cyclos.entities.ads.Ad;
import nl.strohalm.cyclos.entities.ads.Ad.TradeType;
import nl.strohalm.cyclos.entities.ads.AdCategory;
import nl.strohalm.cyclos.entities.ads.imports.AdImport;
import nl.strohalm.cyclos.entities.ads.imports.AdImportResult;
import nl.strohalm.cyclos.entities.ads.imports.ImportedAd;
import nl.strohalm.cyclos.entities.ads.imports.ImportedAdCategory;
import nl.strohalm.cyclos.entities.ads.imports.ImportedAdCustomFieldValue;
import nl.strohalm.cyclos.entities.ads.imports.ImportedAdQuery;
import nl.strohalm.cyclos.entities.customization.fields.AdCustomField;
import nl.strohalm.cyclos.entities.customization.fields.AdCustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.CustomField;
import nl.strohalm.cyclos.entities.customization.fields.CustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.groups.MemberGroupSettings;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.services.customization.AdCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.conversion.CalendarConverter;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.conversion.NumberConverter;
import nl.strohalm.cyclos.utils.csv.CSVReader;
import nl.strohalm.cyclos.utils.csv.UnknownColumnException;
import nl.strohalm.cyclos.utils.query.PageHelper;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

public class AdImportServiceImpl implements AdImportServiceLocal {

    private FetchServiceLocal fetchService;
    private ElementServiceLocal elementService;
    private AdServiceLocal adService;
    private AdCategoryServiceLocal adCategoryService;
    private SettingsServiceLocal settingsService;
    private AdCustomFieldServiceLocal adCustomFieldService;
    private AdImportDAO adImportDao;
    private ImportedAdDAO importedAdDao;
    private ImportedAdCategoryDAO importedAdCategoryDao;

    @Override
    public List<ImportedAdCategory> getNewCategories(final AdImport adImport) {
        return importedAdCategoryDao.getLeafCategories(adImport);
    }

    @Override
    public AdImportResult getSummary(final AdImport adIimport) {
        final AdImportResult result = new AdImportResult();
        final ImportedAdQuery query = new ImportedAdQuery();
        query.setAdImport(adIimport);
        query.setPageForCount();

        // Get the total number of ads
        query.setStatus(ImportedAdQuery.Status.ALL);
        result.setTotal(PageHelper.getTotalCount(importedAdDao.search(query)));

        // Get the number of ads with error
        query.setStatus(ImportedAdQuery.Status.ERROR);
        result.setErrors(PageHelper.getTotalCount(importedAdDao.search(query)));

        // Get the number of new categories
        result.setNewCategories(importedAdCategoryDao.getLeafCategories(adIimport).size());

        return result;
    }

    @Override
    public AdImport importAds(AdImport adImport, final InputStream data) {

        // Validate and save the import
        getValidator().validate(adImport);
        final Currency currency = fetchService.fetch(adImport.getCurrency());
        adImport.setCurrency(currency);
        adImport.setBy(LoggedUser.<Administrator>element());
        adImport.setDate(Calendar.getInstance());
        adImport = adImportDao.insert(adImport);

        // Find out the custom fields
        final List<AdCustomField> customFields = adCustomFieldService.list();
        final Map<String, CustomField> customFieldMap = new HashMap<String, CustomField>(customFields.size());
        for (final AdCustomField customField : customFields) {
            customFieldMap.put(customField.getInternalName().toLowerCase(),
                    fetchService.fetch(customField, CustomField.Relationships.POSSIBLE_VALUES));
        }

        // Find the existing advertisement categories
        final Map<String, AdCategory> existingAdCategoryMap = new LinkedHashMap<String, AdCategory>();
        final List<AdCategory> rootCategories = adCategoryService.listRoot();
        for (final AdCategory adCategory : rootCategories) {
            appendCategory(adCategory, existingAdCategoryMap);
        }
        final Map<String, ImportedAdCategory> importedAdCategoryMap = new LinkedHashMap<String, ImportedAdCategory>();

        // Get the settings
        final LocalSettings localSettings = settingsService.getLocalSettings();
        final char stringQuote = CoercionHelper.coerce(Character.TYPE,
                localSettings.getCsvStringQuote().getValue());
        final char valueSeparator = CoercionHelper.coerce(Character.TYPE,
                localSettings.getCsvValueSeparator().getValue());

        // Read the headers
        BufferedReader in = null;
        List<String> headers;
        try {
            in = new BufferedReader(new InputStreamReader(data, localSettings.getCharset()));
            headers = CSVReader.readLine(in, stringQuote, valueSeparator);
        } catch (final Exception e) {
            throw new ValidationException("adImport.invalidFormat");
        }

        // Import each ad
        try {
            final CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
            int lineNumber = 2; // The first line is the header
            List<String> values;
            while ((values = CSVReader.readLine(in, stringQuote, valueSeparator)) != null) {
                if (values.isEmpty()) {
                    continue;
                }
                importAd(adImport, lineNumber, existingAdCategoryMap, importedAdCategoryMap, customFieldMap,
                        localSettings, headers, values);
                lineNumber++;
                cacheCleaner.clearCache();
            }
        } catch (final IOException e) {
            throw new ValidationException("adImport.errorReading");
        } finally {
            IOUtils.closeQuietly(in);
        }
        return adImport;
    }

    @Override
    public AdImport load(final Long id, final Relationship... fetch) throws EntityNotFoundException {
        return adImportDao.load(id, fetch);
    }

    @Override
    public void processImport(AdImport adImport) {
        adImport = fetchService.fetch(adImport, AdImport.Relationships.CURRENCY);

        final Map<ImportedAdCategory, AdCategory> importedCategories = new HashMap<ImportedAdCategory, AdCategory>();
        // Iterate through each ad
        final ImportedAdQuery adQuery = new ImportedAdQuery();
        adQuery.fetch(ImportedAd.Relationships.EXISTING_CATEGORY, ImportedAd.Relationships.IMPORTED_CATEGORY,
                ImportedAd.Relationships.CUSTOM_VALUES);
        adQuery.setAdImport(adImport);
        adQuery.setStatus(ImportedAdQuery.Status.SUCCESS);
        int count = 0;
        final List<ImportedAd> importedAds = importedAdDao.search(adQuery);
        for (final ImportedAd importedAd : importedAds) {
            processAd(adImport, importedAd, importedCategories);
            if (count % 20 == 0) {
                // Every few records, clear the cache to avoid too many objects in memory
                fetchService.clearCache();
            }
            count++;
        }
        // Delete the import after processing it
        adImportDao.delete(adImport.getId());
    }

    @Override
    public void purgeOld(Calendar time) {
        // Only purge after 1 day of idleness
        time = new TimePeriod(1, TimePeriod.Field.DAYS).remove(time);
        for (final AdImport adImport : adImportDao.listBefore(time)) {
            adImportDao.delete(adImport.getId());
        }
    }

    @Override
    public List<ImportedAd> searchImportedAds(final ImportedAdQuery params) {
        return importedAdDao.search(params);
    }

    public void setAdCategoryServiceLocal(final AdCategoryServiceLocal adCategoryService) {
        this.adCategoryService = adCategoryService;
    }

    public void setAdCustomFieldServiceLocal(final AdCustomFieldServiceLocal adCustomFieldService) {
        this.adCustomFieldService = adCustomFieldService;
    }

    public void setAdImportDao(final AdImportDAO adImportDao) {
        this.adImportDao = adImportDao;
    }

    public void setAdServiceLocal(final AdServiceLocal adService) {
        this.adService = adService;
    }

    public void setElementServiceLocal(final ElementServiceLocal elementService) {
        this.elementService = elementService;
    }

    public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
        this.fetchService = fetchService;
    }

    public void setImportedAdCategoryDao(final ImportedAdCategoryDAO importedAdCategoryDao) {
        this.importedAdCategoryDao = importedAdCategoryDao;
    }

    public void setImportedAdDao(final ImportedAdDAO importedAdDao) {
        this.importedAdDao = importedAdDao;
    }

    public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
        this.settingsService = settingsService;
    }

    @Override
    public void validate(final AdImport AdImport) throws ValidationException {
        getValidator().validate(AdImport);
    }

    /**
     * Recursively add the category and it's children to the map, keyed by the full name
     */
    private void appendCategory(final AdCategory adCategory, final Map<String, AdCategory> existingAdCategoryMap) {
        // Just being cautious to avoid infinite loops in bad behaved databases
        if (existingAdCategoryMap.values().contains(adCategory)) {
            return;
        }
        existingAdCategoryMap.put(adCategory.getFullName(), adCategory);
        for (final AdCategory child : adCategory.getChildren()) {
            appendCategory(child, existingAdCategoryMap);
        }
    }

    private Validator getValidator() {
        final Validator validator = new Validator();
        validator.property("currency").required();
        return validator;
    }

    private Object handleCategory(final ImportedAd ad, final String value,
            final Map<String, AdCategory> existingAdCategoryMap,
            final Map<String, ImportedAdCategory> importedAdCategoryMap) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        final String[] parts = StringUtils.split(value, ':');
        Object category = null;
        String fullPath = null;
        // Validate the max level
        if (parts.length > AdCategory.MAX_LEVEL) {
            ad.setStatus(ImportedAd.Status.TOO_MANY_CATEGORY_LEVELS);
            return null;
        }
        for (String part : parts) {
            part = StringUtils.trimToNull(part);
            if (part == null) {
                ad.setStatus(ImportedAd.Status.INVALID_CATEGORY);
                return null;
            }
            // Calculate the canonical full path (uses colon and a space as separators, as returned by AdCategory.getFullPath())
            if (fullPath == null) {
                fullPath = part;
            } else {
                fullPath += ": " + part;
            }
            // Check whether the category exists
            final AdCategory existingCategory = existingAdCategoryMap.get(fullPath);
            if (existingCategory != null) {
                // There is an existing category
                category = existingCategory;
            } else {
                ImportedAdCategory importedCategory = importedAdCategoryMap.get(fullPath);
                if (importedCategory == null) {
                    // No existing category: create a new imported one
                    importedCategory = new ImportedAdCategory();
                    importedCategory.setAdImport(ad.getImport());
                    importedCategory.setName(part);
                    if (category instanceof AdCategory) {
                        importedCategory.setExistingParent((AdCategory) category);
                    } else if (category instanceof ImportedAdCategory) {
                        importedCategory.setImportedParent((ImportedAdCategory) category);
                    }
                    importedCategory = importedAdCategoryDao.insert(importedCategory);
                    importedAdCategoryMap.put(fullPath, importedCategory);
                }
                category = importedCategory;
            }
        }
        return category;
    }

    private void importAd(final AdImport adImport, final int lineNumber,
            final Map<String, AdCategory> existingAdCategoryMap,
            final Map<String, ImportedAdCategory> importedAdCategoryMap,
            final Map<String, CustomField> customFieldMap, final LocalSettings localSettings,
            final List<String> headers, final List<String> values) {
        final Map<String, String> customFieldValues = new HashMap<String, String>();

        final CalendarConverter dateConverter = localSettings.getRawDateConverter();
        final NumberConverter<BigDecimal> numberConverter = localSettings.getNumberConverter();

        // Insert the ad
        ImportedAd ad = new ImportedAd();
        ad.setLineNumber(lineNumber);
        ad.setImport(adImport);
        ad.setStatus(ImportedAd.Status.SUCCESS);
        ad = importedAdDao.insert(ad);
        ad.setPublicationPeriod(new Period());
        ad.setExternalPublication(true);
        try {
            ad.setCustomValues(new ArrayList<ImportedAdCustomFieldValue>());

            // Process each field. Field names are lowercased to ignore case
            for (int i = 0; i < headers.size() && i < values.size(); i++) {
                final String field = StringUtils.trimToEmpty(headers.get(i)).toLowerCase();
                final String value = StringUtils.trimToNull(values.get(i));
                final boolean valueIsTrue = "true".equalsIgnoreCase(value) || "1".equals(value);
                if ("owner".equals(field)) {
                    if (value != null) {
                        try {
                            final MemberUser user = (MemberUser) elementService.loadUser(value, RelationshipHelper
                                    .nested(User.Relationships.ELEMENT, Element.Relationships.GROUP));
                            ad.setOwner(user.getMember());
                        } catch (final Exception e) {
                            ad.setStatus(ImportedAd.Status.INVALID_OWNER);
                            ad.setErrorArgument1(value);
                        }
                    }
                } else if ("title".equals(field)) {
                    ad.setTitle(value);
                } else if ("description".equals(field)) {
                    ad.setDescription(value);
                } else if ("html".equals(field)) {
                    ad.setHtml(valueIsTrue);
                } else if ("publicationstart".equals(field)) {
                    try {
                        ad.getPublicationPeriod().setBegin(dateConverter.valueOf(value));
                    } catch (final Exception e) {
                        ad.setStatus(ImportedAd.Status.INVALID_PUBLICATION_START);
                        ad.setErrorArgument1(value);
                        break;
                    }
                } else if ("publicationend".equals(field)) {
                    try {
                        ad.getPublicationPeriod().setEnd(dateConverter.valueOf(value));
                    } catch (final Exception e) {
                        ad.setStatus(ImportedAd.Status.INVALID_PUBLICATION_END);
                        ad.setErrorArgument1(value);
                        break;
                    }
                } else if ("tradetype".equals(field)) {
                    // Only search is handled now, as it's the exception. Later, if it's null, offer is implied
                    if ("search".equalsIgnoreCase(value)) {
                        ad.setTradeType(TradeType.SEARCH);
                    }
                } else if ("external".equals(field)) {
                    ad.setExternalPublication(valueIsTrue);
                } else if ("price".equals(field)) {
                    try {
                        ad.setPrice(numberConverter.valueOf(value));
                        if (BigDecimal.ZERO.equals(ad.getPrice())) {
                            ad.setPrice(null);
                        }
                    } catch (final Exception e) {
                        ad.setStatus(ImportedAd.Status.INVALID_PRICE);
                        ad.setErrorArgument1(value);
                        break;
                    }
                } else if ("category".equals(field)) {
                    final Object category = handleCategory(ad, value, existingAdCategoryMap, importedAdCategoryMap);
                    if (category instanceof AdCategory) {
                        ad.setExistingCategory((AdCategory) category);
                    } else if (category instanceof ImportedAdCategory) {
                        ad.setImportedCategory((ImportedAdCategory) category);
                    } else if (ad.getStatus() != null) {
                        // The handleCategory may have set the status. Set the argument and leave
                        ad.setErrorArgument1(value);
                        break;
                    }
                } else if (customFieldMap.containsKey(field)) {
                    // Create a custom field value
                    final ImportedAdCustomFieldValue fieldValue = new ImportedAdCustomFieldValue();
                    fieldValue.setField(customFieldMap.get(field));
                    fieldValue.setValue(value);
                    ad.getCustomValues().add(fieldValue);
                    customFieldValues.put(field, value);
                } else {
                    throw new UnknownColumnException(field);
                }
            }

            // When there was an error, stop processing
            if (ad.getStatus() != ImportedAd.Status.SUCCESS) {
                return;
            }

            // Validate some data
            if (ad.getOwner() == null) {
                ad.setStatus(ImportedAd.Status.MISSING_OWNER);
                return;
            }
            if (ad.getExistingCategory() == null && ad.getImportedCategory() == null) {
                ad.setStatus(ImportedAd.Status.MISSING_CATEGORY);
                return;
            }
            if (ad.getTitle() == null) {
                ad.setStatus(ImportedAd.Status.MISSING_TITLE);
                return;
            }
            if (ad.getDescription() == null) {
                ad.setStatus(ImportedAd.Status.MISSING_DESCRIPTION);
                return;
            }
            // Set some default data
            final MemberGroupSettings groupSettings = ad.getOwner().getMemberGroup().getMemberSettings();
            Calendar begin = ad.getPublicationPeriod().getBegin();
            if (begin == null) {
                // When there's no begin, assume today
                begin = Calendar.getInstance();
                ad.getPublicationPeriod().setBegin(begin);
            }
            final Calendar end = ad.getPublicationPeriod().getEnd();
            if (end == null) {
                // Without end, it's a permanent ad
                // Check whether permanent ads are allowed
                if (!groupSettings.isEnablePermanentAds()) {
                    ad.setStatus(ImportedAd.Status.MISSING_PUBLICATION_PERIOD);
                    return;
                }
                ad.setPermanent(true);
            } else {
                // Validate the publication period
                if (begin.after(end)) {
                    ad.setStatus(ImportedAd.Status.PUBLICATION_BEGIN_AFTER_END);
                    return;
                } else {
                    // Check the max publication time
                    final TimePeriod maxAdPublicationTime = groupSettings.getMaxAdPublicationTime();
                    if (!end.before(maxAdPublicationTime.add(begin))) {
                        ad.setStatus(ImportedAd.Status.MAX_PUBLICATION_EXCEEDED);
                        return;
                    }
                }
            }
            if (ad.getTradeType() == null) {
                ad.setTradeType(TradeType.OFFER);
            }
            switch (groupSettings.getExternalAdPublication()) {
            case DISABLED:
                ad.setExternalPublication(false);
                break;
            case ENABLED:
                ad.setExternalPublication(true);
                break;
            }

            // Save the custom field values
            try {
                adCustomFieldService.saveValues(ad);
            } catch (final Exception e) {
                ad.setStatus(ImportedAd.Status.INVALID_CUSTOM_FIELD);
                if (e instanceof ValidationException) {
                    final ValidationException vex = (ValidationException) e;
                    final Map<String, Collection<ValidationError>> errorsByProperty = vex.getErrorsByProperty();
                    if (MapUtils.isNotEmpty(errorsByProperty)) {
                        final String fieldName = errorsByProperty.keySet().iterator().next();
                        ad.setErrorArgument1(fieldName);
                        final String fieldValue = StringUtils.trimToNull(customFieldValues.get(fieldName));
                        if (fieldValue == null) {
                            // When validation failed and the field is null, it's actually missing
                            ad.setStatus(ImportedAd.Status.MISSING_CUSTOM_FIELD);
                        } else {
                            ad.setErrorArgument2(fieldValue);
                        }
                    }
                }
                return;
            }

        } catch (final UnknownColumnException e) {
            throw e;
        } catch (final Exception e) {
            ad.setStatus(ImportedAd.Status.UNKNOWN_ERROR);
            ad.setErrorArgument1(e.toString());
        } finally {
            importedAdDao.update(ad);
        }

    }

    private void processAd(final AdImport adImport, final ImportedAd importedAd,
            final Map<ImportedAdCategory, AdCategory> importedCategories) {
        // Resolve the category first
        AdCategory category = importedAd.getExistingCategory();
        final ImportedAdCategory importedCategory = importedAd.getImportedCategory();
        if (category == null && importedCategory != null) {
            category = processCategory(importedCategory, importedCategories);
        }
        Ad ad = new Ad();
        ad.setCategory(category);
        // Without this fetch, Hibernate Search will bail, because the IsHasImages method is invoked
        final Member owner = fetchService.fetch(importedAd.getOwner(), Member.Relationships.IMAGES,
                Member.Relationships.CUSTOM_VALUES);
        if (owner != null) {
            owner.setCustomValues(fetchService.fetch(owner.getCustomValues(), CustomFieldValue.Relationships.FIELD,
                    CustomFieldValue.Relationships.POSSIBLE_VALUE));
            ad.setOwner(owner);
        }
        ad.setTradeType(importedAd.getTradeType());
        ad.setTitle(importedAd.getTitle());
        ad.setDescription(importedAd.getDescription());
        ad.setHtml(importedAd.isHtml());
        ad.setPermanent(importedAd.isPermanent());
        ad.setPublicationPeriod(importedAd.getPublicationPeriod());
        ad.setExternalPublication(importedAd.isExternalPublication());
        ad.setPrice(importedAd.getPrice());
        if (ad.getPrice() != null) {
            ad.setCurrency(adImport.getCurrency());
        }

        ad.setCustomValues(new ArrayList<AdCustomFieldValue>());

        // Set the custom values
        final Collection<ImportedAdCustomFieldValue> importedCustomValues = importedAd.getCustomValues();
        if (importedCustomValues != null) {
            for (final ImportedAdCustomFieldValue importedValue : importedCustomValues) {
                final CustomField field = importedValue.getField();
                final AdCustomFieldValue fieldValue = new AdCustomFieldValue();
                fieldValue.setAd(ad);
                fieldValue.setField(field);
                if (field.getType() == CustomField.Type.ENUMERATED) {
                    fieldValue.setPossibleValue(importedValue.getPossibleValue());
                } else if (field.getType() == CustomField.Type.MEMBER) {
                    fieldValue.setMemberValue(importedValue.getMemberValue());
                } else {
                    fieldValue.setStringValue(importedValue.getStringValue());
                }
                ad.getCustomValues().add(fieldValue);
            }
        }

        ad = adService.save(ad);
    }

    private AdCategory processCategory(ImportedAdCategory importedCategory,
            final Map<ImportedAdCategory, AdCategory> importedCategories) {

        // Lookup in the map the already inserted category
        importedCategory = fetchService.fetch(importedCategory, ImportedAdCategory.Relationships.EXISTING_PARENT,
                ImportedAdCategory.Relationships.IMPORTED_PARENT);
        AdCategory category = importedCategories.get(importedCategory);
        if (category == null) {
            // Resolve the parent first
            AdCategory existingParent = importedCategory.getExistingParent();
            final ImportedAdCategory importedParent = importedCategory.getImportedParent();
            if (existingParent == null && importedParent != null) {
                existingParent = processCategory(importedParent, importedCategories);
            }

            // The first time this category is being used. Insert it
            category = new AdCategory();
            category.setParent(existingParent);
            category.setActive(true);
            category.setName(importedCategory.getName());
            category = fetchService.fetch(adCategoryService.save(category));
            importedCategories.put(importedCategory, category);
        }
        return category;
    }
}