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  @Singleton
69  public class PeriscopeAppDetailCreator {
70  
71      private final AppDescriptorRegistry appDescriptorRegistry;
72  
73      private final Map<String, AppDetail> appDetails;
74  
75      @Inject
76      PeriscopeAppDetailCreator(AppDescriptorRegistry appDescriptorRegistry, PeriscopeConfiguration periscopeConfiguration) {
77          this.appDescriptorRegistry = appDescriptorRegistry;
78          this.appDetails = periscopeConfiguration.getAppDetails();
79      }
80  
81      /**
82       * Iterates over given configuration {@link #appDetails} if there is a match, returns it if the corresponding app
83       * exists, an empty optional otherwise.
84       *
85       * Otherwise, searches across {@link AppDescriptorRegistry} provides and finds the first matching {@link AppDescriptor}
86       * which matches the given {@code #appName}.
87       */
88      public Optional<AppDetail> createAppDetail(String appName) {
89          //TODO: this shouldn't be necessary once we have configuration of find-bar.
90          if (appDetails != null) {
91              Optional<AppDetail> matchingAppDetail = appDetails.entrySet().stream()
92                      .filter(entry -> entry.getKey().equalsIgnoreCase(appName) || English.plural(entry.getKey()).equalsIgnoreCase(appName))
93                      .map(Map.Entry::getValue)
94                      .filter(this::appIsValid)
95                      .findFirst();
96              if (matchingAppDetail.isPresent()) {
97                  return matchingAppDetail;
98              }
99          }
100 
101         Collection<DefinitionProvider<AppDescriptor>> providers = appDescriptorRegistry.getAllProviders();
102         for (DefinitionProvider<AppDescriptor> provider : providers) {
103             if (!provider.isValid()) {
104                 continue;
105             }
106 
107             final AppDescriptor descriptor = provider.get();
108             String providerName = descriptor.getName();
109             // This is required otherwise we will fuzzily match random apps
110             // e.g. appName = as will match assets app however, we can't be sure of that at this stage
111             // So simply let user to type more information about the app
112             if (providerName.length() - appName.length() > 2) {
113                 continue;
114             }
115 
116             if (providerName.startsWith(appName) || providerName.startsWith(English.plural(appName))) {
117                 Collection<SubAppDescriptor> subAppDescriptors = descriptor.getSubApps().values();
118                 for (SubAppDescriptor subAppDescriptor : subAppDescriptors) {
119                     if (subAppDescriptor instanceof ContentSubAppDescriptor) {
120                         ContentConnectorDefinition contentConnector = ((ContentSubAppDescriptor) subAppDescriptor).getContentConnector();
121                         if (contentConnector instanceof JcrContentConnectorDefinition) {
122                             String subAppName = descriptor.getSubApps().keySet().iterator().next();
123                             JcrContentConnectorDefinition jcrContentConnectorDefinition = (JcrContentConnectorDefinition) contentConnector;
124                             String workspaceName = jcrContentConnectorDefinition.getWorkspace();
125                             if (StringUtils.isBlank(workspaceName)) {
126                                 continue;
127                             }
128                             Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
129                                     .map(NodeTypeDefinition::getName)
130                                     .collect(Collectors.toSet());
131                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, nodeTypes));
132                         }
133                     }
134                 }
135             }
136         }
137 
138         return Optional.empty();
139     }
140 
141     /**
142      * Iterates over given configuration {@link #appDetails} if there is a match, returns it if a corresponding app
143      * exists, an empty optional otherwise.
144      *
145      * Otherwise, searches across {@link AppDescriptorRegistry} provides and finds the first matching {@link AppDescriptor}
146      * which matches the given {@code #workspace}.
147      */
148     public Optional<AppDetail> createAppDetailForWorkspace(String workspace) {
149         //TODO: this shouldn't be necessary once we have configuration of find-bar.
150         if (appDetails != null) {
151             Optional<AppDetail> matchingAppDetail = appDetails.values().stream()
152                     .filter(appDetail -> appDetail.getWorkspace().equalsIgnoreCase(workspace))
153                     .filter(this::appIsValid)
154                     .findFirst();
155             if (matchingAppDetail.isPresent()) {
156                 return matchingAppDetail;
157             }
158         }
159 
160         Collection<DefinitionProvider<AppDescriptor>> providers = appDescriptorRegistry.getAllProviders();
161         for (DefinitionProvider<AppDescriptor> provider : providers) {
162             if (!provider.isValid()) {
163                 continue;
164             }
165             final AppDescriptor descriptor = provider.get();
166             Collection<SubAppDescriptor> subAppDescriptors = descriptor.getSubApps().values();
167 
168             for (SubAppDescriptor subAppDescriptor : subAppDescriptors) {
169                 if (subAppDescriptor instanceof ContentSubAppDescriptor) {
170                     ContentConnectorDefinition contentConnector = ((ContentSubAppDescriptor) subAppDescriptor).getContentConnector();
171                     if (contentConnector instanceof JcrContentConnectorDefinition) {
172                         JcrContentConnectorDefinition jcrContentConnectorDefinition = (JcrContentConnectorDefinition) contentConnector;
173                         String workspaceName = jcrContentConnectorDefinition.getWorkspace();
174                         Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
175                                 .map(NodeTypeDefinition::getName)
176                                 .collect(Collectors.toSet());
177                         if (StringUtils.isNotBlank(workspaceName) && workspace.equals(workspaceName)) {
178                             final String subAppName = descriptor.getSubApps().keySet().iterator().next();
179                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, nodeTypes));
180                         }
181                     }
182                 }
183             }
184         }
185 
186         return Optional.empty();
187     }
188 
189     private boolean appIsValid(AppDetail appDetail) {
190         try {
191             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appDetail.getAppName());
192             return provider.isValid();
193         } catch (Registry.NoSuchDefinitionException e) {
194             return false;
195         }
196     }
197 
198     private Optional<AppDescriptor> getDescriptor(String appName) {
199         try {
200             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appName);
201             return provider.isValid() ? Optional.of(provider.get()) : Optional.empty();
202         } catch (Registry.NoSuchDefinitionException e) {
203             return Optional.empty();
204         }
205     }
206 
207     /**
208      * Configuration bean for app detail information, mapping apps to workspaces and node-types.
209      * <p>e.g. appName = 'assets', subAppName = 'edit', and workspace = 'dam'
210      */
211     @AllArgsConstructor
212     @Setter
213     @Getter
214     public static class AppDetail {
215         private String appName;
216         private String subAppName;
217         private String workspace;
218         private Set<String> nodeTypes;
219     }
220 }