View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.user2;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URI;
24  import java.net.URISyntaxException;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  
29  import javax.xml.transform.Source;
30  import javax.xml.transform.TransformerException;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.jdom2.Document;
35  import org.jdom2.Element;
36  import org.jdom2.JDOMException;
37  import org.jdom2.Namespace;
38  import org.jdom2.transform.JDOMSource;
39  import org.mycore.common.MCRException;
40  import org.mycore.common.config.MCRConfiguration2;
41  import org.mycore.common.content.MCRFileContent;
42  import org.mycore.common.content.MCRSourceContent;
43  import org.mycore.common.xml.MCRXMLParserFactory;
44  import org.xml.sax.SAXException;
45  
46  /**
47   * Handles {@link MCRRealm} instantiation.
48   * Will create a file <code>${MCR.datadir}/realms.xml</code> if that file does not exist.
49   * You can redefine the location if you define a URI in <code>MCR.users2.Realms.URI</code>.
50   * This class monitors the source file for changes and adapts at runtime.
51   * @author Thomas Scheffler (yagee)
52   *
53   */
54  public class MCRRealmFactory {
55  
56      static final String RESOURCE_REALMS_URI = "resource:realms.xml";
57  
58      static final String REALMS_URI_CFG_KEY = MCRUser2Constants.CONFIG_PREFIX + "Realms.URI";
59  
60      private static final Logger LOGGER = LogManager.getLogger(MCRRealm.class);
61  
62      private static final int REFRESH_DELAY = 5000;
63  
64      private static long lastModified = 0;
65  
66      /** Map of defined realms, key is the ID of the realm */
67      private static HashMap<String, MCRRealm> realmsMap = new HashMap<>();
68  
69      private static HashMap<String, MCRUserAttributeMapper> attributeMapper = new HashMap<>();
70  
71      /** List of defined realms */
72      private static List<MCRRealm> realmsList = new ArrayList<>();
73  
74      /** The local realm, which is the default realm */
75      private static MCRRealm localRealm;
76  
77      private static URI realmsURI;
78  
79      private static File realmsFile;
80  
81      private static long lastChecked;
82  
83      private static Document realmsDocument;
84  
85      static {
86          String dataDirProperty = "MCR.datadir";
87          String dataDir = MCRConfiguration2.getString(dataDirProperty).orElse(null);
88          if (dataDir == null) {
89              LOGGER.warn("{} is undefined.", dataDirProperty);
90              try {
91                  realmsURI = new URI(MCRConfiguration2.getString(REALMS_URI_CFG_KEY).orElse(RESOURCE_REALMS_URI));
92              } catch (URISyntaxException e) {
93                  throw new MCRException(e);
94              }
95          } else {
96              File dataDirFile = new File(dataDir);
97              String realmsCfg = MCRConfiguration2.getString(REALMS_URI_CFG_KEY)
98                  .orElse(dataDirFile.toURI() + "realms.xml");
99              try {
100                 realmsURI = new URI(realmsCfg);
101                 LOGGER.info("Using realms defined in {}", realmsURI);
102                 if ("file".equals(realmsURI.getScheme())) {
103                     realmsFile = new File(realmsURI);
104                     LOGGER.info("Loading realms from file: {}", realmsFile);
105                 } else {
106                     LOGGER.info("Try loading realms with URIResolver for scheme {}", realmsURI);
107                 }
108             } catch (URISyntaxException e) {
109                 throw new MCRException(e);
110             }
111         }
112         loadRealms();
113     }
114 
115     /**
116      * 
117      */
118     private static void loadRealms() {
119         Element root;
120         try {
121             root = getRealms().getRootElement();
122         } catch (SAXException | JDOMException | TransformerException | IOException e) {
123             throw new MCRException("Could not load realms from URI: " + realmsURI);
124         }
125         String localRealmID = root.getAttributeValue("local");
126         HashMap<String, MCRRealm> realmsMap = new HashMap<>();
127 
128         HashMap<String, MCRUserAttributeMapper> attributeMapper = new HashMap<>();
129 
130         List<MCRRealm> realmsList = new ArrayList<>();
131 
132         List<Element> realms = root.getChildren("realm");
133         for (Element child : realms) {
134             String id = child.getAttributeValue("id");
135             MCRRealm realm = new MCRRealm(id);
136 
137             List<Element> labels = child.getChildren("label");
138             for (Element label : labels) {
139                 String text = label.getTextTrim();
140                 String lang = label.getAttributeValue("lang", Namespace.XML_NAMESPACE);
141                 realm.setLabel(lang, text);
142             }
143 
144             realm.setPasswordChangeURL(child.getChildTextTrim("passwordChangeURL"));
145             Element login = child.getChild("login");
146             if (login != null) {
147                 realm.setLoginURL(login.getAttributeValue("url"));
148                 realm.setRedirectParameter(login.getAttributeValue("redirectParameter"));
149                 realm.setRealmParameter(login.getAttributeValue("realmParameter"));
150             }
151             Element createElement = child.getChild("create");
152             if (createElement != null) {
153                 realm.setCreateURL(createElement.getAttributeValue("url"));
154             }
155 
156             attributeMapper.put(id, MCRUserAttributeMapper.instance(child));
157 
158             realmsMap.put(id, realm);
159             realmsList.add(realm);
160             if (localRealmID.equals(id)) {
161                 localRealm = realm;
162             }
163         }
164         MCRRealmFactory.realmsDocument = root.getDocument();
165         MCRRealmFactory.realmsMap = realmsMap;
166         MCRRealmFactory.realmsList = realmsList;
167         MCRRealmFactory.attributeMapper = attributeMapper;
168     }
169 
170     private static Document getRealms() throws JDOMException, TransformerException, SAXException, IOException {
171         if (realmsFile == null) {
172             return MCRSourceContent.getInstance(realmsURI.toASCIIString()).asXML();
173         }
174         if (!realmsFile.exists() || realmsFile.length() == 0) {
175             LOGGER.info("Creating {}...", realmsFile.getAbsolutePath());
176             MCRSourceContent realmsContent = MCRSourceContent.getInstance(RESOURCE_REALMS_URI);
177             realmsContent.sendTo(realmsFile);
178         }
179         updateLastModified();
180         return MCRXMLParserFactory.getNonValidatingParser().parseXML(new MCRFileContent(realmsFile));
181     }
182 
183     /**
184      * Returns the realm with the given ID.
185      * 
186      * @param id the ID of the realm
187      * @return the realm with that ID, or null
188      */
189     public static MCRRealm getRealm(String id) {
190         reInitIfNeeded();
191         return realmsMap.get(id);
192     }
193 
194     public static MCRUserAttributeMapper getAttributeMapper(String id) {
195         reInitIfNeeded();
196         return attributeMapper.get(id);
197     }
198 
199     /**
200      * Returns a list of all defined realms.
201      *  
202      * @return a list of all realms.
203      */
204     public static List<MCRRealm> listRealms() {
205         reInitIfNeeded();
206         return realmsList;
207     }
208 
209     /**
210      * Returns the Realms JDOM document clone. 
211      */
212     public static Document getRealmsDocument() {
213         reInitIfNeeded();
214         return realmsDocument.clone();
215     }
216 
217     /**
218      * Returns the Realms JDOM document as a {@link Source} useful for transformation processes.
219      */
220     static Source getRealmsSource() {
221         reInitIfNeeded();
222         return new JDOMSource(realmsDocument);
223     }
224 
225     /**
226      * Returns the local default realm, as specified by the attribute 'local' in realms.xml
227      * 
228      * @return the local default realm.
229      */
230     public static MCRRealm getLocalRealm() {
231         reInitIfNeeded();
232         return localRealm;
233     }
234 
235     private static boolean reloadingRequired() {
236         boolean reloading = false;
237 
238         long now = System.currentTimeMillis();
239 
240         if (now > lastChecked + REFRESH_DELAY) {
241             lastChecked = now;
242             if (hasChanged()) {
243                 reloading = true;
244             }
245         }
246 
247         return reloading;
248     }
249 
250     private static boolean hasChanged() {
251         if (realmsFile == null || !realmsFile.exists()) {
252             return false;
253         }
254         return realmsFile.lastModified() > lastModified;
255     }
256 
257     private static void updateLastModified() {
258         if (realmsFile == null || !realmsFile.exists()) {
259             return;
260         }
261         lastModified = realmsFile.lastModified();
262     }
263 
264     private static void reInitIfNeeded() {
265         if (reloadingRequired()) {
266             loadRealms();
267         }
268     }
269 
270 }