View Javadoc
1   /**
2    * This file Copyright (c) 2003-2015 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.jackrabbit;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import info.magnolia.cms.core.Path;
38  import info.magnolia.cms.core.SystemProperty;
39  import info.magnolia.repository.Provider;
40  import info.magnolia.repository.RepositoryNotInitializedException;
41  import info.magnolia.repository.definition.RepositoryDefinition;
42  
43  import java.io.File;
44  import java.io.FileInputStream;
45  import java.io.FileNotFoundException;
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.lang.reflect.InvocationTargetException;
49  import java.lang.reflect.Method;
50  import java.util.Hashtable;
51  import java.util.Iterator;
52  import java.util.Map;
53  
54  import javax.jcr.NamespaceException;
55  import javax.jcr.Repository;
56  import javax.jcr.RepositoryException;
57  import javax.jcr.Session;
58  import javax.jcr.SimpleCredentials;
59  import javax.jcr.Workspace;
60  import javax.jcr.nodetype.NoSuchNodeTypeException;
61  import javax.jcr.nodetype.NodeTypeManager;
62  import javax.naming.Context;
63  import javax.naming.InitialContext;
64  import javax.naming.NameNotFoundException;
65  import javax.naming.NamingException;
66  import javax.xml.transform.TransformerFactoryConfigurationError;
67  
68  import org.apache.commons.beanutils.PropertyUtils;
69  import org.apache.commons.io.IOUtils;
70  import org.apache.commons.lang.ArrayUtils;
71  import org.apache.commons.lang.StringUtils;
72  import org.apache.commons.lang.SystemUtils;
73  import org.apache.jackrabbit.core.WorkspaceImpl;
74  import org.apache.jackrabbit.core.jndi.RegistryHelper;
75  import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
76  import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
77  import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader;
78  import org.apache.jackrabbit.spi.Name;
79  import org.slf4j.Logger;
80  import org.slf4j.LoggerFactory;
81  
82  
83  /**
84   * Provider implementation for Apache JackRabbit JCR repository.
85   */
86  public class ProviderImpl implements Provider {
87  
88      /**
89       * Magnolia property (entry in magnolia.properties) with the cluster id, which will be passed to jackrabbit.
90       */
91      private static final String MAGNOLIA_CLUSTERID_PROPERTY = "magnolia.clusterid";
92  
93      /**
94       * Jackrabbit system property for cluster node id.
95       */
96      private static final String JACKRABBIT_CLUSTER_ID_PROPERTY = "org.apache.jackrabbit.core.cluster.node_id";
97  
98      private static final Logger log = LoggerFactory.getLogger(ProviderImpl.class);
99  
100     private static final String CONFIG_FILENAME_KEY = "configFile";
101 
102     private static final String REPOSITORY_HOME_KEY = "repositoryHome";
103 
104     private static final String CONTEXT_FACTORY_CLASS_KEY = "contextFactoryClass";
105 
106     private static final String PROVIDER_URL_KEY = "providerURL";
107 
108     private static final String BIND_NAME_KEY = "bindName";
109 
110     private static final String MGNL_NODETYPES = "/mgnl-nodetypes/magnolia-nodetypes.xml";
111 
112     private static final String CUSTOM_NODETYPES = "customNodeTypes";
113 
114     private RepositoryDefinition repositoryMapping;
115 
116     private Repository repository;
117 
118     private String bindName;
119 
120     private Hashtable<String, Object> jndiEnv;
121 
122     private static final String REPO_HOME_PREFIX = "${repository.home}";
123 
124     private static final int REPO_HOME_SUFIX_LEN = REPO_HOME_PREFIX.length();
125 
126     private static final String sysRepositoryHome = System.getProperty("repository.home");
127 
128     private static final String sysRepositoryHomes = System.getProperty("repository.homes");
129 
130     /**
131      * Finds the physical path to the repository folder.
132      * @param repositoryHome the property set in the repository.xml file
133      * @return the full path resolved to the repository dir
134      */
135     private String getRepositoryHome(final String repositoryHome) {
136         boolean relocate = false;
137         String tmp = repositoryHome;
138         if (repositoryHome.startsWith(REPOSITORY_HOME_KEY)) {
139             tmp = repositoryHome.substring(REPO_HOME_SUFIX_LEN);
140             relocate = true;
141         }
142         /*
143          * Resolve if the path started with the suffix
144          */
145         if (sysRepositoryHome != null && relocate) {
146             return sysRepositoryHome + File.separator + tmp;
147         }
148 
149         /*
150          * This is apply to all repositories if the java property is set
151          */
152         if (sysRepositoryHomes != null) {
153             return sysRepositoryHomes + File.separator + tmp;
154         }
155 
156         /*
157          * Return the same value as before if neither of the above applied
158          */
159         return Path.getAbsoluteFileSystemPath(tmp);
160     }
161 
162     /**
163      * @see info.magnolia.repository.Provider#init(info.magnolia.repository.definition.RepositoryDefinition)
164      */
165     @Override
166     public void init(RepositoryDefinition repositoryMapping) throws RepositoryNotInitializedException {
167         checkXmlSettings();
168 
169         this.repositoryMapping = repositoryMapping;
170         /* connect to repository */
171         Map params = this.repositoryMapping.getParameters();
172         String configFile = (String) params.get(CONFIG_FILENAME_KEY);
173         configFile = Path.getAbsoluteFileSystemPath(configFile);
174         String repositoryHome = (String) params.get(REPOSITORY_HOME_KEY);
175         repositoryHome = getRepositoryHome(repositoryHome);
176 
177         // cleanup the path, to remove eventual ../.. and make it absolute
178         try {
179             File repoHomeFile = new File(repositoryHome);
180             repositoryHome = repoHomeFile.getCanonicalPath();
181         }
182         catch (IOException e1) {
183             // should never happen and it's not a problem at this point, just pass it to jackrabbit and see
184         }
185 
186         String clusterid = SystemProperty.getProperty(MAGNOLIA_CLUSTERID_PROPERTY);
187         if (StringUtils.isNotBlank(clusterid)) {
188             System.setProperty(JACKRABBIT_CLUSTER_ID_PROPERTY, clusterid);
189         }
190 
191         // get it back from system properties, if it has been set elsewhere
192         clusterid = System.getProperty(JACKRABBIT_CLUSTER_ID_PROPERTY);
193 
194         log.info("Loading repository at {} (config file: {}) - cluster id: \"{}\"", new Object[]{
195             repositoryHome,
196             configFile,
197             StringUtils.defaultString(clusterid, "<unset>")});
198 
199         bindName = (String) params.get(BIND_NAME_KEY);
200         jndiEnv = new Hashtable<String, Object>();
201         jndiEnv.put(Context.INITIAL_CONTEXT_FACTORY, params.get(CONTEXT_FACTORY_CLASS_KEY));
202         jndiEnv.put(Context.PROVIDER_URL, params.get(PROVIDER_URL_KEY));
203 
204         try {
205             InitialContext ctx = new InitialContext(jndiEnv);
206             // first try to find the existing object if any
207             try {
208                 this.repository = (Repository) ctx.lookup(bindName);
209             }
210             catch (NameNotFoundException ne) {
211                 log.debug("No JNDI bound Repository found with name {}, trying to initialize a new Repository", bindName);
212                 RegistryHelper.registerRepository(ctx, bindName, configFile, repositoryHome, true);
213                 this.repository = (Repository) ctx.lookup(bindName);
214             }
215             this.validateWorkspaces();
216         }
217         catch (NamingException e) {
218             log.error("Unable to initialize repository: " + e.getMessage(), e);
219             throw new RepositoryNotInitializedException(e);
220         }
221         catch (RepositoryException e) {
222             log.error("Unable to initialize repository: " + e.getMessage(), e);
223             throw new RepositoryNotInitializedException(e);
224         }
225         catch (TransformerFactoryConfigurationError e) {
226             log.error("Unable to initialize repository: " + e.getMessage(), e);
227             throw new RepositoryNotInitializedException(e);
228         }
229     }
230 
231     @Override
232     public void shutdownRepository() {
233         log.info("Shutting down repository bound to '{}'", bindName);
234 
235         try {
236             Context ctx = new InitialContext(jndiEnv);
237             RegistryHelper.unregisterRepository(ctx, bindName);
238         } catch (NamingException e) {
239             log.warn("Unable to shutdown repository " + bindName + ": " + e.getMessage(), e);
240         } catch (Throwable e) {
241             log.error("Failed to shutdown repository " + bindName + ": " + e.getMessage(), e);
242         }
243     }
244 
245     @Override
246     public Repository getUnderlyingRepository() throws RepositoryNotInitializedException {
247         if (this.repository == null) {
248             throw new RepositoryNotInitializedException("Null repository");
249         }
250         return this.repository;
251     }
252 
253     /**
254      * @see info.magnolia.repository.Provider#registerNamespace(java.lang.String, java.lang.String, javax.jcr.Workspace)
255      */
256     @Override
257     public void registerNamespace(String namespacePrefix, String uri, Workspace workspace) throws RepositoryException {
258         try {
259             workspace.getNamespaceRegistry().getURI(namespacePrefix);
260         }
261         catch (NamespaceException e) {
262             if (log.isDebugEnabled()) {
263                 log.debug(e.getMessage());
264             }
265             log.info("Registering prefix [{}] with URI {}", namespacePrefix, uri);
266             workspace.getNamespaceRegistry().registerNamespace(namespacePrefix, uri);
267         }
268     }
269 
270     /**
271      * @see info.magnolia.repository.Provider#unregisterNamespace(java.lang.String, javax.jcr.Workspace)
272      */
273     @Override
274     public void unregisterNamespace(String prefix, Workspace workspace) throws RepositoryException {
275         workspace.getNamespaceRegistry().unregisterNamespace(prefix);
276     }
277 
278     /**
279      * @see info.magnolia.repository.Provider#registerNodeTypes(String)
280      */
281     @Override
282     public void registerNodeTypes() throws RepositoryException {
283         registerNodeTypes(StringUtils.EMPTY);
284     }
285 
286     /**
287      * @see info.magnolia.repository.Provider#registerNodeTypes(java.lang.String)
288      */
289     @Override
290     public void registerNodeTypes(String configuration) throws RepositoryException {
291         if (StringUtils.isEmpty(configuration)) {
292             configuration = this.repositoryMapping.getParameters().get(CUSTOM_NODETYPES);
293         }
294 
295         InputStream xml = getNodeTypeDefinition(configuration);
296         this.registerNodeTypes(xml);
297     }
298 
299     /**
300      * @see info.magnolia.repository.Provider#registerNodeTypes(java.io.InputStream)
301      */
302     @Override
303     public void registerNodeTypes(InputStream xmlStream) throws RepositoryException {
304         SimpleCredentials credentials = new SimpleCredentials(
305             ContentRepository.REPOSITORY_USER,
306             ContentRepository.REPOSITORY_PSWD.toCharArray());
307         Session jcrSession = this.repository.login(credentials);
308 
309         try {
310 
311             Workspace workspace = jcrSession.getWorkspace();
312 
313             // should never happen
314             if (xmlStream == null) {
315                 throw new MissingNodetypesException();
316             }
317 
318             // Use Objects so that it works both with jackrabbit 1.x (NodeTypeDef) and jackrabbit 2
319             // (QNodeTypeDefinition)
320             Object[] types;
321 
322             try {
323                 types = (Object[]) NodeTypeReader.class.getMethod("read", new Class[]{InputStream.class}).invoke(
324                     null,
325                     new Object[]{xmlStream});
326             }
327             catch (Exception e) {
328                 throw new RepositoryException(e.getMessage(), e);
329             }
330             finally {
331                 IOUtils.closeQuietly(xmlStream);
332             }
333 
334             NodeTypeManager ntMgr = workspace.getNodeTypeManager();
335             NodeTypeRegistry ntReg;
336             try {
337                 ntReg = ((NodeTypeManagerImpl) ntMgr).getNodeTypeRegistry();
338             }
339             catch (ClassCastException e) {
340                 // this could happen if the repository provider does not have proper Shared API for the
341                 // application server like at the moment in Jackrabbit
342                 log.debug("Failed to get NodeTypeRegistry: ", e);
343                 return;
344             }
345 
346             for (int j = 0; j < types.length; j++) {
347                 Object def = types[j];
348 
349                 Name ntname;
350                 try {
351                     ntname = (Name) PropertyUtils.getProperty(def, "name");
352                 }
353                 catch (Exception e) {
354                     throw new RepositoryException(e.getMessage(), e);
355                 }
356 
357                 try {
358 
359                     // return value has changed in jackrabbit 2, we still have to use reflection here
360                     // ntReg.getNodeTypeDef(ntname);
361 
362                     Method method = ntReg.getClass().getMethod("getNodeTypeDef", Name.class);
363                     method.invoke(ntReg, ntname);
364                 }
365                 catch (IllegalArgumentException e)
366                 {
367                     throw new RepositoryException(e.getMessage(), e);
368                 }
369                 catch (IllegalAccessException e)
370                 {
371                     throw new RepositoryException(e.getMessage(), e);
372                 }
373                 catch (SecurityException e)
374                 {
375                     throw new RepositoryException(e.getMessage(), e);
376                 }
377                 catch (NoSuchMethodException e)
378                 {
379                     throw new RepositoryException(e.getMessage(), e);
380                 }
381                 catch (InvocationTargetException ite)
382                 {
383                     if (ite.getTargetException() instanceof NoSuchNodeTypeException)
384                     {
385                         log.info("Registering nodetype {} on repository {}", ntname, repositoryMapping.getName());
386 
387                         try
388                         {
389                             // reflection for jackrabbit 1+2 compatibility
390                             getMethod(NodeTypeRegistry.class, "registerNodeType").invoke(ntReg, new Object[]{def });
391                         }
392                         catch (Exception e)
393                         {
394                             throw new RepositoryException(e.getMessage(), e);
395                         }
396                     }
397                 }
398             }
399 
400         }
401         finally {
402             jcrSession.logout();
403         }
404     }
405 
406     private Method getMethod(Class theclass, String methodName) throws NoSuchMethodException {
407         Method[] declaredMethods = theclass.getDeclaredMethods();
408 
409         for (Method method : declaredMethods) {
410             if (method.getName().equals(methodName)) {
411                 return method;
412             }
413         }
414 
415         throw new NoSuchMethodException(theclass.getName() + "." + methodName + "()");
416     }
417 
418     /**
419      * @return InputStream of node type definition file
420      */
421     private InputStream getNodeTypeDefinition(String configuration) {
422 
423         InputStream xml;
424 
425         if (StringUtils.isNotEmpty(configuration)) {
426 
427             // 1: try to load the configured file from the classpath
428             xml = getClass().getResourceAsStream(configuration);
429             if (xml != null) {
430                 log.info("Custom node types registered using {}", configuration);
431                 return xml;
432             }
433 
434             // 2: try to load it from the file system
435             File nodeTypeDefinition = new File(Path.getAbsoluteFileSystemPath(configuration));
436             if (nodeTypeDefinition.exists()) {
437                 try {
438                     return new FileInputStream(nodeTypeDefinition);
439                 }
440                 catch (FileNotFoundException e) {
441                     // should never happen
442                     log.error("File not found: {}", configuration);
443                 }
444             }
445 
446             // 3: defaults to standard nodetypes
447             log.error("Unable to find node type definition: {} for repository {}", configuration, this.repositoryMapping.getName());
448         }
449 
450         // initialize default magnolia nodetypes
451         xml = getClass().getResourceAsStream(MGNL_NODETYPES);
452 
453         return xml;
454     }
455 
456     /**
457      * WORKAROUND for tomcat 5.0/jdk 1.5 problem tomcat\common\endorsed contains an xml-apis.jar needed by tomcat and
458      * loaded before all xmsl stuff present in the jdk (1.4 naming problem). In the xml-apis.jar file the
459      * TransformerFactoryImpl is set to "org.apache.xalan.processor.TransformerFactoryImpl" instead of
460      * "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl". solution: remove the file xml-apis.jar
461      * from the directory OR manually change the javax.xml.transform.TransformerFactory system property
462      */
463     protected void checkXmlSettings() {
464         if (SystemUtils.isJavaVersionAtLeast(1.5f)
465             && "org.apache.xalan.processor.TransformerFactoryImpl".equals(System
466                 .getProperty("javax.xml.transform.TransformerFactory"))) {
467 
468             String transformerClass = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
469 
470             try {
471                 Class.forName(transformerClass);
472 
473                 System.setProperty("javax.xml.transform.TransformerFactory", transformerClass);
474 
475                 log.info("Java 1.5 detected, setting system property \"javax.xml.transform.TransformerFactory\" to \"{}\"", transformerClass);
476             }
477             catch (Throwable e) {
478                 // not in the classpath. We can't assume which one to use, so just go on
479             }
480         }
481     }
482 
483     /**
484      * Checks if all workspaces are present according to the repository mapping, creates any missing workspace.
485      */
486     private void validateWorkspaces() throws RepositoryException {
487         Iterator<String> configuredNames = repositoryMapping.getWorkspaces().iterator();
488         while (configuredNames.hasNext()) {
489             registerWorkspace(configuredNames.next());
490         }
491     }
492 
493     /**
494      * @see info.magnolia.repository.Provider#registerWorkspace(java.lang.String)
495      */
496     @Override
497     public boolean registerWorkspace(String workspaceName) throws RepositoryException {
498         // check if workspace already exists
499         SimpleCredentials credentials = new SimpleCredentials(
500             ContentRepository.REPOSITORY_USER,
501             ContentRepository.REPOSITORY_PSWD.toCharArray());
502         Session jcrSession = this.repository.login(credentials);
503 
504         try {
505             WorkspaceImpl defaultWorkspace = (WorkspaceImpl) jcrSession.getWorkspace();
506             String[] workspaceNames = defaultWorkspace.getAccessibleWorkspaceNames();
507 
508             boolean alreadyExists = ArrayUtils.contains(workspaceNames, workspaceName);
509             if (!alreadyExists) {
510                 defaultWorkspace.createWorkspace(workspaceName);
511             }
512             jcrSession.logout();
513 
514             return !alreadyExists;
515         } catch (ClassCastException e) {
516             // this could happen if the repository provider does not have proper Shared API for the
517             // application server like at the moment in Jackrabbit
518             log.debug("Unable to register workspace, will continue", e);
519         } catch (Throwable t) {
520             log.error("Unable to register workspace, will continue", t);
521         }
522         return false;
523     }
524 
525     @Override
526     public Session getSystemSession(String workspaceName) throws RepositoryException {
527 
528         // FIXME: stop using SystemProperty, but IoC is not ready yet when this is called (config loader calls repo.init() which results in authentication calls being made and this method being invoked
529 
530         String user = SystemProperty.getProperty("magnolia.connection.jcr.admin.userId", SystemProperty.getProperty("magnolia.connection.jcr.userId", "admin"));
531         String pwd = SystemProperty.getProperty("magnolia.connection.jcr.admin.password", SystemProperty.getProperty("magnolia.connection.jcr.password", "admin"));
532         return this.repository.login(new SimpleCredentials(user, pwd.toCharArray()), workspaceName);
533     }
534 
535 }