View Javadoc

1   package org.fedoracommons.funapi.fedora;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.StringReader;
6   import java.io.StringWriter;
7   
8   import java.net.MalformedURLException;
9   import java.net.URL;
10  
11  import java.util.ArrayList;
12  import java.util.HashSet;
13  import java.util.Iterator;
14  import java.util.List;
15  import java.util.Properties;
16  import java.util.Set;
17  
18  import javax.xml.parsers.DocumentBuilder;
19  import javax.xml.parsers.DocumentBuilderFactory;
20  import javax.xml.parsers.ParserConfigurationException;
21  import javax.xml.rpc.ServiceException;
22  
23  import org.apache.commons.httpclient.HttpClient;
24  import org.apache.commons.httpclient.HttpException;
25  import org.apache.commons.httpclient.HttpMethod;
26  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
27  import org.apache.commons.httpclient.UsernamePasswordCredentials;
28  import org.apache.commons.httpclient.auth.AuthScope;
29  import org.apache.commons.httpclient.methods.HeadMethod;
30  
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Element;
33  
34  import org.xml.sax.InputSource;
35  import org.xml.sax.SAXException;
36  
37  import org.codehaus.jackson.JsonFactory;
38  import org.codehaus.jackson.JsonGenerator;
39  import org.codehaus.jackson.map.JsonNode;
40  import org.codehaus.jackson.map.JsonTypeMapper;
41  import org.fedoracommons.funapi.FormatException;
42  import org.fedoracommons.funapi.ObjectResolver;
43  import org.fedoracommons.funapi.UnapiException;
44  import org.fedoracommons.funapi.UnapiFormat;
45  import org.fedoracommons.funapi.UnapiFormats;
46  import org.fedoracommons.funapi.UnapiObject;
47  
48  import fedora.client.FedoraClient;
49  
50  import fedora.common.PID;
51  
52  import fedora.server.access.FedoraAPIA;
53  import fedora.server.management.FedoraAPIM;
54  import fedora.server.types.gen.DatastreamDef;
55  import fedora.server.types.gen.MIMETypedStream;
56  import fedora.server.types.gen.RelationshipTuple;
57  
58  import static org.apache.commons.httpclient.HttpStatus.SC_OK;
59  
60  import static fedora.common.Constants.MODEL;
61  
62  /**
63   * Implementation of ObjectResolver for a Fedora repository.
64   * This implementation will attempt to load a properties file with the name
65   * <code>FedoraResolver.properties</code>.
66   * 
67   * Required properties:
68   * <dl>
69   *   <dt>baseURL</dt>
70   *     <dd>The base URL of the Fedora repository, e.g. http://localhost:8080/fedora/</dd>
71   *   <dt>formatsDatastream</dt>
72   *     <dd>The id of a content model object's inline XML datastream which 
73   *     describes the unAPI formats available for a Fedora object, e.g. 
74   *     UNAPI-FORMATS</dd>
75   * </dl>
76   * 
77   * The formatsDatastream must return an XML document that contains a JSON array
78   * describing the available formats. For example:
79   * <pre>&lt;json&gt;
80  [[&quot;info:fedora/&#42;/DC&quot;,&quot;oai_dc&quot;,&quot;text/xml&quot;,&quot;http://www.openarchives.org/OAI/2.0/oai_dc.xsd&quot;],
81   [&quot;info:fedora/&#42;/demo:dc2mods.sdef/transform&quot;,&quot;mods&quot;,&quot;application/xml&quot;,&quot;http://www.loc.gov/standards/mods/&quot;]]
82  &lt;/json&gt;</pre>
83   * 
84   * The above is a JSON array of arrays surrounded by &lt;json&gt; tags.
85   * Each four-element inner array represents a single unAPI format. 
86   * The first element is the dissemination type URI which will requested object
87   * in the requested format.
88   * The remaining three elements are the unAPI format, type and docs. 
89   * 
90   * @author Edwin Shin
91   * @since 0.1
92   * @version $Id: FedoraResolver.java 33 2008-10-25 19:31:36Z pangloss $
93   */
94  public class FedoraResolver
95          implements ObjectResolver {
96      
97      private Properties props;
98      private URL baseURL;
99      private String username;
100     private String password;
101     private String formatsDS;
102     private String defaultFormats;
103     private final static String FEDORA_URI = "info:fedora/";
104     private FedoraClient fedoraClient;
105     private JsonFactory jsonFactory;
106     private HttpClient httpClient;
107     
108     public FedoraResolver() throws UnapiException {
109         InputStream in = getClass().getResourceAsStream("/FedoraResolver.properties");
110         props = new Properties();
111         try {
112             props.load(in);
113         } catch (IOException e) {
114             throw new UnapiException(e.getMessage(), e);
115         }
116         
117         // e.g. http://localhost:8080/fedora
118         String temp = props.getProperty("baseURL");
119         if (!temp.endsWith("/")) {
120             temp += "/";
121         }
122         try {
123             baseURL = new URL(temp);
124         } catch (MalformedURLException e) {
125             throw new UnapiException(e.getMessage(), e);
126         }
127         
128         username = props.getProperty("username");
129         password = props.getProperty("password");
130         formatsDS = props.getProperty("formatsDatastream");
131         defaultFormats = props.getProperty("defaultFormats");
132         try {
133             fedoraClient = new FedoraClient(baseURL.toString(), username, password);
134         } catch (MalformedURLException e) {
135             throw new UnapiException(e.getMessage(), e);
136         }
137         jsonFactory = new JsonFactory();
138     }
139     
140     public UnapiFormats getFormats() throws UnapiException {
141         UnapiFormats formats;
142         if (defaultFormats == null) {
143             formats = new UnapiFormats(null);
144             UnapiFormat format = new UnapiFormat("oai_dc","text/xml","http://www.openarchives.org/OAI/2.0/oai_dc.xsd");
145             formats.addFormat(format);
146         } else {
147             try {
148                 formats = parseJsonArray(null, defaultFormats);
149             } catch (IOException e) {
150                 throw new UnapiException(e.getMessage(), e);
151             }
152         }
153         return formats;
154     }
155     
156     public UnapiFormats getFormats(String id) throws UnapiException {
157         checkHttpStatusCode(id);
158             
159         UnapiFormats formats;
160         try {
161             formats = parseJsonArray(id, getJsonArray(id));
162         } catch (ServiceException e) {
163             throw new UnapiException(e.getMessage(), e);
164         } catch (IOException e) {
165             throw new UnapiException(e.getMessage(), e);
166         }
167         return formats;
168     }
169 
170     public UnapiObject getObject(String id, String format) throws UnapiException {
171         checkHttpStatusCode(id);
172         String url;
173         try {
174             url = getDisseminationUrl(id, format);
175         } catch (ServiceException e) {
176             throw new FormatException(e.getMessage(), e);
177         } catch (IOException e) {
178             throw new UnapiException(e.getMessage(), e);
179         }
180         return new UnapiObject(url);
181     }
182     
183     /**
184      * Returns a resolvable Fedora URL for a PID or info:fedora uri.
185      * 
186      * @param id
187      * @return
188      */
189     private String getURL(String id) {        
190         String newId = null;
191         if (id.startsWith(FEDORA_URI)) {
192             newId = id.substring(FEDORA_URI.length());
193         } else {
194             newId = id;
195         }
196         return baseURL + "get/" + newId;
197     }
198     
199     /**
200      * 
201      * @param id an info:fedora uri (e.g. info:/fedora/demo:1)
202      * @return a JSON array of arrays, e.g. [["disstype","format","type","docs"],[...]]
203      * @throws IOException
204      * @throws ServiceException
205      * @throws UnapiException 
206      */
207     private String getJsonArray(String id) throws IOException, ServiceException, UnapiException {
208         FedoraAPIA apia;
209         FedoraAPIM apim;
210         apia = fedoraClient.getAPIA();
211         apim = fedoraClient.getAPIM();
212         
213         PID pid = PID.getInstance(id);
214         
215         RelationshipTuple[] tuples = apim.getRelationships(pid.toString(), MODEL.HAS_MODEL.uri);
216         List<String> cmodels = new ArrayList<String>();
217         for (RelationshipTuple tuple: tuples) {
218             cmodels.add(tuple.getObject());
219         }
220         
221         JsonTypeMapper jtMapper = new JsonTypeMapper();
222         JsonNode rootNode = null;
223         for (String cmodel : cmodels) {
224             MIMETypedStream ds = null;
225             // check the content model's datastreams for formatsDS
226             DatastreamDef[] dsDefs = apia.listDatastreams(cmodel, null);
227             for (DatastreamDef dsDef : dsDefs) {
228                 if (dsDef.getID().equals(formatsDS)) {
229                     ds = apia.getDatastreamDissemination(cmodel, formatsDS, null);
230                     continue;
231                 }
232             }
233             if (ds == null) {
234                 continue;
235             }
236             String dsXML = new String(ds.getStream(), "UTF-8");
237             
238             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
239             DocumentBuilder builder;
240             Document doc = null;
241             try {
242                 builder = factory.newDocumentBuilder();
243                 doc = builder.parse(new InputSource(new StringReader(dsXML)));
244             } catch (ParserConfigurationException e) {
245                 throw new UnapiException(e.getMessage(), e);
246             } catch (SAXException e) {
247                 throw new UnapiException(e.getMessage(), e);
248             }
249 
250             Element jsonElement = (Element)doc.getElementsByTagName("json").item(0);
251 
252             String json = jsonElement.getTextContent();
253             JsonNode node = jtMapper.read(jsonFactory.createJsonParser(new StringReader(json)));
254             if (rootNode == null) {
255                 rootNode = node;
256             } else {
257                 Iterator<JsonNode> it = node.getElements();
258                 while(it.hasNext()) {
259                     rootNode.appendElement(it.next());
260                 }
261             }
262         }
263         
264         if (rootNode == null) {
265             return null;
266         } else {
267             StringWriter sw = new StringWriter();
268             JsonGenerator gen = jsonFactory.createJsonGenerator(sw);
269             rootNode.writeTo(gen);
270             gen.close();
271             return sw.toString();
272         }
273     }
274     
275     private UnapiFormats parseJsonArray(String id, String jsonContent) throws IOException, UnapiException {
276         UnapiFormats uFormats = new UnapiFormats(id);
277         if (jsonContent == null) {
278             return uFormats;
279         }
280         Set<String> formats = new HashSet<String>();
281         
282         JsonNode rootNode = new JsonTypeMapper().read(jsonFactory.createJsonParser(new StringReader(jsonContent)));
283         Iterator<JsonNode> it = rootNode.getElements();
284         while (it.hasNext()) {
285             JsonNode innerArray = it.next();
286             String format = innerArray.getElementValue(1).getTextValue();
287             String type = innerArray.getElementValue(2).getTextValue();
288             String docs = innerArray.getElementValue(3).getTextValue();
289             
290             if (formats.add(format)) {
291                 UnapiFormat uFormat = new UnapiFormat(format, type, docs);
292                 uFormats.addFormat(uFormat);
293             }
294         }
295         return uFormats;
296     }
297     
298     private String getDisseminationUrl(String id, String format) throws ServiceException, IOException, UnapiException {
299         String pid = PID.getInstance(id).toString();
300         String dissType = null;
301         
302         String jsonContent = getJsonArray(id);
303         JsonNode rootNode = new JsonTypeMapper().read(jsonFactory.createJsonParser(new StringReader(jsonContent)));
304         
305         Iterator<JsonNode> it = rootNode.getElements();
306         while (it.hasNext()) {
307             JsonNode innerArray = it.next();
308             
309             String fmt = innerArray.getElementValue(1).getTextValue();
310             if (format.equalsIgnoreCase(fmt)) {
311                 dissType = innerArray.getElementValue(0).getTextValue();
312                 break;
313             }
314         }
315         if (dissType == null) {
316             throw new UnapiException("No dissType for " + id);
317         }
318         // dissType example: info:fedora/*/sdef:1/methodFoo
319         dissType = dissType.replaceAll("\\*", pid);
320         return getURL(dissType);
321     }
322     
323     private HttpClient getHttpClient() {
324         if (httpClient != null) {
325             return httpClient;
326         }
327         MultiThreadedHttpConnectionManager connectionManager = 
328             new MultiThreadedHttpConnectionManager();
329 
330         HttpClient client = new HttpClient(connectionManager);
331         client.getParams().setAuthenticationPreemptive(true);
332         if (username != null && password != null) {
333             client.getState().setCredentials(
334                  new AuthScope(baseURL.getHost(), baseURL.getPort(), null),
335                  new UsernamePasswordCredentials(username, password)
336                  );
337         }
338         return client;
339     }
340     
341     protected void setHttpClient(HttpClient httpClient) {
342         this.httpClient = httpClient;
343     }
344     
345     protected void setFedoraClient(FedoraClient fedoraClient) {
346         this.fedoraClient = fedoraClient;
347     }
348     
349     private void checkHttpStatusCode(String id) throws UnapiException {
350         String url = getURL(id);
351         HttpMethod httpMethod = new HeadMethod(url);
352         try {
353             httpMethod.setDoAuthentication(true);
354             httpMethod.setFollowRedirects(true);
355             int status = getHttpClient().executeMethod(httpMethod);
356             if (status != SC_OK) {
357                 throw new UnapiException(status, httpMethod.getStatusText());
358             }
359         } catch (HttpException e) {
360             throw new UnapiException(e.getMessage(), e);
361         } catch (IOException e) {
362             throw new UnapiException(e.getMessage(), e);
363         } finally {
364             httpMethod.releaseConnection();
365         }
366     }
367 }