View Javadoc
1   /**
2    * This file Copyright (c) 2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.admincentral.findbar;
35  
36  import info.magnolia.config.registry.DefinitionProvider;
37  import info.magnolia.config.registry.Registry;
38  import info.magnolia.ui.api.app.AppDescriptor;
39  import info.magnolia.ui.api.app.SubAppDescriptor;
40  import info.magnolia.ui.api.app.registry.AppDescriptorRegistry;
41  import info.magnolia.ui.contentapp.definition.ContentSubAppDescriptor;
42  import info.magnolia.ui.vaadin.integration.contentconnector.ContentConnectorDefinition;
43  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinition;
44  import info.magnolia.ui.vaadin.integration.contentconnector.NodeTypeDefinition;
45  
46  import java.util.Collection;
47  import java.util.Map;
48  import java.util.Optional;
49  import java.util.Set;
50  import java.util.stream.Collectors;
51  
52  import javax.inject.Inject;
53  import javax.inject.Singleton;
54  
55  import org.apache.commons.lang3.StringUtils;
56  import org.atteo.evo.inflector.English;
57  
58  import lombok.AllArgsConstructor;
59  import lombok.Getter;
60  import lombok.Setter;
61  
62  /**
63   * Responsible to provide {@linkplain AppDetail mappings} between workspace and app names.
64   *
65   * <p>First checks if there is a mapping configured for given app name or workspace.
66   * Otherwise, it looks up into the {@link AppDescriptorRegistry} and infers mappings based on typical content-app configuration ({@linkplain JcrContentConnectorDefinition JCR content connector}).
67   *
68   * @deprecated since 6.2. Suppliers config should support app configuration (see {@link info.magnolia.periscope.search.jcr.JcrSearchResultSupplierDefinition#appName})
69   * Otherwise there's a risk of ambiguous situations, e.g. when several apps are configured to work with one workspace.
70   */
71  @Deprecated
72  @Singleton
73  public class PeriscopeAppDetailCreator {
74  
75      private final AppDescriptorRegistry appDescriptorRegistry;
76  
77      private final Map<String, AppDetail> appDetails;
78  
79      @Inject
80      PeriscopeAppDetailCreator(AppDescriptorRegistry appDescriptorRegistry, PeriscopeConfiguration periscopeConfiguration) {
81          this.appDescriptorRegistry = appDescriptorRegistry;
82          this.appDetails = periscopeConfiguration.getAppDetails();
83      }
84  
85      /**
86       * Iterates over given configuration {@link #appDetails} if there is a match, returns it if the corresponding app
87       * exists, an empty optional otherwise.
88       *
89       * Otherwise, searches across {@link AppDescriptorRegistry} provides and finds the first matching {@link AppDescriptor}
90       * which matches the given {@code #appName}.
91       */
92      public Optional<AppDetail> createAppDetail(String appName) {
93          //TODO: this shouldn't be necessary once we have configuration of find-bar.
94          if (appDetails != null) {
95              Optional<AppDetail> matchingAppDetail = appDetails.entrySet().stream()
96                      .filter(entry -> entry.getKey().equalsIgnoreCase(appName) || English.plural(entry.getKey()).equalsIgnoreCase(appName))
97                      .map(Map.Entry::getValue)
98                      .filter(this::appIsValid)
99                      .findFirst();
100             if (matchingAppDetail.isPresent()) {
101                 return matchingAppDetail;
102             }
103         }
104 
105         Collection<DefinitionProvider<AppDescriptor>> providers = appDescriptorRegistry.getAllProviders();
106         for (DefinitionProvider<AppDescriptor> provider : providers) {
107             if (!provider.isValid()) {
108                 continue;
109             }
110 
111             final AppDescriptor descriptor = provider.get();
112             String providerName = descriptor.getName();
113             // This is required otherwise we will fuzzily match random apps
114             // e.g. appName = as will match assets app however, we can't be sure of that at this stage
115             // So simply let user to type more information about the app
116             if (providerName.length() - appName.length() > 2) {
117                 continue;
118             }
119 
120             if (providerName.startsWith(appName) || providerName.startsWith(English.plural(appName))) {
121                 Collection<SubAppDescriptor> subAppDescriptors = descriptor.getSubApps().values();
122                 for (SubAppDescriptor subAppDescriptor : subAppDescriptors) {
123                     if (subAppDescriptor instanceof ContentSubAppDescriptor) {
124                         ContentConnectorDefinition contentConnector = ((ContentSubAppDescriptor) subAppDescriptor).getContentConnector();
125                         if (contentConnector instanceof JcrContentConnectorDefinition) {
126                             String subAppName = descriptor.getSubApps().keySet().iterator().next();
127                             JcrContentConnectorDefinition jcrContentConnectorDefinition = (JcrContentConnectorDefinition) contentConnector;
128                             String workspaceName = jcrContentConnectorDefinition.getWorkspace();
129                             String rootPath = jcrContentConnectorDefinition.getRootPath();
130                             if (StringUtils.isBlank(workspaceName)) {
131                                 continue;
132                             }
133                             Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
134                                     .map(NodeTypeDefinition::getName)
135                                     .collect(Collectors.toSet());
136                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, rootPath, nodeTypes));
137                         }
138                     }
139                 }
140             }
141         }
142 
143         return Optional.empty();
144     }
145 
146     /**
147      * Iterates over given configuration {@link #appDetails} if there is a match, returns it if a corresponding app
148      * exists, an empty optional otherwise.
149      *
150      * Otherwise, searches across {@link AppDescriptorRegistry} provides and finds the first matching {@link AppDescriptor}
151      * which matches the given {@code #workspace}.
152      */
153     public Optional<AppDetail> createAppDetailForWorkspace(String workspace) {
154         //TODO: this shouldn't be necessary once we have configuration of find-bar.
155         if (appDetails != null) {
156             Optional<AppDetail> matchingAppDetail = appDetails.values().stream()
157                     .filter(appDetail -> appDetail.getWorkspace().equalsIgnoreCase(workspace))
158                     .filter(this::appIsValid)
159                     .findFirst();
160             if (matchingAppDetail.isPresent()) {
161                 return matchingAppDetail;
162             }
163         }
164 
165         Collection<DefinitionProvider<AppDescriptor>> providers = appDescriptorRegistry.getAllProviders();
166         for (DefinitionProvider<AppDescriptor> provider : providers) {
167             if (!provider.isValid()) {
168                 continue;
169             }
170             final AppDescriptor descriptor = provider.get();
171             Collection<SubAppDescriptor> subAppDescriptors = descriptor.getSubApps().values();
172 
173             for (SubAppDescriptor subAppDescriptor : subAppDescriptors) {
174                 if (subAppDescriptor instanceof ContentSubAppDescriptor) {
175                     ContentConnectorDefinition contentConnector = ((ContentSubAppDescriptor) subAppDescriptor).getContentConnector();
176                     if (contentConnector instanceof JcrContentConnectorDefinition) {
177                         JcrContentConnectorDefinition jcrContentConnectorDefinition = (JcrContentConnectorDefinition) contentConnector;
178                         String workspaceName = jcrContentConnectorDefinition.getWorkspace();
179                         String rootPath = jcrContentConnectorDefinition.getRootPath();
180                         Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
181                                 .map(NodeTypeDefinition::getName)
182                                 .collect(Collectors.toSet());
183                         if (StringUtils.isNotBlank(workspaceName) && workspace.equals(workspaceName)) {
184                             final String subAppName = descriptor.getSubApps().keySet().iterator().next();
185                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, rootPath, nodeTypes));
186                         }
187                     }
188                 }
189             }
190         }
191 
192         return Optional.empty();
193     }
194 
195     private boolean appIsValid(AppDetail appDetail) {
196         try {
197             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appDetail.getAppName());
198             return provider.isValid();
199         } catch (Registry.NoSuchDefinitionException e) {
200             return false;
201         }
202     }
203 
204     private Optional<AppDescriptor> getDescriptor(String appName) {
205         try {
206             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appName);
207             return provider.isValid() ? Optional.of(provider.get()) : Optional.empty();
208         } catch (Registry.NoSuchDefinitionException e) {
209             return Optional.empty();
210         }
211     }
212 
213     /**
214      * Configuration bean for app detail information, mapping apps to workspaces and node-types.
215      * <p>e.g. appName = 'assets', subAppName = 'edit', and workspace = 'dam'
216      */
217     @AllArgsConstructor
218     @Setter
219     @Getter
220     public static class AppDetail {
221         private String appName;
222         private String subAppName;
223         private String workspace;
224         private String rootPath;
225         private Set<String> nodeTypes;
226     }
227 }