de.iteratec.iteraplan.businesslogic.service.SearchServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.service.SearchServiceImpl.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * This program 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 Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.businesslogic.service;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanQuery.TooManyClauses;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.util.Version;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.reader.ReaderProvider;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;

import de.iteratec.iteraplan.common.Constants;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.MessageAccess;
import de.iteratec.iteraplan.common.UserContext;
import de.iteratec.iteraplan.common.UserContext.Permissions;
import de.iteratec.iteraplan.common.error.IteraplanBusinessException;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.common.util.IteraplanProperties;
import de.iteratec.iteraplan.model.AbstractHierarchicalEntity;
import de.iteratec.iteraplan.model.BuildingBlock;
import de.iteratec.iteraplan.model.InformationSystem;
import de.iteratec.iteraplan.model.Isr2BoAssociation;
import de.iteratec.iteraplan.model.Tcr2IeAssociation;
import de.iteratec.iteraplan.model.TechnicalComponent;
import de.iteratec.iteraplan.model.TypeOfBuildingBlock;
import de.iteratec.iteraplan.model.dto.SearchDTO;
import de.iteratec.iteraplan.model.dto.SearchRowDTO;
import de.iteratec.iteraplan.model.user.TypeOfFunctionalPermission;
import de.iteratec.iteraplan.persistence.dao.GeneralBuildingBlockDAO;
import de.iteratec.iteraplan.persistence.dao.SearchDAO;
import de.iteratec.iteraplan.presentation.dialog.Search.SearchDialogMemory;

/**
 * Provides methods for search.
 */
public class SearchServiceImpl implements SearchService {

    private static final Logger LOGGER = Logger.getIteraplanLogger(SearchServiceImpl.class);

    // number of elements that should be displayed before / after the found word
    private static final int MAX_NUM_FRAGMENTS_REQUIRED = 5;

    // ellipsis followed by separator element (e.g. "..." [block element]). configured via css
    private static final String ELLIPSIS_WITH_SEPARATOR = "... <span class=\"foundinseparator\"></span>";

    private SearchDAO searchDAO;
    private GeneralBuildingBlockDAO buildingBlockDAO;

    private static final TypeOfFunctionalPermission[] APPLICABLE_PERMISSIONS_LISTS = {
            TypeOfFunctionalPermission.ARCHITECTURALDOMAIN, TypeOfFunctionalPermission.BUSINESSDOMAIN,
            TypeOfFunctionalPermission.BUSINESSFUNCTION, TypeOfFunctionalPermission.BUSINESSOBJECT,
            TypeOfFunctionalPermission.BUSINESSPROCESS, TypeOfFunctionalPermission.BUSINESSUNIT,
            TypeOfFunctionalPermission.INFORMATIONSYSTEMDOMAIN,
            TypeOfFunctionalPermission.INFORMATIONSYSTEMINTERFACE,
            TypeOfFunctionalPermission.INFORMATIONSYSTEMRELEASE, TypeOfFunctionalPermission.INFRASTRUCTUREELEMENT,
            TypeOfFunctionalPermission.PRODUCT, TypeOfFunctionalPermission.PROJECT,
            TypeOfFunctionalPermission.TECHNICALCOMPONENTRELEASES };

    private static final String NAME_MSG_KEY = "global.name";
    private static final String VERSION_MSG_KEY = "global.version";
    private static final String DESCRIPTION_MSG_KEY = "global.description";

    /**
     * Maps indexes of the search result array to applicable message keys
     * used in {@link #setFoundIn(SearchRowDTO, Object[], Highlighter)}
     */
    private static final Map<Integer, String> LINE_INDEX_TO_MSG_KEY = new ImmutableMap.Builder<Integer, String>()
            .put(Integer.valueOf(3), NAME_MSG_KEY).put(Integer.valueOf(4), NAME_MSG_KEY)
            .put(Integer.valueOf(5), NAME_MSG_KEY).put(Integer.valueOf(10), NAME_MSG_KEY)
            .put(Integer.valueOf(12), NAME_MSG_KEY).put(Integer.valueOf(6), VERSION_MSG_KEY)
            .put(Integer.valueOf(11), VERSION_MSG_KEY).put(Integer.valueOf(13), VERSION_MSG_KEY)
            .put(Integer.valueOf(7), DESCRIPTION_MSG_KEY).put(Integer.valueOf(8), "search.resultsPeriod_from")
            .put(Integer.valueOf(9), "search.resultsPeriod_to").build();

