View Javadoc

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