Documents configuration schemes in more detail
[mkjsf-moved-to-github.git] / src / main / java / com / indexdata / mkjsf / pazpar2 / ServiceProxyClient.java
1 package com.indexdata.mkjsf.pazpar2;\r
2 \r
3 import static com.indexdata.mkjsf.utils.Utils.nl;\r
4 \r
5 import java.io.BufferedReader;\r
6 import java.io.File;\r
7 import java.io.FileReader;\r
8 import java.io.IOException;\r
9 import java.util.ArrayList;\r
10 import java.util.HashMap;\r
11 import java.util.List;\r
12 import java.util.Map;\r
13 \r
14 import org.apache.http.Header;\r
15 import org.apache.http.HttpEntity;\r
16 import org.apache.http.HttpResponse;\r
17 import org.apache.http.StatusLine;\r
18 import org.apache.http.client.ClientProtocolException;\r
19 import org.apache.http.client.HttpClient;\r
20 import org.apache.http.client.ResponseHandler;\r
21 import org.apache.http.client.methods.HttpGet;\r
22 import org.apache.http.client.methods.HttpPost;\r
23 import org.apache.http.conn.ClientConnectionManager;\r
24 import org.apache.http.conn.scheme.PlainSocketFactory;\r
25 import org.apache.http.conn.scheme.Scheme;\r
26 import org.apache.http.conn.scheme.SchemeRegistry;\r
27 import org.apache.http.entity.ByteArrayEntity;\r
28 import org.apache.http.entity.FileEntity;\r
29 import org.apache.http.impl.client.DefaultHttpClient;\r
30 import org.apache.http.impl.conn.PoolingClientConnectionManager;\r
31 import org.apache.http.util.EntityUtils;\r
32 import org.apache.log4j.Logger;\r
33 \r
34 import com.indexdata.mkjsf.config.Configuration;\r
35 import com.indexdata.mkjsf.config.ConfigurationReader;\r
36 import com.indexdata.mkjsf.errors.ConfigurationException;\r
37 import com.indexdata.mkjsf.errors.MissingConfigurationContextException;\r
38 import com.indexdata.mkjsf.pazpar2.commands.CommandParameter;\r
39 import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Command;\r
40 import com.indexdata.mkjsf.pazpar2.commands.sp.AuthCommand;\r
41 import com.indexdata.mkjsf.pazpar2.data.CommandError;\r
42 import com.indexdata.mkjsf.utils.Utils;\r
43 \r
44 /**\r
45  * Search client handling Service Proxy requests. \r
46  *   \r
47  * <h3>Configuration</h3>\r
48  *\r
49  * Configuration name: proxyclient\r
50  *  \r
51  * <p>When configuring the client using the Mk2Config scheme, this is the prefix to\r
52  * use in the .properties file. When using web.xml context parameters for configuration\r
53  * the configuration name has no effect.</p> \r
54  * \r
55  * <p>ServiceProxyClient will acknowledge following configuration parameters:\r
56  * \r
57  * <ul>\r
58  *  <li>(proxyclient.)SERVICE_PROXY_URL</li>\r
59  * </ul>   \r
60  *   \r
61  * @author Niels Erik\r
62  *\r
63  */\r
64 public class ServiceProxyClient implements SearchClient {\r
65     \r
66   private static final long serialVersionUID = -4031644009579840277L;\r
67   private static Logger logger = Logger.getLogger(ServiceProxyClient.class);\r
68   public static final String MODULENAME = "proxyclient";\r
69   \r
70   public static final String SP_INIT_DOC_PATHS = "SP_INIT_DOC_PATHS";\r
71   private String serviceUrl = "";\r
72   \r
73   private List<String> initDocPaths = null;\r
74   private Configuration config = null;\r
75   \r
76   ProxyPz2ResponseHandler handler = new ProxyPz2ResponseHandler();\r
77   private transient HttpClient client;  \r
78   private Pazpar2Command checkAuth = null;\r
79   private Pazpar2Command ipAuth = null;\r
80 \r
81   public ServiceProxyClient () {\r
82     SchemeRegistry schemeRegistry = new SchemeRegistry();\r
83     schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));\r
84     ClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);\r
85     client = new DefaultHttpClient(cm);\r
86   }\r
87     \r
88   @Override\r
89   public void configure (ConfigurationReader configReader) throws MissingConfigurationContextException {\r
90     logger.info(Utils.objectId(this) + " is configuring using the provided " + Utils.objectId(configReader));\r
91     try {\r
92       config = configReader.getConfiguration(this);      \r
93       serviceUrl = config.get("SERVICE_PROXY_URL");\r
94       this.initDocPaths = config.getMultiProperty(SP_INIT_DOC_PATHS,",");\r
95       checkAuth = new AuthCommand();\r
96       checkAuth.setParameterInState(new CommandParameter("action","=","check"));\r
97       ipAuth = new AuthCommand();\r
98       ipAuth.setParameterInState(new CommandParameter("action","=","ipauth"));\r
99     } catch (MissingConfigurationContextException mcce) {\r
100       throw mcce;\r
101     } catch (ConfigurationException ce) {\r
102       logger.error("Failed to configure Service Proxy client");\r
103       ce.printStackTrace();\r
104     }\r
105   }\r
106     \r
107   public boolean isAuthenticatingClient () {\r
108     return true;\r
109   }\r
110     \r
111   /**\r
112    * Makes the request\r
113    * @param request\r
114    * @return HTTP response as a String\r
115    * @throws ClientProtocolException\r
116    * @throws IOException\r
117    */\r
118   public ClientCommandResponse send(Pazpar2Command command) {\r
119     ClientCommandResponse commandResponse = null;\r
120     String url = serviceUrl + "?" + command.getEncodedQueryString(); \r
121     logger.info("Sending request "+url);    \r
122     HttpGet httpget = new HttpGet(url);     \r
123     byte[] response = null;\r
124     try {\r
125       response = client.execute(httpget, handler);\r
126       if (handler.getStatusCode()==200 && (handler.getContentType().contains("xml") || handler.getContentType().contains("octet-stream"))) {\r
127         logger.trace("Creating command response holding content of type " + handler.getContentType());\r
128         commandResponse = new ClientCommandResponse(handler.getStatusCode(),response,handler.getContentType());\r
129       } else {\r
130         logger.error("Service Proxy status code: " + handler.getStatusCode());\r
131         String errorXml = "";\r
132         if (handler.getContentType().contains("xml")) {\r
133           errorXml = CommandError.insertErrorXml(command.getCommandName(), String.valueOf(handler.getStatusCode()), "Service Proxy error: "+handler.getStatusCode(), new String(response,"UTF-8"));        \r
134         } else {\r
135           if (handler.getContentType().contains("html")) {\r
136             String htmlStrippedOfTags = (new String(response,"UTF-8")).replaceAll("\\<[^>]*>","");\r
137             if (htmlStrippedOfTags.toLowerCase().contains("domain")) {\r
138               errorXml = CommandError.createErrorXml(command.getCommandName(), String.valueOf(handler.getStatusCode()), "Unexpected response type from Service Proxy", "Expected XML from SP but got HTML. It contains the word domain suggesting that the service address was not found.", htmlStrippedOfTags);              \r
139             } else {\r
140               errorXml = CommandError.createErrorXml(command.getCommandName(), String.valueOf(handler.getStatusCode()), "Unexpected response type from Service Proxy", "Expected XML from SP but got HTML", htmlStrippedOfTags);              \r
141             }\r
142           } else {\r
143             errorXml = CommandError.createErrorXml(command.getCommandName(), String.valueOf(handler.getStatusCode()), "Unexpected response type from Service Proxy: "+handler.getContentType(), "Could not process non-XML response from Service Proxy", new String(response,"UTF-8"));\r
144           }          \r
145         }\r
146         commandResponse = new ClientCommandResponse(handler.getStatusCode(),errorXml,handler.getContentType());\r
147       }       \r
148     } catch (Exception e) {\r
149       e.printStackTrace();\r
150       commandResponse = new ClientCommandResponse(handler.getStatusCode(),CommandError.createErrorXml(command.getCommandName(), String.valueOf(handler.getStatusCode()), e.getClass().getSimpleName(), (e.getMessage()!= null ? e.getMessage() : "") + (e.getCause()!=null ? e.getCause().getMessage() : ""), e.getStackTrace().toString()),handler.getContentType());\r
151     }\r
152     return commandResponse; \r
153   }\r
154   \r
155   public class ProxyPz2ResponseHandler implements ResponseHandler<byte[]> {\r
156     private StatusLine statusLine = null;\r
157     private Header contentType = null;\r
158     public byte[] handleResponse(HttpResponse response) throws ClientProtocolException, IOException {\r
159       byte[] resp = null;\r
160       HttpEntity entity = response.getEntity();      \r
161       statusLine = response.getStatusLine();\r
162       if (entity != null) {        \r
163         resp = EntityUtils.toByteArray(entity);        \r
164         contentType = response.getEntity().getContentType();        \r
165       }       \r
166       EntityUtils.consume(entity);      \r
167       return resp;\r
168     }\r
169     public int getStatusCode() {\r
170       return statusLine.getStatusCode();\r
171     }    \r
172     public String getReasonPhrase() {\r
173       return statusLine.getReasonPhrase();\r
174     }\r
175     public String getContentType () {\r
176       return (contentType != null ? contentType.getValue() : "Content-Type not known"); \r
177     }\r
178   }\r
179 \r
180   public int getStatusCode () {\r
181     return handler.getStatusCode();\r
182   }\r
183   \r
184   public String getReasonPhrase() {\r
185     return handler.getReasonPhrase();\r
186   }\r
187 \r
188   /**\r
189    * Does nothing in Service Proxy context\r
190    */\r
191   @Override\r
192   public void setSearchCommand(Pazpar2Command command) {\r
193     // Do nothing, Service Proxy is handling this    \r
194   }\r
195 \r
196   @Override\r
197   public HttpResponseWrapper executeCommand(Pazpar2Command command) {\r
198     return send(command);\r
199   }\r
200 \r
201   public ServiceProxyClient cloneMe() {\r
202     logger.debug("Cloning Pz2Client");\r
203     ServiceProxyClient clone = new ServiceProxyClient();\r
204     clone.client = this.client;\r
205     clone.serviceUrl = this.serviceUrl;\r
206     clone.initDocPaths = this.initDocPaths;\r
207     return clone;\r
208   }\r
209 \r
210   /**\r
211    * Returns default configuration parameters for the client.\r
212    */\r
213   @Override\r
214   public Map<String, String> getDefaults() {    \r
215     return new HashMap<String,String>();\r
216   }\r
217 \r
218   /**\r
219    * Returns the configuration name of the client\r
220    */\r
221   @Override\r
222   public String getModuleName() {\r
223     return MODULENAME;\r
224   }\r
225   \r
226   @Override\r
227   public List<String> documentConfiguration () {\r
228     List<String> doc = new ArrayList<String>();\r
229     doc.add(nl+ MODULENAME + " was configured to access the Pazpar2 service proxy at: " + (serviceUrl.length()>0 ? serviceUrl : "[not defined yet]"));\r
230     return null;\r
231   }\r
232   \r
233   public ClientCommandResponse postInitDoc (String filePath) throws IOException {\r
234     logger.info("Looking to post the file in : [" + filePath +"]");\r
235     HttpPost post = new HttpPost(serviceUrl+"?command=init&includeDebug=yes");\r
236     File initDoc = new File(filePath);\r
237     logger.info("Posting to SP: ");\r
238     if (logger.isDebugEnabled()) {\r
239       BufferedReader reader = new BufferedReader(new FileReader(initDoc));\r
240       String line;\r
241       while ( (line = reader.readLine()) != null) {\r
242         System.out.println(line);\r
243       }\r
244       reader.close();\r
245     }\r
246     post.setEntity(new FileEntity(initDoc));\r
247     byte[] response = client.execute(post, handler);\r
248     logger.debug("Response on POST was: " + new String(response,"UTF-8"));    \r
249     return new ClientCommandResponse(handler.getStatusCode(),response,handler.getContentType());    \r
250   }\r
251   \r
252   public List<String> getInitDocPaths () {\r
253     logger.debug("Get init doc paths ");\r
254     logger.debug("length: " + initDocPaths.size());\r
255     return initDocPaths;\r
256   }\r
257   \r
258   public HttpResponseWrapper postInitDoc(byte[] initDoc, Pazpar2Command command) {\r
259     String requestParameters = command.getEncodedQueryString();\r
260     logger.info("Initiating session with init doc and [" + requestParameters +"]");\r
261     HttpPost post = new HttpPost(serviceUrl+"?" + requestParameters);\r
262     post.setEntity(new ByteArrayEntity(initDoc));\r
263     ClientCommandResponse commandResponse = null;\r
264     byte[] response;\r
265     try {\r
266       response = client.execute(post, handler);\r
267       if (handler.getStatusCode()==200) {\r
268         commandResponse = new ClientCommandResponse(handler.getStatusCode(),response,handler.getContentType());\r
269       } else {\r
270         logger.error("Service Proxy status code: " + handler.getStatusCode());\r
271         commandResponse = new ClientCommandResponse(handler.getStatusCode(),CommandError.insertErrorXml("init", String.valueOf(handler.getStatusCode()), "Service Proxy error: "+handler.getStatusCode(), new String(response,"UTF-8")),"text/xml");                               \r
272       }\r
273     } catch (ClientProtocolException e) {\r
274       logger.error(e.getMessage());\r
275       e.printStackTrace();\r
276       commandResponse = new ClientCommandResponse(-1,CommandError.createErrorXml("init", String.valueOf(handler.getStatusCode()), "Client protocol exception", e.getMessage(), e.getStackTrace().toString()),"text/xml");      \r
277     } catch (IOException e) {\r
278       logger.error(e.getMessage());\r
279       e.printStackTrace();\r
280       commandResponse = new ClientCommandResponse(-1,CommandError.createErrorXml("init", String.valueOf(handler.getStatusCode()), "IO exception", e.getMessage(),e.getStackTrace().toString()),"text/xml");      \r
281     }\r
282     return commandResponse;    \r
283   }\r
284   \r
285   /**\r
286    * Sets the URL of the Service Proxy that should service requests. \r
287    */\r
288   public void setServiceUrl (String url) {    \r
289     serviceUrl = url;\r
290   }\r
291           \r
292   public Configuration getConfiguration () {\r
293     return config;\r
294   }\r
295 \r
296   @Override\r
297   public String getServiceUrl() {    \r
298     return serviceUrl;\r
299   }\r
300 \r
301   /**\r
302    * Returns true if a Service Proxy URL was defined yet.\r
303    */\r
304   @Override\r
305   public boolean hasServiceUrl() {\r
306     return serviceUrl != null && serviceUrl.length()>0;\r
307   }\r
308   \r
309 }\r