    /** {@inheritDoc} */
    public List<BuildingBlock> getSearchBuildingBlocks(TypeOfBuildingBlock buildingBlockType, String searchField) {
        List<BuildingBlock> result = Collections.emptyList();

        // setup search criteria
        SearchDialogMemory searchDialogMemory = new SearchDialogMemory();
        searchDialogMemory.setSearchField(searchField);
        searchDialogMemory.setBuildingBlockTypeFilter(buildingBlockType.getValue());

        // global search
        SearchDTO searchDTO = this.getSearchDTO(searchDialogMemory);

        if (searchDTO != null) {
            Map<String, Collection<SearchRowDTO>> searchMap = searchDTO.getSearchMap();
            if (searchMap != null && searchMap.get(buildingBlockType.getValue()) != null) {
                Collection<SearchRowDTO> rowDTOs = searchMap.get(buildingBlockType.getValue());
                Collection<Integer> ids = new HashSet<Integer>();
                for (SearchRowDTO searchRowDTO : rowDTOs) {
                    ids.add(searchRowDTO.getId());
                }

                result = buildingBlockDAO.loadBuildingBlocks(ids, buildingBlockType.getAssociatedClass());
            }
        }

        return result;
    }

    /** {@inheritDoc} */
    public SearchDTO getSearchDTO(SearchDialogMemory searchDialogMemory) {
        UserContext.getCurrentPerms().assureFunctionalPermission(TypeOfFunctionalPermission.SEARCH);

        String searchField = searchDialogMemory.getSearchField();

        SearchDTO dto = new SearchDTO();

        if (StringUtils.isEmpty(searchField)) {
            return dto;
        }

        // Use an ArrayListMultimap, to ensure the order of the results is preserved.
        Multimap<String, SearchRowDTO> searchMap = ArrayListMultimap.create();

        // Modify the queryString
        String modQueryString = modifyQueryString(searchField);

        // execute the search and put the results into the searchMap
        executeSearchQuery(searchMap, modQueryString, searchDialogMemory.getBuildingBlockTypeFilter());

        dto.setSearchMultiMap(searchMap);

        // if no results were found, and the string is not surrounded by '*' but only one word
        if (searchMap.size() == 0 && isQueryStringModifiable(modQueryString)) {
            Multimap<String, SearchRowDTO> altSearchMap = ArrayListMultimap.create();
            String altQueryString = modifyQueryString(modQueryString);
            executeSearchQuery(altSearchMap, altQueryString, searchDialogMemory.getBuildingBlockTypeFilter());
            dto.setNumberOfAlternativeResults(altSearchMap.size());
            dto.setAlternativeQueryString(altQueryString);
        }

        dto.setSearchMultiMap(searchMap);
        return dto;

    }

    private String modifyQueryString(String queryString) {
        StringBuilder modQueryString = new StringBuilder(queryString);
        // if whitespace included, don't modify
        if (!queryString.contains(" ")) {

            // if the String contains quotes, remove them if they are at the beginning or end
            if (queryString.contains("\"")) {
                if (queryString.endsWith("\"")) {
                    modQueryString.deleteCharAt(queryString.length() - 1);
                }
                if (queryString.charAt(0) == '"') {
                    modQueryString.deleteCharAt(0);
                }
                // no further modification, return the string
                return modQueryString.toString();
            }

            // if the String contains no "*" surround it with "*"
            if (!queryString.contains("*")) {
                modQueryString.insert(0, '*');
                modQueryString.append('*');
            }
        }
        return modQueryString.toString();
    }

    /**
     * Checks if the queryString is in a form that could be surrounded by '*'
     * 
     * @param queryString
     * @return true, if the queryString contains no whitespaces and doesn't start or end with '*'
     */
    private boolean isQueryStringModifiable(String queryString) {
        return (!queryString.contains(" ") && (queryString.charAt(0) != '*' || !queryString.endsWith("*")));
    }

