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