View Javadoc
1   /**
2    * This file Copyright (c) 2003-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.repository;
35  
36  import info.magnolia.audit.MgnlAuditLoggingContentDecorator;
37  import info.magnolia.audit.MgnlAuditLoggingContentDecoratorSessionWrapper;
38  import info.magnolia.cms.core.FileSystemHelper;
39  import info.magnolia.cms.core.version.MgnlVersioningSession;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.init.MagnoliaConfigurationProperties;
42  import info.magnolia.jcr.RuntimeRepositoryException;
43  import info.magnolia.jcr.wrapper.MagnoliaSessionContentDecorator;
44  import info.magnolia.jcr.predicate.AbstractPredicate;
45  import info.magnolia.jcr.util.NodeTypes;
46  import info.magnolia.jcr.util.NodeUtil;
47  import info.magnolia.jcr.wrapper.MgnlLogicalWorkspaceNameMappingWorkspaceDecorator;
48  import info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator;
49  import info.magnolia.objectfactory.Classes;
50  import info.magnolia.objectfactory.Components;
51  import info.magnolia.repository.definition.RepositoryDefinition;
52  import info.magnolia.repository.definition.RepositoryMappingDefinition;
53  import info.magnolia.repository.definition.RepositoryMappingDefinitionReader;
54  import info.magnolia.repository.definition.WorkspaceMappingDefinition;
55  import info.magnolia.repository.mbean.TrackingSessionWrapper;
56  import info.magnolia.stats.JCRStats;
57  
58  import java.io.InputStream;
59  import java.util.Arrays;
60  import java.util.Collection;
61  import java.util.LinkedHashMap;
62  import java.util.LinkedHashSet;
63  import java.util.Map;
64  import java.util.Set;
65  
66  import javax.inject.Inject;
67  import javax.inject.Singleton;
68  import javax.jcr.Credentials;
69  import javax.jcr.NoSuchWorkspaceException;
70  import javax.jcr.Node;
71  import javax.jcr.Repository;
72  import javax.jcr.RepositoryException;
73  import javax.jcr.Session;
74  import javax.jcr.Workspace;
75  
76  import org.apache.commons.io.IOUtils;
77  import org.apache.commons.lang3.StringUtils;
78  import org.slf4j.Logger;
79  import org.slf4j.LoggerFactory;
80  
81  /**
82   * Manages all used Repositories.
83   */
84  @Singleton
85  public final class DefaultRepositoryManager implements RepositoryManager {
86  
87      private static final Logger log = LoggerFactory.getLogger(DefaultRepositoryManager.class);
88  
89      private final WorkspaceMapping workspaceMapping;
90      private final MagnoliaConfigurationProperties magnoliaConfigurationProperties;
91      private final FileSystemHelper fileSystemHelper;
92  
93      @Inject
94      public DefaultRepositoryManager(MagnoliaConfigurationProperties magnoliaConfigurationProperties, WorkspaceMapping workspaceMapping, FileSystemHelper fileSystemHelper) {
95          this.magnoliaConfigurationProperties = magnoliaConfigurationProperties;
96          this.workspaceMapping = workspaceMapping;
97          this.fileSystemHelper = fileSystemHelper;
98      }
99  
100     /**
101      * @deprecated since 5.3.11 - use {@link DefaultRepositoryManager(MagnoliaConfigurationProperties, WorkspaceMapping, FileSystemHelper )} instead.
102      */
103     @Deprecated
104     public DefaultRepositoryManager() {
105         this(Components.getComponent(MagnoliaConfigurationProperties.class), new WorkspaceMapping(), Components.getComponent(FileSystemHelper.class));
106     }
107 
108     /**
109      * @deprecated since 5.5.2 - use {@link DefaultRepositoryManager(MagnoliaConfigurationProperties, WorkspaceMapping, FileSystemHelper )} instead.
110      */
111     @Deprecated
112     public DefaultRepositoryManager(MagnoliaConfigurationProperties magnoliaConfigurationProperties, WorkspaceMapping workspaceMapping) {
113         this(magnoliaConfigurationProperties, workspaceMapping, Components.getComponent(FileSystemHelper.class));
114     }
115 
116     @Override
117     public void init() {
118         log.info("Loading JCR");
119         workspaceMapping.clearRepositories();
120         try {
121             loadRepositories();
122             log.debug("JCR loaded");
123         } catch (Exception e) {
124             log.error(e.getMessage(), e);
125         }
126     }
127 
128     @Override
129     public void shutdown() {
130         log.info("Shutting down JCR");
131         for (RepositoryDefinition repositoryDefinition : workspaceMapping.getRepositoryDefinitions()) {
132             Provider provider = workspaceMapping.getRepositoryProvider(repositoryDefinition.getName());
133             provider.shutdownRepository();
134         }
135         workspaceMapping.clearAll();
136     }
137 
138     @Override
139     public boolean checkIfInitialized() throws RepositoryException {
140         Collection<String> workspaceNames = workspaceMapping.getLogicalWorkspaceNames();
141         for (String workspace : workspaceNames) {
142             if (checkIfInitialized(workspace)) {
143                 return true;
144             }
145         }
146         return false;
147     }
148 
149     @Override
150     public boolean checkIfInitialized(String logicalWorkspace) throws RepositoryException {
151         log.debug("Checking [{}] repository.", logicalWorkspace);
152         // TODO cant we login without using the system context?
153         Session session = MgnlContext.getSystemContext().getJCRSession(logicalWorkspace);
154 
155         if (session == null) {
156             throw new RuntimeException("Repository [" + logicalWorkspace + "] not loaded");
157         }
158 
159         Node startPage = session.getRootNode();
160 
161         // return any kind of children
162         Iterable<Node> children = NodeUtil.getNodes(startPage, new AbstractPredicate<Node>() {
163             @Override
164             public boolean evaluateTyped(Node content) {
165                 String name;
166                 try {
167                     name = content.getName();
168                 } catch (RepositoryException e) {
169                     throw new RuntimeRepositoryException(e);
170                 }
171                 return (!name.startsWith(NodeTypes.JCR_PREFIX) && !name.startsWith(NodeTypes.REP_PREFIX));
172             }
173         });
174 
175         if (children.iterator().hasNext()) {
176             log.debug("Content found in [{}].", logicalWorkspace);
177             return true;
178         }
179         return false;
180     }
181 
182     @Override
183     public void reload() {
184 
185         // TODO what exactly is this method supposed to do?!
186 
187         log.info("Reloading JCR");
188         init();
189     }
190 
191     private void loadRepositories() throws Exception {
192         final String path = magnoliaConfigurationProperties.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CONFIG);
193         if (path == null) {
194             throw new RepositoryNotInitializedException("No value found for property " + MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CONFIG + ": can not start repository.");
195         }
196         final String tokenizedConfig = fileSystemHelper.getTokenizedConfigFile(path);
197         InputStream stream = IOUtils.toInputStream(tokenizedConfig);
198 
199         RepositoryMappingDefinitionReaderitionReader.html#RepositoryMappingDefinitionReader">RepositoryMappingDefinitionReader reader = new RepositoryMappingDefinitionReader();
200         RepositoryMappingDefinition mapping = reader.read(stream);
201 
202         Map<String, WorkspaceMappingDefinition> workspaceMappingDefinitions = new LinkedHashMap<>();
203 
204         for (WorkspaceMappingDefinition definition : mapping.getWorkspaceMappings()) {
205             if (StringUtils.equals(RepositoryConstants.VERSION_STORE, definition.getLogicalWorkspaceName()) || StringUtils.equals(RepositoryConstants.VERSION_STORE, definition.getLogicalWorkspaceName())) {
206                 log.warn("Please remove {} workspace definition from the repositories.xml configuration, this workspace is created automatically for each repository thus does not need to be specified in the configuration file.", RepositoryConstants.VERSION_STORE);
207             } else if (StringUtils.equals(RepositoryConstants.SYSTEM, definition.getLogicalWorkspaceName()) || StringUtils.equals(RepositoryConstants.SYSTEM, definition.getPhysicalWorkspaceName())) {
208                 log.warn("Please remove {} workspace definition from the repositories.xml configuration, this workspace is created automatically for each repository thus does not need to be specified in the configuration file.", RepositoryConstants.SYSTEM);
209             } else {
210                 workspaceMappingDefinitions.put(definition.getLogicalWorkspaceName(), definition);
211             }
212         }
213 
214         mapping.getWorkspaceMappings().clear();
215         mapping.setMappings(workspaceMappingDefinitions);
216 
217         for (RepositoryDefinition repositoryDefinition : mapping.getRepositories()) {
218             if (repositoryDefinition.getWorkspaces().isEmpty()) {
219                 repositoryDefinition.addWorkspace("default");
220             }
221             workspaceMapping.addRepositoryDefinition(repositoryDefinition);
222             loadRepository(repositoryDefinition);
223         }
224 
225         for (WorkspaceMappingDefinition workspaceMapping : mapping.getWorkspaceMappings()) {
226             this.workspaceMapping.addWorkspaceMappingDefinition(workspaceMapping);
227         }
228 
229         // some workspaces might exist even if they are not mapped ... provider knows about them so we should not exclude them.
230         for (RepositoryDefinition repoDefinition : workspaceMapping.getRepositoryDefinitions()) {
231             String repoName = repoDefinition.getName();
232             Provider provider = workspaceMapping.getRepositoryProvider(repoName);
233             Session session = provider.getSystemSession("default");
234             try {
235                 for (String wksName : session.getWorkspace().getAccessibleWorkspaceNames()) {
236                     if (!hasWorkspace(wksName)) {
237                         loadWorkspace(repoName, wksName);
238                     }
239                 }
240             } finally {
241                 session.logout();
242             }
243         }
244     }
245 
246     @Override
247     public void loadRepository(RepositoryDefinition definition) throws RepositoryNotInitializedException, InstantiationException, IllegalAccessException, ClassNotFoundException {
248         log.info("Loading JCR {}", definition.getName());
249 
250         // create also mgnlVersion and mgnlSystem for each repository.
251         Set<String> workspaces = new LinkedHashSet<>(definition.getWorkspaces());
252         workspaces.addAll(Arrays.asList(RepositoryConstants.SYSTEM, RepositoryConstants.VERSION_STORE));
253 
254         definition.getWorkspaces().clear();
255         definition.getWorkspaces().addAll(workspaces);
256 
257         Class<? extends Provider> providerClass = Classes.getClassFactory().forName(definition.getProvider());
258         Provider provider = Components.getComponentProvider().newInstance(providerClass);
259         provider.init(definition);
260         Repository repository = provider.getUnderlyingRepository();
261         workspaceMapping.setRepository(definition.getName(), repository);
262         workspaceMapping.setRepositoryProvider(definition.getName(), provider);
263 
264         if (definition.isLoadOnStartup()) {
265             for (String workspaceId : workspaces) {
266                 registerNameSpacesAndNodeTypes(workspaceId, definition, provider);
267             }
268         }
269     }
270 
271     @Override
272     public void loadWorkspace(String repositoryId, String physicalWorkspaceName) throws RepositoryException {
273         String logicalWorkspaceName;
274         // fallback to old pattern if repo id is empty
275         if (StringUtils.isNotBlank(repositoryId) && (RepositoryConstants.SYSTEM.equals(physicalWorkspaceName) || RepositoryConstants.VERSION_STORE.equals(physicalWorkspaceName))) {
276             logicalWorkspaceName = repositoryId + "-" + physicalWorkspaceName;
277         } else {
278             logicalWorkspaceName = physicalWorkspaceName;
279         }
280 
281         log.info("Loading workspace {} (logical name {}).", physicalWorkspaceName, logicalWorkspaceName);
282 
283         workspaceMapping.addWorkspaceMapping(new WorkspaceMappingDefinition(logicalWorkspaceName, repositoryId, physicalWorkspaceName));
284 
285         Provider provider = getRepositoryProvider(repositoryId);
286         provider.registerWorkspace(physicalWorkspaceName);
287         RepositoryDefinition repositoryDefinition = workspaceMapping.getRepositoryDefinition(repositoryId);
288 
289         registerNameSpacesAndNodeTypes(physicalWorkspaceName, repositoryDefinition, provider);
290     }
291 
292     private void registerNameSpacesAndNodeTypes(String physicalWorkspaceName, RepositoryDefinition repositoryDefinition, Provider provider) {
293         try {
294             Session session = provider.getSystemSession(physicalWorkspaceName);
295             try {
296                 provider.registerNamespace(RepositoryConstants.NAMESPACE_PREFIX, RepositoryConstants.NAMESPACE_URI, session.getWorkspace());
297                 provider.registerNodeTypes();
298             } finally {
299                 session.logout();
300             }
301         } catch (RepositoryException e) {
302             log.error("Failed to initialize workspace {} in repository {}", physicalWorkspaceName, repositoryDefinition.getName(), e);
303         }
304     }
305 
306     @Override
307     public Session getSession(String logicalWorkspaceName, Credentials credentials) throws RepositoryException {
308         WorkspaceMappingDefinition mapping = this.workspaceMapping.getWorkspaceMapping(logicalWorkspaceName);
309         if (mapping == null) throw new NoSuchWorkspaceException(logicalWorkspaceName);
310         Repository repository = getRepository(mapping.getRepositoryName());
311         String physicalWorkspaceName = mapping.getPhysicalWorkspaceName();
312 
313         Session session = repository.login(credentials, physicalWorkspaceName);
314         return wrapSession(session, logicalWorkspaceName);
315     }
316 
317     @Override
318     public Session getSystemSession(String logicalWorkspaceName) throws RepositoryException {
319         WorkspaceMappingDefinition mapping = this.workspaceMapping.getWorkspaceMapping(logicalWorkspaceName);
320         if (mapping == null) {
321             throw new NoSuchWorkspaceException(logicalWorkspaceName);
322         }
323         Provider provider = getRepositoryProvider(mapping.getRepositoryName());
324         return wrapSession(provider.getSystemSession(mapping.getPhysicalWorkspaceName()), logicalWorkspaceName);
325     }
326 
327     @Override
328     public void createWorkspace(String repository, String logicalWorkspaceName) throws RepositoryException {
329         for (WorkspaceMappingDefinition mapping : workspaceMapping.getWorkspaceMappings()) {
330             Session session = getSystemSession(mapping.getLogicalWorkspaceName());
331             try {
332                 if (mapping.getRepositoryName().equals(repository)) {
333                     Workspace workspace = session.getWorkspace();
334                     workspace.createWorkspace(logicalWorkspaceName);
335                     workspaceMapping.addWorkspaceMapping(new WorkspaceMappingDefinition(logicalWorkspaceName, repository, logicalWorkspaceName));
336                     return;
337                 }
338             } finally {
339                 session.logout();
340             }
341         }
342         throw new RepositoryException("Repository [" + repository + "] doesn't exist.");
343     }
344 
345     private Session wrapSession(Session session, String logicalWorkspaceName) {
346         session = new TrackingSessionWrapper(session, JCRStats.getInstance());
347         session = new MgnlLogicalWorkspaceNameMappingWorkspaceDecorator(logicalWorkspaceName, session.getWorkspace().getName()).wrapSession(session);
348         if (logicalWorkspaceName.contains(RepositoryConstants.SYSTEM)) {
349             return new MgnlVersioningSession(session);
350         }
351         if (!logicalWorkspaceName.contains(RepositoryConstants.VERSION_STORE)) {
352             //do not wrap version store in versioning session or we get infinite redirect loop and stack overflow
353             session = new MgnlVersioningSession(session);
354         }
355         // VersioningSessionWrapper has to be applied last, as it will end the chain of restore calls (doesn't forward there)
356         session = new MgnlPropertySettingContentDecorator().wrapSession(session);
357         if (!"imaging".equals(logicalWorkspaceName)) {
358             // don't wrap imaging into audit logging session it's only temporary solution. It will be removed when MAGNOLIA-5012 is resolved.
359             session = new MgnlAuditLoggingContentDecoratorSessionWrapper(session, new MgnlAuditLoggingContentDecorator());
360         }
361         return new MagnoliaSessionContentDecorator().wrapSession(session);
362     }
363 
364     @Override
365     public String getRepositoryNameForWorkspace(String logicalWorkspaceName) {
366         if (hasWorkspace(logicalWorkspaceName)) {
367             return workspaceMapping.getWorkspaceMapping(logicalWorkspaceName).getRepositoryName();
368         }
369         throw new IllegalStateException("No repository known for logical workspace name " + logicalWorkspaceName);
370     }
371 
372     @Override
373     public boolean hasRepository(String repositoryId) {
374         return workspaceMapping.getRepositoryDefinition(repositoryId) != null;
375     }
376 
377     @Override
378     public RepositoryDefinition getRepositoryDefinition(String repositoryId) {
379         return workspaceMapping.getRepositoryDefinition(repositoryId);
380     }
381 
382     @Override
383     public Provider getRepositoryProvider(String repositoryId) {
384         return workspaceMapping.getRepositoryProvider(repositoryId);
385     }
386 
387     @Override
388     public Repository getRepository(String repositoryId) {
389         return workspaceMapping.getRepository(repositoryId);
390     }
391 
392     @Override
393     public void addWorkspaceMapping(WorkspaceMappingDefinition mapping) {
394         workspaceMapping.addWorkspaceMapping(mapping);
395     }
396 
397     @Override
398     public boolean hasWorkspace(String logicalWorkspaceName) {
399         return workspaceMapping.getWorkspaceMapping(logicalWorkspaceName) != null;
400     }
401 
402     @Override
403     public Collection<WorkspaceMappingDefinition> getWorkspaceMappings() {
404         return workspaceMapping.getWorkspaceMappings();
405     }
406 
407     @Override
408     public WorkspaceMappingDefinition getWorkspaceMapping(String logicalWorkspaceName) {
409         return workspaceMapping.getWorkspaceMapping(logicalWorkspaceName);
410     }
411 
412     @Override
413     public Collection<String> getWorkspaceNames() {
414         return workspaceMapping.getLogicalWorkspaceNames();
415     }
416 
417     @Override
418     public Collection<RepositoryDefinition> getRepositoryDefinitions() {
419         return workspaceMapping.getRepositoryDefinitions();
420     }
421 
422     /**
423      * Verify that workspace exists in clustered repository.
424      * @param workspace The name of the workspace
425      * @return {@code true} if workspace is a shared workspace that is configured in a clustered repository
426      */
427     public boolean isClusteredWorkspace(String workspace) {
428         String clusterConfigFile = magnoliaConfigurationProperties.getProperty(MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CLUSTER_CONFIG);
429         if (StringUtils.isBlank(clusterConfigFile)) {
430             return false;
431         }
432 
433         Collection<RepositoryDefinition> repoDefinitions = workspaceMapping.getRepositoryDefinitions();
434         for (RepositoryDefinition repoDefinition : repoDefinitions) {
435             if (repoDefinition.getParameters().containsValue(clusterConfigFile) && repoDefinition.getWorkspaces().contains(workspace)) {
436                 return true;
437             }
438         }
439 
440         return false;
441     }
442 
443     public boolean isClusterMaster() {
444         return magnoliaConfigurationProperties.hasProperty(MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CLUSTER_MASTER) &&
445                 magnoliaConfigurationProperties.getBooleanProperty(MagnoliaConfigurationProperties.MAGNOLIA_REPOSITORIES_CLUSTER_MASTER);
446     }
447 }