package jp.aegif.nemaki.tracker;
import static org.apache.solr.handler.extraction.ExtractingParams.LITERALS_PREFIX;
import static org.apache.solr.handler.extraction.ExtractingParams.UNKNOWN_FIELD_PREFIX;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import jp.aegif.nemaki.util.PropertyKey;
import jp.aegif.nemaki.util.PropertyManager;
import jp.aegif.nemaki.util.StringPool;
import jp.aegif.nemaki.util.impl.PropertyManagerImpl;
import jp.aegif.nemaki.util.Constant;
import org.apache.chemistry.opencmis.client.api.ChangeEvent;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.SecondaryType;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.runtime.ObjectIdImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.core.SolrCore;
public class Registration implements Runnable{
Session cmisSession;
SolrCore core;
SolrServer repositoryServer;
List<ChangeEvent> list;
boolean mimeTypeFilterEnabled;
List<String> allowedMimeTypeFilter;
boolean fulltextEnabled;
Logger logger = Logger.getLogger(Registration.class);
public Registration(Session cmisSession, SolrCore core, SolrServer repositoryServer, List<ChangeEvent> list, boolean fulltextEnabled, boolean mimeTypeFilterEnabled, List<String> allowedMimeTypeFilter){
this.cmisSession = cmisSession;
this.core = core;
this.repositoryServer = repositoryServer;
this.list = list;
this.fulltextEnabled = fulltextEnabled;
this.mimeTypeFilterEnabled = mimeTypeFilterEnabled;
this.allowedMimeTypeFilter = allowedMimeTypeFilter;
}
@Override
public void run() {
//Read MIME-Type filtering
for (ChangeEvent ce : list) {
switch (ce.getChangeType()) {
case CREATED:
registerSolrDocument(ce, fulltextEnabled, mimeTypeFilterEnabled, allowedMimeTypeFilter);
break;
case UPDATED:
registerSolrDocument(ce, fulltextEnabled, mimeTypeFilterEnabled, allowedMimeTypeFilter);
break;
case DELETED:
deleteSolrDocument(ce);
continue;
default:
break;
}
}
}
/**
* Create/Update Solr document
*
* @param ce
* @param fulltextEnabled TODO
*/
private void registerSolrDocument(ChangeEvent ce, boolean fulltextEnabled, boolean mimeTypeFilter, List<String>allowedMimeTypes) {
CmisObject obj = null;
try {
obj = cmisSession.getObject(ce.getObjectId());
} catch (Exception e) {
logger.warn("[objectId=" + ce.getObjectId()
+ "]object is deleted. Skip reading a change event.");
return;
}
AbstractUpdateRequest req = null;
Map<String, Object> map = buildParamMap(obj);
switch (obj.getBaseTypeId()) {
case CMIS_DOCUMENT:
if(fulltextEnabled){
String mimeType = (String) map.get(Constant.FIELD_CONTENT_MIMETYPE);
if(!mimeTypeFilter || CollectionUtils.isNotEmpty(allowedMimeTypes) && allowedMimeTypes.contains(mimeType)){
ContentStream cs = cmisSession.getContentStream(new ObjectIdImpl(
obj.getId()));
req = buildUpdateRequestWithFile(map, cs);
}else{
req = buildUpdateRequest(map);
}
}else{
req = buildUpdateRequest(map);
}
break;
case CMIS_FOLDER:
req = buildUpdateRequest(map);
break;
default:
break;
}
String successMsg = "";
String errMsg = "";
switch (ce.getChangeType()) {
case CREATED:
successMsg = "Successfully created";
errMsg = "Failed to create";
break;
case UPDATED:
successMsg = "Successfully updated";
errMsg = "Failed to update";
break;
default:
break;
}
// Send a request to Solr
try {
repositoryServer.request(req);
logger.info(logPrefix(ce) + successMsg);
} catch (Exception e) {
logger.error(logPrefix(ce) + errMsg, e);
}finally{
// Delete temp files
try {
deleteTempFile(req);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void deleteTempFile(AbstractUpdateRequest req) throws IOException, URISyntaxException{
Collection<org.apache.solr.common.util.ContentStream> streams = req.getContentStreams();
Iterator<org.apache.solr.common.util.ContentStream> itr = streams.iterator();
if(itr.hasNext()){
org.apache.solr.common.util.ContentStream stream = itr.next();
String sourceInfo = stream.getSourceInfo();
if(sourceInfo.startsWith("file:")){
File f = new File(new URI(sourceInfo));
if(f != null && f.isFile()){
f.delete();
}
}
}
}
/**
* Delete Solr document
*
* @param ce
*/
private void deleteSolrDocument(ChangeEvent ce) {
try {
// Check if the SolrDocument exists
SolrQuery solrQuery = new SolrQuery();
solrQuery.setQuery(Constant.FIELD_OBJECT_ID + ":" + ce.getObjectId());
QueryResponse resp = repositoryServer.query(solrQuery);
if (resp != null && resp.getResults() != null) {
if (resp.getResults().getNumFound() == 0) {
logger.info(logPrefix(ce)
+ "DELETED type change event is skipped because there is no SolrDocument");
return;
}
} else {
logger.error(core.getName()
+ ":Something wrong in the connection to Solr server");
}
// Delete
repositoryServer.deleteById(ce.getObjectId());
repositoryServer.commit();
logger.info(logPrefix(ce) + "Successfully deleted");
} catch (Exception e) {
logger.error(logPrefix(ce) + "Failed to ", e);
}
}
private String logPrefix(ChangeEvent ce) {
return "[objectId=" + ce.getObjectId() + "]";
}
/**
* Build update request with file to Solr
*
* @param content
* @param inputStream
* @return
*/
// NOTION: SolrCell seems not to accept a capital property name.
// For example, "parentId" doesn't work.
private AbstractUpdateRequest buildUpdateRequestWithFile(
Map<String, Object> map, ContentStream inputStream) {
ContentStreamUpdateRequest up = new ContentStreamUpdateRequest(
"/update/extract");
// Set File Stream
try {
File file = convertInputStreamToFile(inputStream.getStream());
up.addFile(file, inputStream.getMimeType());
} catch (IOException e) {
e.printStackTrace();
}
// Set field values
// NOTION:
// Cast to String works on the assumption they are already String
// so that ModifiableSolrParams can have an argument Map<String,
// String[]>.
// Any other better way?
Map<String, String[]> m = new HashMap<String, String[]>();
// for a field with capital letters
m.put("lowernames", new String[] { "false" });
// Ignored(for schema.xml, ignoring some SolrCell meta fields)
m.put(UNKNOWN_FIELD_PREFIX, new String[] { "ignored_" });
Set<String> keys = map.keySet();
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
// Multi value
Object val = map.get(key);
if (val instanceof List<?>) {
m.put(LITERALS_PREFIX + key,
((List<String>) val).toArray(new String[0]));
// Single value
} else if (val instanceof String) {
String[] _val = { (String) val };
m.put(LITERALS_PREFIX + key, _val);
}
}
up.setParams(new ModifiableSolrParams(m));
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
return up;
}
/**
* Build an update request to Solr without file
*
* @param content
* @return
*/
public AbstractUpdateRequest buildUpdateRequest(Map<String, Object> map) {
UpdateRequest up = new UpdateRequest();
SolrInputDocument sid = new SolrInputDocument();
// Set SolrDocument parameters
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
sid.addField(key, map.get(key));
}
// Set UpdateRequest
up.add(sid);
// Ignored(for schema.xml, ignoring some SolrCell meta fields)
up.setParam(UNKNOWN_FIELD_PREFIX, "ignored_");
// Set Solr action parameter
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
return up;
}
/**
* @param inputStream
* @return
* @throws IOException
*/
private File convertInputStreamToFile(InputStream inputStream)
throws IOException {
File file = File.createTempFile(
String.valueOf(System.currentTimeMillis()), null);
file.deleteOnExit();
try {
// write the inputStream to a FileOutputStream
OutputStream out = new FileOutputStream(file);
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
inputStream.close();
out.flush();
out.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
return file;
}
/**
*
* @param content
* @return
*/
private Map<String, Object> buildParamMap(CmisObject object) {
Map<String, Object> map = new HashMap<String, Object>();
buildBaseParamMap(map, object);
// BaseType specific property
switch (object.getBaseTypeId()) {
case CMIS_DOCUMENT:
map.put(Constant.FIELD_CONTENT_ID,
object.getPropertyValue(PropertyIds.CONTENT_STREAM_ID));
map.put(Constant.FIELD_CONTENT_NAME, object
.getPropertyValue(PropertyIds.CONTENT_STREAM_FILE_NAME));
map.put(Constant.FIELD_CONTENT_MIMETYPE, object
.getPropertyValue(PropertyIds.CONTENT_STREAM_MIME_TYPE));
map.put(Constant.FIELD_CONTENT_LENGTH,
object.getPropertyValue(PropertyIds.CONTENT_STREAM_LENGTH)
.toString());
map.put(Constant.FIELD_VERSION_LABEL,
object.getPropertyValue(PropertyIds.VERSION_LABEL));
String isMajorVersion = (object
.getPropertyValue(PropertyIds.IS_MAJOR_VERSION) == null) ? null
: object.getPropertyValue(PropertyIds.IS_MAJOR_VERSION)
.toString();
map.put(Constant.FIELD_IS_MAJOR_VEERSION, isMajorVersion);
map.put(Constant.FIELD_VERSION_SERIES_ID,
object.getPropertyValue(PropertyIds.VERSION_SERIES_ID));
String isCheckedOut = (object
.getPropertyValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT) == null) ? null
: object.getPropertyValue(
PropertyIds.IS_VERSION_SERIES_CHECKED_OUT)
.toString();
map.put(Constant.FIELD_IS_CHECKEDOUT, isCheckedOut);
map.put(Constant.FIELD_CHECKEDOUT_ID,
object.getPropertyValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID));
map.put(Constant.FIELD_CHECKEDOUT_BY,
object.getPropertyValue(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY));
map.put(Constant.FIELD_CHECKIN_COMMENT,
object.getPropertyValue(PropertyIds.CHECKIN_COMMENT));
String isPrivateWorkingCopy = (object
.getPropertyValue(PropertyIds.IS_PRIVATE_WORKING_COPY) == null) ? null
: object.getPropertyValue(
PropertyIds.IS_PRIVATE_WORKING_COPY).toString();
map.put(Constant.FIELD_IS_PRIVATE_WORKING_COPY, isPrivateWorkingCopy);
ObjectParentData parent= getParent(object);
map.put(Constant.FIELD_PARENT_ID,
parent.getObject().getId());
break;
case CMIS_FOLDER:
map.put(Constant.FIELD_PARENT_ID,
object.getPropertyValue(PropertyIds.PARENT_ID));
map.put(Constant.FIELD_PATH, object.getPropertyValue(PropertyIds.PATH));
default:
break;
}
// SubType & Secondary property
buildDynamicParamMap(map, object);
return map;
}
private void buildBaseParamMap(Map<String, Object> map, CmisObject object) {
String repositoryId = cmisSession.getRepositoryInfo().getId();
String objectId = object.getId();
map.put(Constant.FIELD_ID, buildUniqueId(repositoryId, objectId));
map.put(Constant.FIELD_REPOSITORY_ID, repositoryId);
map.put(Constant.FIELD_OBJECT_ID, objectId);
map.put(Constant.FIELD_NAME, object.getName());
map.put(Constant.FIELD_DESCRIPTION, object.getDescription());
map.put(Constant.FIELD_BASE_TYPE, object.getBaseTypeId().value());
map.put(Constant.FIELD_OBJECT_TYPE, object.getType().getQueryName());
map.put(Constant.FIELD_SECONDARY_OBJECT_TYPE_IDS, getSecondaryIds(object));
map.put(Constant.FIELD_CREATED, getUTC(object.getCreationDate()));
map.put(Constant.FIELD_CREATOR, object.getCreatedBy());
map.put(Constant.FIELD_MODIFIED, getUTC(object.getLastModificationDate()));
map.put(Constant.FIELD_MODIFIER, object.getLastModifiedBy());
}
private String buildUniqueId(String repositoryId, String objectId){
return repositoryId + "_" + objectId;
}
private List<String> getSecondaryIds(CmisObject object) {
List<SecondaryType> secondaryTypes = object.getSecondaryTypes();
if (CollectionUtils.isEmpty(secondaryTypes)) {
return new ArrayList<String>();
} else {
List<String> list = new ArrayList<String>();
Iterator<SecondaryType> iterator = secondaryTypes.iterator();
while (iterator.hasNext()) {
list.add(iterator.next().getId());
}
return list;
}
}
/**
* For properties other than those of baseType. They are indexed regardless
* of its "queryable" flag in case the flag is changed later.
*
* @param map
* @param object
*/
private void buildDynamicParamMap(Map<String, Object> map, CmisObject object) {
Map<String, PropertyDefinition<?>> propDefs = object.getType()
.getPropertyDefinitions();
Map<String, PropertyDefinition<?>> basePropDefs = object.getBaseType()
.getPropertyDefinitions();
for (String propId : propDefs.keySet()) {
if (!basePropDefs.containsKey(propId)) {
boolean isSecondary = false;
// Secondary type
List<SecondaryType> secs = object.getSecondaryTypes();
if (CollectionUtils.isNotEmpty(secs)) {
for (SecondaryType sec : secs) {
Map<String, PropertyDefinition<?>> secondaryPropDefs = sec
.getPropertyDefinitions();
// Secondary specific property
if (secondaryPropDefs.containsKey(propId)) {
String type = "dynamic.property."
+ sec.getQueryName() + Constant.SEPARATOR + propId;
map.put(type, object.getPropertyValue(propId));
isSecondary = true;
break;
}
}
}
// Non-Secondary type
if (!isSecondary) {
String type = "dynamic.property." + propId;
map.put(type, object.getPropertyValue(propId));
}
}
}
}
private ObjectParentData getParent(CmisObject object){
List<ObjectParentData> parents =
cmisSession
.getBinding()
.getNavigationService()
.getObjectParents(cmisSession.getRepositoryInfo().getId(),
object.getId(), null, false, null, null, true, null);
return parents.get(0);
}
/**
*
* @param cal
* @return
*/
private String getUTC(GregorianCalendar cal) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
String timestamp = df.format(cal.getTime());
return timestamp;
}
}