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                             String rootPath = jcrContentConnectorDefinition.getRootPath();
126                             if (StringUtils.isBlank(workspaceName)) {
127                                 continue;
128                             }
129                             Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
130                                     .map(NodeTypeDefinition::getName)
131                                     .collect(Collectors.toSet());
132                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, rootPath, nodeTypes));
133                         }
134                     }
135                 }
136             }
137         }
138 
139         return Optional.empty();
140     }
141 
142     /**
143      * Iterates over given configuration {@link #appDetails} if there is a match, returns it if a corresponding app
144      * exists, an empty optional otherwise.
145      *
146      * Otherwise, searches across {@link AppDescriptorRegistry} provides and finds the first matching {@link AppDescriptor}
147      * which matches the given {@code #workspace}.
148      */
149     public Optional<AppDetail> createAppDetailForWorkspace(String workspace) {
150         //TODO: this shouldn't be necessary once we have configuration of find-bar.
151         if (appDetails != null) {
152             Optional<AppDetail> matchingAppDetail = appDetails.values().stream()
153                     .filter(appDetail -> appDetail.getWorkspace().equalsIgnoreCase(workspace))
154                     .filter(this::appIsValid)
155                     .findFirst();
156             if (matchingAppDetail.isPresent()) {
157                 return matchingAppDetail;
158             }
159         }
160 
161         Collection<DefinitionProvider<AppDescriptor>> providers = appDescriptorRegistry.getAllProviders();
162         for (DefinitionProvider<AppDescriptor> provider : providers) {
163             if (!provider.isValid()) {
164                 continue;
165             }
166             final AppDescriptor descriptor = provider.get();
167             Collection<SubAppDescriptor> subAppDescriptors = descriptor.getSubApps().values();
168 
169             for (SubAppDescriptor subAppDescriptor : subAppDescriptors) {
170                 if (subAppDescriptor instanceof ContentSubAppDescriptor) {
171                     ContentConnectorDefinition contentConnector = ((ContentSubAppDescriptor) subAppDescriptor).getContentConnector();
172                     if (contentConnector instanceof JcrContentConnectorDefinition) {
173                         JcrContentConnectorDefinition jcrContentConnectorDefinition = (JcrContentConnectorDefinition) contentConnector;
174                         String workspaceName = jcrContentConnectorDefinition.getWorkspace();
175                         String rootPath = jcrContentConnectorDefinition.getRootPath();
176                         Set<String> nodeTypes = jcrContentConnectorDefinition.getNodeTypes().stream()
177                                 .map(NodeTypeDefinition::getName)
178                                 .collect(Collectors.toSet());
179                         if (StringUtils.isNotBlank(workspaceName) && workspace.equals(workspaceName)) {
180                             final String subAppName = descriptor.getSubApps().keySet().iterator().next();
181                             return Optional.of(new AppDetail(descriptor.getName(), subAppName, workspaceName, rootPath, nodeTypes));
182                         }
183                     }
184                 }
185             }
186         }
187 
188         return Optional.empty();
189     }
190 
191     private boolean appIsValid(AppDetail appDetail) {
192         try {
193             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appDetail.getAppName());
194             return provider.isValid();
195         } catch (Registry.NoSuchDefinitionException e) {
196             return false;
197         }
198     }
199 
200     private Optional<AppDescriptor> getDescriptor(String appName) {
201         try {
202             DefinitionProvider<AppDescriptor> provider = appDescriptorRegistry.getProvider(appName);
203             return provider.isValid() ? Optional.of(provider.get()) : Optional.empty();
204         } catch (Registry.NoSuchDefinitionException e) {
205             return Optional.empty();
206         }
207     }
208 
209     /**
210      * Configuration bean for app detail information, mapping apps to workspaces and node-types.
211      * <p>e.g. appName = 'assets', subAppName = 'edit', and workspace = 'dam'
212      */
213     @AllArgsConstructor
214     @Setter
215     @Getter
216     public static class AppDetail {
217         private String appName;
218         private String subAppName;
219         private String workspace;
220         private String rootPath;
221         private Set<String> nodeTypes;
222     }
223 }