/** * Copyright 2010 The University of North Carolina at Chapel Hill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package unc.lib.cdr.workbench.xwalk; import edu.unc.lib.schemas.acl.AclPackage; import gov.loc.mets.AmdSecType; import gov.loc.mets.DivType; import gov.loc.mets.MDTYPEType; import gov.loc.mets.MdSecType; import gov.loc.mets.MdWrapType; import gov.loc.mets.MetsFactory; import gov.loc.mets.MetsPackage; import gov.loc.mets.MetsType; import gov.loc.mets.XmlDataType1; import gov.loc.mets.util.METSConstants; import gov.loc.mets.util.METSUtils; import gov.loc.mods.mods.MODSPackage; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CompoundCommand; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xml.type.internal.XMLCalendar; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.RemoveCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import unc.lib.cdr.workbench.project.MetsProjectNature; import unc.lib.cdr.workbench.rcp.Activator; import crosswalk.CrossWalk; import crosswalk.CrosswalkPackage; import crosswalk.DataException; import crosswalk.Editable; import crosswalk.EditingContainer; import crosswalk.MetsSource; import crosswalk.OutputElement; import crosswalk.OutputMetadataSections; import crosswalk.OutputProfile; import crosswalk.RecordMatcherStrategy; import crosswalk.RecordMatches; import crosswalk.RecordOutOfRangeException; import crosswalk.WalkWidget; public class CrosswalkJob extends Job { public static final String crosswalkJobFamilyObject = "crosswalkJobFamily"; @Override public boolean belongsTo(Object family) { if (family == crosswalkJobFamilyObject) return true; return super.belongsTo(family); } private static final Logger LOG = LoggerFactory .getLogger(CrosswalkJob.class); private IFile file = null; private MetsProjectNature nature = null; private MetsType mets = null; private CrossWalk cw = null; private String groupId; // private Map<String, MdSecType> oldMdSecs; /** * For each profile, a list of the objects to be added to the METS root. */ private HashMap<OutputProfile, Map<String, MdSecType>> profile2MetsAdditions; public CrosswalkJob(IFile crosswalkFile) { super("Running " + crosswalkFile.getName()); this.file = crosswalkFile; this.nature = MetsProjectNature.get(this.file.getProject()); this.mets = nature.getMets(); this.groupId = file.getName(); } @Override protected IStatus run(IProgressMonitor monitor) { LOG.debug("running crosswalk: {}", file.getName()); clearProblemMarkers(file); try { setupCrosswalk(); } catch (CoreException e) { return e.getStatus(); } // setup profile to record map this.profile2MetsAdditions = new HashMap<OutputProfile, Map<String, MdSecType>>(); for (OutputProfile profile : cw.getOutputProfiles()) profile2MetsAdditions .put(profile, new HashMap<String, MdSecType>()); try { generateNewMetadataRecords(monitor); } catch (CoreException e) { return e.getStatus(); } nature.getCommandStack().execute(getLinkCleanupCommand()); // remove the old mdSecs for this crosswalk nature.getCommandStack().execute( RemoveCommand.create(nature.getEditingDomain(), getCurrentCrosswalkMdSecs())); // add the new records to mets nature.getCommandStack().execute(getRecordAddCommand()); try { nature.save(); } catch (CoreException e1) { return e1.getStatus(); } nature.getCommandStack().execute(getMatcherLinkComand()); try { nature.save(); } catch (CoreException e1) { return e1.getStatus(); } return Status.OK_STATUS; } private Command getMatcherLinkComand() { // find the matcher strategies CompoundCommand autoLinkCommand = new CompoundCommand(); for (WalkWidget w : cw.getWidgets()) { if (w instanceof RecordMatcherStrategy) { RecordMatcherStrategy s = (RecordMatcherStrategy) w; try { s.run(); RecordMatches matches = s.getMatches(); LOG.debug("Got matches: {} matched, {} record collisions, {} resource collisions.", matches.getMatches().size(), matches.getRecordCollisions().size(), matches.getResourceCollisions().size()); // TODO set warnings for collisions // set links and status for matches for (Map.Entry<DivType, String> match : matches .getMatches().entrySet()) { String divID = match.getKey().getID(); // for each profile, add links to matched records for(OutputProfile profile : this.cw.getOutputProfiles()) { String mdID = makeMdSecID(file, profile, match.getValue()); MdSecType md = this.profile2MetsAdditions.get(profile).get(mdID); if(md == null) continue; md.setSTATUS(METSConstants.MD_STATUS_CROSSWALK_LINKED); // old links should have been removed earlier // add the CW link autoLinkCommand.append(AddCommand.create( nature.getEditingDomain(), match.getKey(), profile.getMetadataSection().getDivReference(), md)); } } } catch (Exception e) { setProblemMarker(e.getLocalizedMessage(), file); LOG.error("failure in record matcher", e); } } } return autoLinkCommand; } private Command getRecordAddCommand() { CompoundCommand result = new CompoundCommand(); for (OutputProfile profile : cw.getOutputProfiles()) { if (profile.getMetadataSection().equals( OutputMetadataSections.DMD_SEC)) { result.append(AddCommand.create(nature.getEditingDomain(), mets, MetsPackage.eINSTANCE.getMetsType_DmdSec(), this.profile2MetsAdditions.get(profile).values())); } else { for (MdSecType md : this.profile2MetsAdditions.get(profile) .values()) { AmdSecType amd = MetsFactory.eINSTANCE.createAmdSecType(); switch (profile.getMetadataSection()) { case DIGIPROV_MD: amd.getDigiprovMD().add(md); break; case RIGHTS_MD: amd.getRightsMD().add(md); break; case SOURCE_MD: amd.getSourceMD().add(md); break; case TECH_MD: amd.getTechMD().add(md); break; } result.append(AddCommand.create(nature.getEditingDomain(), mets, MetsPackage.eINSTANCE.getMetsType_AmdSec(), amd)); } } } return result; } private CompoundCommand getLinkCleanupCommand() { // cleanup links CompoundCommand command = new CompoundCommand(); // build set of new mdSec elements Map<String, MdSecType> newMdSecIDs = new HashMap<String, MdSecType>(); for (OutputProfile profile : cw.getOutputProfiles()) newMdSecIDs.putAll(profile2MetsAdditions.get(profile)); DivType bag = METSUtils.findBagDiv(mets); Iterator<EObject> bagChildren = bag.eAllContents(); while (bagChildren.hasNext()) { EObject o = bagChildren.next(); if (o instanceof DivType) { DivType div = (DivType) o; for (MdSecType md : div.getDmdSec()) { if (groupId.equals(md.getGROUPID())) { updateCrosswalkLink(div, md, newMdSecIDs, command, MetsPackage.eINSTANCE.getDivType_DmdSec()); } } for (MdSecType md : div.getMdSec()) { if (groupId.equals(md.getGROUPID())) { updateCrosswalkLink(div, md, newMdSecIDs, command, MetsPackage.eINSTANCE.getDivType_MdSec()); } } } } return command; } /** * Determines link update behavior prior to matcher run. Only pass links to * mdSecs created by this crosswalk. * * @param md * @param newMdSecIDs * @param updateLinksCommand * @param linkReference */ private void updateCrosswalkLink(DivType div, MdSecType md, Map<String, MdSecType> newMdSecIDs, CompoundCommand updateLinksCommand, EReference linkReference) { if (METSConstants.MD_STATUS_CROSSWALK_LINKED.equals(md.getSTATUS())) { // remove links established by the CW updateLinksCommand.append(RemoveCommand.create( nature.getEditingDomain(), div, linkReference, md)); } else if (!newMdSecIDs.containsKey(md.getID())) { // remove links to mdSecs that no longer exist updateLinksCommand.append(RemoveCommand.create( nature.getEditingDomain(), div, linkReference, md)); } else { // migrate links you want to keep to new mds MdSecType newMd = newMdSecIDs.get(md.getID()); // preserve user linked status if (md.getSTATUS().equals( METSConstants.MD_STATUS_CROSSWALK_USER_LINKED)) { newMd.setSTATUS(METSConstants.MD_STATUS_CROSSWALK_USER_LINKED); } updateLinksCommand.append(RemoveCommand.create( nature.getEditingDomain(), div, linkReference, md)); updateLinksCommand.append(AddCommand.create( nature.getEditingDomain(), div, linkReference, newMd)); } } private void generateNewMetadataRecords(IProgressMonitor monitor) throws CoreException { // create new records, update status if old mdSec was user linked try { while (true) { for (OutputProfile profile : cw.getOutputProfiles()) { //System.out.println("in profile: "+profile.getName()); if (monitor.isCanceled()) throw new CoreException(Status.CANCEL_STATUS); MdSecType md = processRecord(profile); if (md != null) { profile2MetsAdditions.get(profile).put(md.getID(), md); } } cw.getDataSource().NextRecord(); } } catch (RecordOutOfRangeException ignored) { } catch (DataException e) { setProblemMarker(e.getMessage(), file); } } private void setupCrosswalk() throws CoreException { ResourceSet resourceSet = new ResourceSetImpl(); AclPackage.eINSTANCE.eClass(); MODSPackage.eINSTANCE.eClass(); resourceSet.getPackageRegistry().put(CrosswalkPackage.eNS_URI, CrosswalkPackage.eINSTANCE); resourceSet.getPackageRegistry().put(MODSPackage.eNS_URI, MODSPackage.eINSTANCE); resourceSet.getPackageRegistry().put(AclPackage.eNS_URI, AclPackage.eINSTANCE); @SuppressWarnings("rawtypes") Map xmlOptions = new HashMap(); java.net.URI uri = file.getLocationURI(); Resource crosswalkResource = null; crosswalkResource = resourceSet.getResource( URI.createURI(uri.toASCIIString()), true); try { crosswalkResource.load(xmlOptions); } catch (IOException e) { setProblemMarker(e.getLocalizedMessage(), file); } this.cw = null; for (EObject o : crosswalkResource.getContents()) { if (o instanceof EditingContainer) { Editable editable = ((EditingContainer) o).getModel(); if (editable instanceof CrossWalk) { cw = (CrossWalk) editable; } } } if (cw == null) { setProblemMarker("No CrossWalk element in this file.", file); throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "There was no crosswalk element in the file.")); } if (cw.getDataSource() == null) { setProblemMarker("No data source defined in this CrossWalk", file); throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "There was no data source defined in the crosswalk file.")); } try { cw.getDataSource().Reset(); } catch (DataException e) { LOG.debug("cannot reset data source", e); setProblemMarker(e.getLocalizedMessage(), file); throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Cannot reset the data source in the crosswalk file.")); } cw.setMetsSource(new MetsSource() { @Override public MetsType getMets() { return mets; } }); cw.setCurrentUser(System.getProperty("user.name")); } /** * @param cw * @param mets */ private MdSecType processRecord(OutputProfile profile) { EClass outputElementClass = null; if(profile.isStartMappingAtChildren()) { outputElementClass = profile.getParentMappedFeature().getEReferenceType(); } else { outputElementClass = profile.getParentMappedFeature().getEContainingClass(); } EObject outputElement = outputElementClass.getEPackage().getEFactoryInstance().create(outputElementClass); for (OutputElement e : cw.getElements()) { e.updateRecord(outputElement); } //for(EObject child : outputElement.eContents()) System.out.println(child); if (outputElement.eContents() == null || outputElement.eContents().isEmpty()) { return null; } // detect if ID already present. MdSecType md = MetsFactory.eINSTANCE.createMdSecType(); md.setGROUPID(this.groupId); md.setSTATUS(METSConstants.MD_STATUS_CROSSWALK_NOT_LINKED); md.setCREATED(new XMLCalendar(new java.util.Date(System .currentTimeMillis()), XMLCalendar.DATETIME)); String recordID = cw.getDataSource().getRecordID(); md.setID(makeMdSecID(file, profile, recordID)); MdWrapType wrap = MetsFactory.eINSTANCE.createMdWrapType(); MDTYPEType t = MDTYPEType.get(profile.getMetadataType()); if (t == null) t = MDTYPEType.OTHER; wrap.setMDTYPE(t); if (MDTYPEType.OTHER.equals(t)) wrap.setOTHERMDTYPE(profile.getMetadataType()); wrap.setLABEL(profile.getMetadataLabel() +" "+ recordID); XmlDataType1 xml = MetsFactory.eINSTANCE.createXmlDataType1(); // root was mapped, grab nested feature if(!profile.isStartMappingAtChildren()) { outputElement = outputElement.eContents().get(0); } xml.getAny().add(profile.getParentMappedFeature(), outputElement); wrap.setXmlData(xml); md.setMdWrap(wrap); return md; } private Set<EObject> getCurrentCrosswalkMdSecs() { // build a list of the old mdSecs for this crosswalk Set<EObject> result = new HashSet<EObject>(); if (mets.getDmdSec() != null) { for (MdSecType md : mets.getDmdSec()) { if (groupId.equals(md.getGROUPID())) { result.add(md); } } } if (mets.getAmdSec() != null) { for (AmdSecType amd : mets.getAmdSec()) { if (amd.getDigiprovMD() != null) { for (MdSecType md : amd.getDigiprovMD()) { if (groupId.equals(md.getGROUPID())) { result.add(amd); } } } if (amd.getRightsMD() != null) { for (MdSecType md : amd.getRightsMD()) { if (groupId.equals(md.getGROUPID())) { result.add(amd); } } } if (amd.getSourceMD() != null) { for (MdSecType md : amd.getSourceMD()) { if (groupId.equals(md.getGROUPID())) { result.add(amd); } } } if (amd.getTechMD() != null) { for (MdSecType md : amd.getTechMD()) { if (groupId.equals(md.getGROUPID())) { result.add(amd); } } } } } return result; } /** * @param file * the Crosswalk Definition File * @param recordID * @return */ private String makeMdSecID(IFile crosswalkFile, OutputProfile profile, String recordID) { return "cw_" + crosswalkFile.hashCode() + "_" + profile.hashCode() + "_" + recordID; } /** * @param f */ private void clearProblemMarkers(IFile f) { int depth = IResource.DEPTH_INFINITE; try { f.deleteMarkers(IMarker.PROBLEM, true, depth); } catch (CoreException e) { LOG.debug("problem clearing markers", e); } } /** * @param e * @param f */ private void setProblemMarker(String msg, IFile f) { try { IMarker m = f.createMarker(IMarker.PROBLEM); m.setAttribute(IMarker.MESSAGE, msg); } catch (CoreException e1) { LOG.error("there was a problem setting the problem marker: {}", msg); } } }