    /**
     * Execute the hibernate search
     * 
     * @param searchMap
     *          Multimap of SearhRowDTOs contains the results
     * @param queryString
     *          the query string which the user entered
     */
    private void executeSearchQuery(Multimap<String, SearchRowDTO> searchMap, String queryString,
            String buildingBlockTypeFilter) {
        // reader provider is required to close the reader after search has finished
        ReaderProvider readerProvider = searchDAO.getReaderProvider();
        IndexReader reader = searchDAO.openReader(readerProvider, getClassArray());

        // if the reader is null (i.e. the user has no functional permissions to search any one of the
        // building blocks) return without executing a search
        if (reader == null) {
            return;
        }

        try {
            // index fields that will be searched
            String[] productFields = { "attributeValueAssignments.attributeValue.valueString", "name", "version",
                    "description", "informationSystem.name", "technicalComponent.name", "runtimePeriod.start",
                    "runtimePeriod.end", "informationSystemReleaseA.informationSystem.name",
                    "informationSystemReleaseA.version", "informationSystemReleaseB.informationSystem.name",
                    "informationSystemReleaseB.version" };

            QueryParser parser = new MultiFieldQueryParser(Version.LUCENE_31, productFields,
                    new StandardAnalyzer(Version.LUCENE_31));

            // allow wildcard * at the beginning of a query string
            parser.setAllowLeadingWildcard(true);
            // automatically put quotes around the search term for phrase search, because that's what most people expect
            parser.setAutoGeneratePhraseQueries(true);
            // workaround for known issue with highlighter and wildcard queries
            parser.setMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);

            Query luceneQuery = null;
            try {
                // parse the query string
                luceneQuery = parser.parse(queryString);

                // rewrite luceneQuery for highlighting
                luceneQuery = luceneQuery.rewrite(reader);

            } catch (TooManyClauses tmcEx) {
                throw new IteraplanBusinessException(IteraplanErrorMessages.LUCENE_QUERY_TOO_COMPLEX, tmcEx);
            } catch (ParseException e) {
                throw new IteraplanBusinessException(IteraplanErrorMessages.LUCENE_QUERY_PARSE_FAILED, e);
            } catch (IOException e) {
                throw new IteraplanTechnicalException(IteraplanErrorMessages.LUCENE_QUERY_REWRITE_FAILED, e);
            }

            // the found content is being highlighted
            SimpleHTMLFormatter sHtmlF = new SimpleHTMLFormatter("<span class=\"highlighted\">", "</span>");
            Highlighter highlighter = new Highlighter(sHtmlF, new QueryScorer(luceneQuery));
            highlighter.setTextFragmenter(new SimpleFragmenter(40));

            // sorted by Building block types
            SortField sortField = new SortField("buildingBlockType.name", SortField.STRING);
            Sort sort = new Sort(sortField);

            String[] projections = { "id", "buildingBlockType.name", FullTextQuery.DOCUMENT, "name",
                    "informationSystem.name", "technicalComponent.name", "version", "description",
                    "runtimePeriod.start", "runtimePeriod.end", "informationSystemReleaseA.informationSystem.name",
                    "informationSystemReleaseA.version", "informationSystemReleaseB.informationSystem.name",
                    "informationSystemReleaseB.version" };

            List<Object[]> results = searchDAO.search(luceneQuery, sort, getClassArray(), projections);

            addResultsToSearchMap(searchMap, results, buildingBlockTypeFilter, highlighter);
        } finally {
            readerProvider.closeReader(reader);
        }

    }

    /**
     * Adds the results to the searchMap.
     * 
     * @param searchMap
     * @param results
     */
    private void addResultsToSearchMap(Multimap<String, SearchRowDTO> searchMap, List<Object[]> results,
            String buildingBlockTypeFilter, Highlighter highlighter) {

        for (Object[] line : results) {
            // create a new SearchRowDTO
            SearchRowDTO container = new SearchRowDTO();

            // retrieve the attributeStringForSearchIndexing from the index document
            // must be identical with the method name in @see{BuildingBlock}
            Document doc = (Document) line[2];

            // skip this item and don't add it to the result if the indexed datasource is not equal
            // to the current datasource. If the indexed datasource is null, the item will be added.
            if (doc.getField("activeDataSource") != null
                    && !UserContext.getActiveDatasource().equals(doc.getField("activeDataSource").stringValue())) {
                continue;
            }
            // save the id in the SearchRowDTO
            if (line[0] == null) {
                LOGGER.warn(
                        "Found Entity without id in the searchindex. Skipping this entity. Index might be corrupt.");
                continue;
            } else {
                container.setId((Integer) line[0]);
            }
            // Save the BuildingblockType in the SearchRowDTO
            if (!saveBBTypeToDTO(buildingBlockTypeFilter, (String) line[1], container)) {
                continue;
            }

            line[2] = getAttributeStringFieldValue(doc);

            saveNameToDTO((String) line[3], (String) line[4], (String) line[5], container);

            // if filterVirtualElements is set to true and the current element is a virtual element, don't
            // add it to the search results
            if (IteraplanProperties.getBooleanProperty(Constants.HIBERNATE_SEARCH_FILTER_VIRTUAL_ELEMENTS)
                    && AbstractHierarchicalEntity.TOP_LEVEL_NAME.equals(container.getName())) {
                continue;
            }

            saveVersionToDTO(container, (String) line[6]);

            // save description in the SearchRowDTO
            container.setDescription((String) line[7]);

            saveNameOfISIsToDTO(line, container);

            setFoundIn(container, line, highlighter);

            // add SearchRowDTO to map
            searchMap.put(container.getBuildingBlockType(), container);
        }

    }

    /**
     * Saves "Name" of the InformationSystemInterface (name of connected ISRs) (if result is an ISI)
     * @param line
     *          search result
     * @param container
     *          DTO to save the name in
     */
    private void saveNameOfISIsToDTO(Object[] line, SearchRowDTO container) {
        // check if line represents an InformationSystemInterface (line[10] = IS A, line[12] = IS B)
        if (!StringUtils.isEmpty((String) line[10]) && !StringUtils.isEmpty((String) line[12])) {
            StringBuffer buf = new StringBuffer();
            // IS A
            buf.append(line[10]);
            // Release of IS A
            if (!StringUtils.isEmpty((String) line[11])) {
                buf.append("# " + line[11]);
            }
            // IS B
            buf.append(" <-> " + line[12]);
            // Release of IS B
            if (!StringUtils.isEmpty((String) line[13])) {
                buf.append("# " + line[13]);
            }
            container.setName(buf.toString());
        }
    }

    /**
     * Saves version in the SearchRowDTO
     * @param container
     *          DTO to save the version in
     * @param version
     *          String representing the version
     */
    private void saveVersionToDTO(SearchRowDTO container, String version) {
        if (version == null) {
            container.setVersion(null);
        } else if (!StringUtils.isEmpty(version)) {
            container.setVersion("# " + version);
        }
    }

    /**
     * Saves name in SearchRowDTO (either Name, InformationSystem.Name or TechnicalComponent.Name will be used)
     * @param name
     *          Name
     * @param isName
     *          InformationSystem.Name
     * @param tcName
     *          TechnicalComponent.Name
     * @param container
     *          DTO to save the name in
     */
    private void saveNameToDTO(String name, String isName, String tcName, SearchRowDTO container) {
        if (!StringUtils.isEmpty(name)) {
            // Name
            container.setName(name);
        } else if (!StringUtils.isEmpty(isName)) {
            // Name of the information system
            container.setName(isName);
        } else if (!StringUtils.isEmpty(tcName)) {
            // Name of the TechnicalComponent
            container.setName(tcName);
        }
    }

    private String getAttributeStringFieldValue(Document doc) {
        Field attributeStringField = doc.getField("attributeStringForSearchIndexing");
        if (attributeStringField != null) {
            return attributeStringField.stringValue();
        } else {
            return "";
        }
    }

    /**
     * Saves the BuildingblockType in the SearchRowDTO
     * @param buildingBlockTypeFilter
     *          Filter-BBType to allow only this BuildingBlockType to be saved. If empty, all BBTypes are allowed.
     * @param bbtString
     *          String representing the BuildingBlockType
     * @param container
     *          DTO to save the BuildingBlockType in
     * @return true if successful, false otherwise
     */
    private boolean saveBBTypeToDTO(String buildingBlockTypeFilter, String bbtString, SearchRowDTO container) {
        if (StringUtils.isEmpty(bbtString)) {
            LOGGER.warn(
                    "Found Entity without buildingBlockType in the searchindex. Skipping this entity. Index might be corrupt.");
        } else if (!StringUtils.isEmpty(buildingBlockTypeFilter) && !bbtString.equals(buildingBlockTypeFilter)) {
            LOGGER.info(String.format("Skipping this buildingBlockType due to filtering only '%s' building blocks",
                    buildingBlockTypeFilter));
        } else {
            container.setBuildingBlockType(bbtString);
            return true;
        }
        return false;
    }

    /**
     * Adds the information where the search string was found to the SearchRowDTO.
     * 
     * @param container
     * @param line
     * @param highlighter
     *          A match highlighter that places markers at the matched words in the object
     */
    private void setFoundIn(SearchRowDTO container, Object[] line, Highlighter highlighter) {
        // retrieve the locale of the user
        Locale locale = UserContext.getCurrentLocale();
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_31);

        List<String> foundIn = new ArrayList<String>();
        for (int m = 2; m < line.length; m++) {

            if (line[m] != null) {
                String text = line[m].toString();

                if (!StringUtils.isEmpty(text)) {
                    if (m != 2) {
                        String foundInHighlighted = getFoundInHighlightedString(highlighter, analyzer, text);
                        if (foundInHighlighted != null && foundInHighlighted.compareTo("") != 0) {
                            foundIn.add(MessageAccess.getStringOrNull(LINE_INDEX_TO_MSG_KEY.get(Integer.valueOf(m)),
                                    locale) + ": " + foundInHighlighted);
                        }
                    } // If found in attributeString
                    else {
                        String[] splittedText = text.split(Pattern.quote("^^^"));
                        for (int n = 0; n < splittedText.length; n++) {
                            String foundInHighlighted = getFoundInHighlightedString(highlighter, analyzer,
                                    splittedText[n]);
                            if (foundInHighlighted != null && foundInHighlighted.compareTo("") != 0) {
                                foundIn.add(foundInHighlighted);
                            }
                        }
                    }
                }
                container.setFoundIn(foundIn);
            }
        }
    }

    private String getFoundInHighlightedString(Highlighter highlighter, Analyzer analyzer, String text) {
        String foundInHighlighted = null;
        // create a token stream from the text
        TokenStream tokenStream = analyzer.tokenStream("", new StringReader(text));
        try {
            // mark the found queryString in the token stream
            foundInHighlighted = highlighter.getBestFragments(tokenStream, text, MAX_NUM_FRAGMENTS_REQUIRED,
                    ELLIPSIS_WITH_SEPARATOR);
        } catch (IOException e) {
            throw new IteraplanTechnicalException(e);
        } catch (InvalidTokenOffsetsException e) {
            throw new IteraplanTechnicalException(e);
        }
        return foundInHighlighted;
    }

    /**
     * Retrieves the folders to search in, depending on the users rights
     */
    private Class<?>[] getClassArray() {
        Permissions userPermChecker = UserContext.getCurrentPerms();
        // list with entity classes to search in
        Set<Class<?>> classlist = new HashSet<Class<?>>();

        // iterate over all relevant functional permissions and collect the respective
        // building block class, if the user has the acces permission
        for (TypeOfFunctionalPermission funcPerm : APPLICABLE_PERMISSIONS_LISTS) {
            if (userPermChecker.userHasFunctionalPermission(funcPerm)) {
                classlist.add(TypeOfFunctionalPermission.PERMISSION_TO_CLASS_MAP.get(funcPerm));
            }
        }

        // contents of the class list is saved in an array, as the method createFullTextQuery only
        // accepts class arrays.
        return classlist.toArray(new Class<?>[classlist.size()]);
    }

    /**
     * {@inheritDoc}
     */
    public void createIndex(PurgeMode purge) {
        LOGGER.info("Creating New Index");

        HashSet<Class<?>> classList = new HashSet<Class<?>>();

        // iterate over all relevant functional permissions and collect the corresponding
        // building block class
        for (TypeOfFunctionalPermission funcPerm : APPLICABLE_PERMISSIONS_LISTS) {
            classList.add(TypeOfFunctionalPermission.PERMISSION_TO_CLASS_MAP.get(funcPerm));
        }
        // add classes which are not covered by functional permissions
        classList.add(InformationSystem.class);
        classList.add(TechnicalComponent.class);
        classList.add(Tcr2IeAssociation.class);
        classList.add(Isr2BoAssociation.class);

        if (PurgeMode.PURGE.equals(purge)) {
            searchDAO.purgeIndexes(classList);
        }

        searchDAO.createIndexes(classList);
        LOGGER.info("Finished Indexing");
    }

    public void setSearchDAO(SearchDAO searchDAO) {
        this.searchDAO = searchDAO;
    }

    public SearchDAO getSearchDAO() {
        return searchDAO;
    }

    public void setBuildingBlockDAO(GeneralBuildingBlockDAO buildingBlockDAO) {
        this.buildingBlockDAO = buildingBlockDAO;
    }
}