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