001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thierry Delprat
018 */
019package org.nuxeo.apidoc.search;
020
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.commons.text.StrBuilder;
028import org.nuxeo.apidoc.adapters.BaseNuxeoArtifactDocAdapter;
029import org.nuxeo.apidoc.adapters.BundleGroupDocAdapter;
030import org.nuxeo.apidoc.adapters.BundleInfoDocAdapter;
031import org.nuxeo.apidoc.adapters.ComponentInfoDocAdapter;
032import org.nuxeo.apidoc.adapters.ExtensionInfoDocAdapter;
033import org.nuxeo.apidoc.adapters.ExtensionPointInfoDocAdapter;
034import org.nuxeo.apidoc.adapters.ServiceInfoDocAdapter;
035import org.nuxeo.apidoc.api.BundleGroup;
036import org.nuxeo.apidoc.api.BundleInfo;
037import org.nuxeo.apidoc.api.ComponentInfo;
038import org.nuxeo.apidoc.api.DocumentationItem;
039import org.nuxeo.apidoc.api.ExtensionInfo;
040import org.nuxeo.apidoc.api.ExtensionPointInfo;
041import org.nuxeo.apidoc.api.NuxeoArtifact;
042import org.nuxeo.apidoc.api.QueryHelper;
043import org.nuxeo.apidoc.api.ServiceInfo;
044import org.nuxeo.apidoc.repository.RepositoryDistributionSnapshot;
045import org.nuxeo.apidoc.snapshot.DistributionSnapshot;
046import org.nuxeo.apidoc.snapshot.SnapshotManager;
047import org.nuxeo.ecm.core.api.CoreSession;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentModelList;
050import org.nuxeo.ecm.core.query.sql.NXQL;
051import org.nuxeo.elasticsearch.api.ElasticSearchService;
052import org.nuxeo.elasticsearch.query.NxQueryBuilder;
053import org.nuxeo.runtime.api.Framework;
054
055public class ArtifactSearcherImpl implements ArtifactSearcher {
056
057    protected static final int MAX_RESULTS = 1000;
058
059    protected NuxeoArtifact mapDoc2Artifact(DocumentModel doc) {
060        NuxeoArtifact artifact = null;
061
062        switch (doc.getType()) {
063            case BundleGroup.TYPE_NAME:
064                artifact = new BundleGroupDocAdapter(doc);
065                break;
066            case BundleInfo.TYPE_NAME:
067                artifact = new BundleInfoDocAdapter(doc);
068                break;
069            case ComponentInfo.TYPE_NAME:
070                artifact = new ComponentInfoDocAdapter(doc);
071                break;
072            case ExtensionPointInfo.TYPE_NAME:
073                artifact = new ExtensionPointInfoDocAdapter(doc);
074                break;
075            case ExtensionInfo.TYPE_NAME:
076                artifact = new ExtensionInfoDocAdapter(doc);
077                break;
078            case DistributionSnapshot.TYPE_NAME:
079                artifact = new RepositoryDistributionSnapshot(doc);
080                break;
081            case ServiceInfo.TYPE_NAME:
082                artifact = new ServiceInfoDocAdapter(doc);
083                break;
084        }
085
086        return artifact;
087    }
088
089    @Override
090    public List<NuxeoArtifact> searchArtifact(CoreSession session, String distribId, String fulltext) {
091        List<NuxeoArtifact> result = new ArrayList<>();
092
093        DistributionSnapshot snap = Framework.getService(SnapshotManager.class).getSnapshot(distribId, session);
094        if (!(snap instanceof RepositoryDistributionSnapshot)) {
095            return Collections.emptyList();
096        }
097
098        DocumentModel dist = ((RepositoryDistributionSnapshot) snap).getDoc();
099        StrBuilder q = new StrBuilder("SELECT * FROM Document WHERE ");
100        q.append("ecm:path STARTSWITH '").append(dist.getPathAsString()).append("'");
101        String query = q.toString();
102        if (fulltext != null) {
103            query += " AND " + NXQL.ECM_FULLTEXT + " = " + NXQL.escapeString(fulltext);
104        }
105
106        ElasticSearchService ess = Framework.getService(ElasticSearchService.class);
107        DocumentModelList docs = ess.query(new NxQueryBuilder(session).nxql(query).limit(MAX_RESULTS));
108        for (DocumentModel doc : docs) {
109            NuxeoArtifact artifact = mapDoc2Artifact(doc);
110            if (artifact != null) {
111                result.add(artifact);
112            }
113        }
114        return result;
115    }
116
117    @Override
118    public List<DocumentationItem> searchDocumentation(CoreSession session, String distribId, String fulltext,
119            String targetType) {
120        DistributionSnapshot snap = Framework.getService(SnapshotManager.class).getSnapshot(distribId, session);
121        DocumentModel dist = ((RepositoryDistributionSnapshot) snap).getDoc();
122        String query = QueryHelper.select(DocumentationItem.TYPE_NAME, dist, NXQL.ECM_FULLTEXT, fulltext);
123        if (targetType != null) {
124            query += " AND " + DocumentationItem.PROP_TARGET_TYPE + " = " + NXQL.escapeString(targetType);
125        }
126
127        ElasticSearchService ess = Framework.getService(ElasticSearchService.class);
128        DocumentModelList docs = ess.query(new NxQueryBuilder(session).nxql(query).limit(MAX_RESULTS));
129        List<DocumentationItem> result = new ArrayList<>();
130        for (DocumentModel doc : docs) {
131            DocumentationItem docItem = doc.getAdapter(DocumentationItem.class);
132            if (docItem != null) {
133                result.add(docItem);
134            }
135        }
136        return result;
137    }
138
139    @Override
140    public List<NuxeoArtifact> filterArtifact(CoreSession session, String distribId, String type, String fulltext) {
141        List<NuxeoArtifact> result = new ArrayList<>();
142
143        List<NuxeoArtifact> matchingArtifacts = searchArtifact(session, distribId, fulltext);
144        List<DocumentationItem> matchingDocumentationItems = searchDocumentation(session, distribId, fulltext, null);
145
146        Map<String, ArtifactWithWeight> sortMap = new HashMap<>();
147
148        for (NuxeoArtifact matchingArtifact : matchingArtifacts) {
149            ArtifactWithWeight artifactWithWeight;
150            NuxeoArtifact matchingParentArtifact = resolveInTree(session, distribId, matchingArtifact, type);
151            if (matchingParentArtifact != null) {
152                artifactWithWeight = new ArtifactWithWeight(matchingParentArtifact);
153            } else if (matchingArtifact.getArtifactType().equals(type)) {
154                artifactWithWeight = new ArtifactWithWeight(matchingArtifact);
155            } else {
156                continue;
157            }
158
159            String id = artifactWithWeight.getArtifact().getId();
160            if (sortMap.containsKey(id)) {
161                sortMap.get(id).addHit();
162            } else {
163                sortMap.put(id, new ArtifactWithWeight(matchingParentArtifact));
164            }
165        }
166
167        for (DocumentationItem matchingDocumentationItem : matchingDocumentationItems) {
168            NuxeoArtifact resultArtifact = resolveInTree(session, distribId, matchingDocumentationItem, type);
169            if (resultArtifact != null) {
170                if (sortMap.containsKey(resultArtifact.getId())) {
171                    sortMap.get(resultArtifact.getId()).addHit();
172                } else {
173                    sortMap.put(resultArtifact.getId(), new ArtifactWithWeight(resultArtifact));
174                }
175            }
176        }
177
178        List<ArtifactWithWeight> artifacts = new ArrayList<>(sortMap.values());
179        Collections.sort(artifacts);
180
181        for (ArtifactWithWeight item : artifacts) {
182            result.add(item.getArtifact());
183        }
184        return result;
185    }
186
187    protected NuxeoArtifact resolveInTree(CoreSession session, String distribId, NuxeoArtifact matchingArtifact,
188            String searchedType) {
189        String cType = matchingArtifact.getArtifactType();
190        if (cType.equals(searchedType)) {
191            return matchingArtifact;
192        }
193        BaseNuxeoArtifactDocAdapter docAdapter = (BaseNuxeoArtifactDocAdapter) matchingArtifact;
194        DocumentModel doc = docAdapter.getDoc();
195        List<DocumentModel> parents = session.getParentDocuments(doc.getRef());
196        Collections.reverse(parents);
197        for (DocumentModel parent : parents) {
198            if (parent.getType().equals(searchedType)) {
199                return mapDoc2Artifact(parent);
200            }
201        }
202        return null;
203    }
204
205    protected NuxeoArtifact resolveInTree(CoreSession session, String distribId,
206            DocumentationItem matchingDocumentationItem, String searchedType) {
207        DistributionSnapshot snap = Framework.getService(SnapshotManager.class).getSnapshot(distribId, session);
208        String targetId = matchingDocumentationItem.getTarget();
209        String targetType = matchingDocumentationItem.getTargetType();
210        NuxeoArtifact artifact;
211        switch (targetType) {
212            case BundleGroup.TYPE_NAME:
213                artifact = snap.getBundleGroup(targetId);
214                break;
215            case BundleInfo.TYPE_NAME:
216                artifact = snap.getBundle(targetId);
217                break;
218            case ComponentInfo.TYPE_NAME:
219                artifact = snap.getComponent(targetId);
220                break;
221            case ExtensionPointInfo.TYPE_NAME:
222                artifact = snap.getExtensionPoint(targetId);
223                break;
224            case ExtensionInfo.TYPE_NAME:
225                artifact = snap.getContribution(targetId);
226                break;
227            case ServiceInfo.TYPE_NAME:
228                artifact = snap.getService(targetId);
229                break;
230            default:
231                return null;
232        }
233        return resolveInTree(session, distribId, artifact, searchedType);
234    }
235
236}